11// Copyright (c) .NET Foundation and contributors. All rights reserved.
22// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33
4+ using System . Collections . Generic ;
45using System . CommandLine . Help ;
56using System . IO ;
7+ using System . Linq ;
68using FluentAssertions ;
79using Xunit ;
810using static System . Environment ;
@@ -29,9 +31,9 @@ public Customization()
2931 private HelpBuilder GetHelpBuilder ( int maxWidth ) => new ( maxWidth ) ;
3032
3133 [ Fact ]
32- public void Option_can_customize_default_value ( )
34+ public void Option_can_customize_displayed_default_value ( )
3335 {
34- var option = new Option < string > ( "--the-option" ) { DefaultValueFactory = ( _ ) => "not 42" } ;
36+ var option = new Option < string > ( "--the-option" ) { DefaultValueFactory = _ => "not 42" } ;
3537 var command = new Command ( "the-command" , "command help" )
3638 {
3739 option
@@ -136,9 +138,9 @@ public void Option_can_customize_second_column_text_based_on_parse_result()
136138 ctx . Command . Equals ( commandA )
137139 ? optionADescription
138140 : optionBDescription ) ;
139- command . Options . Add ( new HelpOption ( )
141+ command . Options . Add ( new HelpOption
140142 {
141- Action = new HelpAction ( )
143+ Action = new HelpAction
142144 {
143145 Builder = helpBuilder
144146 }
@@ -201,7 +203,7 @@ public void Command_arguments_can_customize_second_column_text()
201203 var argument = new Argument < string > ( "some-arg" )
202204 {
203205 Description = "Default description" ,
204- DefaultValueFactory = ( _ ) => "not 42"
206+ DefaultValueFactory = _ => "not 42"
205207 } ;
206208 var command = new Command ( "the-command" , "command help" )
207209 {
@@ -313,9 +315,9 @@ public void Argument_can_fallback_to_default_when_customizing(
313315
314316 CommandLineConfiguration config = new ( command ) ;
315317
316- command . Options . Add ( new HelpOption ( )
318+ command . Options . Add ( new HelpOption
317319 {
318- Action = new HelpAction ( )
320+ Action = new HelpAction
319321 {
320322 Builder = helpBuilder
321323 }
@@ -325,6 +327,246 @@ public void Argument_can_fallback_to_default_when_customizing(
325327 command . Parse ( "test -h" , config ) . Invoke ( ) ;
326328 config . Output . ToString ( ) . Should ( ) . MatchRegex ( expected ) ;
327329 }
330+
331+
332+ [ Fact ]
333+ public void Individual_symbols_can_be_customized ( )
334+ {
335+ var subcommand = new Command ( "subcommand" , "The default command description" ) ;
336+ var option = new Option < int > ( "-x" ) { Description = "The default option description" } ;
337+ var argument = new Argument < int > ( "int-value" ) { Description = "The default argument description" } ;
338+
339+ var rootCommand = new RootCommand
340+ {
341+ subcommand ,
342+ option ,
343+ argument ,
344+ } ;
345+
346+ CommandLineConfiguration config = new ( rootCommand )
347+ {
348+ Output = new StringWriter ( )
349+ } ;
350+
351+ ParseResult parseResult = rootCommand . Parse ( "-h" , config ) ;
352+
353+ if ( parseResult . Action is HelpAction helpAction )
354+ {
355+ helpAction . Builder . CustomizeSymbol ( subcommand , secondColumnText : "The custom command description" ) ;
356+ helpAction . Builder . CustomizeSymbol ( option , secondColumnText : "The custom option description" ) ;
357+ helpAction . Builder . CustomizeSymbol ( argument , secondColumnText : "The custom argument description" ) ;
358+ }
359+
360+ parseResult . Invoke ( ) ;
361+
362+ config . Output
363+ . ToString ( )
364+ . Should ( )
365+ . ContainAll ( "The custom command description" ,
366+ "The custom option description" ,
367+ "The custom argument description" ) ;
368+ }
369+
370+ [ Fact ]
371+ public void Help_sections_can_be_replaced ( )
372+ {
373+ CommandLineConfiguration config = new ( new RootCommand ( ) )
374+ {
375+ Output = new StringWriter ( )
376+ } ;
377+
378+ ParseResult parseResult = config . Parse ( "-h" ) ;
379+
380+ if ( parseResult . Action is HelpAction helpAction )
381+ {
382+ helpAction . Builder . CustomizeLayout ( CustomLayout ) ;
383+ }
384+
385+ parseResult . Invoke ( ) ;
386+
387+ config . Output . ToString ( ) . Should ( ) . Be ( $ "one{ NewLine } { NewLine } two{ NewLine } { NewLine } three{ NewLine } { NewLine } { NewLine } ") ;
388+
389+ IEnumerable < Action < HelpContext > > CustomLayout ( HelpContext _ )
390+ {
391+ yield return ctx => ctx . Output . WriteLine ( "one" ) ;
392+ yield return ctx => ctx . Output . WriteLine ( "two" ) ;
393+ yield return ctx => ctx . Output . WriteLine ( "three" ) ;
394+ }
395+ }
396+
397+ [ Fact ]
398+ public void Help_sections_can_be_supplemented ( )
399+ {
400+ CommandLineConfiguration config = new ( new RootCommand ( "hello" ) )
401+ {
402+ Output = new StringWriter ( ) ,
403+ } ;
404+
405+ ParseResult parseResult = config . Parse ( "-h" ) ;
406+
407+ if ( parseResult . Action is HelpAction helpAction )
408+ {
409+ helpAction . Builder . CustomizeLayout ( CustomLayout ) ;
410+ }
411+
412+ parseResult . Invoke ( ) ;
413+
414+ var output = config . Output . ToString ( ) ;
415+ var defaultHelp = GetDefaultHelp ( config . RootCommand ) ;
416+
417+ var expected = $ "first{ NewLine } { NewLine } { defaultHelp } last{ NewLine } { NewLine } ";
418+
419+ output . Should ( ) . Be ( expected ) ;
420+
421+ IEnumerable < Action < HelpContext > > CustomLayout ( HelpContext _ )
422+ {
423+ yield return ctx => ctx . Output . WriteLine ( "first" ) ;
424+
425+ foreach ( var section in HelpBuilder . Default . GetLayout ( ) )
426+ {
427+ yield return section ;
428+ }
429+
430+ yield return ctx => ctx . Output . WriteLine ( "last" ) ;
431+ }
432+ }
433+
434+ [ Fact ]
435+ public void Layout_can_be_composed_dynamically_based_on_context ( )
436+ {
437+ HelpBuilder helpBuilder = new ( ) ;
438+ var commandWithTypicalHelp = new Command ( "typical" ) ;
439+ var commandWithCustomHelp = new Command ( "custom" ) ;
440+ var command = new RootCommand
441+ {
442+ commandWithTypicalHelp ,
443+ commandWithCustomHelp
444+ } ;
445+
446+ command . Options . OfType < HelpOption > ( ) . Single ( ) . Action = new HelpAction ( )
447+ {
448+ Builder = helpBuilder
449+ } ;
450+
451+ var config = new CommandLineConfiguration ( command ) ;
452+ helpBuilder . CustomizeLayout ( c =>
453+ c . Command == commandWithTypicalHelp
454+ ? HelpBuilder . Default . GetLayout ( )
455+ : new Action < HelpContext > [ ]
456+ {
457+ c => c . Output . WriteLine ( "Custom layout!" )
458+ }
459+ . Concat ( HelpBuilder . Default . GetLayout ( ) ) ) ;
460+
461+ var typicalOutput = new StringWriter ( ) ;
462+ config . Output = typicalOutput ;
463+ command . Parse ( "typical -h" , config ) . Invoke ( ) ;
464+
465+ var customOutput = new StringWriter ( ) ;
466+ config . Output = customOutput ;
467+ command . Parse ( "custom -h" , config ) . Invoke ( ) ;
468+
469+ typicalOutput . ToString ( ) . Should ( ) . Be ( GetDefaultHelp ( commandWithTypicalHelp , false ) ) ;
470+ customOutput . ToString ( ) . Should ( ) . Be ( $ "Custom layout!{ NewLine } { NewLine } { GetDefaultHelp ( commandWithCustomHelp , false ) } ") ;
471+ }
472+
473+ [ Fact ]
474+ public void Help_default_sections_can_be_wrapped ( )
475+ {
476+ Command command = new ( "test" )
477+ {
478+ new Option < string > ( "--option" )
479+ {
480+ Description = "option description" ,
481+ HelpName = "option"
482+ } ,
483+ new HelpOption
484+ {
485+ Action = new HelpAction
486+ {
487+ Builder = new HelpBuilder ( 30 )
488+ }
489+ }
490+ } ;
491+
492+ CommandLineConfiguration config = new ( command )
493+ {
494+ Output = new StringWriter ( )
495+ } ;
496+
497+ config . Invoke ( "test -h" ) ;
498+
499+ string result = config . Output . ToString ( ) ;
500+ result . Should ( ) . Be (
501+ $ "Description:{ NewLine } { NewLine } " +
502+ $ "Usage:{ NewLine } test [options]{ NewLine } { NewLine } " +
503+ $ "Options:{ NewLine } " +
504+ $ " --option option { NewLine } " +
505+ $ " <option> description{ NewLine } " +
506+ $ " -?, -h, Show help and { NewLine } " +
507+ $ " --help usage { NewLine } " +
508+ $ " information{ NewLine } { NewLine } { NewLine } ") ;
509+ }
510+
511+ [ Fact ]
512+ public void Help_customized_sections_can_be_wrapped ( )
513+ {
514+ CommandLineConfiguration config = new ( new RootCommand ( ) )
515+ {
516+ Output = new StringWriter ( )
517+ } ;
518+
519+ ParseResult parseResult = config . Parse ( "-h" ) ;
520+
521+ if ( parseResult . Action is HelpAction helpAction )
522+ {
523+ helpAction . Builder = new HelpBuilder ( 10 ) ;
524+ helpAction . Builder . CustomizeLayout ( CustomLayout ) ;
525+ }
526+
527+ parseResult . Invoke ( ) ;
528+
529+ string result = config . Output . ToString ( ) ;
530+ result . Should ( ) . Be ( $ " 123 123{ NewLine } 456 456{ NewLine } 78 789{ NewLine } 0{ NewLine } { NewLine } { NewLine } ") ;
531+
532+ IEnumerable < Action < HelpContext > > CustomLayout ( HelpContext _ )
533+ {
534+ yield return ctx => ctx . HelpBuilder . WriteColumns ( new [ ] { new TwoColumnHelpRow ( "12345678" , "1234567890" ) } , ctx ) ;
535+ }
536+ }
537+
538+ private string GetDefaultHelp ( Command command , bool trimOneNewline = true )
539+ {
540+ // The command might have already defined a HelpOption with custom settings,
541+ // we need to overwrite it to get the actual defaults.
542+ HelpOption defaultHelp = new ( ) ;
543+ // HelpOption overrides Equals and treats every other instance of same type as equal
544+ int index = command . Options . IndexOf ( defaultHelp ) ;
545+ if ( index >= 0 )
546+ {
547+ command . Options [ index ] = defaultHelp ;
548+ }
549+ else
550+ {
551+ command . Options . Add ( defaultHelp ) ;
552+ }
553+
554+ CommandLineConfiguration config = new ( command )
555+ {
556+ Output = new StringWriter ( )
557+ } ;
558+
559+ config . Invoke ( "-h" ) ;
560+
561+ var output = config . Output . ToString ( ) ;
562+
563+ if ( trimOneNewline )
564+ {
565+ output = output . Substring ( 0 , output . Length - NewLine . Length ) ;
566+ }
567+
568+ return output ;
569+ }
328570 }
329571 }
330572}
0 commit comments