diff --git a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt index e0da19de0f..14df623534 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt +++ b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt @@ -72,8 +72,6 @@ System.CommandLine public CommandLineBuilder UseExceptionHandler(System.Func onException = null, System.Int32 errorExitCode = 1) public CommandLineBuilder UseHelp(System.Nullable maxWidth = null) public CommandLineBuilder UseHelp(System.String name, System.String[] helpAliases) - public CommandLineBuilder UseHelp(System.Action customize, System.Nullable maxWidth = null) - public CommandLineBuilder UseHelpBuilder(System.Func getHelpBuilder) public CommandLineBuilder UseParseDirective(System.Int32 errorExitCode = 1) public CommandLineBuilder UseParseErrorReporting(System.Int32 errorExitCode = 1) public CommandLineBuilder UseSuggestDirective() @@ -83,7 +81,7 @@ System.CommandLine public CommandLineBuilder UseVersionOption(System.String name, System.String[] aliases) public class CommandLineConfiguration public static CommandLineBuilder CreateBuilder(Command rootCommand) - .ctor(Command command, System.Boolean enablePosixBundling = True, System.Boolean enableTokenReplacement = True, System.Func helpBuilderFactory = null, System.CommandLine.Parsing.TryReplaceToken tokenReplacer = null) + .ctor(Command command, System.Boolean enablePosixBundling = True, System.Boolean enableTokenReplacement = True, System.CommandLine.Parsing.TryReplaceToken tokenReplacer = null) public System.Collections.Generic.IReadOnlyList Directives { get; } public System.Boolean EnablePosixBundling { get; } public System.Boolean EnableTokenReplacement { get; } @@ -106,6 +104,7 @@ System.CommandLine public System.Collections.Generic.IEnumerable GetCompletions(System.CommandLine.Completions.CompletionContext context) public class EnvironmentVariablesDirective : Directive .ctor() + public CliAction Action { get; set; } public interface IConsole : System.CommandLine.IO.IStandardError, System.CommandLine.IO.IStandardIn, System.CommandLine.IO.IStandardOut public abstract class Option : Symbol, System.CommandLine.Binding.IValueDescriptor public CliAction Action { get; set; } @@ -133,6 +132,7 @@ System.CommandLine public static Option AcceptExistingOnly(this Option option) public class ParseDirective : Directive .ctor(System.Int32 errorExitCode = 1) + public CliAction Action { get; set; } public class ParseResult public CliAction Action { get; } public System.CommandLine.Parsing.CommandResult CommandResult { get; } @@ -160,6 +160,7 @@ System.CommandLine .ctor(System.String description = ) public class SuggestDirective : Directive .ctor() + public CliAction Action { get; set; } public abstract class Symbol public System.String Description { get; set; } public System.Boolean IsHidden { get; set; } @@ -196,6 +197,11 @@ System.CommandLine.Completions public TextCompletionContext AtCursorPosition(System.Int32 position) public class TokenCompletionContext : CompletionContext System.CommandLine.Help + public class HelpAction : System.CommandLine.CliAction + .ctor() + public HelpBuilder Builder { get; set; } + public System.Int32 Invoke(System.CommandLine.Invocation.InvocationContext context) + public System.Threading.Tasks.Task InvokeAsync(System.CommandLine.Invocation.InvocationContext context, System.Threading.CancellationToken cancellationToken = null) public class HelpBuilder .ctor(System.Int32 maxWidth = 2147483647) public System.Int32 MaxWidth { get; } @@ -226,6 +232,12 @@ System.CommandLine.Help public HelpBuilder HelpBuilder { get; } public System.IO.TextWriter Output { get; } public System.CommandLine.ParseResult ParseResult { get; } + public class HelpOption : System.CommandLine.Option, System.CommandLine.Binding.IValueDescriptor + .ctor(System.String name, System.String[] aliases) + .ctor() + public System.CommandLine.CliAction Action { get; set; } + public System.Boolean Equals(System.Object obj) + public System.Int32 GetHashCode() public class TwoColumnHelpRow, System.IEquatable .ctor(System.String firstColumnText, System.String secondColumnText) public System.String FirstColumnText { get; } @@ -237,7 +249,6 @@ System.CommandLine.Invocation public class InvocationContext .ctor(System.CommandLine.ParseResult parseResult, System.CommandLine.IConsole console = null) public System.CommandLine.IConsole Console { get; set; } - public System.CommandLine.Help.HelpBuilder HelpBuilder { get; } public System.CommandLine.ParseResult ParseResult { get; set; } public T GetValue(Option option) public T GetValue(Argument argument) diff --git a/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs b/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs index 91796ac8f4..721582646b 100644 --- a/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs +++ b/src/System.CommandLine.Generator.Tests/GeneratedCommandHandlerTests.cs @@ -135,30 +135,26 @@ public async Task Can_generate_handler_with_well_know_parameters_types() InvocationContext? boundInvocationContext = null; IConsole? boundConsole = null; ParseResult? boundParseResult = null; - HelpBuilder? boundHelpBuilder = null; void Execute( InvocationContext invocationContext, IConsole console, - ParseResult parseResult, - HelpBuilder helpBuilder) + ParseResult parseResult) { boundInvocationContext = invocationContext; boundConsole = console; boundParseResult = parseResult; - boundHelpBuilder = helpBuilder; } var command = new Command("command"); - command.SetHandler>(Execute); + command.SetHandler>(Execute); await command.InvokeAsync("command", _console); boundInvocationContext.Should().NotBeNull(); boundConsole.Should().Be(_console); boundParseResult.Should().NotBeNull(); - boundHelpBuilder.Should().NotBeNull(); } [Fact] diff --git a/src/System.CommandLine.Generator/Parameters/HelpBuilderParameter.cs b/src/System.CommandLine.Generator/Parameters/HelpBuilderParameter.cs deleted file mode 100644 index e58ab4e8ab..0000000000 --- a/src/System.CommandLine.Generator/Parameters/HelpBuilderParameter.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace System.CommandLine.Generator.Parameters -{ - internal class HelpBuilderParameter : Parameter, IEquatable - { - public HelpBuilderParameter(ITypeSymbol helpBuilderType) - : base(helpBuilderType) - { - } - - public override string GetValueFromContext() - => "context.HelpBuilder"; - - public override int GetHashCode() - => base.GetHashCode(); - - public override bool Equals(object? obj) - => Equals(obj as HelpBuilderParameter); - - public bool Equals(HelpBuilderParameter? other) - { - if (other is null) return false; - return base.Equals(other); - } - } -} diff --git a/src/System.CommandLine.Generator/WellKnownTypes.cs b/src/System.CommandLine.Generator/WellKnownTypes.cs index 8586491283..58db8188e3 100644 --- a/src/System.CommandLine.Generator/WellKnownTypes.cs +++ b/src/System.CommandLine.Generator/WellKnownTypes.cs @@ -51,12 +51,6 @@ internal bool TryGet(ISymbol symbol, out Parameter? parameter) return true; } - if (Comparer.Equals(HelpBuilder, symbol)) - { - parameter = new HelpBuilderParameter(HelpBuilder); - return true; - } - if (symbol.MetadataName == "System.CommandLine.Binding.BindingContext" && symbol is INamedTypeSymbol bindingContext) { parameter = new BindingContextParameter(bindingContext); diff --git a/src/System.CommandLine.NamingConventionBinder/ServiceProvider.cs b/src/System.CommandLine.NamingConventionBinder/ServiceProvider.cs index b08fa94934..a0ce93c8be 100644 --- a/src/System.CommandLine.NamingConventionBinder/ServiceProvider.cs +++ b/src/System.CommandLine.NamingConventionBinder/ServiceProvider.cs @@ -18,7 +18,6 @@ internal ServiceProvider(BindingContext bindingContext) { [typeof(ParseResult)] = _ => bindingContext.ParseResult, [typeof(IConsole)] = _ => bindingContext.Console, - [typeof(HelpBuilder)] = _ => bindingContext.ParseResult.Configuration.HelpBuilderFactory(bindingContext.InvocationContext), [typeof(BindingContext)] = _ => bindingContext }; } diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs index 078496dda4..4d98e11fb0 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs @@ -92,17 +92,21 @@ public void Option_can_customize_first_column_text_based_on_parse_result() ctx.Command.Equals(commandA) ? optionAFirstColumnText : optionBFirstColumnText); - var config = new CommandLineBuilder(command) - .UseDefaults() - .UseHelpBuilder(_ => helpBuilder) - .Build(); + command.Options.Add(new HelpOption() + { + Action = new HelpAction() + { + Builder = helpBuilder + } + }); var console = new TestConsole(); - config.Invoke("root a -h", console); + var config = CommandLineConfiguration.CreateBuilder(command).Build(); + command.Parse("root a -h", config).Invoke(console); console.Out.ToString().Should().Contain(optionAFirstColumnText); console = new TestConsole(); - config.Invoke("root b -h", console); + command.Parse("root b -h", config).Invoke(console); console.Out.ToString().Should().Contain(optionBFirstColumnText); } @@ -130,10 +134,15 @@ public void Option_can_customize_second_column_text_based_on_parse_result() ctx.Command.Equals(commandA) ? optionADescription : optionBDescription); + command.Options.Add(new HelpOption() + { + Action = new HelpAction() + { + Builder = helpBuilder + } + }); var config = new CommandLineBuilder(command) - .UseDefaults() - .UseHelpBuilder(_ => helpBuilder) .Build(); var console = new TestConsole(); @@ -253,14 +262,16 @@ public void Option_can_fallback_to_default_when_customizing(bool conditionA, boo firstColumnText: ctx => conditionA ? "custom 1st" : HelpBuilder.Default.GetOptionUsageLabel(option), secondColumnText: ctx => conditionB ? "custom 2nd" : option.Description ?? string.Empty); - - var config = new CommandLineBuilder(command) - .UseDefaults() - .UseHelpBuilder(_ => helpBuilder) - .Build(); + command.Options.Add(new HelpOption() + { + Action = new HelpAction() + { + Builder = helpBuilder + } + }); var console = new TestConsole(); - config.Invoke("test -h", console); + command.Parse("test -h", new CommandLineBuilder(command).Build()).Invoke(console); console.Out.ToString().Should().MatchRegex(expected); } @@ -295,13 +306,18 @@ public void Argument_can_fallback_to_default_when_customizing( defaultValue: ctx => conditionC ? "custom def" : HelpBuilder.Default.GetArgumentDefaultValue(argument)); - var config = new CommandLineBuilder(command) - .UseDefaults() - .UseHelpBuilder(_ => helpBuilder) - .Build(); + var config = new CommandLineBuilder(command).Build(); + + command.Options.Add(new HelpOption() + { + Action = new HelpAction() + { + Builder = helpBuilder + } + }); var console = new TestConsole(); - config.Invoke("test -h", console); + command.Parse("test -h", config).Invoke(console); console.Out.ToString().Should().MatchRegex(expected); } } diff --git a/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs index ee2936e22c..dddf8f9a1b 100644 --- a/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs @@ -126,61 +126,12 @@ public void When_command_handler_throws_then_Invoke_does_not_handle_the_exceptio } [Fact] - public async Task When_no_help_builder_is_specified_it_uses_default_implementation() + public void When_no_help_builder_is_specified_it_uses_default_implementation() { - bool handlerWasCalled = false; + HelpOption helpOption = new(); - var command = new Command("help-command"); - command.SetAction((context, cancellationToken) => - { - handlerWasCalled = true; - context.HelpBuilder.Should().NotBeNull(); - return Task.FromResult(0); - }); - - var config = new CommandLineBuilder(new RootCommand - { - command - }) - .Build(); - - await config.InvokeAsync("help-command", new TestConsole()); - - handlerWasCalled.Should().BeTrue(); - } - - [Fact] - public async Task When_help_builder_factory_is_specified_it_is_used_to_create_the_help_builder() - { - bool handlerWasCalled = false; - bool factoryWasCalled = false; - - HelpBuilder createdHelpBuilder = null; - - var command = new Command("help-command"); - command.SetAction((context, cancellationToken) => - { - handlerWasCalled = true; - context.HelpBuilder.Should().Be(createdHelpBuilder); - createdHelpBuilder.Should().NotBeNull(); - return Task.FromResult(0); - }); - - var config = new CommandLineBuilder(new RootCommand - { - command - }) - .UseHelpBuilder(context => - { - factoryWasCalled = true; - return createdHelpBuilder = new HelpBuilder(); - }) - .Build(); - - await config.InvokeAsync("help-command", new TestConsole()); - - handlerWasCalled.Should().BeTrue(); - factoryWasCalled.Should().BeTrue(); + helpOption.Action.Should().NotBeNull(); + (helpOption.Action as HelpAction).Builder.Should().NotBeNull(); } } } diff --git a/src/System.CommandLine.Tests/UseHelpTests.cs b/src/System.CommandLine.Tests/UseHelpTests.cs index 3dc7268af0..ad1eceb357 100644 --- a/src/System.CommandLine.Tests/UseHelpTests.cs +++ b/src/System.CommandLine.Tests/UseHelpTests.cs @@ -270,17 +270,18 @@ public void Individual_symbols_can_be_customized() argument }; - var config = 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(); - config.Invoke("-h", console); + + ParseResult parseResult = rootCommand.Parse("-h"); + + 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(console); console.Out .ToString() @@ -294,11 +295,19 @@ public void Individual_symbols_can_be_customized() public void Help_sections_can_be_replaced() { var config = new CommandLineBuilder(new RootCommand()) - .UseHelp(ctx => ctx.HelpBuilder.CustomizeLayout(CustomLayout)) + .UseHelp() .Build(); var console = new TestConsole(); - config.Invoke("-h", console); + + ParseResult parseResult = config.RootCommand.Parse("-h"); + + if (parseResult.Action is HelpAction helpAction) + { + helpAction.Builder.CustomizeLayout(CustomLayout); + } + + parseResult.Invoke(console); console.Out.ToString().Should().Be($"one{NewLine}{NewLine}two{NewLine}{NewLine}three{NewLine}{NewLine}{NewLine}"); @@ -315,11 +324,18 @@ public void Help_sections_can_be_supplemented() { var command = new RootCommand("hello"); var config = new CommandLineBuilder(command) - .UseHelp(ctx => ctx.HelpBuilder.CustomizeLayout(CustomLayout)) + .UseHelp() .Build(); + ParseResult parseResult = config.RootCommand.Parse("-h"); + + if (parseResult.Action is HelpAction helpAction) + { + helpAction.Builder.CustomizeLayout(CustomLayout); + } + var console = new TestConsole(); - config.Invoke("-h", console); + parseResult.Invoke(console); var output = console.Out.ToString(); var defaultHelp = GetDefaultHelp(command); @@ -344,33 +360,37 @@ IEnumerable> CustomLayout(HelpContext _) [Fact] public void Layout_can_be_composed_dynamically_based_on_context() { + HelpBuilder helpBuilder = new(); var commandWithTypicalHelp = new Command("typical"); var commandWithCustomHelp = new Command("custom"); var command = new RootCommand { commandWithTypicalHelp, - commandWithCustomHelp + commandWithCustomHelp, + new HelpOption() + { + Action = new HelpAction() + { + Builder = helpBuilder + } + } }; - var config = new CommandLineBuilder(command) - .UseHelp( - ctx => - ctx.HelpBuilder - .CustomizeLayout(c => - c.Command == commandWithTypicalHelp - ? HelpBuilder.Default.GetLayout() - : new Action[] - { - c => c.Output.WriteLine("Custom layout!") - } - .Concat(HelpBuilder.Default.GetLayout()))) - .Build(); + var config = new CommandLineBuilder(command).Build(); + helpBuilder.CustomizeLayout(c => + c.Command == commandWithTypicalHelp + ? HelpBuilder.Default.GetLayout() + : new Action[] + { + c => c.Output.WriteLine("Custom layout!") + } + .Concat(HelpBuilder.Default.GetLayout())); var typicalOutput = new TestConsole(); - config.Invoke("typical -h", typicalOutput); + command.Parse("typical -h", config).Invoke(typicalOutput); var customOutput = new TestConsole(); - config.Invoke("custom -h", customOutput); + command.Parse("custom -h", config).Invoke(customOutput); typicalOutput.Out.ToString().Should().Be(GetDefaultHelp(commandWithTypicalHelp, false)); customOutput.Out.ToString().Should().Be($"Custom layout!{NewLine}{NewLine}{GetDefaultHelp(commandWithCustomHelp, false)}"); @@ -413,11 +433,20 @@ public void Help_default_sections_can_be_wrapped() public void Help_customized_sections_can_be_wrapped() { var config = new CommandLineBuilder(new RootCommand()) - .UseHelp(ctx => ctx.HelpBuilder.CustomizeLayout(CustomLayout), maxWidth: 10) + .UseHelp() .Build(); var console = new TestConsole(); - config.Invoke("-h", console); + + ParseResult parseResult = config.RootCommand.Parse("-h"); + + if (parseResult.Action is HelpAction helpAction) + { + helpAction.Builder = new HelpBuilder(10); + helpAction.Builder.CustomizeLayout(CustomLayout); + } + + parseResult.Invoke(console); string result = console.Out.ToString(); result.Should().Be($" 123 123{NewLine} 456 456{NewLine} 78 789{NewLine} 0{NewLine}{NewLine}{NewLine}"); diff --git a/src/System.CommandLine/Builder/CommandLineBuilder.cs b/src/System.CommandLine/Builder/CommandLineBuilder.cs index 0cb93ee581..376dc11cb1 100644 --- a/src/System.CommandLine/Builder/CommandLineBuilder.cs +++ b/src/System.CommandLine/Builder/CommandLineBuilder.cs @@ -31,8 +31,6 @@ public partial class CommandLineBuilder internal TimeSpan? ProcessTerminationTimeout; - private Action? _customizeHelpBuilder; - private Func? _helpBuilderFactory; /// The root command of the application. public CommandLineBuilder(Command rootCommand) @@ -45,34 +43,10 @@ public CommandLineBuilder(Command rootCommand) /// public Command Command { get; } - internal void CustomizeHelpLayout(Action customize) => - _customizeHelpBuilder = customize; - - internal void UseHelpBuilderFactory(Func factory) => - _helpBuilderFactory = factory; - - private Func GetHelpBuilderFactory() - { - return CreateHelpBuilder; - - HelpBuilder CreateHelpBuilder(InvocationContext invocationContext) - { - var helpBuilder = _helpBuilderFactory is { } - ? _helpBuilderFactory(invocationContext) - : CommandLineConfiguration.DefaultHelpBuilderFactory(invocationContext, MaxHelpWidth); - - helpBuilder.OnCustomize = _customizeHelpBuilder; - - return helpBuilder; - } - } - internal HelpOption? HelpOption; internal VersionOption? VersionOption; - internal int? MaxHelpWidth; - internal TryReplaceToken? TokenReplacer; public List Directives => _directives ??= new (); @@ -92,7 +66,6 @@ public CommandLineConfiguration Build() => maxLevenshteinDistance: MaxLevenshteinDistance, exceptionHandler: ExceptionHandler, processTerminationTimeout: ProcessTerminationTimeout, - helpBuilderFactory: GetHelpBuilderFactory(), tokenReplacer: TokenReplacer); } } diff --git a/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs b/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs index e920454cc5..af988e3844 100644 --- a/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs +++ b/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs @@ -141,7 +141,13 @@ int Default(Exception exception, InvocationContext context) /// The reference to this instance. public CommandLineBuilder UseHelp(int? maxWidth = null) { - return UseHelp(new HelpOption(), maxWidth); + return UseHelp(new HelpOption() + { + Action = new HelpAction() + { + Builder = new HelpBuilder(maxWidth ?? int.MaxValue) + } + }); } /// @@ -156,51 +162,17 @@ public CommandLineBuilder UseHelp(string name, params string[] helpAliases) return UseHelp(new HelpOption(name, helpAliases)); } - /// - /// Configures the application to show help when one of the specified option aliases are used on the command line. - /// - /// The specified aliases will override the default values. - /// A delegate that will be called to customize help if help is requested. - /// Maximum output width for default help builder. - /// The reference to this instance. - public CommandLineBuilder UseHelp(Action customize, int? maxWidth = null) - { - CustomizeHelpLayout(customize); - - if (HelpOption is null) - { - UseHelp(new HelpOption(), maxWidth); - } - - return this; - } - - internal CommandLineBuilder UseHelp(HelpOption helpOption, int? maxWidth = null) + internal CommandLineBuilder UseHelp(HelpOption helpOption) { if (HelpOption is null) { HelpOption = helpOption; OverwriteOrAdd(Command, helpOption); - - MaxHelpWidth = maxWidth; } return this; } - /// - /// Specifies an to be used to format help output when help is requested. - /// - /// A delegate that returns an instance of - /// The reference to this instance. - public CommandLineBuilder UseHelpBuilder( - Func getHelpBuilder) - { - UseHelpBuilderFactory(getHelpBuilder); - - return this; - } - /// /// If the parse result contains errors, this exit code will be used when the process exits. /// The reference to this instance. diff --git a/src/System.CommandLine/CommandLineConfiguration.cs b/src/System.CommandLine/CommandLineConfiguration.cs index 5c4218f9a5..afb6b280b9 100644 --- a/src/System.CommandLine/CommandLineConfiguration.cs +++ b/src/System.CommandLine/CommandLineConfiguration.cs @@ -35,7 +35,6 @@ public class CommandLineConfiguration internal readonly TimeSpan? ProcessTerminationTimeout; - private Func? _helpBuilderFactory; private TryReplaceToken? _tokenReplacer; /// @@ -44,13 +43,11 @@ public class CommandLineConfiguration /// The root command for the parser. /// to enable POSIX bundling; otherwise, . /// to enable token replacement; otherwise, . - /// Provide a custom help builder. /// Replaces the specified token with any number of other tokens. public CommandLineConfiguration( Command command, bool enablePosixBundling = true, bool enableTokenReplacement = true, - Func? helpBuilderFactory = null, TryReplaceToken? tokenReplacer = null) : this( command, @@ -60,7 +57,6 @@ public CommandLineConfiguration( parseErrorReportingExitCode: null, maxLevenshteinDistance: 0, processTerminationTimeout: null, - helpBuilderFactory: helpBuilderFactory, tokenReplacer: tokenReplacer, exceptionHandler: null) { @@ -74,7 +70,6 @@ internal CommandLineConfiguration( int? parseErrorReportingExitCode, int maxLevenshteinDistance, TimeSpan? processTerminationTimeout, - Func? helpBuilderFactory, TryReplaceToken? tokenReplacer, Func? exceptionHandler) { @@ -86,24 +81,12 @@ internal CommandLineConfiguration( MaxLevenshteinDistance = maxLevenshteinDistance; ProcessTerminationTimeout = processTerminationTimeout; - _helpBuilderFactory = helpBuilderFactory; _tokenReplacer = tokenReplacer; ExceptionHandler = exceptionHandler; } public static CommandLineBuilder CreateBuilder(Command rootCommand) => new CommandLineBuilder(rootCommand); - internal static HelpBuilder DefaultHelpBuilderFactory(InvocationContext context, int? requestedMaxWidth = null) - { - int maxWidth = requestedMaxWidth ?? int.MaxValue; - if (requestedMaxWidth is null && context.Console is SystemConsole systemConsole) - { - maxWidth = systemConsole.GetWindowWidth(); - } - - return new HelpBuilder(maxWidth); - } - /// /// Gets the enabled directives. /// @@ -125,8 +108,6 @@ internal static HelpBuilder DefaultHelpBuilderFactory(InvocationContext context, /// public bool EnableTokenReplacement { get; } - internal Func HelpBuilderFactory => _helpBuilderFactory ??= context => DefaultHelpBuilderFactory(context); - internal TryReplaceToken? TokenReplacer => EnableTokenReplacement ? _tokenReplacer ??= DefaultTokenReplacer diff --git a/src/System.CommandLine/Directive.cs b/src/System.CommandLine/Directive.cs index 42d90405f4..55e414febf 100644 --- a/src/System.CommandLine/Directive.cs +++ b/src/System.CommandLine/Directive.cs @@ -27,7 +27,7 @@ public Directive(string name) /// Gets or sets the for the Directive. The handler represents the action /// that will be performed when the Directive is invoked. /// - public CliAction? Action { get; set; } + public virtual CliAction? Action { get; set; } public override IEnumerable GetCompletions(CompletionContext context) => Array.Empty(); diff --git a/src/System.CommandLine/EnvironmentVariablesDirective.cs b/src/System.CommandLine/EnvironmentVariablesDirective.cs index fd4e59f03d..8714f90b59 100644 --- a/src/System.CommandLine/EnvironmentVariablesDirective.cs +++ b/src/System.CommandLine/EnvironmentVariablesDirective.cs @@ -10,8 +10,18 @@ namespace System.CommandLine /// public sealed class EnvironmentVariablesDirective : Directive { + private CliAction? _action; + public EnvironmentVariablesDirective() : base("env") - => Action = new EnvironmentVariablesDirectiveAction(this); + { + } + + /// + public override CliAction? Action + { + get => _action ??= new EnvironmentVariablesDirectiveAction(this); + set => _action = value ?? throw new ArgumentNullException(nameof(value)); + } private sealed class EnvironmentVariablesDirectiveAction : CliAction { diff --git a/src/System.CommandLine/Help/HelpOption.cs b/src/System.CommandLine/Help/HelpOption.cs index bdf85feae1..f9573f38f4 100644 --- a/src/System.CommandLine/Help/HelpOption.cs +++ b/src/System.CommandLine/Help/HelpOption.cs @@ -1,60 +1,34 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.CommandLine.Invocation; -using System.CommandLine.IO; -using System.Threading; -using System.Threading.Tasks; - namespace System.CommandLine.Help { - internal class HelpOption : Option + public sealed class HelpOption : Option { - internal HelpOption(string name, string[] aliases) + private CliAction? _action; + + public HelpOption(string name, string[] aliases) : base(name, aliases, new Argument(name) { Arity = ArgumentArity.Zero }) { AppliesToSelfAndChildren = true; Description = LocalizationResources.HelpOptionDescription(); - Action = new HelpOptionAction(); } - internal HelpOption() : this("--help", new[] { "-h", "/h", "-?", "/?" }) + public HelpOption() : this("--help", new[] { "-h", "/h", "-?", "/?" }) { } + /// + public override CliAction? Action + { + get => _action ??= new HelpAction(); + set => _action = value ?? throw new ArgumentNullException(nameof(value)); + } + internal override bool IsGreedy => false; public override bool Equals(object? obj) => obj is HelpOption; public override int GetHashCode() => typeof(HelpOption).GetHashCode(); - - private sealed class HelpOptionAction : CliAction - { - private static void Display(InvocationContext context) - { - var output = context.Console.Out.CreateTextWriter(); - - var helpContext = new HelpContext(context.HelpBuilder, - context.ParseResult.CommandResult.Command, - output, - context.ParseResult); - - context.HelpBuilder.Write(helpContext); - } - - public override int Invoke(InvocationContext context) - { - Display(context); - - return 0; - } - - public override Task InvokeAsync(InvocationContext context, CancellationToken cancellationToken = default) - => cancellationToken.IsCancellationRequested - ? Task.FromCanceled(cancellationToken) - : Task.FromResult(Invoke(context)); - } - - } } \ No newline at end of file diff --git a/src/System.CommandLine/Help/HelpOptionAction.cs b/src/System.CommandLine/Help/HelpOptionAction.cs new file mode 100644 index 0000000000..8793513ad3 --- /dev/null +++ b/src/System.CommandLine/Help/HelpOptionAction.cs @@ -0,0 +1,40 @@ +using System.CommandLine.Invocation; +using System.CommandLine.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace System.CommandLine.Help +{ + public sealed class HelpAction : CliAction + { + private HelpBuilder? _builder; + + /// + /// Specifies an to be used to format help output when help is requested. + /// + public HelpBuilder Builder + { + get => _builder ??= new HelpBuilder(Console.IsOutputRedirected ? int.MaxValue : Console.WindowWidth); + set => _builder = value ?? throw new ArgumentNullException(nameof(value)); + } + + public override int Invoke(InvocationContext context) + { + var output = context.Console.Out.CreateTextWriter(); + + var helpContext = new HelpContext(Builder, + context.ParseResult.CommandResult.Command, + output, + context.ParseResult); + + Builder.Write(helpContext); + + return 0; + } + + public override Task InvokeAsync(InvocationContext context, CancellationToken cancellationToken = default) + => cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : Task.FromResult(Invoke(context)); + } +} diff --git a/src/System.CommandLine/Help/VersionOption.cs b/src/System.CommandLine/Help/VersionOption.cs index 1c48691089..296500c649 100644 --- a/src/System.CommandLine/Help/VersionOption.cs +++ b/src/System.CommandLine/Help/VersionOption.cs @@ -10,13 +10,14 @@ namespace System.CommandLine.Help { - internal class VersionOption : Option + internal sealed class VersionOption : Option { + private CliAction? _action; + internal VersionOption() : base("--version", new Argument("--version") { Arity = ArgumentArity.Zero }) { Description = LocalizationResources.VersionOptionDescription(); - Action = new VersionOptionAction(); AddValidators(); } @@ -24,10 +25,16 @@ internal VersionOption(string name, string[] aliases) : base(name, aliases) { Description = LocalizationResources.VersionOptionDescription(); - Action = new VersionOptionAction(); AddValidators(); } + /// + public override CliAction? Action + { + get => _action ??= new VersionOptionAction(); + set => _action = value ?? throw new ArgumentNullException(nameof(value)); + } + private void AddValidators() { Validators.Add(static result => diff --git a/src/System.CommandLine/IO/SystemConsole.cs b/src/System.CommandLine/IO/SystemConsole.cs index 15cb6e59c3..83f1699dd1 100644 --- a/src/System.CommandLine/IO/SystemConsole.cs +++ b/src/System.CommandLine/IO/SystemConsole.cs @@ -32,8 +32,6 @@ public SystemConsole() /// public bool IsInputRedirected => Console.IsInputRedirected; - internal int GetWindowWidth() => IsOutputRedirected ? int.MaxValue : Console.WindowWidth; - private struct StandardErrorStreamWriter : IStandardStreamWriter { public static readonly StandardErrorStreamWriter Instance = new(); diff --git a/src/System.CommandLine/Invocation/InvocationContext.cs b/src/System.CommandLine/Invocation/InvocationContext.cs index 861c568026..2a1a1f5112 100644 --- a/src/System.CommandLine/Invocation/InvocationContext.cs +++ b/src/System.CommandLine/Invocation/InvocationContext.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.CommandLine.Help; using System.CommandLine.IO; namespace System.CommandLine.Invocation @@ -11,7 +10,6 @@ namespace System.CommandLine.Invocation /// public sealed class InvocationContext { - private HelpBuilder? _helpBuilder; private IConsole? _console; /// The result of the current parse operation. @@ -31,11 +29,6 @@ public IConsole Console set => _console = value; } - /// - /// Enables writing help output. - /// - public HelpBuilder HelpBuilder => _helpBuilder ??= ParseResult.Configuration.HelpBuilderFactory(this); - /// /// The parse result for the current invocation. /// diff --git a/src/System.CommandLine/Option.cs b/src/System.CommandLine/Option.cs index 59c0f990d4..b40a9e533f 100644 --- a/src/System.CommandLine/Option.cs +++ b/src/System.CommandLine/Option.cs @@ -108,7 +108,7 @@ internal virtual bool IsGreedy /// Gets or sets the for the Option. The handler represents the action /// that will be performed when the Option is invoked. /// - public CliAction? Action { get; set; } + public virtual CliAction? Action { get; set; } string IValueDescriptor.ValueName => Name; diff --git a/src/System.CommandLine/ParseDirective.cs b/src/System.CommandLine/ParseDirective.cs index 3778e553cd..e8c9bdaed7 100644 --- a/src/System.CommandLine/ParseDirective.cs +++ b/src/System.CommandLine/ParseDirective.cs @@ -11,9 +11,24 @@ namespace System.CommandLine /// public sealed class ParseDirective : Directive { + private const int DefaultErrorExitCode = 1; + private CliAction? _action; + /// If the parse result contains errors, this exit code will be used when the process exits. - public ParseDirective(int errorExitCode = 1) : base("parse") - => Action = new ParseDirectiveAction(errorExitCode); + public ParseDirective(int errorExitCode = DefaultErrorExitCode) : base("parse") + { + if (errorExitCode != DefaultErrorExitCode) + { + Action = new ParseDirectiveAction(errorExitCode); + } + } + + /// + public override CliAction? Action + { + get => _action ??= new ParseDirectiveAction(DefaultErrorExitCode); + set => _action = value ?? throw new ArgumentNullException(nameof(value)); + } private sealed class ParseDirectiveAction : CliAction { diff --git a/src/System.CommandLine/SuggestDirective.cs b/src/System.CommandLine/SuggestDirective.cs index b8e68a07ee..9e808541a4 100644 --- a/src/System.CommandLine/SuggestDirective.cs +++ b/src/System.CommandLine/SuggestDirective.cs @@ -13,8 +13,18 @@ namespace System.CommandLine /// The dotnet-suggest tool requires the suggest directive to be enabled for an application to provide completions. public sealed class SuggestDirective : Directive { + private CliAction? _action; + public SuggestDirective() : base("suggest") - => Action = new SuggestDirectiveAction(this); + { + } + + /// + public override CliAction? Action + { + get => _action ??= new SuggestDirectiveAction(this); + set => _action = value ?? throw new ArgumentNullException(nameof(value)); + } private sealed class SuggestDirectiveAction : CliAction {