diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28731172b1..9d97a2dde9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ Contributing ============ -Please read [.NET Guidelines](https://github.com/dotnet/runtime/blob/master/CONTRIBUTING.md) for more general information about coding styles, source structure, making pull requests, and more. +Please read [.NET Guidelines](https://github.com/dotnet/runtime/blob/main/CONTRIBUTING.md) for more general information about coding styles, source structure, making pull requests, and more. ## Developer guide @@ -9,7 +9,7 @@ This project can be developed on any platform. To get started, follow instructio ### Prerequisites -This project depends on .NET 7. Before working on the project, check that the [.NET SDK](https://dotnet.microsoft.com/en-us/download) is installed. +This project depends on the .NET 9 SDK. Before working on the project, check that the [.NET SDK](https://dotnet.microsoft.com/en-us/download) is installed. ### Visual Studio diff --git a/Directory.Packages.props b/Directory.Packages.props index 0efd501b78..4de7f06b05 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,6 +10,8 @@ + + @@ -20,9 +22,10 @@ + - + \ No newline at end of file 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 d6319ed1be..45cc9f778a 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 @@ -153,10 +153,11 @@ 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 + public class VersionOption : Option .ctor() .ctor(System.String name, System.String[] aliases) public System.CommandLine.Invocation.CommandLineAction Action { get; set; } + public System.Type ValueType { get; } System.CommandLine.Completions public class CompletionContext public static CompletionContext Empty { get; } @@ -185,10 +186,11 @@ System.CommandLine.Help public class HelpAction : System.CommandLine.Invocation.SynchronousCommandLineAction .ctor() public System.Int32 Invoke(System.CommandLine.ParseResult parseResult) - public class HelpOption : System.CommandLine.Option + public class HelpOption : System.CommandLine.Option .ctor() .ctor(System.String name, System.String[] aliases) public System.CommandLine.Invocation.CommandLineAction Action { get; set; } + public System.Type ValueType { get; } System.CommandLine.Invocation public abstract class AsynchronousCommandLineAction : CommandLineAction public System.Threading.Tasks.Task InvokeAsync(System.CommandLine.ParseResult parseResult, System.Threading.CancellationToken cancellationToken = null) diff --git a/src/System.CommandLine.Tests/ArgumentTests.cs b/src/System.CommandLine.Tests/ArgumentTests.cs index 037934cbab..8672fbd379 100644 --- a/src/System.CommandLine.Tests/ArgumentTests.cs +++ b/src/System.CommandLine.Tests/ArgumentTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using System.Linq; +using FluentAssertions.Execution; using Xunit; namespace System.CommandLine.Tests; @@ -41,6 +42,53 @@ public void When_there_is_no_default_value_then_GetDefaultValue_throws() .Be("Argument \"the-arg\" does not have a default value"); } + [Fact] + public void GetRequiredValue_does_not_throw_when_help_is_requested_and_DefaultValueFactory_is_set() + { + var argument = new Argument("the-arg") + { + DefaultValueFactory = _ => "default" + }; + + var result = new RootCommand { argument }.Parse("-h"); + + using var _ = new AssertionScope(); + + result.Invoking(r => r.GetRequiredValue(argument)).Should().NotThrow(); + result.GetRequiredValue(argument).Should().Be("default"); + + result.Invoking(r => r.GetRequiredValue("the-arg")).Should().NotThrow(); + result.GetRequiredValue("the-arg").Should().Be("default"); + + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void When_there_is_no_default_value_then_GetDefaultValue_does_not_throw_for_bool() + { + var argument = new Argument("the-arg"); + + argument.GetDefaultValue().Should().Be(false); + } + + [Fact] + public void When_there_is_no_default_value_then_GetRequiredValue_does_not_throw_for_bool() + { + var argument = new Argument("the-arg"); + + var result = new RootCommand { argument }.Parse(""); + + using var _ = new AssertionScope(); + + result.Invoking(r => r.GetRequiredValue(argument)).Should().NotThrow(); + result.GetRequiredValue(argument).Should().BeFalse(); + + result.Invoking(r => r.GetRequiredValue("the-arg")).Should().NotThrow(); + result.GetRequiredValue("the-arg").Should().BeFalse(); + + result.Errors.Should().BeEmpty(); + } + [Fact] public void Argument_of_enum_can_limit_enum_members_as_valid_values() { diff --git a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs index 7e2fcec4bc..454942a873 100644 --- a/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs +++ b/src/System.CommandLine.Tests/Binding/TypeConversionTests.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Net; +using FluentAssertions.Execution; using Xunit; namespace System.CommandLine.Tests.Binding @@ -585,6 +586,7 @@ public void Values_can_be_correctly_converted_to_Uri_when_custom_parser_is_provi [Fact] public void Options_with_arguments_specified_can_be_correctly_converted_to_bool_without_the_parser_specifying_a_custom_converter() { + using var _ = new AssertionScope(); GetValue(new Option("-x"), "-x false").Should().BeFalse(); GetValue(new Option("-x"), "-x true").Should().BeTrue(); } diff --git a/src/System.CommandLine.Tests/DirectiveTests.cs b/src/System.CommandLine.Tests/DirectiveTests.cs index b1e9bd33ba..9d2851726d 100644 --- a/src/System.CommandLine.Tests/DirectiveTests.cs +++ b/src/System.CommandLine.Tests/DirectiveTests.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.Parsing; using System.Linq; using System.Threading.Tasks; using FluentAssertions; @@ -22,14 +23,14 @@ public void Directives_should_be_considered_as_unmatched_tokens_when_they_are_no } [Fact] - public void Raw_tokens_still_hold_directives() + public void Tokens_still_hold_directives() { Directive directive = new ("parse"); ParseResult result = Parse(new Option("-y"), directive, "[parse] -y"); result.GetResult(directive).Should().NotBeNull(); - result.Tokens.Should().Contain(t => t.Value == "[parse]"); + result.Tokens.Should().Contain(t => t.Value == "[parse]" && t.Type == TokenType.Directive); } [Fact] diff --git a/src/System.CommandLine.Tests/GetValueByNameParserTests.cs b/src/System.CommandLine.Tests/GetValueByNameParserTests.cs index 7a2a8721e7..b787337c16 100644 --- a/src/System.CommandLine.Tests/GetValueByNameParserTests.cs +++ b/src/System.CommandLine.Tests/GetValueByNameParserTests.cs @@ -368,4 +368,30 @@ public void Recursive_option_on_parent_command_can_be_looked_up_when_subcommand_ result.GetValue("--opt").Should().Be("hello"); } + + [Fact] + public void When_argument_type_is_unknown_then_named_lookup_can_be_used_to_get_value_as_supertype() + { + var command = new RootCommand + { + new Argument("arg") + }; + + var result = command.Parse("value"); + + result.GetValue("arg").Should().Be("value"); + } + + [Fact] + public void When_option_type_is_unknown_then_named_lookup_can_be_used_to_get_value_as_supertype() + { + var command = new RootCommand + { + new Option("-x") + }; + + var result = command.Parse("-x value"); + + result.GetValue("-x").Should().Be("value"); + } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/GlobalOptionTests.cs b/src/System.CommandLine.Tests/GlobalOptionTests.cs index 1acb39213c..7c51a2e751 100644 --- a/src/System.CommandLine.Tests/GlobalOptionTests.cs +++ b/src/System.CommandLine.Tests/GlobalOptionTests.cs @@ -25,8 +25,8 @@ public void When_a_required_global_option_is_omitted_it_results_in_an_error() { var command = new Command("child"); var rootCommand = new RootCommand { command }; - command.SetAction((_) => { }); - var requiredOption = new Option("--i-must-be-set") + command.SetAction(_ => { }); + var requiredOption = new Option("--i-must-be-set") { Required = true, Recursive = true @@ -45,7 +45,7 @@ public void When_a_required_global_option_is_omitted_it_results_in_an_error() public void When_a_required_global_option_has_multiple_aliases_the_error_message_uses_the_name() { var rootCommand = new RootCommand(); - var requiredOption = new Option("-i", "--i-must-be-set") + var requiredOption = new Option("-i", "--i-must-be-set") { Required = true, Recursive = true diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs index b2d9335ed3..22cced02a0 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.Customization.cs @@ -328,7 +328,6 @@ public void Argument_can_fallback_to_default_when_customizing( config.Output.ToString().Should().MatchRegex(expected); } - [Fact] public void Individual_symbols_can_be_customized() { diff --git a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs index 89fb9fca8a..4a735a4aad 100644 --- a/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs +++ b/src/System.CommandLine.Tests/Help/HelpBuilderTests.cs @@ -778,13 +778,15 @@ public void Help_describes_default_value_for_argument() help.Should().Contain("[default: the-arg-value]"); } - [Fact] - public void Help_does_not_show_default_value_for_argument_when_default_value_is_empty() + [Theory] + [InlineData("")] + [InlineData(null)] + public void Help_does_not_show_default_value_for_argument_when_default_value_is_null_or_empty(string defaultValue) { var argument = new Argument("the-arg") { Description = "The argument description", - DefaultValueFactory = (_) => "" + DefaultValueFactory = _ => defaultValue }; var command = new Command("the-command", "The command description") @@ -798,7 +800,32 @@ public void Help_does_not_show_default_value_for_argument_when_default_value_is_ var help = _console.ToString(); - help.Should().NotContain("[default"); + help.Should().NotContain("[]"); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void Help_does_not_show_default_value_for_option_when_default_value_is_null_or_empty(string defaultValue) + { + var argument = new Option("--opt") + { + Description = "The option description", + DefaultValueFactory = _ => defaultValue + }; + + var command = new Command("the-command", "The command description") + { + argument + }; + + var helpBuilder = GetHelpBuilder(SmallMaxWidth); + + helpBuilder.Write(command, _console); + + var help = _console.ToString(); + + help.Should().NotContain("[]"); } [Fact] diff --git a/src/System.CommandLine.Tests/OptionTests.cs b/src/System.CommandLine.Tests/OptionTests.cs index 7f34e9d3dc..804af82386 100644 --- a/src/System.CommandLine.Tests/OptionTests.cs +++ b/src/System.CommandLine.Tests/OptionTests.cs @@ -4,6 +4,7 @@ using System.CommandLine.Parsing; using FluentAssertions; using System.Linq; +using FluentAssertions.Execution; using Xunit; namespace System.CommandLine.Tests @@ -272,16 +273,82 @@ public void Option_T_default_value_factory_can_be_set_after_instantiation() { var option = new Option("-x"); - option.DefaultValueFactory = (_) => 123; + option.DefaultValueFactory = _ => 123; - new RootCommand { option } - .Parse("") + var parseResult = new RootCommand { option }.Parse(""); + + parseResult .GetResult(option) .GetValueOrDefault() .Should() .Be(123); } + [Fact] + public void When_there_is_no_default_value_then_GetRequiredValue_does_not_throw_for_bool() + { + var option = new Option("-x"); + + var result = new RootCommand { option }.Parse(""); + + using var _ = new AssertionScope(); + + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.GetRequiredValue(option).Should().BeFalse(); + + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue("-x").Should().BeFalse(); + + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void GetRequiredValue_does_not_throw_when_help_is_requested_and_DefaultValueFactory_is_set() + { + var option = new Option("-x") + { + DefaultValueFactory = _ => "default" + }; + + var result = new RootCommand { option }.Parse("-h"); + + using var _ = new AssertionScope(); + + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.GetRequiredValue(option).Should().Be("default"); + + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue("-x").Should().Be("default"); + + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void When_there_is_no_default_value_then_GetDefaultValue_does_not_throw_for_bool() + { + var option = new Option("-x"); + + option.GetDefaultValue().Should().Be(false); + } + + [Fact] + public void When_there_is_a_default_value_then_GetRequiredValue_does_not_throw() + { + var option = new Option("-x") + { + Required = true, + DefaultValueFactory = _ => "default" + }; + + var result = new RootCommand { option }.Parse(""); + + using var _ = new AssertionScope(); + + result.Invoking(r => r.GetRequiredValue(option)).Should().NotThrow(); + result.Invoking(r => r.GetRequiredValue("-x")).Should().NotThrow(); + result.GetRequiredValue(option).Should().Be("default"); + } + [Fact] public void Option_T_default_value_is_validated() { @@ -322,9 +389,6 @@ public void Option_of_boolean_defaults_to_false_when_not_specified() var result = new RootCommand { option }.Parse(""); - result.GetResult(option) - .Should() - .BeNull(); result.GetValue(option) .Should() .BeFalse(); @@ -405,6 +469,8 @@ public void Multiple_identifier_token_instances_without_argument_tokens_can_be_p var result = root.Parse("-v -v -v"); + using var _ = new AssertionScope(); + result.GetValue(option).Should().BeTrue(); result.GetRequiredValue(option).Should().BeTrue(); result.GetRequiredValue(option.Name).Should().BeTrue(); diff --git a/src/System.CommandLine.Tests/ParserTests.cs b/src/System.CommandLine.Tests/ParserTests.cs index e9c9fd709e..410c67bc82 100644 --- a/src/System.CommandLine.Tests/ParserTests.cs +++ b/src/System.CommandLine.Tests/ParserTests.cs @@ -10,7 +10,6 @@ using System.Linq; using FluentAssertions.Common; using Xunit; -using Xunit.Abstractions; namespace System.CommandLine.Tests { @@ -25,12 +24,12 @@ private T GetValue(ParseResult parseResult, Argument argument) [Fact] public void An_option_can_be_checked_by_object_instance() { - var option = new Option("--flag"); - var option2 = new Option("--flag2"); - var result = new RootCommand { option, option2 } - .Parse("--flag"); + var option1 = new Option("--option1"); + var option2 = new Option("--option2"); - result.GetResult(option).Should().NotBeNull(); + var result = new RootCommand { option1, option2 }.Parse("--option1"); + + result.GetResult(option1).Should().NotBeNull(); result.GetResult(option2).Should().BeNull(); } @@ -166,7 +165,9 @@ public void Option_long_forms_do_not_get_unbundled() result.CommandResult .Children - .Select(o => ((OptionResult)o).Option.Name) + .OfType() + .Where(r => !r.Implicit) + .Select(o => o.Option.Name) .Should() .BeEquivalentTo("--xyz"); } @@ -660,9 +661,9 @@ public void When_options_with_the_same_name_are_defined_on_parent_and_child_comm .Should() .BeOfType() .Which - .Children + .Command .Should() - .AllBeAssignableTo(); + .Be(outer); result.CommandResult .Children .Should() @@ -673,25 +674,17 @@ public void When_options_with_the_same_name_are_defined_on_parent_and_child_comm public void When_options_with_the_same_name_are_defined_on_parent_and_child_commands_and_specified_in_between_then_it_attaches_to_the_outer_command() { var outer = new Command("outer"); - outer.Options.Add(new Option("-x")); + var outerOption = new Option("-x"); + outer.Options.Add(outerOption); var inner = new Command("inner"); - inner.Options.Add(new Option("-x")); + var innerOption = new Option("-x"); + inner.Options.Add(innerOption); outer.Subcommands.Add(inner); var result = outer.Parse("outer -x inner"); - result.CommandResult - .Children - .Should() - .BeEmpty(); - result.CommandResult - .Parent - .Should() - .BeOfType() - .Which - .Children - .Should() - .ContainSingle(o => o is OptionResult && ((OptionResult)o).Option.Name == "-x"); + result.GetValue(outerOption).Should().BeTrue(); + result.GetValue(innerOption).Should().BeFalse(); } [Fact] @@ -1049,8 +1042,8 @@ public void Option_and_Command_can_have_the_same_alias() [Fact] public void Options_can_have_the_same_alias_differentiated_only_by_prefix() { - var option1 = new Option("-a"); - var option2 = new Option("--a"); + var option1 = new Option("-a"); + var option2 = new Option("--a"); var parser = new RootCommand { @@ -1058,16 +1051,16 @@ public void Options_can_have_the_same_alias_differentiated_only_by_prefix() option2 }; - parser.Parse("-a").CommandResult + parser.Parse("-a value").CommandResult .Children .Select(s => ((OptionResult)s).Option) .Should() - .BeEquivalentTo(new[] { option1 }); - parser.Parse("--a").CommandResult + .BeEquivalentTo([option1]); + parser.Parse("--a value").CommandResult .Children .Select(s => ((OptionResult)s).Option) .Should() - .BeEquivalentTo(new[] { option2 }); + .BeEquivalentTo([option2]); } [Theory] diff --git a/src/System.CommandLine.Tests/ResponseFileTests.cs b/src/System.CommandLine.Tests/ResponseFileTests.cs index 17e34ee9ae..5404fdee22 100644 --- a/src/System.CommandLine.Tests/ResponseFileTests.cs +++ b/src/System.CommandLine.Tests/ResponseFileTests.cs @@ -211,8 +211,8 @@ public void Response_file_can_contain_comments_which_are_ignored_when_loaded() [Fact] public void When_response_file_does_not_exist_then_error_is_returned() { - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + var optionOne = new Option("-x"); + var optionTwo = new Option("-y"); var result = new RootCommand { @@ -229,8 +229,8 @@ public void When_response_file_does_not_exist_then_error_is_returned() [Fact] public void When_response_filepath_is_not_specified_then_error_is_returned() { - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + var optionOne = new Option("-x"); + var optionTwo = new Option("-y"); var result = new RootCommand { @@ -253,8 +253,8 @@ public void When_response_filepath_is_not_specified_then_error_is_returned() public void When_response_file_cannot_be_read_then_specified_error_is_returned() { var nonexistent = Path.GetTempFileName(); - var optionOne = new Option("--flag"); - var optionTwo = new Option("--flag2"); + var optionOne = new Option("--flag"); + var optionTwo = new Option("--flag2"); using (File.Open(nonexistent, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { diff --git a/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj b/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj index 29b661c36f..e448549980 100644 --- a/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj +++ b/src/System.CommandLine.Tests/System.CommandLine.Tests.csproj @@ -6,12 +6,12 @@ $(DefaultExcludesInProjectFolder);TestApps\** $(NoWarn);CS8632 - - + + - + @@ -24,11 +24,11 @@ - + - + @@ -36,13 +36,17 @@ + + + + + - + diff --git a/src/System.CommandLine.Tests/VersionOptionTests.cs b/src/System.CommandLine.Tests/VersionOptionTests.cs index f7552c84f3..b796349136 100644 --- a/src/System.CommandLine.Tests/VersionOptionTests.cs +++ b/src/System.CommandLine.Tests/VersionOptionTests.cs @@ -1,12 +1,12 @@ // 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 FluentAssertions; +using FluentAssertions.Execution; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using FluentAssertions; using Xunit; using static System.Environment; @@ -160,17 +160,16 @@ public async Task Version_can_specify_additional_alias() { RootCommand rootCommand = new(); - for (int i = 0; i < rootCommand.Options.Count; i++) - { - if (rootCommand.Options[i] is VersionOption) - rootCommand.Options[i] = new VersionOption("-v", "-version"); - } + rootCommand.Options.Clear(); + rootCommand.Add(new VersionOption("-v", "-version")); CommandLineConfiguration configuration = new(rootCommand) { Output = new StringWriter() }; + using var _ = new AssertionScope(); + await configuration.InvokeAsync("-v"); configuration.Output.ToString().Should().Be($"{version}{NewLine}"); @@ -180,18 +179,19 @@ public async Task Version_can_specify_additional_alias() } [Fact] - public void Version_is_not_valid_with_other_tokens_uses_custom_alias() + public void Version_is_not_valid_with_other_tokens_when_it_uses_custom_alias() { var childCommand = new Command("subcommand"); - childCommand.SetAction((_) => { }); + childCommand.SetAction(_ => { }); var rootCommand = new RootCommand { childCommand }; - rootCommand.Options[1] = new VersionOption("-v"); + rootCommand.Options.Clear(); + rootCommand.Add(new VersionOption("-v")); - rootCommand.SetAction((_) => { }); + rootCommand.SetAction(_ => { }); CommandLineConfiguration configuration = new(rootCommand) { diff --git a/src/System.CommandLine/Argument.cs b/src/System.CommandLine/Argument.cs index aae85e2f26..61c41c309c 100644 --- a/src/System.CommandLine/Argument.cs +++ b/src/System.CommandLine/Argument.cs @@ -131,5 +131,20 @@ public override IEnumerable GetCompletions(CompletionContext con public override string ToString() => $"{nameof(Argument)}: {Name}"; internal bool IsBoolean() => ValueType == typeof(bool) || ValueType == typeof(bool?); + + internal static Argument None { get; } = new NoArgument(); + + internal class NoArgument : Argument + { + internal NoArgument() : base("@none") + { + } + + public override Type ValueType { get; } = typeof(void); + + internal override object? GetDefaultValue(ArgumentResult argumentResult) => null; + + public override bool HasDefaultValue => false; + } } } diff --git a/src/System.CommandLine/Argument{T}.cs b/src/System.CommandLine/Argument{T}.cs index 42263f819e..7bc9c5835b 100644 --- a/src/System.CommandLine/Argument{T}.cs +++ b/src/System.CommandLine/Argument{T}.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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; @@ -10,6 +10,7 @@ namespace System.CommandLine public class Argument : Argument { private Func? _customParser; + private Func? _defaultValueFactory; /// /// Initializes a new instance of the Argument class. @@ -27,7 +28,23 @@ public Argument(string name) : base(name) /// The same instance can be set as , in such case /// the delegate is also invoked when an input was provided. /// - public Func? DefaultValueFactory { get; set; } + public Func? DefaultValueFactory + { + get + { + if (_defaultValueFactory is null) + { + switch (this) + { + case Argument boolArgument: + boolArgument.DefaultValueFactory = _ => false; + break; + } + } + return _defaultValueFactory; + } + set => _defaultValueFactory = value; + } /// /// A custom argument parser. diff --git a/src/System.CommandLine/Help/HelpBuilder.Default.cs b/src/System.CommandLine/Help/HelpBuilder.Default.cs index 23199dab53..33e53c0da0 100644 --- a/src/System.CommandLine/Help/HelpBuilder.Default.cs +++ b/src/System.CommandLine/Help/HelpBuilder.Default.cs @@ -18,13 +18,17 @@ public static class Default /// /// Gets an argument's default value to be displayed in help. /// - /// The argument or option to get the default value for. - public static string GetArgumentDefaultValue(Symbol parameter) + /// The argument or option to get the default value for. + public static string GetArgumentDefaultValue(Symbol symbol) { - return parameter switch + return symbol switch { - Argument argument => argument.HasDefaultValue ? ToString(argument.GetDefaultValue()) : "", - Option option => option.HasDefaultValue ? ToString(option.GetDefaultValue()) : "", + Argument argument => ShouldShowDefaultValue(argument) + ? ToString(argument.GetDefaultValue()) + : "", + Option option => ShouldShowDefaultValue(option) + ? ToString(option.GetDefaultValue()) + : "", _ => throw new InvalidOperationException("Symbol must be an Argument or Option.") }; @@ -37,6 +41,22 @@ public static string GetArgumentDefaultValue(Symbol parameter) }; } + public static bool ShouldShowDefaultValue(Symbol symbol) => + symbol switch + { + Option option => ShouldShowDefaultValue(option), + Argument argument => ShouldShowDefaultValue(argument), + _ => false + }; + + public static bool ShouldShowDefaultValue(Option option) => + option.HasDefaultValue && + !(option.ValueType == typeof(bool) || option.ValueType == typeof(bool?)); + + public static bool ShouldShowDefaultValue(Argument argument) => + argument.HasDefaultValue && + !(argument.ValueType == typeof(bool) || argument.ValueType == typeof(bool?)); + /// /// Gets the description for an argument (typically used in the second column text in the arguments section). /// diff --git a/src/System.CommandLine/Help/HelpBuilder.cs b/src/System.CommandLine/Help/HelpBuilder.cs index 2a035e24f4..af6046d898 100644 --- a/src/System.CommandLine/Help/HelpBuilder.cs +++ b/src/System.CommandLine/Help/HelpBuilder.cs @@ -301,7 +301,7 @@ private string FormatArgumentUsage(IList arguments) if (isOptional) { sb.Append($"[<{argument.Name}>{arityIndicator}"); - (end ??= new ()).Add(']'); + (end ??= []).Add(']'); } else { @@ -315,11 +315,11 @@ private string FormatArgumentUsage(IList arguments) { sb.Length--; - if (end is { }) + if (end is not null) { while (end.Count > 0) { - sb.Append(end[end.Count - 1]); + sb.Append(end[^1]); end.RemoveAt(end.Count - 1); } } @@ -348,7 +348,7 @@ private static IEnumerable WrapText(string text, int maxWidth) } //First handle existing new lines - var parts = text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); + var parts = text.Split(["\r\n", "\n"], StringSplitOptions.None); foreach (string part in parts) { @@ -405,7 +405,7 @@ public TwoColumnHelpRow GetTwoColumnRow( Customization? customization = null; - if (_customizationsBySymbol is { }) + if (_customizationsBySymbol is not null) { _customizationsBySymbol.TryGetValue(symbol, out customization); } @@ -436,8 +436,8 @@ TwoColumnHelpRow GetOptionOrCommandRow() //in case symbol description is customized, do not output default value //default value output is not customizable for identifier symbols - var defaultValueDescription = customizedSymbolDescription == null - ? GetSymbolDefaultValue(symbol) + var defaultValueDescription = customizedSymbolDescription is null + ? GetOptionOrCommandDefaultValue() : string.Empty; var secondColumnText = $"{symbolDescription} {defaultValueDescription}".Trim(); @@ -454,7 +454,8 @@ TwoColumnHelpRow GetCommandArgumentRow(Argument argument) customization?.GetSecondColumn?.Invoke(context) ?? Default.GetArgumentDescription(argument); var defaultValueDescription = - argument.HasDefaultValue + Default.ShouldShowDefaultValue(argument) && + !string.IsNullOrEmpty(GetArgumentDefaultValue(context.Command, argument, true, context)) ? $"[{GetArgumentDefaultValue(context.Command, argument, true, context)}]" : ""; @@ -463,17 +464,25 @@ TwoColumnHelpRow GetCommandArgumentRow(Argument argument) return new TwoColumnHelpRow(firstColumnText, secondColumnText); } - string GetSymbolDefaultValue(Symbol symbol) + string GetOptionOrCommandDefaultValue() { var arguments = symbol.GetParameters(); - var defaultArguments = arguments.Where(x => !x.Hidden && (x is Argument { HasDefaultValue: true } || x is Option { HasDefaultValue: true })).ToArray(); + var defaultArguments = arguments.Where(x => !x.Hidden && Default.ShouldShowDefaultValue(x)).ToArray(); - if (defaultArguments.Length == 0) return ""; + if (defaultArguments.Length == 0) + { + return ""; + } var isSingleArgument = defaultArguments.Length == 1; - var argumentDefaultValues = defaultArguments - .Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context)); - return $"[{string.Join(", ", argumentDefaultValues)}]"; + var argumentDefaultValues = string.Join( + ", ", + defaultArguments + .Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context))); + + return string.IsNullOrEmpty(argumentDefaultValues) + ? "" + : $"[{argumentDefaultValues}]"; } } @@ -483,10 +492,6 @@ private string GetArgumentDefaultValue( bool displayArgumentName, HelpContext context) { - string label = displayArgumentName - ? LocalizationResources.HelpArgumentDefaultValueLabel() - : parameter.Name; - string? displayedDefaultValue = null; if (_customizationsBySymbol is not null) @@ -510,6 +515,9 @@ private string GetArgumentDefaultValue( return ""; } + string label = displayArgumentName + ? LocalizationResources.HelpArgumentDefaultValueLabel() + : parameter.Name; return $"{label}: {displayedDefaultValue}"; } diff --git a/src/System.CommandLine/Help/HelpBuilderExtensions.cs b/src/System.CommandLine/Help/HelpBuilderExtensions.cs index a130f13220..4fb1999a64 100644 --- a/src/System.CommandLine/Help/HelpBuilderExtensions.cs +++ b/src/System.CommandLine/Help/HelpBuilderExtensions.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; using System.Collections.Generic; namespace System.CommandLine.Help @@ -30,21 +31,13 @@ internal static IEnumerable GetParameters(this Symbol symbol) internal static (string? Prefix, string Alias) SplitPrefix(this string rawAlias) { - if (rawAlias[0] == '/') + return rawAlias[0] switch { - return ("/", rawAlias.Substring(1)); - } - else if (rawAlias[0] == '-') - { - if (rawAlias.Length > 1 && rawAlias[1] == '-') - { - return ("--", rawAlias.Substring(2)); - } - - return ("-", rawAlias.Substring(1)); - } - - return (null, rawAlias); + '/' => ("/", rawAlias[1..]), + '-' when rawAlias.Length > 1 && rawAlias[1] is '-' => ("--", rawAlias[2..]), + '-' => ("-", rawAlias[1..]), + _ => (null, rawAlias) + }; } internal static IEnumerable RecurseWhileNotNull(this T? source, Func next) where T : class diff --git a/src/System.CommandLine/Help/HelpOption.cs b/src/System.CommandLine/Help/HelpOption.cs index 17957a179c..f1d40583e9 100644 --- a/src/System.CommandLine/Help/HelpOption.cs +++ b/src/System.CommandLine/Help/HelpOption.cs @@ -8,7 +8,7 @@ namespace System.CommandLine.Help /// /// A standard option that indicates that command line help should be displayed. /// - public sealed class HelpOption : Option + public sealed class HelpOption : Option { private CommandLineAction? _action; @@ -22,7 +22,7 @@ public sealed class HelpOption : Option /// /? /// /// - public HelpOption() : this("--help", new[] { "-h", "/h", "-?", "/?" }) + public HelpOption() : this("--help", ["-h", "/h", "-?", "/?"]) { } @@ -30,10 +30,11 @@ 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 }) + : base(name, aliases) { Recursive = true; Description = LocalizationResources.HelpOptionDescription(); + Arity = ArgumentArity.Zero; } /// @@ -42,5 +43,9 @@ public override CommandLineAction? Action get => _action ??= new HelpAction(); set => _action = value ?? throw new ArgumentNullException(nameof(value)); } + + internal override Argument Argument => Argument.None; + + public override Type ValueType => typeof(void); } } \ No newline at end of file diff --git a/src/System.CommandLine/Parsing/CommandResult.cs b/src/System.CommandLine/Parsing/CommandResult.cs index 35482c5d1a..6561f5a386 100644 --- a/src/System.CommandLine/Parsing/CommandResult.cs +++ b/src/System.CommandLine/Parsing/CommandResult.cs @@ -71,16 +71,16 @@ internal void Validate(bool completeValidation) if (Command.HasOptions) { - ValidateOptions(completeValidation); + ValidateOptionsAndAddDefaultResults(completeValidation); } if (Command.HasArguments) { - ValidateArguments(completeValidation); + ValidateArgumentsAndAddDefaultResults(completeValidation); } } - private void ValidateOptions(bool completeValidation) + private void ValidateOptionsAndAddDefaultResults(bool completeValidation) { var options = Command.Options; for (var i = 0; i < options.Count; i++) @@ -105,7 +105,7 @@ private void ValidateOptions(bool completeValidation) argumentResult = new(optionResult.Option.Argument, SymbolResultTree, optionResult); SymbolResultTree.Add(optionResult.Option.Argument, argumentResult); - if (option.Required && !option.Argument.HasDefaultValue) + if (option is { Required: true, Argument.HasDefaultValue: false }) { argumentResult.AddError(LocalizationResources.RequiredOptionWasNotProvided(option.Name)); continue; @@ -148,7 +148,7 @@ private void ValidateOptions(bool completeValidation) } } - private void ValidateArguments(bool completeValidation) + private void ValidateArgumentsAndAddDefaultResults(bool completeValidation) { var arguments = Command.Arguments; for (var i = 0; i < arguments.Count; i++) diff --git a/src/System.CommandLine/Parsing/OptionResult.cs b/src/System.CommandLine/Parsing/OptionResult.cs index 489c80d38d..478a3a9d23 100644 --- a/src/System.CommandLine/Parsing/OptionResult.cs +++ b/src/System.CommandLine/Parsing/OptionResult.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.CommandLine.Binding; -using System.Diagnostics.CodeAnalysis; using System.Linq; namespace System.CommandLine.Parsing diff --git a/src/System.CommandLine/Parsing/ParseOperation.cs b/src/System.CommandLine/Parsing/ParseOperation.cs index 8ebdc84d01..de7dcf2d9c 100644 --- a/src/System.CommandLine/Parsing/ParseOperation.cs +++ b/src/System.CommandLine/Parsing/ParseOperation.cs @@ -58,9 +58,11 @@ internal ParseResult Parse() ParseCommandChildren(); - if (!_isHelpRequested) + ValidateAndAddDefaultResults(); + + if (_isHelpRequested) { - Validate(); + _symbolResultTree.Errors?.Clear(); } if (_primaryAction is null) @@ -366,7 +368,7 @@ private void AddCurrentTokenToUnmatched() _symbolResultTree.AddUnmatchedToken(CurrentToken, _innermostCommandResult, _rootCommandResult); } - private void Validate() + private void ValidateAndAddDefaultResults() { // Only the inner most command goes through complete validation, // for other commands only a subset of options is checked. diff --git a/src/System.CommandLine/Parsing/SymbolResultTree.cs b/src/System.CommandLine/Parsing/SymbolResultTree.cs index 4778c4093e..49abc6be86 100644 --- a/src/System.CommandLine/Parsing/SymbolResultTree.cs +++ b/src/System.CommandLine/Parsing/SymbolResultTree.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. +// 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; diff --git a/src/System.CommandLine/System.CommandLine.csproj b/src/System.CommandLine/System.CommandLine.csproj index 09fd8f326a..a11b00a17e 100644 --- a/src/System.CommandLine/System.CommandLine.csproj +++ b/src/System.CommandLine/System.CommandLine.csproj @@ -1,12 +1,11 @@ - $(NetMinimum);netstandard2.0 + $(NetMinimum);netstandard2.0;$(NetFrameworkMinimum) true enable Support for parsing command lines, supporting both POSIX and Windows conventions and shell-agnostic command line completions. true - portable @@ -14,12 +13,13 @@ true true - + - + + diff --git a/src/System.CommandLine/VersionOption.cs b/src/System.CommandLine/VersionOption.cs index 9ba607198b..6c31ab1485 100644 --- a/src/System.CommandLine/VersionOption.cs +++ b/src/System.CommandLine/VersionOption.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.CommandLine.Invocation; using System.CommandLine.Parsing; using System.Linq; @@ -10,14 +11,14 @@ namespace System.CommandLine /// /// A standard option that indicates that version information should be displayed for the app. /// - public sealed class VersionOption : Option + public sealed class VersionOption : Option { private CommandLineAction? _action; /// /// 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()) + public VersionOption() : this("--version") { } @@ -25,10 +26,11 @@ public VersionOption() : this("--version", Array.Empty()) /// 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 }) + : base(name, aliases) { Description = LocalizationResources.VersionOptionDescription(); AddValidators(); + Arity = ArgumentArity.Zero; } /// @@ -43,7 +45,9 @@ private void AddValidators() Validators.Add(static result => { if (result.Parent is CommandResult parent && - parent.Children.Any(r => r is not OptionResult { Option: VersionOption })) + parent.Children.Any(r => + r is not OptionResult { Option: VersionOption } && + r is not OptionResult { Implicit: true })) { result.AddError(LocalizationResources.VersionOptionCannotBeCombinedWithOtherArguments(result.IdentifierToken?.Value ?? result.Option.Name)); } @@ -52,6 +56,11 @@ private void AddValidators() internal override bool Greedy => false; + internal override Argument Argument => Argument.None; + + /// + public override Type ValueType => typeof(void); + private sealed class VersionOptionAction : SynchronousCommandLineAction { public override int Invoke(ParseResult parseResult) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy