10000 make HelpBuilder and related APIs internal by adamsitnik · Pull Request #2563 · dotnet/command-line-api · GitHub
[go: up one dir, main page]

Skip to content

make HelpBuilder and related APIs internal #2563

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
public System.Collections.Generic.List<System.Action<System.CommandLine.Parsing.OptionResult>> Validators { get; }
public System.Type ValueType { get; }
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
public System.Object GetDefaultValue()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed this to be able to avoid using Option.Argument (internal getter), it contributes to #2526

public class Option<T> : Option
.ctor(System.String name, System.String[] aliases)
public Func<System.CommandLine.Parsing.ArgumentResult,T> CustomParser { get; set; }
Expand Down Expand Up @@ -128,6 +129,7 @@
public System.CommandLine.Parsing.OptionResult GetResult(Option option)
public System.CommandLine.Parsing.DirectiveResult GetResult(Directive directive)
public System.CommandLine.Parsing.SymbolResult GetResult(Symbol symbol)
public System.CommandLine.Parsing.SymbolResult GetResult(System.String name)
public T GetValue<T>(Argument<T> argument)
public T GetValue<T>(Option<T> option)
public T GetValue<T>(System.String name)
Expand Down Expand Up @@ -178,48 +180,11 @@ System.CommandLine.Completions
System.CommandLine.Help
public class HelpAction : System.CommandLine.Invocation.SynchronousCommandLineAction
.ctor()
public HelpBuilder Builder { get; set; }
public System.Int32 Invoke(System.CommandLine.ParseResult parseResult)
public class HelpBuilder
.ctor(System.Int32 maxWidth = 2147483647)
public System.Int32 MaxWidth { get; }
public System.Void CustomizeLayout(System.Func<HelpContext,System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>>> getLayout)
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)
public System.Void CustomizeSymbol(System.CommandLine.Symbol symbol, System.String firstColumnText = null, System.String secondColumnText = null, System.String defaultValue = null)
public TwoColumnHelpRow GetTwoColumnRow(System.CommandLine.Symbol symbol, HelpContext context)
public System.Void Write(HelpContext context)
public System.Void Write(System.CommandLine.Command command, System.IO.TextWriter writer)
public System.Void WriteColumns(System.Collections.Generic.IReadOnlyList<TwoColumnHelpRow> items, HelpContext context)
static class Default
public static System.Func<HelpContext,System.Boolean> AdditionalArgumentsSection()
public static System.Func<HelpContext,System.Boolean> CommandArgumentsSection()
public static System.Func<HelpContext,System.Boolean> CommandUsageSection()
public static System.String GetArgumentDefaultValue(System.CommandLine.Argument argument)
public static System.String GetArgumentDescription(System.CommandLine.Argument argument)
public static System.String GetArgumentUsageLabel(System.CommandLine.Argument argument)
public static System.String GetCommandUsageLabel(System.CommandLine.Command symbol)
public static System.Collections.Generic.IEnumerable<System.Func<HelpContext,System.Boolean>> GetLayout()
public static System.String GetOptionUsageLabel(System.CommandLine.Option symbol)
public static System.Func<HelpContext,System.Boolean> OptionsSection()
public static System.Func<HelpContext,System.Boolean> SubcommandsSection()
public static System.Func<HelpContext,System.Boolean> SynopsisSection()
public class HelpContext
.ctor(HelpBuilder helpBuilder, System.CommandLine.Command command, System.IO.TextWriter output, System.CommandLine.ParseResult parseResult = null)
public System.CommandLine.Command Command { get; }
public HelpBuilder HelpBuilder { get; }
public System.IO.TextWriter Output { get; }
public System.CommandLine.ParseResult ParseResult { get; }
public class HelpOption : System.CommandLine.Option<System.Boolean>
.ctor()
.ctor(System.String name, System.String[] aliases)
public System.CommandLine.Invocation.CommandLineAction Action { get; set; }
public class TwoColumnHelpRow, System.IEquatable<TwoColumnHelpRow>
.ctor(System.String firstColumnText, System.String secondColumnText)
public System.String FirstColumnText { get; }
public System.String SecondColumnText { get; }
public System.Boolean Equals(System.Object obj)
public System.Boolean Equals(TwoColumnHelpRow other)
public System.Int32 GetHashCode()
System.CommandLine.Invocation
public abstract class AsynchronousCommandLineAction : CommandLineAction
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(System.CommandLine.ParseResult parseResult, System.Threading.CancellationToken cancellationToken = null)
Expand Down
42 changes: 42 additions & 0 deletions src/System.CommandLine.Tests/GetValueByNameParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,48 @@ public void When_an_option_and_argument_use_same_name_on_the_same_level_of_the_t
.Where(ex => ex.Message == $"Command {command.Name} has more than one child named \"{sameName}\".");
}

[Fact]
public void When_options_use_same_name_on_different_levels_of_the_tree_no_exception_is_thrown()
{
const string sameName = "same";

RootCommand command = new()
{
new Command("left")
{
new Option<int>(sameName)
},
new Command("right")
{
new Option<int>(sameName)
},
};

command.Parse($"left {sameName} 1").GetValue<int>(sameName).Should().Be(1);
command.Parse($"right {sameName} 2").GetValue<int>(sameName).Should().Be(2);
}

[Fact]
public void When_the_same_option_used_in_different_levels_of_the_tree_no_exception_is_thrown()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a test for a bug I've hit while working on dotnet/sdk#49181:

System.InvalidOperationException: Command build has more than one child named "--interactive".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd quibble about the test name here. The intent is that the option is correctly attributed to the correct descendant.

{
Option<int> multipleParents = new("--int");

RootCommand command = new()
{
new Command("left")
{
multipleParents
},
new Command("right")
{
multipleParents
},
};

command.Parse($"left {multipleParents.Name} 1").GetValue<int>(multipleParents.Name).Should().Be(1);
command.Parse($"right {multipleParents.Name} 2").GetValue<int>(multipleParents.Name).Should().Be(2);
}

[Fact]
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()
{
Expand Down
35 changes: 35 additions & 0 deletions src/System.CommandLine.Tests/Help/CustomHelpAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.CommandLine.Invocation;

namespace System.CommandLine.Help
{
/// <summary>
/// Provides command line help.
/// </summary>
public sealed class CustomHelpAction : SynchronousCommandLineAction
{
private HelpBuilder? _builder;

/// <summary>
/// Specifies an <see cref="Builder"/> to be used to format help output when help is requested.
/// </summary>
internal HelpBuilder Builder
{
get => _builder ??= new HelpBuilder(Console.IsOutputRedirected ? int.MaxValue : Console.WindowWidth);
set => _builder = value ?? throw new ArgumentNullException(nameof(value));
}

/// <inheritdoc />
public override int Invoke(ParseResult parseResult)
{
var output = parseResult.Configuration.Output;

var helpContext = new HelpContext(Builder,
parseResult.CommandResult.Command,
output);

Builder.Write(helpContext);

return 0;
}
}
}
2 changes: 1 addition & 1 deletion src/System.CommandLine.Tests/Help/HelpBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.CommandLine.Tests.Help
{
public static class HelpBuilderExtensions
{
public static void Write(
internal static void Write(
this HelpBuilder builder,
Command command,
TextWriter writer) =>
Expand Down
64 changes: 30 additions & 34 deletions src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public void Option_can_customize_first_column_text_based_on_parse_result()
: optionBFirstColumnText);
command.Options.Add(new HelpOption()
{
Action = new HelpAction()
Action = new CustomHelpAction()
{
Builder = helpBuilder
}
Expand Down Expand Up @@ -140,7 +140,7 @@ public void Option_can_customize_second_column_text_based_on_parse_result()
: optionBDescription);
command.Options.Add(new HelpOption
{
Action = new HelpAction
Action = new CustomHelpAction
{
Builder = helpBuilder
}
Expand Down Expand Up @@ -269,7 +269,7 @@ public void Option_can_fallback_to_default_when_customizing(bool conditionA, boo

command.Options.Add(new HelpOption
{
Action = new HelpAction
Action = new CustomHelpAction
{
Builder = helpBuilder
}
Expand Down Expand Up @@ -317,7 +317,7 @@ public void Argument_can_fallback_to_default_when_customizing(

command.Options.Add(new HelpOption
{
Action = new HelpAction
Action = new CustomHelpAction
{
Builder = helpBuilder
}
Expand All @@ -336,11 +336,20 @@ public void Individual_symbols_can_be_customized()
var option = new Option<int>("-x") { Description = "The default option description" };
var argument = new Argument<int>("int-value") { Description = "The default argument description" };

var rootCommand = new RootCommand
CustomHelpAction helpAction = new();
helpAction.Builder.CustomizeSymbol(subcommand, secondColumnText: "The custom command description");
helpAction.Builder.CustomizeSymbol(option, secondColumnText: "The custom option description");
helpAction.Builder.CustomizeSymbol(argument, secondColumnText: "The custom argument description");

var rootCommand = new Command("name")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using Command rather than RootCommand as it allows me to avoid the need of replacing HelpOption defined in RootCommand.Options

{
subcommand,
option,
argument,
new HelpOption()
{
Action = helpAction
}
};

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

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

if (parseResult.Action is HelpAction helpAction)
{
helpAction.Builder.CustomizeSymbol(subcommand, secondColumnText: "The custom command description");
helpAction.Builder.CustomizeSymbol(option, secondColumnText: "The custom option description");
helpAction.Builder.CustomizeSymbol(argument, secondColumnText: "The custom argument description");
}

parseResult.Invoke();

config.Output
Expand All @@ -370,18 +372,16 @@ public void Individual_symbols_can_be_customized()
[Fact]
public void Help_sections_can_be_replaced()
{
CommandLineConfiguration config = new(new RootCommand())
CustomHelpAction helpAction = new();
helpAction.Builder.CustomizeLayout(CustomLayout);

CommandLineConfiguration config = new(new Command("name") { new HelpOption() { Action = helpAction} })
{
Output = new StringWriter()
};

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

if (parseResult.Action is HelpAction helpAction)
{
helpAction.Builder.CustomizeLayout(CustomLayout);
}

parseResult.Invoke();

config.Output.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}");
Expand All @@ -397,20 +397,18 @@ IEnumerable<Func<HelpContext, bool>> CustomLayout(HelpContext _)
[Fact]
public void Help_sections_can_be_supplemented()
{
CommandLineConfiguration config = new(new RootCommand("hello"))
CustomHelpAction helpAction = new();
helpAction.Builder.CustomizeLayout(CustomLayout);

CommandLineConfiguration config = new(new Command("hello") { new HelpOption() { Action = helpAction } })
{
Output = new StringWriter(),
};

var defaultHelp = GetDefaultHelp(config.RootCommand);
var defaultHelp = GetDefaultHelp(new Command("hello"));

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

if (parseResult.Action is HelpAction helpAction)
{
helpAction.Builder.CustomizeLayout(CustomLayout);
}

parseResult.Invoke();

var output = config.Output.ToString();
Expand Down Expand Up @@ -444,7 +442,7 @@ public void Layout_can_be_composed_dynamically_based_on_context()
commandWithCustomHelp
};

command.Options.OfType<HelpOption>().Single().Action = new HelpAction
command.Options.OfType<HelpOption>().Single().Action = new CustomHelpAction
{
Builder = helpBuilder
};
Expand Down Expand Up @@ -480,7 +478,7 @@ public void Help_default_sections_can_be_wrapped()
},
new HelpOption
{
Action = new HelpAction
Action = new CustomHelpAction
{
Builder = new HelpBuilder(30)
}
Expand Down Expand Up @@ -509,19 +507,17 @@ public void Help_default_sections_can_be_wrapped()
[Fact]
public void Help_customized_sections_can_be_wrapped()
{
CommandLineConfiguration config = new(new RootCommand())
CustomHelpAction helpAction = new();
helpAction.Builder = new HelpBuilder(10);
helpAction.Builder.CustomizeLayout(CustomLayout);

CommandLineConfiguration config = new(new Command("name") { new HelpOption() { Action = helpAction } })
{
Output = new StringWriter()
};

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

if (parseResult.Action is HelpAction helpAction)
{
helpAction.Builder = new HelpBuilder(10);
helpAction.Builder.CustomizeLayout(CustomLayout);
}

parseResult.Invoke();

string result = config.Output.ToString();
Expand Down
Loading
Loading
0