diff --git a/samples/HostingPlayground/Program.cs b/samples/HostingPlayground/Program.cs index 569aa87ec1..3f91ffec37 100644 --- a/samples/HostingPlayground/Program.cs +++ b/samples/HostingPlayground/Program.cs @@ -3,7 +3,6 @@ using System.CommandLine.NamingConventionBinder; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.CommandLine.Parsing; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using static HostingPlayground.HostingPlaygroundLogEvents; @@ -21,11 +20,9 @@ static Task Main(string[] args) => BuildCommandLine() services.AddSingleton(); }); }) - .UseDefaults() - .Build() .InvokeAsync(args); - private static CommandLineBuilder BuildCommandLine() + private static CommandLineConfiguration BuildCommandLine() { var root = new RootCommand(@"$ dotnet run --name 'Joe'"){ new Option("--name"){ @@ -33,7 +30,7 @@ private static CommandLineBuilder BuildCommandLine() } }; root.Action = CommandHandler.Create(Run); - return new CommandLineBuilder(root); + return new CommandLineConfiguration(root); } private static void Run(GreeterOptions options, IHost host) diff --git a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_Hosting_api_is_not_changed.approved.txt b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_Hosting_api_is_not_changed.approved.txt index 2515292bf5..b6fc1b7f1a 100644 --- a/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_Hosting_api_is_not_changed.approved.txt +++ b/src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_Hosting_api_is_not_changed.approved.txt @@ -7,8 +7,8 @@ System.CommandLine.Hosting public static System.CommandLine.Invocation.InvocationContext GetInvocationContext(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder) public static System.CommandLine.Invocation.InvocationContext GetInvocationContext(this Microsoft.Extensions.Hosting.HostBuilderContext context) public static System.CommandLine.Command UseCommandHandler(this System.CommandLine.Command command) - public static System.CommandLine.CommandLineBuilder UseHost(this System.CommandLine.CommandLineBuilder builder, System.Action configureHost = null) - public static System.CommandLine.CommandLineBuilder UseHost(this System.CommandLine.CommandLineBuilder builder, System.Func hostBuilderFactory, System.Action configureHost = null) + public static System.CommandLine.CommandLineConfiguration UseHost(this System.CommandLine.CommandLineConfiguration builder, System.Action configureHost = null) + public static System.CommandLine.CommandLineConfiguration UseHost(this System.CommandLine.CommandLineConfiguration builder, System.Func hostBuilderFactory, System.Action configureHost = null) public static Microsoft.Extensions.Hosting.IHostBuilder UseInvocationLifetime(this Microsoft.Extensions.Hosting.IHostBuilder host, System.CommandLine.Invocation.InvocationContext invocation, System.Action configureOptions = null) public class InvocationLifetime, Microsoft.Extensions.Hosting.IHostLifetime .ctor(Microsoft.Extensions.Options.IOptions options, Microsoft.Extensions.Hosting.IHostEnvironment environment, Microsoft.Extensions.Hosting.IHostApplicationLifetime applicationLifetime, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory = null) 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 6ecff1a482..7a076e571c 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 @@ -55,38 +55,24 @@ System.CommandLine public ParseResult Parse(System.String commandLine, CommandLineConfiguration configuration = null) public System.Void SetAction(System.Action action) public System.Void SetAction(System.Func action) - public class CommandLineBuilder + public class CommandLineConfiguration .ctor(Command rootCommand) - public Command Command { get; } public System.Collections.Generic.List Directives { get; } - public CommandLineConfiguration Build() - public CommandLineBuilder CancelOnProcessTermination(System.Nullable timeout = null) - public CommandLineBuilder EnablePosixBundling(System.Boolean value = True) - public CommandLineBuilder UseDefaults() - public CommandLineBuilder UseEnvironmentVariableDirective() - 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 UseParseDirective(System.Int32 errorExitCode = 1) - public CommandLineBuilder UseParseErrorReporting(System.Int32 errorExitCode = 1) - public CommandLineBuilder UseSuggestDirective() - public CommandLineBuilder UseTokenReplacer(System.CommandLine.Parsing.TryReplaceToken replaceToken) - public CommandLineBuilder UseTypoCorrections(System.Int32 maxLevenshteinDistance = 3) - public CommandLineBuilder UseVersionOption() - 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.CommandLine.Parsing.TryReplaceToken tokenReplacer = null) - public System.Collections.Generic.IReadOnlyList Directives { get; } - public System.Boolean EnablePosixBundling { get; } - public System.Boolean EnableTokenReplacement { get; } + public System.Boolean EnableDefaultExceptionHandler { get; set; } + public System.Boolean EnableParseErrorReporting { get; set; } + public System.Boolean EnablePosixBundling { get; set; } + public System.Boolean EnableTypoCorrections { get; set; } public System.IO.TextWriter Error { get; set; } public System.IO.TextWriter Output { get; set; } + public System.Nullable ProcessTerminationTimeout { get; set; } + public System.CommandLine.Parsing.TryReplaceToken ResponseFileTokenReplacer { get; set; } public Command RootCommand { get; } public System.Int32 Invoke(System.String commandLine) public System.Int32 Invoke(System.String[] args) public System.Threading.Tasks.Task InvokeAsync(System.String commandLine, System.Threading.CancellationToken cancellationToken = null) public System.Threading.Tasks.Task InvokeAsync(System.String[] args, System.Threading.CancellationToken cancellationToken = null) + public ParseResult Parse(System.Collections.Generic.IReadOnlyList args) + public ParseResult Parse(System.String commandLine) public System.Void ThrowIfInvalid() public class CommandLineConfigurationException : System.Exception, System.Runtime.Serialization.ISerializable .ctor(System.String message) @@ -164,6 +150,12 @@ System.CommandLine public System.Collections.Generic.IEnumerable Parents { get; } public System.Collections.Generic.IEnumerable GetCompletions(System.CommandLine.Completions.CompletionContext context) public System.String ToString() + public class VersionOption : Option, System.CommandLine.Binding.IValueDescriptor + .ctor() + .ctor(System.String name, System.String[] aliases) + public CliAction Action { get; set; } + public System.Boolean Equals(System.Object obj) + public System.Int32 GetHashCode() System.CommandLine.Binding public interface IValueDescriptor public System.Boolean HasDefaultValue { get; } @@ -229,8 +221,8 @@ System.CommandLine.Help 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() + .ctor(System.String name, System.String[] aliases) public System.CommandLine.CliAction Action { get; set; } public System.Boolean Equals(System.Object obj) public System.Int32 GetHashCode() diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_CustomScenarios.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_CustomScenarios.cs index c0af04fb20..7417555cb6 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_CustomScenarios.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_CustomScenarios.cs @@ -26,7 +26,7 @@ public void SetupOneOptWithNestedCommand() _rootCommand.Subcommands.Add(nestedCommand); _testSymbolsAsString = "root_command nested_command -opt1 321"; - _configuration = CommandLineConfiguration.CreateBuilder(_rootCommand).UseDefaults().Build(); + _configuration = new CommandLineConfiguration(_rootCommand); } [Benchmark] diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Directives_Suggest.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Directives_Suggest.cs index 736390331b..175e6d5997 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Directives_Suggest.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Directives_Suggest.cs @@ -31,10 +31,11 @@ public void Setup() vegetableOption }; - _configuration = new CommandLineBuilder(eatCommand) - .UseSuggestDirective() - .Build(); - _configuration.Output = System.IO.TextWriter.Null; + _configuration = new CommandLineConfiguration(eatCommand) + { + Directives = { new SuggestDirective() }, + Output = System.IO.TextWriter.Null + }; } [Params( diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_NestedCommands.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_NestedCommands.cs index e5c2aecfd8..f51da10836 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_NestedCommands.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_NestedCommands.cs @@ -62,7 +62,7 @@ public void SetupRootCommand() } _rootCommand = rootCommand; - _configuration = CommandLineConfiguration.CreateBuilder(rootCommand).UseDefaults().Build(); + _configuration = new CommandLineConfiguration(rootCommand); } [Benchmark] diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs index 67f0d5e3b4..cecf51690c 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs @@ -63,6 +63,6 @@ public void SetupParserFromOptions_Parse() } [Benchmark] - public ParseResult ParserFromOptions_Parse() => _testConfiguration.RootCommand.Parse(_testSymbolsAsString, _testConfiguration); + public ParseResult ParserFromOptions_Parse() => _testConfiguration.Parse(_testSymbolsAsString); } } diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs index f7cf13933b..5cdb9bb9db 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs @@ -59,6 +59,6 @@ public void SetupParserFromOptionsWithArguments_Parse() } [Benchmark] - public ParseResult ParserFromOptionsWithArguments_Parse() => _configuration.RootCommand.Parse(_testSymbolsAsString, _configuration); + public ParseResult ParserFromOptionsWithArguments_Parse() => _configuration.Parse(_testSymbolsAsString); } } diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs index 222fbac158..d4ee112456 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs @@ -21,10 +21,10 @@ public Perf_Parser_ParseResult() { var option = new Option("-opt"); - _configuration = - new CommandLineBuilder(new RootCommand { option }) - .UseParseDirective() - .Build(); + _configuration = new CommandLineConfiguration(new RootCommand { option }) + { + Directives = { new ParseDirective() } + }; } public IEnumerable GenerateTestInputs() @@ -39,12 +39,12 @@ public IEnumerable GenerateTestInputs() public IEnumerable GenerateTestParseResults() => GenerateTestInputs() - .Select(input => new BdnParam(_configuration.RootCommand.Parse(input, _configuration), input)); + .Select(input => new BdnParam(_configuration.Parse(input), input)); [Benchmark] [ArgumentsSource(nameof(GenerateTestInputs))] public ParseResult ParseResult_Directives(string input) - => _configuration.RootCommand.Parse(input, _configuration); + => _configuration.Parse(input); [Benchmark] [ArgumentsSource(nameof(GenerateTestParseResults))] diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Simple.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Simple.cs index 1e55ff0227..f8ccc1a091 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Simple.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Simple.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Attributes; -using System.CommandLine.Parsing; using System.Threading.Tasks; namespace System.CommandLine.Benchmarks.CommandLine @@ -17,10 +16,10 @@ public class Perf_Parser_Simple public Task DefaultsAsync() => BuildCommand().Parse(Args).InvokeAsync(); [Benchmark] - public int MinimalSync() => new CommandLineBuilder(BuildCommand()).Build().Invoke(Args); + public int MinimalSync() => BuildMinimalConfig(BuildCommand()).Invoke(Args); [Benchmark] - public Task MinimalAsync() => new CommandLineBuilder(BuildCommand()).Build().InvokeAsync(Args); + public Task MinimalAsync() => BuildMinimalConfig(BuildCommand()).InvokeAsync(Args); private static RootCommand BuildCommand() { @@ -41,5 +40,14 @@ private static RootCommand BuildCommand() return command; } + + private static CommandLineConfiguration BuildMinimalConfig(Command command) + { + CommandLineConfiguration config = new(command); + config.Directives.Clear(); + config.EnableDefaultExceptionHandler = false; + config.ProcessTerminationTimeout = null; + return config; + } } } diff --git a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_TypoCorrection.cs b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_TypoCorrection.cs index cc8571394f..bc9b455409 100644 --- a/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_TypoCorrection.cs +++ b/src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_TypoCorrection.cs @@ -21,10 +21,11 @@ public Perf_Parser_TypoCorrection() { var option = new Option("--0123456789"); - _configuration = new CommandLineBuilder(new RootCommand { option }) - .UseTypoCorrections() - .Build(); - _configuration.Output = System.IO.TextWriter.Null; + _configuration = new CommandLineConfiguration(new RootCommand { option }) + { + EnableTypoCorrections = true, + Output = System.IO.TextWriter.Null + }; } public IEnumerable> GenerateTestParseResults() @@ -48,7 +49,7 @@ public IEnumerable> GenerateTestParseResults() "--1023546798", "--1032546798" } - .Select(opt => new BdnParam(_configuration.RootCommand.Parse(opt, _configuration), opt)); + .Select(opt => new BdnParam(_configuration.Parse(opt), opt)); [Benchmark] [ArgumentsSource(nameof(GenerateTestParseResults))] diff --git a/src/System.CommandLine.Benchmarks/Helpers/Utils.cs b/src/System.CommandLine.Benchmarks/Helpers/Utils.cs index b030fc4aab..adac5e4f4c 100644 --- a/src/System.CommandLine.Benchmarks/Helpers/Utils.cs +++ b/src/System.CommandLine.Benchmarks/Helpers/Utils.cs @@ -53,7 +53,7 @@ public static CommandLineConfiguration CreateConfiguration(this IEnumerable InvokeMethodAsync( configuration.Output = standardOutput ?? Console.Out; configuration.Error = standardError ?? Console.Error; - return configuration.RootCommand.Parse(args, configuration).InvokeAsync(); + return configuration.Parse(args).InvokeAsync(); } public static int InvokeMethod( @@ -102,23 +101,20 @@ public static int InvokeMethod( configuration.Output = standardOutput ?? Console.Out; configuration.Error = standardError ?? Console.Error; - return configuration.RootCommand.Parse(args, configuration).Invoke(); + return configuration.Parse(args).Invoke(); } private static CommandLineConfiguration BuildConfiguration(MethodInfo method, string xmlDocsFilePath, object target) { - var builder = new CommandLineBuilder(new RootCommand()) + return new CommandLineConfiguration(new RootCommand()) .ConfigureRootCommandFromMethod(method, target) - .ConfigureHelpFromXmlComments(method, xmlDocsFilePath) - .UseDefaults(); - - return builder.Build(); + .ConfigureHelpFromXmlComments(method, xmlDocsFilePath); } - public static CommandLineBuilder ConfigureRootCommandFromMethod( - this CommandLineBuilder builder, + public static CommandLineConfiguration ConfigureRootCommandFromMethod( + this CommandLineConfiguration builder, MethodInfo method, object target = null) { @@ -132,7 +128,7 @@ public static CommandLineBuilder ConfigureRootCommandFromMethod( throw new ArgumentNullException(nameof(method)); } - builder.Command.ConfigureFromMethod(method, target); + builder.RootCommand.ConfigureFromMethod(method, target); return builder; } @@ -172,8 +168,8 @@ public static void ConfigureFromMethod( command.Action = CommandHandler.Create(method, target); } - public static CommandLineBuilder ConfigureHelpFromXmlComments( - this CommandLineBuilder builder, + public static CommandLineConfiguration ConfigureHelpFromXmlComments( + this CommandLineConfiguration builder, MethodInfo method, string xmlDocsFilePath) { @@ -192,13 +188,13 @@ public static CommandLineBuilder ConfigureHelpFromXmlComments( if (xmlDocs.TryGetMethodDescription(method, out CommandHelpMetadata metadata) && metadata.Description != null) { - builder.Command.Description = metadata.Description; + builder.RootCommand.Description = metadata.Description; foreach (var parameterDescription in metadata.ParameterDescriptions) { var kebabCasedParameterName = parameterDescription.Key.ToKebabCase(); - var option = builder.Command.Options.FirstOrDefault(o => HasAliasIgnoringPrefix(o, kebabCasedParameterName)); + var option = builder.RootCommand.Options.FirstOrDefault(o => HasAliasIgnoringPrefix(o, kebabCasedParameterName)); if (option != null) { @@ -206,9 +202,9 @@ public static CommandLineBuilder ConfigureHelpFromXmlComments( } else { - for (var i = 0; i < builder.Command.Arguments.Count; i++) + for (var i = 0; i < builder.RootCommand.Arguments.Count; i++) { - var argument = builder.Command.Arguments[i]; + var argument = builder.RootCommand.Arguments[i]; if (string.Equals( argument.Name, kebabCasedParameterName, diff --git a/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs b/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs index 17c2fd5c66..99ce79a5e1 100644 --- a/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs +++ b/src/System.CommandLine.Hosting.Tests/HostingHandlerTest.cs @@ -18,7 +18,7 @@ public static async Task Constructor_Injection_Injects_Service() { var service = new MyService(); - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new MyCommand().UseCommandHandler() ) .UseHost((builder) => { @@ -26,8 +26,7 @@ public static async Task Constructor_Injection_Injects_Service() { services.AddTransient(x => service); }); - }) - .Build(); + }); var result = await config.InvokeAsync(new string[] { "--int-option", "54"}); @@ -37,15 +36,14 @@ public static async Task Constructor_Injection_Injects_Service() [Fact] public static async Task Parameter_is_available_in_property() { - var config = new CommandLineBuilder(new MyCommand().UseCommandHandler()) + var config = new CommandLineConfiguration(new MyCommand().UseCommandHandler()) .UseHost(host => { host.ConfigureServices(services => { services.AddTransient(); }); - }) - .Build(); + }); var result = await config.InvokeAsync(new string[] { "--int-option", "54"}); @@ -59,7 +57,7 @@ public static async Task Can_have_different_handlers_based_on_command() root.Subcommands.Add(new MyCommand().UseCommandHandler()); root.Subcommands.Add(new MyOtherCommand().UseCommandHandler()); - var config = new CommandLineBuilder(root) + var config = new CommandLineConfiguration(root) .UseHost(host => { host.ConfigureServices(services => @@ -69,8 +67,7 @@ public static async Task Can_have_different_handlers_based_on_command() Action = () => 100 }); }); - }) - .Build(); + }); var result = await config.InvokeAsync(new string[] { "mycommand", "--int-option", "54" }); @@ -87,15 +84,14 @@ public static async Task Can_bind_to_arguments_via_injection() var service = new MyService(); var cmd = new RootCommand(); cmd.Subcommands.Add(new MyOtherCommand().UseCommandHandler()); - var config = new CommandLineBuilder(cmd) + var config = new CommandLineConfiguration(cmd) .UseHost(host => { host.ConfigureServices(services => { services.AddSingleton(service); }); - }) - .Build(); + }); var result = await config.InvokeAsync(new string[] { "myothercommand", "TEST" }); @@ -110,14 +106,13 @@ public static async Task Invokes_DerivedClass() var cmd = new RootCommand(); cmd.Subcommands.Add(new MyCommand().UseCommandHandler()); cmd.Subcommands.Add(new MyOtherCommand().UseCommandHandler()); - var config = new CommandLineBuilder(cmd) + var config = new CommandLineConfiguration(cmd) .UseHost((builder) => { builder.ConfigureServices(services => { services.AddTransient(x => service); }); - }) - .Build(); + }); await config.InvokeAsync(new string[] { "mycommand", "--int-option", "54" }); service.Value.Should().Be(54); diff --git a/src/System.CommandLine.Hosting.Tests/HostingTests.cs b/src/System.CommandLine.Hosting.Tests/HostingTests.cs index a9d539b878..a67f319857 100644 --- a/src/System.CommandLine.Hosting.Tests/HostingTests.cs +++ b/src/System.CommandLine.Hosting.Tests/HostingTests.cs @@ -28,11 +28,10 @@ void Execute(IHost host) hostFromHandler = host; } - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new RootCommand { Action = CommandHandler.Create(Execute) } ) - .UseHost() - .Build(); + .UseHost(); await config.InvokeAsync(Array.Empty()); @@ -44,13 +43,12 @@ public async static Task UseHost_adds_invocation_context_to_HostBuilder_Properti { InvocationContext invocationContext = null; - var config = new CommandLineBuilder(new RootCommand()) + var config = new CommandLineConfiguration(new RootCommand()) .UseHost(host => { if (host.Properties.TryGetValue(typeof(InvocationContext), out var ctx)) invocationContext = ctx as InvocationContext; - }) - .Build(); + }); await config.InvokeAsync(Array.Empty()); @@ -72,11 +70,10 @@ void Execute(IHost host) parseResult = services.GetRequiredService(); } - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new RootCommand { Action = CommandHandler.Create(Execute) } ) - .UseHost() - .Build(); + .UseHost(); await config.InvokeAsync(Array.Empty()); @@ -100,7 +97,7 @@ void Execute(IHost host) testConfigValue = config[testKey]; } - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new RootCommand { Action = CommandHandler.Create(Execute), @@ -113,8 +110,7 @@ void Execute(IHost host) { config.AddCommandLine(args); }); - }) - .Build(); + }); await config.InvokeAsync(commandLineArgs); @@ -136,7 +132,7 @@ void Execute(IHost host) testConfigValue = config[testKey]; } - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new RootCommand { Action = CommandHandler.Create(Execute), @@ -151,8 +147,7 @@ void Execute(IHost host) }); return host; - }) - .Build(); + }); await config.InvokeAsync(commandLineArgs); @@ -174,13 +169,12 @@ void Execute(IHost host) testConfigValue = config[testKey]; } - var config = new CommandLineBuilder( + var config = new CommandLineConfiguration( new RootCommand { Action = CommandHandler.Create(Execute) }) - .UseHost() - .Build(); + .UseHost(); await config.InvokeAsync(commandLine); @@ -203,7 +197,7 @@ public static void UseHost_binds_parsed_arguments_to_options() .Value; }); - int result = new CommandLineBuilder(rootCmd) + int result = new CommandLineConfiguration(rootCmd) .UseHost(host => { host.ConfigureServices(services => @@ -211,7 +205,6 @@ public static void UseHost_binds_parsed_arguments_to_options() services.AddOptions().BindCommandLine(); }); }) - .Build() .Invoke(commandLine); Assert.Equal(0, result); @@ -253,14 +246,13 @@ public void SubCommand(int myArgument) public async static Task GetInvocationContext_returns_non_null_instance() { bool ctxAsserted = false; - var config = new CommandLineBuilder(new RootCommand()) + var config = new CommandLineConfiguration(new RootCommand()) .UseHost(hostBuilder => { InvocationContext ctx = hostBuilder.GetInvocationContext(); ctx.Should().NotBeNull(); ctxAsserted = true; - }) - .Build(); + }); await config.InvokeAsync(string.Empty); ctxAsserted.Should().BeTrue(); @@ -270,7 +262,7 @@ public async static Task GetInvocationContext_returns_non_null_instance() public async static Task GetInvocationContext_in_ConfigureServices_returns_non_null_instance() { bool ctxAsserted = false; - var config = new CommandLineBuilder(new RootCommand()) + var config = new CommandLineConfiguration(new RootCommand()) .UseHost(hostBuilder => { hostBuilder.ConfigureServices((hostingCtx, services) => @@ -279,8 +271,7 @@ public async static Task GetInvocationContext_in_ConfigureServices_returns_non_n invocationCtx.Should().NotBeNull(); ctxAsserted = true; }); - }) - .Build(); + }); await config.InvokeAsync(string.Empty); ctxAsserted.Should().BeTrue(); diff --git a/src/System.CommandLine.Hosting/HostingAction.cs b/src/System.CommandLine.Hosting/HostingAction.cs index c4414a5531..dc58396ab9 100644 --- a/src/System.CommandLine.Hosting/HostingAction.cs +++ b/src/System.CommandLine.Hosting/HostingAction.cs @@ -19,6 +19,7 @@ internal sealed class HostingAction : BindingHandler internal static void SetHandlers(Command command, Func hostBuilderFactory, Action configureHost) { command.Action = new HostingAction(hostBuilderFactory, configureHost, command.Action); + command.TreatUnmatchedTokensAsErrors = false; // to pass unmatched Tokens to host builder factory foreach (Command subCommand in command.Subcommands) { diff --git a/src/System.CommandLine.Hosting/HostingExtensions.cs b/src/System.CommandLine.Hosting/HostingExtensions.cs index ab87656e22..6938873f2d 100644 --- a/src/System.CommandLine.Hosting/HostingExtensions.cs +++ b/src/System.CommandLine.Hosting/HostingExtensions.cs @@ -7,23 +7,24 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; +using System.CommandLine.Help; namespace System.CommandLine.Hosting { public static class HostingExtensions { - public static CommandLineBuilder UseHost(this CommandLineBuilder builder, + public static CommandLineConfiguration UseHost(this CommandLineConfiguration builder, Func hostBuilderFactory, Action configureHost = null) { builder.Directives.Add(new Directive("config")); - HostingAction.SetHandlers(builder.Command, hostBuilderFactory, configureHost); + HostingAction.SetHandlers(builder.RootCommand, hostBuilderFactory, configureHost); return builder; } - public static CommandLineBuilder UseHost(this CommandLineBuilder builder, + public static CommandLineConfiguration UseHost(this CommandLineConfiguration builder, Action configureHost = null ) => UseHost(builder, null, configureHost); diff --git a/src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs b/src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs index 64af01a3dd..69341f01b3 100644 --- a/src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs +++ b/src/System.CommandLine.NamingConventionBinder.Tests/ModelBinderTests.cs @@ -617,8 +617,7 @@ public void Default_values_from_options_with_the_same_type_are_bound_and_use_the second = two; }); - var config = new CommandLineBuilder(rootCommand) - .Build(); + var config = new CommandLineConfiguration(rootCommand); config.Invoke(""); diff --git a/src/System.CommandLine.Rendering.Tests/ViewRenderingTests.cs b/src/System.CommandLine.Rendering.Tests/ViewRenderingTests.cs index acd4a9b26c..aa233787e2 100644 --- a/src/System.CommandLine.Rendering.Tests/ViewRenderingTests.cs +++ b/src/System.CommandLine.Rendering.Tests/ViewRenderingTests.cs @@ -31,7 +31,7 @@ public void Views_can_be_used_for_specific_types() terminal.Append(new ParseResultView(parseResult)); }); - var config = new CommandLineBuilder(command).Build(); + var config = new CommandLineConfiguration(command); config.Invoke(""); diff --git a/src/System.CommandLine.Suggest.Tests/EndToEndTestApp/Program.cs b/src/System.CommandLine.Suggest.Tests/EndToEndTestApp/Program.cs index ecae807171..f809e38a33 100644 --- a/src/System.CommandLine.Suggest.Tests/EndToEndTestApp/Program.cs +++ b/src/System.CommandLine.Suggest.Tests/EndToEndTestApp/Program.cs @@ -1,4 +1,5 @@ using System.CommandLine; +using System.CommandLine.Help; using System.CommandLine.Invocation; using System.CommandLine.Parsing; using System.Threading.Tasks; @@ -20,7 +21,7 @@ static async Task Main(string[] args) appleOption, bananaOption, cherryOption, - durianOption, + durianOption, }; rootCommand.SetAction((InvocationContext ctx, CancellationToken cancellationToken) => @@ -33,9 +34,7 @@ static async Task Main(string[] args) return Task.CompletedTask; }); - var commandLine = new CommandLineBuilder(rootCommand) - .UseDefaults() - .Build(); + CommandLineConfiguration commandLine = new (rootCommand); await commandLine.InvokeAsync(args); } diff --git a/src/System.CommandLine.Suggest/SuggestionDispatcher.cs b/src/System.CommandLine.Suggest/SuggestionDispatcher.cs index 8684a00b9c..4eb2e7a08f 100644 --- a/src/System.CommandLine.Suggest/SuggestionDispatcher.cs +++ b/src/System.CommandLine.Suggest/SuggestionDispatcher.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.CommandLine.Help; using System.CommandLine.Invocation; using System.IO; using System.Linq; @@ -68,17 +69,17 @@ public SuggestionDispatcher(ISuggestionRegistration suggestionRegistration, ISug ListCommand, GetCommand, RegisterCommand, - CompleteScriptCommand + CompleteScriptCommand, }; root.TreatUnmatchedTokensAsErrors = false; - Configuration = new CommandLineBuilder(root) - .UseVersionOption() - .UseHelp() - .UseParseDirective() - .UseSuggestDirective() - .UseParseErrorReporting() - .UseExceptionHandler() - .Build(); + Configuration = new CommandLineConfiguration(root) + { + Directives = + { + new ParseDirective(), + new SuggestDirective(), + } + }; } private Command CompleteScriptCommand { get; } diff --git a/src/System.CommandLine.Tests/CompletionTests.cs b/src/System.CommandLine.Tests/CompletionTests.cs index 90e9854c76..a8fc5177a3 100644 --- a/src/System.CommandLine.Tests/CompletionTests.cs +++ b/src/System.CommandLine.Tests/CompletionTests.cs @@ -68,7 +68,7 @@ public void Command_GetCompletions_returns_available_option_aliases_for_global_o subcommand2 }; - var rootCommand = new RootCommand + var rootCommand = new Command("root") { subcommand1 }; @@ -198,7 +198,7 @@ public void Command_GetCompletions_with_text_to_match_orders_by_match_position_t [Fact] public void When_an_option_has_a_default_value_it_will_still_be_suggested() { - var command = new RootCommand + var command = new Command("test") { new Option("--apple") { DefaultValueFactory = (_) => "cortland" }, new Option("--banana"), @@ -675,11 +675,7 @@ public void When_caller_does_the_tokenizing_then_argument_completions_are_based_ CreateOptionWithAcceptOnlyFromAmong(name: "three", "three-a", "three-b", "three-c") }; - var configuration = new CommandLineBuilder(new RootCommand - { - command - }) - .Build(); + var configuration = new CommandLineConfiguration(command); var result = command.Parse("outer two b", configuration); @@ -770,7 +766,7 @@ public void When_parsing_from_text_if_the_proximate_option_is_completed_then_com CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), new Option("--langVersion") }; - var configuration = new CommandLineBuilder(command).Build(); + var configuration = new CommandLineConfiguration(command); var completions = command.Parse("--framework net7.0 --l", configuration).GetCompletions(); completions.Select(item => item.Label) @@ -787,7 +783,7 @@ public void When_parsing_from_array_if_the_proximate_option_is_completed_then_co CreateOptionWithAcceptOnlyFromAmong(name: "--language", "C#"), new Option("--langVersion") }; - var configuration = new CommandLineBuilder(command).Build(); + var configuration = new CommandLineConfiguration(command); var completions = command.Parse(new[]{"--framework","net7.0","--l"}, configuration).GetCompletions(); completions.Select(item => item.Label) @@ -833,15 +829,16 @@ public void Options_that_have_been_specified_to_their_maximum_arity_are_not_sugg [Fact] public void When_current_symbol_is_an_option_that_requires_arguments_then_parent_symbol_completions_are_omitted() { - var configuration = new CommandLineBuilder(new RootCommand + var configuration = new CommandLineConfiguration(new RootCommand { new Option("--allows-one"), new Option("--allows-many") }) - .UseSuggestDirective() - .Build(); + { + Directives = { new SuggestDirective() } + }; - var completions = configuration.RootCommand.Parse("--allows-one ", configuration).GetCompletions(); + var completions = configuration.Parse("--allows-one ").GetCompletions(); completions.Should().BeEmpty(); } @@ -946,7 +943,7 @@ public void Completions_for_options_provide_a_description() var description = "The option before -y."; var option = new Option("-x") { Description = description }; - var completions = new RootCommand { option }.GetCompletions(CompletionContext.Empty); + var completions = new Command("test") { option }.GetCompletions(CompletionContext.Empty); completions.Should().ContainSingle() .Which @@ -961,7 +958,7 @@ public void Completions_for_subcommands_provide_a_description() var description = "The description for the subcommand"; var subcommand = new Command("-x", description); - var completions = new RootCommand { subcommand }.GetCompletions(CompletionContext.Empty); + var completions = new Command("test") { subcommand }.GetCompletions(CompletionContext.Empty); completions.Should().ContainSingle() .Which diff --git a/src/System.CommandLine.Tests/DirectiveTests.cs b/src/System.CommandLine.Tests/DirectiveTests.cs index 938985e3c2..53d8bff8cc 100644 --- a/src/System.CommandLine.Tests/DirectiveTests.cs +++ b/src/System.CommandLine.Tests/DirectiveTests.cs @@ -37,11 +37,11 @@ public void Multiple_directives_are_allowed() RootCommand root = new() { new Option("-y") }; Directive parseDirective = new ("parse"); Directive suggestDirective = new ("suggest"); - CommandLineBuilder builder = new(root); - builder.Directives.Add(parseDirective); - builder.Directives.Add(suggestDirective); + CommandLineConfiguration config = new(root); + config.Directives.Add(parseDirective); + config.Directives.Add(suggestDirective); - var result = root.Parse("[parse] [suggest] -y", builder.Build()); + var result = root.Parse("[parse] [suggest] -y", config); result.FindResultFor(parseDirective).Should().NotBeNull(); result.FindResultFor(suggestDirective).Should().NotBeNull(); @@ -125,10 +125,10 @@ public void When_a_directive_is_specified_more_than_once_then_its_values_are_agg private static ParseResult Parse(Option option, Directive directive, string commandLine) { RootCommand root = new() { option }; - CommandLineBuilder builder = new(root); - builder.Directives.Add(directive); + CommandLineConfiguration config = new(root); + config.Directives.Add(directive); - return root.Parse(commandLine, builder.Build()); + return root.Parse(commandLine, config); } } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs b/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs index c58350b8c0..870ab12b0c 100644 --- a/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs +++ b/src/System.CommandLine.Tests/EnvironmentVariableDirectiveTests.cs @@ -25,9 +25,10 @@ public async Task Sets_environment_variable_to_value() Environment.GetEnvironmentVariable(variable).Should().Be(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env:{variable}={value}]" }); @@ -47,9 +48,10 @@ public async Task Trims_environment_variable_name() Environment.GetEnvironmentVariable(variable).Should().Be(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env: {variable} ={value}]" }); @@ -69,9 +71,10 @@ public async Task Trims_environment_variable_value() Environment.GetEnvironmentVariable(variable).Should().Be(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env:{variable}= {value} ]" }); @@ -91,9 +94,10 @@ public async Task Sets_environment_variable_value_containing_equals_sign() Environment.GetEnvironmentVariable(variable).Should().Be(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env:{variable}={value}]" }); @@ -112,9 +116,10 @@ public async Task Ignores_environment_directive_without_equals_sign() Environment.GetEnvironmentVariable(variable).Should().BeNull(); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env:{variable}]" }); @@ -134,9 +139,10 @@ public static async Task Ignores_environment_directive_with_empty_variable_name( env.Values.Cast().Should().NotContain(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env:={value}]" }); @@ -156,9 +162,10 @@ public static async Task Ignores_environment_directive_with_whitespace_variable_ env.Values.Cast().Should().NotContain(value); }); - var config = new CommandLineBuilder(rootCommand) - .UseEnvironmentVariableDirective() - .Build(); + var config = new CommandLineConfiguration(rootCommand) + { + Directives = { new EnvironmentVariablesDirective() } + }; await config.InvokeAsync(new[] { $"[env: ={value}]" }); diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs index 7868b4f0ed..20d943a617 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs @@ -99,8 +99,10 @@ public void Option_can_customize_first_column_text_based_on_parse_result() }); var console = new StringWriter(); - var config = CommandLineConfiguration.CreateBuilder(command).Build(); - config.Output = console; + var config = new CommandLineConfiguration(command) + { + Output = console + }; command.Parse("root a -h", config).Invoke(); console.ToString().Should().Contain(optionAFirstColumnText); @@ -142,10 +144,11 @@ public void Option_can_customize_second_column_text_based_on_parse_result() } }); - var config = new CommandLineBuilder(command) - .Build(); + var config = new CommandLineConfiguration(command) + { + Output = new StringWriter() + }; - config.Output = new StringWriter(); config.Invoke("root a -h"); config.Output.ToString().Should().Contain($"option {optionADescription}"); @@ -270,7 +273,7 @@ public void Option_can_fallback_to_default_when_customizing(bool conditionA, boo } }); - CommandLineConfiguration config = new CommandLineBuilder(command).Build(); + CommandLineConfiguration config = new (command); var console = new StringWriter(); config.Output = console; command.Parse("test -h", config).Invoke(); @@ -308,7 +311,7 @@ public void Argument_can_fallback_to_default_when_customizing( defaultValue: ctx => conditionC ? "custom def" : HelpBuilder.Default.GetArgumentDefaultValue(argument)); - var config = new CommandLineBuilder(command).Build(); + CommandLineConfiguration config = new (command); command.Options.Add(new HelpOption() { diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs index 1c9df4292f..85e066af4f 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs @@ -913,11 +913,10 @@ public void Command_shared_arguments_with_one_or_more_arity_are_displayed_as_bei [Fact] public void Options_section_is_not_included_if_no_options_configured() { - var commandLineBuilder = new CommandLineBuilder(new RootCommand - { - new Command("outer", "description for outer") - }) - .Command; + var commandLineBuilder = new Command("noOptions") + { + new Command("outer", "description for outer") + }; _helpBuilder.Write(commandLineBuilder, _console); @@ -941,7 +940,8 @@ public void Options_section_includes_option_with_empty_description() var command = new Command("the-command", "Does things.") { new Option("-x"), - new Option("-n") + new Option("-n"), + new HelpOption() }; _helpBuilder.Write(command, _console); @@ -1117,7 +1117,7 @@ public void Options_section_properly_wraps() var alias = "--option-alias-for-a-command-that-is-long-enough-to-wrap-to-a-new-line"; var description = "Option description that is long enough to wrap."; - var command = new RootCommand + var command = new Command("test") { new Option(alias) { Description = description } }; @@ -1176,9 +1176,7 @@ public void Required_options_are_indicated_when_argument_is_named() [Fact] public void Help_option_is_shown_in_help() { - var configuration = new CommandLineBuilder(new RootCommand()) - .UseHelp() - .Build(); + var configuration = new CommandLineConfiguration(new RootCommand()); _helpBuilder.Write(configuration.RootCommand, _console); diff --git a/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs index ce59fdc250..997760cd54 100644 --- a/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs +++ b/src/System.CommandLine.Tests/Invocation/CancelOnProcessTerminationTests.cs @@ -57,10 +57,10 @@ private static Task Program(string[] args) }; command.Action = new CustomCliAction(); - return new CommandLineBuilder(command) - .CancelOnProcessTermination() - .Build() - .InvokeAsync(args); + return new CommandLineConfiguration(command) + { + ProcessTerminationTimeout = TimeSpan.FromSeconds(2) + }.InvokeAsync(args); } private sealed class CustomCliAction : CliAction diff --git a/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs index 80784cebc6..93766b81d7 100644 --- a/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs +++ b/src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs @@ -22,12 +22,11 @@ public async Task InvokeAsync_chooses_the_appropriate_command() var second = new Command("second"); second.SetAction((_) => secondWasCalled = true); - var config = new CommandLineBuilder(new RootCommand + var config = new CommandLineConfiguration(new RootCommand { first, second - }) - .Build(); + }); await config.InvokeAsync("first"); @@ -47,12 +46,11 @@ public void Invoke_chooses_the_appropriate_command() var second = new Command("second"); second.SetAction((_) => secondWasCalled = true); - var config = new CommandLineBuilder(new RootCommand + var config = new CommandLineConfiguration(new RootCommand { first, second - }) - .Build(); + }); config.Invoke("first"); @@ -61,16 +59,15 @@ public void Invoke_chooses_the_appropriate_command() } [Fact] - public void When_command_handler_throws_then_InvokeAsync_does_not_handle_the_exception() + public void When_default_exception_handler_is_disabled_InvokeAsync_does_not_swallow_action_exceptions() { var command = new Command("the-command"); command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); - var config = new CommandLineBuilder(new RootCommand - { - command - }) - .Build(); + CommandLineConfiguration config = new (command) + { + EnableDefaultExceptionHandler = false + }; Func invoke = async () => await config.InvokeAsync("the-command"); @@ -83,13 +80,15 @@ public void When_command_handler_throws_then_InvokeAsync_does_not_handle_the_exc } [Fact] - public void When_command_handler_throws_then_Invoke_does_not_handle_the_exception() + public void When_default_exception_handler_is_disabled_command_handler_exceptions_are_propagated() { var command = new Command("the-command"); command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); - var config = new CommandLineBuilder(command) - .Build(); + CommandLineConfiguration config = new (command) + { + EnableDefaultExceptionHandler = false + }; Func invoke = () => command.Parse("the-command", config).Invoke(); diff --git a/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs b/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs index 900fe36d20..fb18eb0e97 100644 --- a/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs +++ b/src/System.CommandLine.Tests/Invocation/TypoCorrectionTests.cs @@ -11,14 +11,16 @@ public class TypoCorrectionTests [Fact] public async Task When_option_is_mistyped_it_is_suggested() { - var option = new Option("info"); - RootCommand rootCommand = new () { option }; + RootCommand rootCommand = new () + { + new Option("info") + }; - var config = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("niof", config); @@ -33,11 +35,11 @@ public async Task When_there_are_no_matches_then_nothing_is_suggested() var option = new Option("info"); RootCommand rootCommand = new() { option }; - var configuration = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("zzzzzzz", configuration); @@ -52,11 +54,11 @@ public async Task When_command_is_mistyped_it_is_suggested() var command = new Command("restore"); RootCommand rootCommand = new() { command }; - var configuration = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("sertor", configuration); @@ -79,11 +81,11 @@ public async Task When_there_are_multiple_matches_it_picks_the_best_matches() aOption, beenOption }; - var configuration = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("een", configuration); @@ -105,11 +107,11 @@ public async Task Hidden_commands_are_not_suggested() beenCommand }; - var configuration = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("een", configuration); @@ -128,11 +130,12 @@ public async Task Arguments_are_not_suggested() argument, command }; - var configuration = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + EnableTypoCorrections = true, + EnableParseErrorReporting = false, + Output = new StringWriter() + }; var result = rootCommand.Parse("een", configuration); @@ -153,11 +156,11 @@ public async Task Hidden_options_are_not_suggested() seenOption, beenOption }; - var config = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("een", config); @@ -174,11 +177,11 @@ public async Task Suggestions_favor_matches_with_prefix() new Option("/call", "-call", "--call"), new Option("/email", "-email", "--email") }; - var config = - new CommandLineBuilder(rootCommand) - .UseTypoCorrections() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + EnableTypoCorrections = true, + Output = new StringWriter() + }; var result = rootCommand.Parse("-all", config); await result.InvokeAsync(); diff --git a/src/System.CommandLine.Tests/ParseDirectiveTests.cs b/src/System.CommandLine.Tests/ParseDirectiveTests.cs index 983b00d260..f86e2fb478 100644 --- a/src/System.CommandLine.Tests/ParseDirectiveTests.cs +++ b/src/System.CommandLine.Tests/ParseDirectiveTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using FluentAssertions; +using System.CommandLine.Help; using System.CommandLine.Parsing; using System.IO; using System.Threading.Tasks; @@ -28,10 +29,11 @@ public async Task Parse_directive_writes_parse_diagram() var option = new Option("-c", "--count"); subcommand.Options.Add(option); - var config = new CommandLineBuilder(rootCommand) - .UseParseDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new ParseDirective() } + }; var result = rootCommand.Parse("[parse] subcommand -c 34 --nonexistent wat", config); @@ -48,14 +50,14 @@ public async Task Parse_directive_writes_parse_diagram() [Fact] public async Task When_parse_directive_is_used_the_help_is_not_displayed() { - var rootCommand = new RootCommand(); + RootCommand rootCommand = new (); + + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new ParseDirective() } + }; - var config = new CommandLineBuilder(rootCommand) - .UseParseDirective() - .UseVersionOption() - .UseHelp() - .Build(); - config.Output = new StringWriter(); var result = rootCommand.Parse("[parse] --help", config); output.WriteLine(result.Diagram()); @@ -71,14 +73,13 @@ public async Task When_parse_directive_is_used_the_help_is_not_displayed() [Fact] public async Task When_parse_directive_is_used_the_version_is_not_displayed() { - var rootCommand = new RootCommand(); + RootCommand rootCommand = new(); - var config = new CommandLineBuilder(rootCommand) - .UseParseDirective() - .UseVersionOption() - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new ParseDirective() } + }; var result = rootCommand.Parse("[parse] --version", config); @@ -95,12 +96,18 @@ public async Task When_parse_directive_is_used_the_version_is_not_displayed() [Fact] public async Task When_there_are_no_errors_then_parse_directive_sets_exit_code_0() { - var command = new RootCommand + RootCommand command = new () { new Option("-x") }; - var exitCode = await command.Parse("[parse] -x 123").InvokeAsync(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + Directives = { new ParseDirective() } + }; + + var exitCode = await command.Parse("[parse] -x 123", config).InvokeAsync(); exitCode.Should().Be(0); } @@ -108,12 +115,18 @@ public async Task When_there_are_no_errors_then_parse_directive_sets_exit_code_0 [Fact] public async Task When_there_are_errors_then_parse_directive_sets_exit_code_1() { - var command = new RootCommand + RootCommand command = new() { new Option("-x") }; - var exitCode = await command.Parse("[parse] -x not-an-int").InvokeAsync(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + Directives = { new ParseDirective() } + }; + + var exitCode = await command.Parse("[parse] -x not-an-int", config).InvokeAsync(); exitCode.Should().Be(1); } @@ -121,15 +134,18 @@ public async Task When_there_are_errors_then_parse_directive_sets_exit_code_1() [Fact] public async Task When_there_are_errors_then_parse_directive_sets_exit_code_to_custom_value() { - var command = new RootCommand + RootCommand command = new () { new Option("-x") }; - int exitCode = await new CommandLineBuilder(command) - .UseParseDirective(errorExitCode: 42) - .Build() - .InvokeAsync("[parse] -x not-an-int"); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + Directives = { new ParseDirective(errorExitCode: 42) } + }; + + int exitCode = await config.InvokeAsync("[parse] -x not-an-int"); exitCode.Should().Be(42); } diff --git a/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs b/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs index 7266d0c752..930ebc6ffa 100644 --- a/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs +++ b/src/System.CommandLine.Tests/ParserTests.DoubleDash.cs @@ -22,10 +22,7 @@ public void Subsequent_tokens_are_parsed_as_arguments_even_if_they_match_option_ argument }; - var configuration = new CommandLineBuilder(rootCommand) - .Build(); - - var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo", configuration); + var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); result.FindResultFor(option).Should().NotBeNull(); @@ -47,8 +44,7 @@ public void Unmatched_tokens_is_empty() argument }; - var config = new CommandLineBuilder(rootCommand).Build(); - var result = rootCommand.Parse("-o \"some stuff\" -- --one -x -y -z -o:foo", config); + var result = rootCommand.Parse("-o \"some stuff\" -- --one -x -y -z -o:foo"); result.UnmatchedTokens.Should().BeEmpty(); } @@ -63,9 +59,8 @@ public void No_errors_are_generated() option, argument }; - var config = new CommandLineBuilder(rootCommand).Build(); - var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo", config); + var result = rootCommand.Parse("-o \"some stuff\" -- -o --one -x -y -z -o:foo"); result.Errors.Should().BeEmpty(); } @@ -79,9 +74,7 @@ public void A_second_double_dash_is_parsed_as_an_argument() argument }; - var configuration = new CommandLineBuilder(rootCommand).Build(); - - var result = rootCommand.Parse("a b c -- -- d", configuration); + var result = rootCommand.Parse("a b c -- -- d"); var strings = result.GetValue(argument); diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index bfa6bf199d..7112145dec 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -145,9 +145,10 @@ public void Options_short_forms_do_not_get_unbundled_if_unbundling_is_turned_off } }; - var configuration = new CommandLineBuilder(rootCommand) - .EnablePosixBundling(false) - .Build(); + CommandLineConfiguration configuration = new (rootCommand) + { + EnablePosixBundling = false + }; var result = rootCommand.Parse("the-command -xyz", configuration); diff --git a/src/System.CommandLine.Tests/ResponseFileTests.cs b/src/System.CommandLine.Tests/ResponseFileTests.cs index 2e2d649318..de8e26e8e2 100644 --- a/src/System.CommandLine.Tests/ResponseFileTests.cs +++ b/src/System.CommandLine.Tests/ResponseFileTests.cs @@ -287,8 +287,7 @@ public void When_response_file_parse_as_space_separated_returns_expected_values( optionOne, optionTwo }; - var config = new CommandLineBuilder(rootCommand) - .Build(); + CommandLineConfiguration config = new (rootCommand); var result = rootCommand.Parse($"@{responseFile}", config); @@ -299,14 +298,15 @@ public void When_response_file_parse_as_space_separated_returns_expected_values( [Fact] public void When_response_file_processing_is_disabled_then_it_returns_response_file_name_as_argument() { - var command = new RootCommand + RootCommand command = new () { new Argument>("arg") }; - var configuration = new CommandLineConfiguration( - command, - enableTokenReplacement: false); - + CommandLineConfiguration configuration = new(command) + { + ResponseFileTokenReplacer = null + }; + var result = Parser.Parse(command, "@file.rsp", configuration); result.Tokens diff --git a/src/System.CommandLine.Tests/SuggestDirectiveTests.cs b/src/System.CommandLine.Tests/SuggestDirectiveTests.cs index a1aa3a1e66..5b60a817ee 100644 --- a/src/System.CommandLine.Tests/SuggestDirectiveTests.cs +++ b/src/System.CommandLine.Tests/SuggestDirectiveTests.cs @@ -1,6 +1,7 @@ // 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.IO; using System.Threading.Tasks; using FluentAssertions; @@ -36,10 +37,11 @@ public SuggestDirectiveTests() public async Task It_writes_suggestions_for_option_arguments_when_under_subcommand() { RootCommand rootCommand = new () { _eatCommand }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse($"[suggest:13] \"eat --fruit\"", config); @@ -59,10 +61,11 @@ public async Task It_writes_suggestions_for_option_arguments_when_under_root_com _fruitOption, _vegetableOption }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse($"[suggest:8] \"--fruit\"", config); @@ -80,10 +83,11 @@ public async Task It_writes_suggestions_for_option_arguments_when_under_root_com public async Task It_writes_suggestions_for_option_aliases_under_subcommand(string commandLine) { RootCommand rootCommand = new() { _eatCommand }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse(commandLine, config); @@ -107,10 +111,11 @@ public async Task It_writes_suggestions_for_option_aliases_under_root_command(st _vegetableOption, _fruitOption }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse(input, config); await result.InvokeAsync(); @@ -118,17 +123,18 @@ public async Task It_writes_suggestions_for_option_aliases_under_root_command(st config.Output .ToString() .Should() - .Be($"--fruit{NewLine}--vegetable{NewLine}"); + .Be($"--fruit{NewLine}--help{NewLine}--vegetable{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}"); } [Fact] public async Task It_writes_suggestions_for_subcommand_aliases_under_root_command() { RootCommand rootCommand = new() { _eatCommand }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse("[suggest]", config); await result.InvokeAsync(); @@ -136,7 +142,7 @@ public async Task It_writes_suggestions_for_subcommand_aliases_under_root_comman config.Output .ToString() .Should() - .Be($"eat{NewLine}"); + .Be($"--help{NewLine}--version{NewLine}-?{NewLine}-h{NewLine}/?{NewLine}/h{NewLine}eat{NewLine}"); } [Fact] @@ -147,10 +153,11 @@ public async Task It_writes_suggestions_for_partial_option_aliases_under_root_co _fruitOption, _vegetableOption }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse($"[suggest:1] \"f\"", config); @@ -169,11 +176,12 @@ public async Task It_writes_suggestions_for_partial_subcommand_aliases_under_roo { _eatCommand, new Command("wash-dishes") - }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + }; + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; var result = rootCommand.Parse("[suggest:1] \"d\"", config); @@ -191,13 +199,13 @@ public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliase RootCommand rootCommand = new () { _eatCommand, - new Command("wash-dishes") + new Command("wash-dishes"), + }; + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } }; - var config = new CommandLineBuilder(rootCommand) - .UseSuggestDirective() - .UseVersionOption() - .Build(); - config.Output = new StringWriter(); var result = rootCommand.Parse("[suggest:5] \"--ver\"", config); @@ -212,16 +220,18 @@ public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliase [Fact] public async Task It_writes_suggestions_for_partial_option_and_subcommand_aliases_under_root_command_with_an_argument() { - var config = new CommandLineBuilder(new Command("parent") - { - new Command("child"), - new Option("--option1"), - new Option("--option2"), - new Argument("arg") - }) - .UseSuggestDirective() - .Build(); - config.Output = new StringWriter(); + Command command = new("parent") + { + new Command("child"), + new Option("--option1"), + new Option("--option2"), + new Argument("arg") + }; + CommandLineConfiguration config = new (command) + { + Output = new StringWriter(), + Directives = { new SuggestDirective() } + }; await config.InvokeAsync("[suggest:3] \"opt\""); @@ -238,7 +248,7 @@ public async Task It_does_not_repeat_suggestion_for_already_specified_bool_optio { new Option("--bool-option") }; - var config = new CommandLineConfiguration(command) + CommandLineConfiguration config = new (command) { Output = new StringWriter() }; diff --git a/src/System.CommandLine.Tests/TestApps/NativeAOT/Program.cs b/src/System.CommandLine.Tests/TestApps/NativeAOT/Program.cs index b81cf144e7..b6be32deec 100644 --- a/src/System.CommandLine.Tests/TestApps/NativeAOT/Program.cs +++ b/src/System.CommandLine.Tests/TestApps/NativeAOT/Program.cs @@ -18,7 +18,7 @@ private static int Main(string[] args) command.SetAction(Run); - return new CommandLineBuilder(command).Build().Invoke(args); + return new CommandLineConfiguration(command).Invoke(args); void Run(InvocationContext context) { diff --git a/src/System.CommandLine.Tests/TokenReplacementTests.cs b/src/System.CommandLine.Tests/TokenReplacementTests.cs index 6ac968111b..c96af9b0c2 100644 --- a/src/System.CommandLine.Tests/TokenReplacementTests.cs +++ b/src/System.CommandLine.Tests/TokenReplacementTests.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using System.CommandLine.Parsing; using FluentAssertions; using Xunit; @@ -19,15 +18,16 @@ public void Token_replacer_receives_the_token_from_the_command_line_with_the_lea string receivedToken = null; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - receivedToken = tokenToReplace; - tokens = null; - message = "oops!"; - return false; - }) - .Build(); + CommandLineConfiguration config = new (command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + receivedToken = tokenToReplace; + tokens = null; + message = "oops!"; + return false; + } + }; command.Parse("@replace-me", config); @@ -41,14 +41,15 @@ public void Token_replacer_can_expand_argument_values() var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = new[] { "123" }; - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = new[] { "123" }; + message = null; + return true; + } + }; var result = command.Parse("@replace-me", config); @@ -64,14 +65,15 @@ public void Custom_token_replacer_can_expand_option_argument_values() var command = new RootCommand { option }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = new[] { "123" }; - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = new[] { "123" }; + message = null; + return true; + } + }; var result = command.Parse("-x @replace-me", config); @@ -87,14 +89,15 @@ public void Custom_token_replacer_can_expand_subcommands_and_options_and_argumen var command = new RootCommand { new Command("subcommand") { option } }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = new[] { "subcommand", "-x", "123" }; - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = new[] { "subcommand", "-x", "123" }; + message = null; + return true; + } + }; var result = command.Parse("@replace-me", config); @@ -110,14 +113,15 @@ public void Expanded_tokens_containing_whitespace_are_parsed_as_single_tokens() var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = new[] { "one two three" }; - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = new[] { "one two three" }; + message = null; + return true; + } + }; var result = command.Parse("@replace-me", config); @@ -131,14 +135,15 @@ public void Token_replacer_can_set_a_custom_error_message() var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = null; - message = "oops!"; - return false; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = null; + message = "oops!"; + return false; + } + }; var result = command.Parse("@replace-me", config); @@ -154,14 +159,15 @@ public void When_token_replacer_returns_false_without_setting_an_error_message_t var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = null; - message = null; - return false; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = null; + message = null; + return false; + } + }; var result = command.Parse("@replace-me", config); @@ -177,14 +183,15 @@ public void Token_replacer_will_delete_token_when_delegate_returns_true_and_sets var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = null; - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = null; + message = null; + return true; + } + }; var result = command.Parse("@replace-me", config); @@ -200,14 +207,15 @@ public void Token_replacer_will_delete_token_when_delegate_returns_true_and_sets var command = new RootCommand { argument }; - var config = new CommandLineBuilder(command) - .UseTokenReplacer((string tokenToReplace, out IReadOnlyList tokens, out string message) => - { - tokens = Array.Empty(); - message = null; - return true; - }) - .Build(); + CommandLineConfiguration config = new(command) + { + ResponseFileTokenReplacer = (string tokenToReplace, out IReadOnlyList tokens, out string message) => + { + tokens = Array.Empty(); + message = null; + return true; + } + }; var result = command.Parse("@replace-me", config); diff --git a/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs b/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs index 5a52f99884..5acf4b59b5 100644 --- a/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs +++ b/src/System.CommandLine.Tests/UseExceptionHandlerTests.cs @@ -16,12 +16,10 @@ public async Task UseExceptionHandler_catches_command_handler_exceptions_and_set var command = new Command("the-command"); command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); - var config = new CommandLineBuilder(new RootCommand - { - command - }) - .UseExceptionHandler() - .Build(); + CommandLineConfiguration config = new(command) + { + Error = new StringWriter(), + }; var resultCode = await config.InvokeAsync("the-command"); @@ -34,13 +32,10 @@ public async Task UseExceptionHandler_catches_command_handler_exceptions_and_wri var command = new Command("the-command"); command.SetAction((_, __) => Task.FromException(new Exception("oops!"))); - var config = new CommandLineBuilder(new RootCommand - { - command - }) - .UseExceptionHandler() - .Build(); - config.Error = new StringWriter(); + CommandLineConfiguration config = new(command) + { + Error = new StringWriter(), + }; await config.InvokeAsync("the-command"); @@ -53,10 +48,11 @@ public async Task When_thrown_exception_is_from_cancelation_no_output_is_generat Command command = new("the-command"); command.SetAction((_, __) => throw new OperationCanceledException()); - var config = new CommandLineBuilder(command) - .UseExceptionHandler() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + Error = new StringWriter() + }; int resultCode = await config .InvokeAsync("the-command"); @@ -65,41 +61,38 @@ public async Task When_thrown_exception_is_from_cancelation_no_output_is_generat resultCode.Should().NotBe(0); } - [Fact] - public async Task UseExceptionHandler_output_can_be_customized() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Exception_output_can_be_customized(bool async) { + Exception expectedException = new ("oops!"); Command command = new("the-command"); - command.SetAction((_, __) => throw new Exception("oops!")); - - var config = new CommandLineBuilder(command) - .UseExceptionHandler((exception, context) => - { - context.ParseResult.Configuration.Output.Write("Well that's awkward."); - return 22; - }) - .Build(); - config.Output = new StringWriter(); - - int resultCode = await config - .InvokeAsync("the-command"); - - config.Output.ToString().Should().Be("Well that's awkward."); + command.SetAction((_, __) => throw expectedException); + + CommandLineConfiguration config = new(command) + { + Error = new StringWriter(), + EnableDefaultExceptionHandler = false + }; + + ParseResult parseResult = command.Parse("the-command", config); + + int resultCode = 0; + + try + { + resultCode = async ? await parseResult.InvokeAsync() : parseResult.Invoke(); + } + catch (Exception ex) + { + ex.Should().Be(expectedException); + parseResult.Configuration.Error.Write("Well that's awkward."); + resultCode = 22; + } + + config.Error.ToString().Should().Be("Well that's awkward."); resultCode.Should().Be(22); } - - [Fact] - public async Task UseExceptionHandler_set_custom_result_code() - { - Command command = new("the-command"); - command.SetAction((_, __) => throw new Exception("oops!")); - var config = new CommandLineBuilder(command) - .UseExceptionHandler(errorExitCode: 42) - .Build(); - - int resultCode = await config - .InvokeAsync("the-command"); - - resultCode.Should().Be(42); - } } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/UseHelpTests.cs b/src/System.CommandLine.Tests/UseHelpTests.cs index 8f476ae655..c5448c9135 100644 --- a/src/System.CommandLine.Tests/UseHelpTests.cs +++ b/src/System.CommandLine.Tests/UseHelpTests.cs @@ -17,18 +17,18 @@ public class UseHelpTests [Fact] public async Task UseHelp_writes_help_for_the_specified_command() { - var command = new Command("command"); - var subcommand = new Command("subcommand"); - command.Subcommands.Add(subcommand); + Command command = new RootCommand() + { + new Command("command") + { + new Command("subcommand") + } + }; - var config = - new CommandLineBuilder(new RootCommand - { - command - }) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; var result = command.Parse("command subcommand --help", config); @@ -41,19 +41,15 @@ public async Task UseHelp_writes_help_for_the_specified_command() public async Task UseHelp_interrupts_execution_of_the_specified_command() { var wasCalled = false; - var command = new Command("command"); + var command = new Command("command") { new HelpOption() }; var subcommand = new Command("subcommand"); subcommand.SetAction((_) => wasCalled = true); command.Subcommands.Add(subcommand); - var config = - new CommandLineBuilder(new RootCommand - { - command - }) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; int result = await command.Parse("command subcommand --help", config).InvokeAsync(); @@ -67,13 +63,10 @@ public async Task UseHelp_interrupts_execution_of_the_specified_command() [InlineData("/?")] public async Task UseHelp_accepts_default_values(string value) { - var config = - new CommandLineBuilder(new RootCommand - { - new Command("command") - }) - .UseHelp() - .Build(); + CommandLineConfiguration config = new(new Command("command") { new HelpOption() }) + { + Output = new StringWriter() + }; StringWriter console = new(); config.Output = console; @@ -88,32 +81,23 @@ public async Task UseHelp_does_not_display_when_option_defined_with_same_alias() { var command = new Command("command"); command.Options.Add(new Option("-h")); - - var config = - new CommandLineBuilder(new RootCommand - { - command - }) - .UseHelp() - .Build(); - StringWriter console = new(); - config.Output = console; + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; int result = await command.Parse("command -h", config).InvokeAsync(); - console.ToString().Should().BeEmpty(); + config.Output.ToString().Should().BeEmpty(); } [Fact] public void There_are_no_parse_errors_when_help_is_invoked_on_root_command() { - RootCommand rootCommand = new (); - var config = new CommandLineBuilder(rootCommand) - .UseHelp() - .Build(); + RootCommand rootCommand = new(); - var result = rootCommand.Parse("-h", config); + var result = rootCommand.Parse("-h"); result.Errors .Should() @@ -125,14 +109,10 @@ public void There_are_no_parse_errors_when_help_is_invoked_on_subcommand() { var root = new RootCommand { - new Command("subcommand") + new Command("subcommand"), }; - var config = new CommandLineBuilder(root) - .UseHelp() - .Build(); - - var result = root.Parse("subcommand -h", config); + var result = root.Parse("subcommand -h"); result.Errors .Should() @@ -144,14 +124,10 @@ public void There_are_no_parse_errors_when_help_is_invoked_on_a_command_with_sub { var root = new RootCommand { - new Command("subcommand") + new Command("subcommand"), }; - var config = new CommandLineBuilder(root) - .UseHelp() - .Build(); - - var result = root.Parse("-h", config); + var result = root.Parse("-h"); result.Errors.Should().BeEmpty(); } @@ -167,77 +143,25 @@ public void There_are_no_parse_errors_when_help_is_invoked_on_a_command_with_req }, }; - var configuration = new CommandLineBuilder(command) - .UseHelp() - .Build(); - var result = command.Parse("-h", configuration); + var result = command.Parse("-h"); result.Errors.Should().BeEmpty(); } [Theory] - [InlineData("-h")] - [InlineData("inner -h")] - public void UseHelp_can_be_called_more_than_once_on_the_same_CommandLineBuilder(string commandline) + [InlineData("/lost")] + [InlineData("--confused")] + public async Task HelpOption_with_custom_aliases_uses_aliases(string helpAlias) { - var root = new RootCommand + RootCommand command = new() { - new Command("inner") + new HelpOption("/lost", "--confused") }; - - var config = new CommandLineBuilder(root) - .UseHelp() - .UseHelp() - .Build(); - config.Output = new StringWriter(); - - config.Invoke(commandline); - - config.Output.ToString().Should().Contain("Usage:"); - } - - [Theory] - [InlineData("-h")] - [InlineData("inner -h")] - public void UseHelp_can_be_called_more_than_once_on_the_same_command_with_different_CommandLineBuilders(string commandline) - { - var root = new RootCommand + CommandLineConfiguration config = new(command) { - new Command("inner") + Output = new StringWriter() }; - var config = new CommandLineBuilder(root) - .UseHelp() - .Build(); - var console1 = new StringWriter(); - config.Output = console1; - - config.Invoke(commandline); - - console1.ToString().Should().Contain("Usage:"); - - var parser2 = new CommandLineBuilder(root) - .UseHelp() - .Build(); - var console2 = new StringWriter(); - parser2.Output = console2; - - parser2.Invoke(commandline); - - console2.ToString().Should().Contain("Usage:"); - } - - [Theory] - [InlineData("/lost")] - [InlineData("--confused")] - public async Task UseHelp_with_custom_aliases_uses_aliases(string helpAlias) - { - var config = - new CommandLineBuilder(new RootCommand()) - .UseHelp("/lost", "--confused") - .Build(); - config.Output = new StringWriter(); - await config.InvokeAsync(helpAlias); config.Output.ToString().Should().Contain("Usage:"); @@ -251,11 +175,15 @@ public async Task UseHelp_with_custom_aliases_uses_aliases(string helpAlias) [InlineData("/?")] public async Task UseHelp_with_custom_aliases_default_aliases_replaced(string helpAlias) { - var config = - new CommandLineBuilder(new RootCommand()) - .UseHelp("--confused") - .Build(); - config.Output = new StringWriter(); + RootCommand command = new(); + command.Options.Clear(); + command.Options.Add(new HelpOption("--confused")); + + CommandLineConfiguration config = new(command) + { + Output = new StringWriter(), + EnableParseErrorReporting = false + }; await config.InvokeAsync(helpAlias); @@ -274,11 +202,12 @@ public void Individual_symbols_can_be_customized() subcommand, option, argument, - new HelpOption(), }; - CommandLineConfiguration config = new (rootCommand); - config.Output = new StringWriter(); + CommandLineConfiguration config = new (rootCommand) + { + Output = new StringWriter() + }; ParseResult parseResult = rootCommand.Parse("-h", config); @@ -302,12 +231,12 @@ public void Individual_symbols_can_be_customized() [Fact] public void Help_sections_can_be_replaced() { - var config = new CommandLineBuilder(new RootCommand()) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(new RootCommand()) + { + Output = new StringWriter() + }; - ParseResult parseResult = config.RootCommand.Parse("-h", config); + ParseResult parseResult = config.Parse("-h"); if (parseResult.Action is HelpAction helpAction) { @@ -329,13 +258,12 @@ IEnumerable> CustomLayout(HelpContext _) [Fact] public void Help_sections_can_be_supplemented() { - var command = new RootCommand("hello"); - var config = new CommandLineBuilder(command) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(new RootCommand("hello")) + { + Output = new StringWriter(), + }; - ParseResult parseResult = config.RootCommand.Parse("-h", config); + ParseResult parseResult = config.Parse("-h"); if (parseResult.Action is HelpAction helpAction) { @@ -345,7 +273,7 @@ public void Help_sections_can_be_supplemented() parseResult.Invoke(); var output = config.Output.ToString(); - var defaultHelp = GetDefaultHelp(command); + var defaultHelp = GetDefaultHelp(config.RootCommand); var expected = $"first{NewLine}{NewLine}{defaultHelp}last{NewLine}{NewLine}"; @@ -373,17 +301,15 @@ public void Layout_can_be_composed_dynamically_based_on_context() var command = new RootCommand { commandWithTypicalHelp, - commandWithCustomHelp, - new HelpOption() - { - Action = new HelpAction() - { - Builder = helpBuilder - } - } + commandWithCustomHelp + }; + + command.Options.OfType().Single().Action = new HelpAction() + { + Builder = helpBuilder }; - var config = new CommandLineBuilder(command).Build(); + var config = new CommandLineConfiguration(command); helpBuilder.CustomizeLayout(c => c.Command == commandWithTypicalHelp ? HelpBuilder.Default.GetLayout() @@ -414,13 +340,20 @@ public void Help_default_sections_can_be_wrapped() { Description = "option description", HelpName = "option" + }, + new HelpOption() + { + Action = new HelpAction() + { + Builder = new HelpBuilder(30) + } } }; - var config = new CommandLineBuilder(command) - .UseHelp(maxWidth: 30) - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; config.Invoke("test -h"); @@ -441,12 +374,12 @@ public void Help_default_sections_can_be_wrapped() [Fact] public void Help_customized_sections_can_be_wrapped() { - var config = new CommandLineBuilder(new RootCommand()) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration config = new(new RootCommand()) + { + Output = new StringWriter() + }; - ParseResult parseResult = config.RootCommand.Parse("-h", config); + ParseResult parseResult = config.Parse("-h"); if (parseResult.Action is HelpAction helpAction) { @@ -467,10 +400,24 @@ IEnumerable> CustomLayout(HelpContext _) private string GetDefaultHelp(Command command, bool trimOneNewline = true) { - var config = new CommandLineBuilder(command) - .UseHelp() - .Build(); - config.Output = new StringWriter(); + // The command might have already defined a HelpOption with custom settings, + // we need to overwrite it to get the actual defaults. + HelpOption defaultHelp = new(); + // HelpOption overrides Equals and treats every other instance of same type as equal + int index = command.Options.IndexOf(defaultHelp); + if (index >= 0) + { + command.Options[index] = defaultHelp; + } + else + { + command.Options.Add(defaultHelp); + } + + CommandLineConfiguration config = new(command) + { + Output = new StringWriter() + }; config.Invoke("-h"); diff --git a/src/System.CommandLine.Tests/UseParseErrorReportingTests.cs b/src/System.CommandLine.Tests/UseParseErrorReportingTests.cs index 7ca8b1b9a0..d3c8552940 100644 --- a/src/System.CommandLine.Tests/UseParseErrorReportingTests.cs +++ b/src/System.CommandLine.Tests/UseParseErrorReportingTests.cs @@ -4,7 +4,8 @@ // 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.Parsing; +using System.CommandLine.Help; +using System.Linq; using FluentAssertions; using Xunit; @@ -17,13 +18,14 @@ public void Parse_error_reporting_reports_error_when_help_is_used_and_required_s { var root = new RootCommand { - new Command("inner") + new Command("inner"), + new HelpOption() }; - var config = new CommandLineBuilder(root) - .UseParseErrorReporting() - .UseHelp() - .Build(); + CommandLineConfiguration config = new (root) + { + EnableParseErrorReporting = true + }; var parseResult = root.Parse("", config); @@ -35,18 +37,26 @@ public void Parse_error_reporting_reports_error_when_help_is_used_and_required_s } [Fact] - public void Parse_error_uses_custom_error_result_code() + public void User_can_customize_parse_error_result_code() { var root = new RootCommand { new Command("inner") }; - var config = new CommandLineBuilder(root) - .UseParseErrorReporting(errorExitCode: 42) - .Build(); + CommandLineConfiguration config = new (root) + { + EnableParseErrorReporting = true + }; + + ParseResult parseResult = root.Parse("", config); - int result = config.Invoke(""); + int result = parseResult.Invoke(); + + if (parseResult.Errors.Any()) + { + result = 42; + } result.Should().Be(42); } diff --git a/src/System.CommandLine.Tests/VersionOptionTests.cs b/src/System.CommandLine.Tests/VersionOptionTests.cs index 66870bd373..4db6e86733 100644 --- a/src/System.CommandLine.Tests/VersionOptionTests.cs +++ b/src/System.CommandLine.Tests/VersionOptionTests.cs @@ -1,6 +1,7 @@ // 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.IO; using System.Linq; using System.Reflection; @@ -20,10 +21,10 @@ public class VersionOptionTests [Fact] public async Task When_the_version_option_is_specified_then_the_version_is_written_to_standard_out() { - var configuration = new CommandLineBuilder(new RootCommand()) - .UseVersionOption() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(new RootCommand()) + { + Output = new StringWriter() + }; await configuration.InvokeAsync("--version"); @@ -37,12 +38,12 @@ public async Task When_the_version_option_is_specified_then_invocation_is_short_ var rootCommand = new RootCommand(); rootCommand.SetAction((_) => wasCalled = true); - var config = new CommandLineBuilder(rootCommand) - .UseVersionOption() - .Build(); - config.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; - await config.InvokeAsync("--version"); + await configuration.InvokeAsync("--version"); wasCalled.Should().BeFalse(); } @@ -50,11 +51,10 @@ public async Task When_the_version_option_is_specified_then_invocation_is_short_ [Fact] public async Task Version_option_appears_in_help() { - var configuration = new CommandLineBuilder(new RootCommand()) - .UseHelp() - .UseVersionOption() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(new RootCommand()) + { + Output = new StringWriter() + }; await configuration.InvokeAsync("--help"); @@ -72,14 +72,14 @@ public async Task When_the_version_option_is_specified_and_there_are_default_opt new Option("-x") { DefaultValueFactory = (_) => true - } + }, }; rootCommand.SetAction((_) => { }); - var configuration = new CommandLineBuilder(rootCommand) - .UseVersionOption() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; await configuration.InvokeAsync("--version"); @@ -89,16 +89,16 @@ public async Task When_the_version_option_is_specified_and_there_are_default_opt [Fact] public async Task When_the_version_option_is_specified_and_there_are_default_arguments_then_the_version_is_written_to_standard_out() { - var rootCommand = new RootCommand + RootCommand rootCommand = new () { - new Argument("x") { DefaultValueFactory =(_) => true } + new Argument("x") { DefaultValueFactory =(_) => true }, }; rootCommand.SetAction((_) => { }); - var configuration = new CommandLineBuilder(rootCommand) - .UseVersionOption() - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; await configuration.InvokeAsync("--version"); @@ -115,13 +115,14 @@ public void Version_is_not_valid_with_other_tokens(string commandLine) var rootCommand = new RootCommand { subcommand, - new Option("-x") + new Option("-x"), }; rootCommand.SetAction((_) => { }); - var configuration = new CommandLineBuilder(rootCommand) - .UseVersionOption() - .Build(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; var result = rootCommand.Parse(commandLine, configuration); @@ -136,13 +137,14 @@ public void Version_option_is_not_added_to_subcommands() var rootCommand = new RootCommand { - childCommand, + childCommand }; rootCommand.SetAction((_) => { }); - var configuration = new CommandLineBuilder(rootCommand) - .UseVersionOption() - .Build(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; configuration .RootCommand @@ -154,30 +156,20 @@ public void Version_option_is_not_added_to_subcommands() } [Fact] - public async Task Version_not_added_if_it_exists() + public async Task Version_can_specify_additional_alias() { - // Adding an option multiple times can occur two ways in - // real world scenarios - invocation can be invoked twice - // or the author may have their own version switch but - // still want other defaults. - var configuration = new CommandLineBuilder(new RootCommand()) - .UseVersionOption() - .UseVersionOption() - .Build(); - configuration.Output = new StringWriter(); - - await configuration.InvokeAsync("--version"); + RootCommand rootCommand = new(); - configuration.Output.ToString().Should().Be($"{version}{NewLine}"); - } + for (int i = 0; i < rootCommand.Options.Count; i++) + { + if (rootCommand.Options[i] is VersionOption) + rootCommand.Options[i] = new VersionOption("-v", "-version"); + } - [Fact] - public async Task Version_can_specify_additional_alias() - { - var configuration = new CommandLineBuilder(new RootCommand()) - .UseVersionOption("-v", "-version") - .Build(); - configuration.Output = new StringWriter(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; await configuration.InvokeAsync("-v"); configuration.Output.ToString().Should().Be($"{version}{NewLine}"); @@ -196,11 +188,15 @@ public void Version_is_not_valid_with_other_tokens_uses_custom_alias() { childCommand }; + + rootCommand.Options[1] = new VersionOption("-v"); + rootCommand.SetAction((_) => { }); - var configuration = new CommandLineBuilder(rootCommand) - .UseVersionOption("-v") - .Build(); + CommandLineConfiguration configuration = new(rootCommand) + { + Output = new StringWriter() + }; var result = rootCommand.Parse("-v subcommand", configuration); diff --git a/src/System.CommandLine/Builder/CommandLineBuilder.cs b/src/System.CommandLine/Builder/CommandLineBuilder.cs deleted file mode 100644 index 376dc11cb1..0000000000 --- a/src/System.CommandLine/Builder/CommandLineBuilder.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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.Collections.Generic; -using System.CommandLine.Binding; -using System.CommandLine.Help; -using System.CommandLine.Invocation; -using System.CommandLine.Parsing; - -namespace System.CommandLine -{ - /// - /// Enables composition of command line configurations. - /// - public partial class CommandLineBuilder - { - /// - internal bool EnablePosixBundlingFlag = true; - - /// - internal bool EnableTokenReplacement = true; - - /// - internal int? ParseErrorReportingExitCode; - - /// - internal int MaxLevenshteinDistance; - - /// - internal Func? ExceptionHandler; - - internal TimeSpan? ProcessTerminationTimeout; - - - /// The root command of the application. - public CommandLineBuilder(Command rootCommand) - { - Command = rootCommand ?? throw new ArgumentNullException(nameof(rootCommand)); - } - - /// - /// The command that the builder uses the root of the parser. - /// - public Command Command { get; } - - internal HelpOption? HelpOption; - - internal VersionOption? VersionOption; - - internal TryReplaceToken? TokenReplacer; - - public List Directives => _directives ??= new (); - - private List? _directives; - - /// - /// Creates a parser based on the configuration of the command line builder. - /// - public CommandLineConfiguration Build() => - new ( - Command, - _directives, - enablePosixBundling: EnablePosixBundlingFlag, - enableTokenReplacement: EnableTokenReplacement, - parseErrorReportingExitCode: ParseErrorReportingExitCode, - maxLevenshteinDistance: MaxLevenshteinDistance, - exceptionHandler: ExceptionHandler, - processTerminationTimeout: ProcessTerminationTimeout, - tokenReplacer: TokenReplacer); - } -} diff --git a/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs b/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs deleted file mode 100644 index 0067273bca..0000000000 --- a/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs +++ /dev/null @@ -1,290 +0,0 @@ -// 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.Invocation; -using System.CommandLine.Parsing; -using System.Threading; - -namespace System.CommandLine -{ - public partial class CommandLineBuilder - { - /// - /// Enables signaling and handling of process termination via a that can be passed to a during invocation. - /// - /// - /// Optional timeout for the command to process the exit cancellation. - /// If not passed, a default timeout of 2 seconds is enforced. - /// If positive value is passed - command is forcefully terminated after the timeout with exit code 130 (as if was not called). - /// Host enforced timeout for ProcessExit event cannot be extended - default is 2 seconds: https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.processexit?view=net-6.0. - /// - /// The reference to this instance. - public CommandLineBuilder CancelOnProcessTermination(TimeSpan? timeout = null) - { - ProcessTerminationTimeout = timeout ?? TimeSpan.FromSeconds(2); - - return this; - } - - /// - /// Enables the parser to recognize and expand POSIX-style bundled options. - /// - /// to parse POSIX bundles; otherwise, . - /// The reference to this instance. - /// - /// POSIX conventions recommend that single-character options be allowed to be specified together after a single - prefix. When is set to , the following command lines are equivalent: - /// - /// - /// > myapp -a -b -c - /// > myapp -abc - /// - /// - /// If an argument is provided after an option bundle, it applies to the last option in the bundle. When is set to , all of the following command lines are equivalent: - /// - /// > myapp -a -b -c arg - /// > myapp -abc arg - /// > myapp -abcarg - /// - /// - /// - public CommandLineBuilder EnablePosixBundling(bool value = true) - { - EnablePosixBundlingFlag = value; - - return this; - } - - /// - /// The reference to this instance. - public CommandLineBuilder UseEnvironmentVariableDirective() - { - Directives.Add(new EnvironmentVariablesDirective()); - - return this; - } - - /// - /// Uses the default configuration. - /// - /// Calling this method is the equivalent to calling: - /// - /// builder - /// .UseVersionOption() - /// .UseHelp() - /// .UseEnvironmentVariableDirective() - /// .UseParseDirective() - /// .UseSuggestDirective() - /// .RegisterWithDotnetSuggest() - /// .UseTypoCorrections() - /// .UseParseErrorReporting() - /// .UseExceptionHandler() - /// .CancelOnProcessTermination(); - /// - /// - /// The reference to this instance. - public CommandLineBuilder UseDefaults() - { - return UseVersionOption() - .UseHelp() - .UseEnvironmentVariableDirective() - .UseParseDirective() - .UseSuggestDirective() - .UseTypoCorrections() - .UseParseErrorReporting() - .UseExceptionHandler() - .CancelOnProcessTermination(); - } - - /// - /// Enables an exception handler to catch any unhandled exceptions thrown by a command handler during invocation. - /// - /// A delegate that will be called when an exception is thrown by a command handler. - /// It needs to return an exit code to be used when an exception is thrown. - /// The exit code to be used when an exception is thrown. - /// The reference to this instance. - public CommandLineBuilder UseExceptionHandler(Func? onException = null, int errorExitCode = 1) - { - ExceptionHandler = onException ?? Default; - - return this; - - int Default(Exception exception, InvocationContext context) - { - if (exception is not OperationCanceledException) - { - ConsoleHelpers.ResetTerminalForegroundColor(); - ConsoleHelpers.SetTerminalForegroundRed(); - - context.ParseResult.Configuration.Error.Write(LocalizationResources.ExceptionHandlerHeader()); - context.ParseResult.Configuration.Error.WriteLine(exception.ToString()); - - ConsoleHelpers.ResetTerminalForegroundColor(); - } - return errorExitCode; - } - } - - /// - /// Configures the application to show help when one of the following options are specified on the command line: - /// - /// -h - /// /h - /// --help - /// -? - /// /? - /// - /// - /// Maximum output width for default help builder. - /// The reference to this instance. - public CommandLineBuilder UseHelp(int? maxWidth = null) - { - return UseHelp(new HelpOption() - { - Action = new HelpAction() - { - Builder = new HelpBuilder(maxWidth ?? int.MaxValue) - } - }); - } - - /// - /// 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. - /// The name of the help option. - /// The set of aliases that can be specified on the command line to request help. - /// The reference to this instance. - public CommandLineBuilder UseHelp(string name, params string[] helpAliases) - { - return UseHelp(new HelpOption(name, helpAliases)); - } - - internal CommandLineBuilder UseHelp(HelpOption helpOption) - { - if (HelpOption is null) - { - HelpOption = helpOption; - - OverwriteOrAdd(Command, helpOption); - } - return this; - } - - /// - /// If the parse result contains errors, this exit code will be used when the process exits. - /// The reference to this instance. - public CommandLineBuilder UseParseDirective( - int errorExitCode = 1) - { - Directives.Add(new ParseDirective(errorExitCode)); - - return this; - } - - /// - /// Configures the command line to write error information to standard error when there are errors parsing command line input. - /// - /// The exit code to use when parser errors occur. - /// The reference to this instance. - public CommandLineBuilder UseParseErrorReporting( - int errorExitCode = 1) - { - ParseErrorReportingExitCode = errorExitCode; - - return this; - } - - /// - /// The reference to this instance. - public CommandLineBuilder UseSuggestDirective() - { - Directives.Add(new SuggestDirective()); - - return this; - } - - /// - /// Configures the application to provide alternative suggestions when a parse error is detected. - /// - /// The maximum Levenshtein distance for suggestions based on detected typos in command line input. - /// The reference to this instance. - public CommandLineBuilder UseTypoCorrections( - int maxLevenshteinDistance = 3) - { - if (maxLevenshteinDistance <= 0) - { - throw new ArgumentOutOfRangeException(nameof(maxLevenshteinDistance)); - } - - MaxLevenshteinDistance = maxLevenshteinDistance; - - return this; - } - - /// - /// Specifies a delegate used to replace any token prefixed with @ with zero or more other tokens, prior to parsing. - /// - /// Replaces the specified token with any number of other tokens. - /// The reference to this instance. - public CommandLineBuilder UseTokenReplacer(TryReplaceToken? replaceToken) - { - EnableTokenReplacement = replaceToken is not null; - TokenReplacer = replaceToken; - - return this; - } - - /// - /// Enables the use of a option (defaulting to the alias --version) which when specified in command line input will short circuit normal command handling and instead write out version information before exiting. - /// - /// The reference to this instance. - public CommandLineBuilder UseVersionOption() - { - if (VersionOption is null) - { - OverwriteOrAdd(Command, VersionOption = new()); - } - - return this; - } - - /// - /// The name of the version option. - /// One or more aliases to use instead of the default to signal that version information should be displayed. - /// The reference to this instance. - public CommandLineBuilder UseVersionOption(string name, params string[] aliases) - { - if (VersionOption is null) - { - OverwriteOrAdd(Command, VersionOption = new(name, aliases)); - } - - return this; - } - - /// - /// Creating a config from Command might cause side effects for Command. - /// The config type may add Options to the Command. - /// Since single command can be parsed multiple times with different configs, - /// we need to handle it properly. - /// Ideally config should not mutate Command at all. - /// - private static void OverwriteOrAdd(Command command, T option) where T : Option - { - if (command.HasOptions) - { - for (int i = 0; i < command.Options.Count; i++) - { - if (command.Options[i] is T) - { - command.Options[i] = option; - return; - } - } - } - - command.Options.Add(option); - } - } -} diff --git a/src/System.CommandLine/CommandLineConfiguration.cs b/src/System.CommandLine/CommandLineConfiguration.cs index 275587b8be..0d5c581074 100644 --- a/src/System.CommandLine/CommandLineConfiguration.cs +++ b/src/System.CommandLine/CommandLineConfiguration.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; -using System.CommandLine.Invocation; using System.CommandLine.Parsing; using System.Linq; using System.Threading.Tasks; @@ -19,108 +18,76 @@ public class CommandLineConfiguration private TextWriter? _output, _error; /// - /// A delegate that will be called when an exception is thrown by a command handler. + /// Initializes a new instance of the class. /// - internal readonly Func? ExceptionHandler; + /// The root command for the parser. + public CommandLineConfiguration(Command rootCommand) + { + RootCommand = rootCommand ?? throw new ArgumentNullException(nameof(rootCommand)); + Directives = new() + { + new SuggestDirective() + }; + } /// - /// The exit code to use when parser errors occur. + /// Gets a mutable list of the enabled directives. + /// Currently only is enabled by default. /// - internal readonly int? ParseErrorReportingExitCode; + public List Directives { get; } /// - /// The maximum Levenshtein distance for suggestions based on detected typos in command line input. + /// Enables the parser to recognize and expand POSIX-style bundled options. /// - internal readonly int MaxLevenshteinDistance; - - internal readonly TimeSpan? ProcessTerminationTimeout; - - private TryReplaceToken? _tokenReplacer; + /// to parse POSIX bundles; otherwise, . + /// + /// POSIX conventions recommend that single-character options be allowed to be specified together after a single - prefix. When is set to , the following command lines are equivalent: + /// + /// + /// > myapp -a -b -c + /// > myapp -abc + /// + /// + /// If an argument is provided after an option bundle, it applies to the last option in the bundle. When is set to , all of the following command lines are equivalent: + /// + /// > myapp -a -b -c arg + /// > myapp -abc arg + /// > myapp -abcarg + /// + /// + /// + public bool EnablePosixBundling { get; set; } = true; /// - /// Initializes a new instance of the CommandLineConfiguration class. + /// Enables a default exception handler to catch any unhandled exceptions thrown during invocation. Enabled by default. /// - /// The root command for the parser. - /// to enable POSIX bundling; otherwise, . - /// to enable token replacement; otherwise, . - /// Replaces the specified token with any number of other tokens. - public CommandLineConfiguration( - Command command, - bool enablePosixBundling = true, - bool enableTokenReplacement = true, - TryReplaceToken? tokenReplacer = null) - : this( - command, - directives: null, - enablePosixBundling: enablePosixBundling, - enableTokenReplacement: enableTokenReplacement, - parseErrorReportingExitCode: null, - maxLevenshteinDistance: 0, - processTerminationTimeout: null, - tokenReplacer: tokenReplacer, - exceptionHandler: null) - { - } - - internal CommandLineConfiguration( - Command command, - List? directives, - bool enablePosixBundling, - bool enableTokenReplacement, - int? parseErrorReportingExitCode, - int maxLevenshteinDistance, - TimeSpan? processTerminationTimeout, - TryReplaceToken? tokenReplacer, - Func? exceptionHandler) - { - RootCommand = command ?? throw new ArgumentNullException(nameof(command)); - Directives = directives is not null ? directives : Array.Empty(); - EnableTokenReplacement = enableTokenReplacement; - EnablePosixBundling = enablePosixBundling; - ParseErrorReportingExitCode = parseErrorReportingExitCode; - MaxLevenshteinDistance = maxLevenshteinDistance; - ProcessTerminationTimeout = processTerminationTimeout; - - _tokenReplacer = tokenReplacer; - ExceptionHandler = exceptionHandler; - } + public bool EnableDefaultExceptionHandler { get; set; } = true; - public static CommandLineBuilder CreateBuilder(Command rootCommand) => new CommandLineBuilder(rootCommand); + /// + /// Configures the command line to write error information to standard error when there are errors parsing command line input. Enabled by default. + /// + public bool EnableParseErrorReporting { get; set; } = true; /// - /// Gets the enabled directives. + /// Configures the application to provide alternative suggestions when a parse error is detected. Disabled by default. /// - public IReadOnlyList Directives { get; } + public bool EnableTypoCorrections { get; set; } = false; /// - /// Gets a value indicating whether POSIX bundling is enabled. + /// Enables signaling and handling of process termination (Ctrl+C, SIGINT, SIGTERM) via a + /// that can be passed to a during invocation. + /// If not provided, a default timeout of 2 seconds is enforced. /// - /// - /// POSIX recommends that single-character options be allowed to be specified together after a single - prefix. - /// - public bool EnablePosixBundling { get; } + public TimeSpan? ProcessTerminationTimeout { get; set; } = TimeSpan.FromSeconds(2); /// - /// Gets a value indicating whether token replacement is enabled. + /// Response file token replacer, enabled by default. + /// To disable response files support, this property needs to be set to null. /// /// /// When enabled, any token prefixed with @ can be replaced with zero or more other tokens. This is mostly commonly used to expand tokens from response files and interpolate them into a command line prior to parsing. /// - public bool EnableTokenReplacement { get; } - - internal TryReplaceToken? TokenReplacer => - EnableTokenReplacement - ? _tokenReplacer ??= DefaultTokenReplacer - : null; - - private bool DefaultTokenReplacer( - string tokenToReplace, - out IReadOnlyList? replacementTokens, - out string? errorMessage) => - StringExtensions.TryReadResponseFile( - tokenToReplace, - out replacementTokens, - out errorMessage); + public TryReplaceToken? ResponseFileTokenReplacer { get; set; } = StringExtensions.TryReadResponseFile; /// /// Gets the root command. @@ -150,6 +117,23 @@ public TextWriter Error set => _error = value ?? throw new ArgumentNullException(nameof(value), "Use TextWriter.Null to disable the output"); } + /// + /// Parses an array strings using the configured . + /// + /// The string arguments to parse. + /// A parse result describing the outcome of the parse operation. + public ParseResult Parse(IReadOnlyList args) + => Parser.Parse(RootCommand, args, this); + + /// + /// Parses a command line string value using the configured . + /// + /// The command line string input will be split into tokens as if it had been passed on the command line. + /// A command line string to parse, which can include spaces and quotes equivalent to what can be entered into a terminal. + /// A parse result describing the outcome of the parse operation. + public ParseResult Parse(string commandLine) + => Parser.Parse(RootCommand, commandLine, this); + /// /// Parses a command line string value and invokes the handler for the indicated command. /// diff --git a/src/System.CommandLine/Help/HelpOption.cs b/src/System.CommandLine/Help/HelpOption.cs index f9573f38f4..6512984ca3 100644 --- a/src/System.CommandLine/Help/HelpOption.cs +++ b/src/System.CommandLine/Help/HelpOption.cs @@ -7,15 +7,28 @@ public sealed class HelpOption : Option { private CliAction? _action; - public HelpOption(string name, string[] aliases) - : base(name, aliases, new Argument(name) { Arity = ArgumentArity.Zero }) + /// + /// When added to a , it configures the application to show help when one of the following options are specified on the command line: + /// + /// -h + /// /h + /// --help + /// -? + /// /? + /// + /// + public HelpOption() : this("--help", new[] { "-h", "/h", "-?", "/?" }) { - AppliesToSelfAndChildren = true; - Description = LocalizationResources.HelpOptionDescription(); } - public HelpOption() : this("--help", new[] { "-h", "/h", "-?", "/?" }) + /// + /// When added to a , it configures the application to show help when given name or one of the aliases are specified on the command line. + /// + public HelpOption(string name, params string[] aliases) + : base(name, aliases, new Argument(name) { Arity = ArgumentArity.Zero }) { + AppliesToSelfAndChildren = true; + Description = LocalizationResources.HelpOptionDescription(); } /// diff --git a/src/System.CommandLine/Help/VersionOption.cs b/src/System.CommandLine/Help/VersionOption.cs index 3cbb9d2473..ecae101bd4 100644 --- a/src/System.CommandLine/Help/VersionOption.cs +++ b/src/System.CommandLine/Help/VersionOption.cs @@ -7,21 +7,24 @@ using System.Threading; using System.Threading.Tasks; -namespace System.CommandLine.Help +namespace System.CommandLine { - internal sealed class VersionOption : Option + public sealed class VersionOption : Option { private CliAction? _action; - internal VersionOption() - : base("--version", new Argument("--version") { Arity = ArgumentArity.Zero }) + /// + /// When added to a , it enables the use of a --version option, which when specified in command line input will short circuit normal command handling and instead write out version information before exiting. + /// + public VersionOption() : this("--version", Array.Empty()) { - Description = LocalizationResources.VersionOptionDescription(); - AddValidators(); } - internal VersionOption(string name, string[] aliases) - : base(name, aliases) + /// + /// When added to a , it enables the use of a provided option name and aliases, which when specified in command line input will short circuit normal command handling and instead write out version information before exiting. + /// + public VersionOption(string name, params string[] aliases) + : base(name, aliases, new Argument("--version") { Arity = ArgumentArity.Zero }) { Description = LocalizationResources.VersionOptionDescription(); AddValidators(); diff --git a/src/System.CommandLine/Invocation/InvocationPipeline.cs b/src/System.CommandLine/Invocation/InvocationPipeline.cs index 56395ccfc2..0c3eaa73b6 100644 --- a/src/System.CommandLine/Invocation/InvocationPipeline.cs +++ b/src/System.CommandLine/Invocation/InvocationPipeline.cs @@ -39,9 +39,9 @@ internal static async Task InvokeAsync(ParseResult parseResult, Cancellatio return await firstCompletedTask; // return the result or propagate the exception } } - catch (Exception ex) when (parseResult.Configuration.ExceptionHandler is not null) + catch (Exception ex) when (parseResult.Configuration.EnableDefaultExceptionHandler) { - return parseResult.Configuration.ExceptionHandler(ex, context); + return DefaultExceptionHandler(ex, parseResult.Configuration); } finally { @@ -62,10 +62,25 @@ internal static int Invoke(ParseResult parseResult) { return parseResult.Action.Invoke(context); } - catch (Exception ex) when (parseResult.Configuration.ExceptionHandler is not null) + catch (Exception ex) when (parseResult.Configuration.EnableDefaultExceptionHandler) { - return parseResult.Configuration.ExceptionHandler(ex, context); + return DefaultExceptionHandler(ex, parseResult.Configuration); } } + + private static int DefaultExceptionHandler(Exception exception, CommandLineConfiguration config) + { + if (exception is not OperationCanceledException) + { + ConsoleHelpers.ResetTerminalForegroundColor(); + ConsoleHelpers.SetTerminalForegroundRed(); + + config.Error.Write(LocalizationResources.ExceptionHandlerHeader()); + config.Error.WriteLine(exception.ToString()); + + ConsoleHelpers.ResetTerminalForegroundColor(); + } + return 1; + } } } diff --git a/src/System.CommandLine/Invocation/ParseErrorResult.cs b/src/System.CommandLine/Invocation/ParseErrorResult.cs index 3f78da3f3b..35a32b0967 100644 --- a/src/System.CommandLine/Invocation/ParseErrorResult.cs +++ b/src/System.CommandLine/Invocation/ParseErrorResult.cs @@ -25,7 +25,7 @@ public override int Invoke(InvocationContext context) new HelpOption().Action!.Invoke(context); - return context.ParseResult.Configuration.ParseErrorReportingExitCode!.Value; + return 1; } public override Task InvokeAsync(InvocationContext context, CancellationToken cancellationToken = default) diff --git a/src/System.CommandLine/Invocation/TypoCorrection.cs b/src/System.CommandLine/Invocation/TypoCorrection.cs index 469a05d780..66fe914a44 100644 --- a/src/System.CommandLine/Invocation/TypoCorrection.cs +++ b/src/System.CommandLine/Invocation/TypoCorrection.cs @@ -10,6 +10,8 @@ namespace System.CommandLine.Invocation { internal sealed class TypoCorrectionAction : CliAction { + private const int MaxLevenshteinDistance = 3; + public override int Invoke(InvocationContext context) => ProvideSuggestions(context); @@ -21,7 +23,6 @@ public override Task InvokeAsync(InvocationContext context, CancellationTok private static int ProvideSuggestions(InvocationContext context) { ParseResult result = context.ParseResult; - int maxLevenshteinDistance = result.Configuration.MaxLevenshteinDistance; var unmatchedTokens = result.UnmatchedTokens; for (var i = 0; i < unmatchedTokens.Length; i++) @@ -29,7 +30,7 @@ private static int ProvideSuggestions(InvocationContext context) var token = unmatchedTokens[i]; bool first = true; - foreach (string suggestion in GetPossibleTokens(result.CommandResult.Command, token, maxLevenshteinDistance)) + foreach (string suggestion in GetPossibleTokens(result.CommandResult.Command, token)) { if (first) { @@ -44,7 +45,7 @@ private static int ProvideSuggestions(InvocationContext context) return 0; } - private static IEnumerable GetPossibleTokens(Command targetSymbol, string token, int maxLevenshteinDistance) + private static IEnumerable GetPossibleTokens(Command targetSymbol, string token) { if (!targetSymbol.HasOptions && !targetSymbol.HasSubcommands) { @@ -72,7 +73,7 @@ private static IEnumerable GetPossibleTokens(Command targetSymbol, strin int? bestDistance = null; return possibleMatches .Select(possibleMatch => (possibleMatch, distance:GetDistance(token, possibleMatch))) - .Where(tuple => tuple.distance <= maxLevenshteinDistance) + .Where(tuple => tuple.distance <= MaxLevenshteinDistance) .OrderBy(tuple => tuple.distance) .ThenByDescending(tuple => GetStartsWithDistance(token, tuple.possibleMatch)) .TakeWhile(tuple => diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index eef796257c..80c028323b 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -62,15 +62,15 @@ internal ParseResult Parse() if (_action is null) { - if (_configuration.ParseErrorReportingExitCode.HasValue && _symbolResultTree.ErrorCount > 0) - { - _action = new ParseErrorResultAction(); - } - else if (_configuration.MaxLevenshteinDistance > 0 && _rootCommandResult.Command.TreatUnmatchedTokensAsErrors + if (_configuration.EnableTypoCorrections && _rootCommandResult.Command.TreatUnmatchedTokensAsErrors && _symbolResultTree.UnmatchedTokens is not null) { _action = new TypoCorrectionAction(); } + else if (_configuration.EnableParseErrorReporting && _symbolResultTree.ErrorCount > 0) + { + _action = new ParseErrorResultAction(); + } } return new ( diff --git a/src/System.CommandLine/Parsing/Parser.cs b/src/System.CommandLine/Parsing/Parser.cs index 79e4753306..39218ff4a2 100644 --- a/src/System.CommandLine/Parsing/Parser.cs +++ b/src/System.CommandLine/Parsing/Parser.cs @@ -146,7 +146,7 @@ private static ParseResult Parse( throw new ArgumentNullException(nameof(arguments)); } - configuration ??= CommandLineConfiguration.CreateBuilder(command).UseDefaults().Build(); + configuration ??= new CommandLineConfiguration(command); arguments.Tokenize( configuration, diff --git a/src/System.CommandLine/Parsing/StringExtensions.cs b/src/System.CommandLine/Parsing/StringExtensions.cs index 36646e4ec7..3ec10678d6 100644 --- a/src/System.CommandLine/Parsing/StringExtensions.cs +++ b/src/System.CommandLine/Parsing/StringExtensions.cs @@ -115,8 +115,7 @@ internal static void Tokenize( } } - if (configuration.EnableTokenReplacement && - configuration.TokenReplacer is { } replacer && + if (configuration.ResponseFileTokenReplacer is { } replacer && arg.GetReplaceableTokenValue() is { } value) { if (replacer( @@ -415,9 +414,7 @@ private static Dictionary ValidTokens(this Command command, IRead for (int directiveIndex = 0; directiveIndex < directives.Count; directiveIndex++) { Directive directive = directives[directiveIndex]; - tokens.Add( - directive.Name, - new Token(directive.Name, TokenType.Directive, directive, Token.ImplicitPosition)); + tokens[directive.Name] = new Token(directive.Name, TokenType.Directive, directive, Token.ImplicitPosition); } } diff --git a/src/System.CommandLine/RootCommand.cs b/src/System.CommandLine/RootCommand.cs index 25746bcde9..1d191513f4 100644 --- a/src/System.CommandLine/RootCommand.cs +++ b/src/System.CommandLine/RootCommand.cs @@ -1,6 +1,7 @@ // 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.IO; using System.Reflection; @@ -24,6 +25,8 @@ public class RootCommand : Command /// The description of the command, shown in help. public RootCommand(string description = "") : base(ExecutableName, description) { + Options.Add(new HelpOption()); + Options.Add(new VersionOption()); } internal static Assembly GetAssembly()