Skip to content
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 @@ -265,7 +265,7 @@ System.CommandLine.Builder
public static CommandLineBuilder UseExceptionHandler(System.Action<System.Exception,System.CommandLine.Invocation.InvocationContext> onException = null, System.Nullable<System.Int32> errorExitCode = null)
public static CommandLineBuilder UseHelp()
public static CommandLineBuilder UseHelp(System.String[] helpAliases)
public static CommandLineBuilder UseHelp(System.Func<System.CommandLine.Help.HelpContext,System.Collections.Generic.IEnumerable<System.CommandLine.Help.HelpDelegate>> layout)
public static CommandLineBuilder UseHelp(System.Action<System.CommandLine.Help.HelpContext> customize)
public static TBuilder UseHelpBuilder<TBuilder>(System.Func<System.CommandLine.Binding.BindingContext,System.CommandLine.Help.HelpBuilder> getHelpBuilder)
public static CommandLineBuilder UseLocalizationResources(System.CommandLine.LocalizationResources validationMessages)
public static CommandLineBuilder UseMiddleware(System.CommandLine.Invocation.InvocationMiddleware middleware, System.CommandLine.Invocation.MiddlewareOrder order = Default)
Expand Down Expand Up @@ -322,23 +322,23 @@ System.CommandLine.Completions
public class TokenCompletionContext : CompletionContext
System.CommandLine.Help
public class HelpBuilder
public static HelpDelegate AdditionalArgumentsSection()
public static HelpDelegate CommandArgumentsSection()
public static HelpDelegate CommandUsageSection()
public static System.Collections.Generic.IEnumerable<HelpDelegate> DefaultLayout()
public static HelpDelegate OptionsSection()
public static HelpDelegate SubcommandsSection()
public static HelpDelegate SynopsisSection()
.ctor(System.CommandLine.LocalizationResources localizationResources, System.Int32 maxWidth = 2147483647, System.Func<HelpContext,System.Collections.Generic.IEnumerable<HelpDelegate>> getLayout = null)
public static HelpSectionDelegate AdditionalArgumentsSection()
public static HelpSectionDelegate CommandArgumentsSection()
public static HelpSectionDelegate CommandUsageSection()
public static System.Collections.Generic.IEnumerable<HelpSectionDelegate> DefaultLayout()
public static HelpSectionDelegate OptionsSection()
public static HelpSectionDelegate SubcommandsSection()
public static HelpSectionDelegate SynopsisSection()
.ctor(System.CommandLine.LocalizationResources localizationResources, System.Int32 maxWidth = 2147483647)
public System.CommandLine.LocalizationResources LocalizationResources { get; }
public System.Int32 MaxWidth { get; }
public System.Void Customize(System.CommandLine.ISymbol symbol, System.Func<HelpContext,System.String> firstColumnText = null, System.Func<HelpContext,System.String> secondColumnText = null, System.Func<HelpContext,System.String> defaultValue = null)
public System.Collections.Generic.IEnumerable<HelpDelegate> GetLayout(HelpContext context)
public TwoColumnHelpRow GetTwoColumnRow(System.CommandLine.IIdentifierSymbol symbol, HelpContext context)
public System.Void CustomizeLayout(System.Func<HelpContext,System.Collections.Generic.IEnumerable<HelpSectionDelegate>> getLayout)
public System.Void CustomizeSymbol(System.CommandLine.ISymbol symbol, System.Func<HelpContext,System.String> firstColumnText = null, System.Func<HelpContext,System.String> secondColumnText = null, System.Func<HelpContext,System.String> defaultValue = null)
public TwoColumnHelpRow GetTwoColumnRow(System.CommandLine.ISymbol symbol, HelpContext context)
public System.Void Write(HelpContext context)
public System.Void WriteColumns(System.Collections.Generic.IReadOnlyList<TwoColumnHelpRow> items, HelpContext context)
public static class HelpBuilderExtensions
public static System.Void Customize(System.CommandLine.ISymbol symbol, System.String firstColumnText = null, System.String secondColumnText = null, System.String defaultValue = null)
public static System.Void CustomizeSymbol(System.CommandLine.ISymbol symbol, System.String firstColumnText = null, System.String secondColumnText = null, System.String defaultValue = null)
public static System.Void Write(System.CommandLine.ICommand command, System.IO.TextWriter writer)
public class HelpContext
.ctor(HelpBuilder helpBuilder, System.CommandLine.ICommand command, System.IO.TextWriter output, System.CommandLine.Parsing.ParseResult parseResult = null, System.Nullable<System.Int32> maxWidth = 2147483647)
Expand All @@ -347,7 +347,7 @@ System.CommandLine.Help
public System.Int32 MaxWidth { get; }
public System.IO.TextWriter Output { get; }
public System.CommandLine.Parsing.ParseResult ParseResult { get; }
public delegate HelpDelegate : System.MulticastDelegate, System.ICloneable, System.Runtime.Serialization.ISerializable
public delegate HelpSectionDelegate : System.MulticastDelegate, System.ICloneable, System.Runtime.Serialization.ISerializable
.ctor(System.Object object, System.IntPtr method)
public System.IAsyncResult BeginInvoke(HelpContext context, System.AsyncCallback callback, System.Object object)
public System.Void EndInvoke(System.IAsyncResult result)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public partial class HelpBuilderTests
{
[Fact]
[UseReporter(typeof(DiffReporter))]
public void Help_describes_default_values_for_complex_root_command_scenario()
public void Help_layout_has_not_changed()
{
var command = new RootCommand(description: "Test description")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Option_can_customize_default_value()
option
};

_helpBuilder.Customize(option, defaultValue: "42");
_helpBuilder.CustomizeSymbol(option, defaultValue: "42");

_helpBuilder.Write(command, _console);
var expected =
Expand All @@ -61,7 +61,7 @@ public void Option_can_customize_first_column_text()
option
};

_helpBuilder.Customize(option, firstColumnText: "other-name");
_helpBuilder.CustomizeSymbol(option, firstColumnText: "other-name");

_helpBuilder.Write(command, _console);
var expected =
Expand Down Expand Up @@ -91,7 +91,7 @@ public void Option_can_customize_first_column_text_based_on_parse_result()
var optionBFirstColumnText = "option b help";

var helpBuilder = new HelpBuilder(LocalizationResources.Instance, LargeMaxWidth);
helpBuilder.Customize(option, firstColumnText: ctx =>
helpBuilder.CustomizeSymbol(option, firstColumnText: ctx =>
ctx.Command.Equals(commandA)
? optionAFirstColumnText
: optionBFirstColumnText);
Expand Down Expand Up @@ -129,7 +129,7 @@ public void Option_can_customize_second_column_text_based_on_parse_result()
var optionBDescription = "option b help";

var helpBuilder = new HelpBuilder(LocalizationResources.Instance, LargeMaxWidth);
helpBuilder.Customize(option, secondColumnText: ctx =>
helpBuilder.CustomizeSymbol(option, secondColumnText: ctx =>
ctx.Command.Equals(commandA)
? optionADescription
: optionBDescription);
Expand Down Expand Up @@ -157,7 +157,7 @@ public void Subcommand_can_customize_first_column_text()
subcommand
};

_helpBuilder.Customize(subcommand, firstColumnText: "other-name");
_helpBuilder.CustomizeSymbol(subcommand, firstColumnText: "other-name");

_helpBuilder.Write(command, _console);
var expected =
Expand All @@ -176,7 +176,7 @@ public void Command_arguments_can_customize_first_column_text()
argument
};

_helpBuilder.Customize(argument, firstColumnText: "<CUSTOM-ARG-NAME>");
_helpBuilder.CustomizeSymbol(argument, firstColumnText: "<CUSTOM-ARG-NAME>");

_helpBuilder.Write(command, _console);
var expected =
Expand All @@ -195,7 +195,7 @@ public void Command_arguments_can_customize_second_column_text()
argument
};

_helpBuilder.Customize(argument, secondColumnText: "Custom description");
_helpBuilder.CustomizeSymbol(argument, secondColumnText: "Custom description");

_helpBuilder.Write(command, _console);
var expected =
Expand All @@ -214,7 +214,7 @@ public void Command_arguments_can_customize_default_value()
argument
};

_helpBuilder.Customize(argument, defaultValue: "42");
_helpBuilder.CustomizeSymbol(argument, defaultValue: "42");

_helpBuilder.Write(command, _console);
var expected =
Expand All @@ -227,7 +227,7 @@ public void Command_arguments_can_customize_default_value()
[Fact]
public void Customize_throws_when_symbol_is_null()
{
Action action = () => new HelpBuilder(LocalizationResources.Instance).Customize(null!, "");
Action action = () => new HelpBuilder(LocalizationResources.Instance).CustomizeSymbol(null!, "");
action.Should().Throw<ArgumentNullException>();
}
}
Expand Down
59 changes: 48 additions & 11 deletions src/System.CommandLine.Tests/UseHelpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,53 @@ public async Task UseHelp_with_custom_aliases_default_aliases_replaced(string he
_console.Out.ToString().Should().Be("");
}

[Fact]
public void Individual_symbols_can_be_customized()
{
var subcommand = new Command("subcommand", "The default command description");
var option = new Option<int>("-x", "The default option description");
var argument = new Argument<int>("int-value", "The default argument description");

var rootCommand = new RootCommand
{
subcommand,
option,
argument
};

var parser = new CommandLineBuilder(rootCommand)
.UseHelp(ctx =>
{
ctx.HelpBuilder.CustomizeSymbol(subcommand, secondColumnText: "The custom command description");
ctx.HelpBuilder.CustomizeSymbol(option, secondColumnText: "The custom option description");
ctx.HelpBuilder.CustomizeSymbol(argument, secondColumnText: "The custom argument description");
})
.Build();

var console = new TestConsole();
parser.Invoke("-h", console);

console.Out
.ToString()
.Should()
.ContainAll("The custom command description",
"The custom option description",
"The custom argument description");
}

[Fact]
public void Help_sections_can_be_replaced()
{
var parser = new CommandLineBuilder()
.UseHelp(_ => CustomLayout())
.UseHelp(ctx => ctx.HelpBuilder.CustomizeLayout(CustomLayout))
.Build();

var console = new TestConsole();
parser.Invoke("-h", console);

console.Out.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}{NewLine}");

IEnumerable<HelpDelegate> CustomLayout()
IEnumerable<HelpSectionDelegate> CustomLayout(HelpContext _)
{
yield return ctx => ctx.Output.WriteLine("one");
yield return ctx => ctx.Output.WriteLine("two");
Expand All @@ -283,7 +316,7 @@ public void Help_sections_can_be_supplemented()
{
var command = new RootCommand("hello");
var parser = new CommandLineBuilder(command)
.UseHelp(_ => CustomLayout())
.UseHelp(ctx => ctx.HelpBuilder.CustomizeLayout(CustomLayout))
.Build();

var console = new TestConsole();
Expand All @@ -296,7 +329,7 @@ public void Help_sections_can_be_supplemented()

output.Should().Be(expected);

IEnumerable<HelpDelegate> CustomLayout()
IEnumerable<HelpSectionDelegate> CustomLayout(HelpContext _)
{
yield return ctx => ctx.Output.WriteLine("first");

Expand All @@ -321,13 +354,17 @@ public void Layout_can_be_composed_dynamically_based_on_context()
};

var parser = new CommandLineBuilder(command)
.UseHelp(context => context.Command == commandWithTypicalHelp
? HelpBuilder.DefaultLayout()
: new HelpDelegate[]
{
c => c.Output.WriteLine("Custom layout!")
}
.Concat(HelpBuilder.DefaultLayout()))
.UseHelp(
ctx =>
ctx.HelpBuilder
.CustomizeLayout(c =>
c.Command == commandWithTypicalHelp
? HelpBuilder.DefaultLayout()
: new HelpSectionDelegate[]
{
c => c.Output.WriteLine("Custom layout!")
}
.Concat(HelpBuilder.DefaultLayout())))
.Build();

var typicalOutput = new TestConsole();
Expand Down
3 changes: 2 additions & 1 deletion src/System.CommandLine/Binding/BindingContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace System.CommandLine.Binding
public sealed class BindingContext : IServiceProvider
{
private IConsole _console;
private HelpBuilder? _helpBuilder;

/// <param name="parseResult">The parse result used for binding to command line input.</param>
/// <param name="console">A console instance used for writing output.</param>
Expand All @@ -38,7 +39,7 @@ public BindingContext(

internal IConsoleFactory? ConsoleFactory { get; set; }

internal HelpBuilder HelpBuilder => (HelpBuilder)ServiceProvider.GetService(typeof(HelpBuilder))!;
internal HelpBuilder HelpBuilder => _helpBuilder ??= (HelpBuilder)ServiceProvider.GetService(typeof(HelpBuilder))!;

/// <summary>
/// The console to which output should be written during the current invocation.
Expand Down
29 changes: 25 additions & 4 deletions src/System.CommandLine/Builder/CommandLineBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.CommandLine.Help;
using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.Linq;

namespace System.CommandLine.Builder
{
Expand All @@ -17,6 +16,8 @@ public class CommandLineBuilder
{
private readonly List<(InvocationMiddleware middleware, int order)> _middlewareList = new();
private LocalizationResources? _localizationResources;
private Action<HelpContext>? _customizeHelpBuilder;
private Func<BindingContext, HelpBuilder>? _helpBuilderFactory;

/// <param name="rootCommand">The root command of the application.</param>
public CommandLineBuilder(Command? rootCommand = null)
Expand Down Expand Up @@ -51,7 +52,27 @@ public CommandLineBuilder(Command? rootCommand = null)
/// </summary>
public ResponseFileHandling ResponseFileHandling { get; set; }

internal Func<BindingContext, HelpBuilder>? HelpBuilderFactory { get; set; }
internal void CustomizeHelpLayout(Action<HelpContext> customize) =>
_customizeHelpBuilder = customize;

internal void UseHelpBuilderFactory(Func<BindingContext, HelpBuilder> factory) =>
_helpBuilderFactory = factory;

private Func<BindingContext, HelpBuilder> GetHelpBuilderFactory()
{
return CreateHelpBuilder;

HelpBuilder CreateHelpBuilder(BindingContext bindingContext)
{
var helpBuilder = _helpBuilderFactory is { }
? _helpBuilderFactory(bindingContext)
: new HelpBuilder(LocalizationResources);

helpBuilder.OnCustomize = _customizeHelpBuilder;

return helpBuilder;
}
}

internal HelpOption? HelpOption { get; set; }

Expand All @@ -62,7 +83,7 @@ internal LocalizationResources LocalizationResources
get => _localizationResources ??= LocalizationResources.Instance;
set => _localizationResources = value;
}

/// <summary>
/// Creates a parser based on the configuration of the command line builder.
/// </summary>
Expand All @@ -77,7 +98,7 @@ public Parser Build()
resources: LocalizationResources,
responseFileHandling: ResponseFileHandling,
middlewarePipeline: GetMiddleware(),
helpBuilderFactory: HelpBuilderFactory));
helpBuilderFactory: GetHelpBuilderFactory()));

return parser;
}
Expand Down
16 changes: 11 additions & 5 deletions src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -436,14 +436,20 @@ public static CommandLineBuilder UseHelp(
/// </summary>
/// <remarks>The specified aliases will override the default values.</remarks>
/// <param name="builder">A command line builder.</param>
/// <param name="layout">Defines the sections to be written for command line help.</param>
/// <param name="customize">A delegate that will be called to customize help if help is requested.</param>
/// <returns>The same instance of <see cref="CommandLineBuilder"/>.</returns>
public static CommandLineBuilder UseHelp(
this CommandLineBuilder builder,
Func<HelpContext, IEnumerable<HelpDelegate>> layout)
Action<HelpContext> customize)
{
return builder.UseHelpBuilder(_ => new HelpBuilder(builder.LocalizationResources, getLayout: layout))
.UseHelp(new HelpOption(() => builder.LocalizationResources));
builder.CustomizeHelpLayout(customize);

if (builder.HelpOption is null)
{
builder.UseHelp(new HelpOption(() => builder.LocalizationResources));
}

return builder;
}

internal static CommandLineBuilder UseHelp(
Expand Down Expand Up @@ -480,7 +486,7 @@ public static TBuilder UseHelpBuilder<TBuilder>(this TBuilder builder,
{
throw new ArgumentNullException(nameof(builder));
}
builder.HelpBuilderFactory = getHelpBuilder;
builder.UseHelpBuilderFactory(getHelpBuilder);
return builder;
}

Expand Down
Loading