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)