Skip to content

Commit 8363e58

Browse files
authored
make HelpBuilder and related APIs internal (#2563)
* make HelpBuilder and related APIs internal * don't use InternalsVisibleTo, link the code and stop using internal APIs, * expose Option.GetDefaultValue * add tests based on CLR and diagnostics tools use cases * add ParseResult.GetResult(string name) * The same symbol can be added to multiple commands and have multiple parents. We can't allow for name duplicates within the same parent (command).
1 parent e22447d commit 8363e58

20 files changed

+385
-248
lines changed

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
public System.Collections.Generic.List<System.Action<System.CommandLine.Parsing.OptionResult>> Validators { get; }
101101
public System.Type ValueType { get; }
102102
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
103+
public System.Object GetDefaultValue()
103104
public class Option<T> : Option
104105
.ctor(System.String name, System.String[] aliases)
105106
public Func<System.CommandLine.Parsing.ArgumentResult,T> CustomParser { get; set; }
@@ -128,6 +129,7 @@
128129
public System.CommandLine.Parsing.OptionResult GetResult(Option option)
129130
public System.CommandLine.Parsing.DirectiveResult GetResult(Directive directive)
130131
public System.CommandLine.Parsing.SymbolResult GetResult(Symbol symbol)
132+
public System.CommandLine.Parsing.SymbolResult GetResult(System.String name)
131133
public T GetValue<T>(Argument<T> argument)
132134
public T GetValue<T>(Option<T> option)
133135
public T GetValue<T>(System.String name)
@@ -178,48 +180,11 @@ System.CommandLine.Completions
178180
System.CommandLine.Help
179181
public class HelpAction : System.CommandLine.Invocation.SynchronousCommandLineAction
180182
.ctor()
181-
public HelpBuilder Builder { get; set; }
182183
public System.Int32 Invoke(System.CommandLine.ParseResult parseResult)
183-
public class HelpBuilder
184-
.ctor(System.Int32 maxWidth = 2147483647)
185-
public System.Int32 MaxWidth { get; }
186-
public System.Void CustomizeLayout(System.Func<HelpContext,System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>>> getLayout)
187-
public System.Void CustomizeSymbol(System.CommandLine.Symbol symbol, System.Func<HelpContext,System.String> firstColumnText = null, System.Func<HelpContext,System.String> secondColumnText = null, System.Func<HelpContext,System.String> defaultValue = null)
188-
public System.Void CustomizeSymbol(System.CommandLine.Symbol symbol, System.String firstColumnText = null, System.String secondColumnText = null, System.String defaultValue = null)
189-
public TwoColumnHelpRow GetTwoColumnRow(System.CommandLine.Symbol symbol, HelpContext context)
190-
public System.Void Write(HelpContext context)
191-
public System.Void Write(System.CommandLine.Command command, System.IO.TextWriter writer)
192-
public System.Void WriteColumns(System.Collections.Generic.IReadOnlyList<TwoColumnHelpRow> items, HelpContext context)
193-
static class Default
194-
public static System.Func<HelpContext,System.Boolean> AdditionalArgumentsSection()
195-
public static System.Func<HelpContext,System.Boolean> CommandArgumentsSection()
196-
public static System.Func<HelpContext,System.Boolean> CommandUsageSection()
197-
public static System.String GetArgumentDefaultValue(System.CommandLine.Argument argument)
198-
public static System.String GetArgumentDescription(System.CommandLine.Argument argument)
199-
public static System.String GetArgumentUsageLabel(System.CommandLine.Argument argument)
200-
public static System.String GetCommandUsageLabel(System.CommandLine.Command symbol)
201-
public static System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>> GetLayout()
202-
public static System.String GetOptionUsageLabel(System.CommandLine.Option symbol)
203-
public static System.Func<HelpContext,System.Boolean> OptionsSection()
204-
public static System.Func<HelpContext,System.Boolean> SubcommandsSection()
205-
public static System.Func<HelpContext,System.Boolean> SynopsisSection()
206-
public class HelpContext
207-
.ctor(HelpBuilder helpBuilder, System.CommandLine.Command command, System.IO.TextWriter output, System.CommandLine.ParseResult parseResult = null)
208-
public System.CommandLine.Command Command { get; }
209-
public HelpBuilder HelpBuilder { get; }
210-
public System.IO.TextWriter Output { get; }
211-
public System.CommandLine.ParseResult ParseResult { get; }
212184
public class HelpOption : System.CommandLine.Option<System.Boolean>
213185
.ctor()
214186
.ctor(System.String name, System.String[] aliases)
215187
public System.CommandLine.Invocation.CommandLineAction Action { get; set; }
216-
public class TwoColumnHelpRow, System.IEquatable<TwoColumnHelpRow>
217-
.ctor(System.String firstColumnText, System.String secondColumnText)
218-
public System.String FirstColumnText { get; }
219-
public System.String SecondColumnText { get; }
220-
public System.Boolean Equals(System.Object obj)
221-
public System.Boolean Equals(TwoColumnHelpRow other)
222-
public System.Int32 GetHashCode()
223188
System.CommandLine.Invocation
224189
public abstract class AsynchronousCommandLineAction : CommandLineAction
225190
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(System.CommandLine.ParseResult parseResult, System.Threading.CancellationToken cancellationToken = null)

src/System.CommandLine.Tests/GetValueByNameParserTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,48 @@ public void When_an_option_and_argument_use_same_name_on_the_same_level_of_the_t
194194
.Where(ex => ex.Message == $"Command {command.Name} has more than one child named \"{sameName}\".");
195195
}
196196

197+
[Fact]
198+
public void When_options_use_same_name_on_different_levels_of_the_tree_no_exception_is_thrown()
199+
{
200+
const string sameName = "same";
201+
202+
RootCommand command = new()
203+
{
204+
new Command("left")
205+
{
206+
new Option<int>(sameName)
207+
},
208+
new Command("right")
209+
{
210+
new Option<int>(sameName)
211+
},
212+
};
213+
214+
command.Parse($"left {sameName} 1").GetValue<int>(sameName).Should().Be(1);
215+
command.Parse($"right {sameName} 2").GetValue<int>(sameName).Should().Be(2);
216+
}
217+
218+
[Fact]
219+
public void When_the_same_option_used_in_different_levels_of_the_tree_no_exception_is_thrown()
220+
{
221+
Option<int> multipleParents = new("--int");
222+
223+
RootCommand command = new()
224+
{
225+
new Command("left")
226+
{
227+
multipleParents
228+
},
229+
new Command("right")
230+
{
231+
multipleParents
232+
},
233+
};
234+
235+
command.Parse($"left {multipleParents.Name} 1").GetValue<int>(multipleParents.Name).Should().Be(1);
236+
command.Parse($"right {multipleParents.Name} 2").GetValue<int>(multipleParents.Name).Should().Be(2);
237+
}
238+
197239
[Fact]
198240
public void When_an_option_and_argument_use_same_name_on_different_levels_of_the_tree_the_value_which_belongs_to_parsed_command_is_returned()
199241
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.CommandLine.Invocation;
2+
3+
namespace System.CommandLine.Help
4+
{
5+
/// <summary>
6+
/// Provides command line help.
7+
/// </summary>
8+
public sealed class CustomHelpAction : SynchronousCommandLineAction
9+
{
10+
private HelpBuilder? _builder;
11+
12+
/// <summary>
13+
/// Specifies an <see cref="Builder"/> to be used to format help output when help is requested.
14+
/// </summary>
15+
internal HelpBuilder Builder
16+
{
17+
get => _builder ??= new HelpBuilder(Console.IsOutputRedirected ? int.MaxValue : Console.WindowWidth);
18+
set => _builder = value ?? throw new ArgumentNullException(nameof(value));
19+
}
20+
21+
/// <inheritdoc />
22+
public override int Invoke(ParseResult parseResult)
23+
{
24+
var output = parseResult.Configuration.Output;
25+
26+
var helpContext = new HelpContext(Builder,
27+
parseResult.CommandResult.Command,
28+
output);
29+
30+
Builder.Write(helpContext);
31+
32+
return 0;
33+
}
34+
}
35+
}

src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace System.CommandLine.Tests.Help
88
{
99
public static class HelpBuilderExtensions
1010
{
11-
public static void Write(
11+
internal static void Write(
1212
this HelpBuilder builder,
1313
Command command,
1414
TextWriter writer) =>

src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void Option_can_customize_first_column_text_based_on_parse_result()
9494
: optionBFirstColumnText);
9595
command.Options.Add(new HelpOption()
9696
{
97-
Action = new HelpAction()
97+
Action = new CustomHelpAction()
9898
{
9999
Builder = helpBuilder
100100
}
@@ -140,7 +140,7 @@ public void Option_can_customize_second_column_text_based_on_parse_result()
140140
: optionBDescription);
141141
command.Options.Add(new HelpOption
142142
{
143-
Action = new HelpAction
143+
Action = new CustomHelpAction
144144
{
145145
Builder = helpBuilder
146146
}
@@ -269,7 +269,7 @@ public void Option_can_fallback_to_default_when_customizing(bool conditionA, boo
269269

270270
command.Options.Add(new HelpOption
271271
{
272-
Action = new HelpAction
272+
Action = new CustomHelpAction
273273
{
274274
Builder = helpBuilder
275275
}
@@ -317,7 +317,7 @@ public void Argument_can_fallback_to_default_when_customizing(
317317

318318
command.Options.Add(new HelpOption
319319
{
320-
Action = new HelpAction
320+
Action = new CustomHelpAction
321321
{
322322
Builder = helpBuilder
323323
}
@@ -336,11 +336,20 @@ public void Individual_symbols_can_be_customized()
336336
var option = new Option<int>("-x") { Description = "The default option description" };
337337
var argument = new Argument<int>("int-value") { Description = "The default argument description" };
338338

339-
var rootCommand = new RootCommand
339+
CustomHelpAction helpAction = new();
340+
helpAction.Builder.CustomizeSymbol(subcommand, secondColumnText: "The custom command description");
341+
helpAction.Builder.CustomizeSymbol(option, secondColumnText: "The custom option description");
342+
helpAction.Builder.CustomizeSymbol(argument, secondColumnText: "The custom argument description");
343+
344+
var rootCommand = new Command("name")
340345
{
341346
subcommand,
342347
option,
343348
argument,
349+
new HelpOption()
350+
{
351+
Action = helpAction
352+
}
344353
};
345354

346355
CommandLineConfiguration config = new(rootCommand)
@@ -350,13 +359,6 @@ public void Individual_symbols_can_be_customized()
350359

351360
ParseResult parseResult = rootCommand.Parse("-h", config);
352361

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-
360362
parseResult.Invoke();
361363

362364
config.Output
@@ -370,18 +372,16 @@ public void Individual_symbols_can_be_customized()
370372
[Fact]
371373
public void Help_sections_can_be_replaced()
372374
{
373-
CommandLineConfiguration config = new(new RootCommand())
375+
CustomHelpAction helpAction = new();
376+
helpAction.Builder.CustomizeLayout(CustomLayout);
377+
378+
CommandLineConfiguration config = new(new Command("name") { new HelpOption() { Action = helpAction} })
374379
{
375380
Output = new StringWriter()
376381
};
377382

378383
ParseResult parseResult = config.Parse("-h");
379384

380-
if (parseResult.Action is HelpAction helpAction)
381-
{
382-
helpAction.Builder.CustomizeLayout(CustomLayout);
383-
}
384-
385385
parseResult.Invoke();
386386

387387
config.Output.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}");
@@ -397,20 +397,18 @@ IEnumerable<Func<HelpContext, bool>> CustomLayout(HelpContext _)
397397
[Fact]
398398
public void Help_sections_can_be_supplemented()
399399
{
400-
CommandLineConfiguration config = new(new RootCommand("hello"))
400+
CustomHelpAction helpAction = new();
401+
helpAction.Builder.CustomizeLayout(CustomLayout);
402+
403+
CommandLineConfiguration config = new(new Command("hello") { new HelpOption() { Action = helpAction } })
401404
{
402405
Output = new StringWriter(),
403406
};
404407

405-
var defaultHelp = GetDefaultHelp(config.RootCommand);
408+
var defaultHelp = GetDefaultHelp(new Command("hello"));
406409

407410
ParseResult parseResult = config.Parse("-h");
408411

409-
if (parseResult.Action is HelpAction helpAction)
410-
{
411-
helpAction.Builder.CustomizeLayout(CustomLayout);
412-
}
413-
414412
parseResult.Invoke();
415413

416414
var output = config.Output.ToString();
@@ -444,7 +442,7 @@ public void Layout_can_be_composed_dynamically_based_on_context()
444442
commandWithCustomHelp
445443
};
446444

447-
command.Options.OfType<HelpOption>().Single().Action = new HelpAction
445+
command.Options.OfType<HelpOption>().Single().Action = new CustomHelpAction
448446
{
449447
Builder = helpBuilder
450448
};
@@ -480,7 +478,7 @@ public void Help_default_sections_can_be_wrapped()
480478
},
481479
new HelpOption
482480
{
483-
Action = new HelpAction
481+
Action = new CustomHelpAction
484482
{
485483
Builder = new HelpBuilder(30)
486484
}
@@ -509,19 +507,17 @@ public void Help_default_sections_can_be_wrapped()
509507
[Fact]
510508
public void Help_customized_sections_can_be_wrapped()
511509
{
512-
CommandLineConfiguration config = new(new RootCommand())
510+
CustomHelpAction helpAction = new();
511+
helpAction.Builder = new HelpBuilder(10);
512+
helpAction.Builder.CustomizeLayout(CustomLayout);
513+
514+
CommandLineConfiguration config = new(new Command("name") { new HelpOption() { Action = helpAction } })
513515
{
514516
Output = new StringWriter()
515517
};
516518

517519
ParseResult parseResult = config.Parse("-h");
518520

519-
if (parseResult.Action is HelpAction helpAction)
520-
{
521-
helpAction.Builder = new HelpBuilder(10);
522-
helpAction.Builder.CustomizeLayout(CustomLayout);
523-
}
524-
525521
parseResult.Invoke();
526522

527523
string result = config.Output.ToString();

0 commit comments

Comments
 (0)