diff --git a/System.CommandLine.v3.ncrunchsolution b/System.CommandLine.v3.ncrunchsolution index ed49d122c2..2154fa17a0 100644 --- a/System.CommandLine.v3.ncrunchsolution +++ b/System.CommandLine.v3.ncrunchsolution @@ -2,8 +2,8 @@ True - TargetFrameworks = net8.0 - TargetFramework = net8.0 + TargetFrameworks = net10.0 + TargetFramework = net10.0 False True 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 6f542d6fe6..ec7df0cb46 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 @@ -35,6 +35,7 @@ public static Argument AcceptLegalFileNamesOnly(this Argument argument) public static Argument AcceptLegalFilePathsOnly(this Argument argument) public static Argument AcceptOnlyFromAmong(this Argument argument, System.String[] values) + public static Argument AcceptOnlyFromAmong(this Argument argument, System.StringComparer comparer, System.String[] values) public class Command : Symbol, System.Collections.IEnumerable .ctor(System.String name, System.String description = null) public System.CommandLine.Invocation.CommandLineAction Action { get; set; } @@ -99,6 +100,7 @@ public Option AcceptLegalFileNamesOnly() public Option AcceptLegalFilePathsOnly() public Option AcceptOnlyFromAmong(System.String[] values) + public Option AcceptOnlyFromAmong(System.StringComparer comparer, System.String[] values) public static class OptionValidation public static Option AcceptExistingOnly(this Option option) public static Option AcceptExistingOnly(this Option option) diff --git a/src/System.CommandLine.Tests/ArgumentTests.cs b/src/System.CommandLine.Tests/ArgumentTests.cs index 8672fbd379..ffd9ac7ea6 100644 --- a/src/System.CommandLine.Tests/ArgumentTests.cs +++ b/src/System.CommandLine.Tests/ArgumentTests.cs @@ -107,4 +107,39 @@ public void Argument_of_enum_can_limit_enum_members_as_valid_values() .Should() .BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" }); } + + [Fact] + public void AcceptOnlyFromAmong_with_comparer_is_case_insensitive() + { + var argument = new Argument("name"); + argument.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2"); + + Command command = new("run") + { + argument + }; + + var result = command.Parse("run name1"); + + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void AcceptOnlyFromAmong_with_comparer_rejects_invalid_values() + { + var argument = new Argument("name"); + argument.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2"); + + Command command = new("run") + { + argument + }; + + var result = command.Parse("run NAME3"); + + result.Errors + .Select(e => e.Message) + .Should() + .BeEquivalentTo(new[] { $"Argument 'NAME3' not recognized. Must be one of:\n\t'NAME1'\n\t'NAME2'" }); + } } \ No newline at end of file diff --git a/src/System.CommandLine.Tests/OptionTests.cs b/src/System.CommandLine.Tests/OptionTests.cs index f898c25024..9e9bd0375f 100644 --- a/src/System.CommandLine.Tests/OptionTests.cs +++ b/src/System.CommandLine.Tests/OptionTests.cs @@ -408,6 +408,31 @@ public void Option_of_enum_can_limit_enum_members_as_valid_values() .BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" }); } + [Fact] + public void AcceptOnlyFromAmong_with_comparer_is_case_insensitive() + { + Option option = new("--name"); + option.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2"); + + var result = new RootCommand { option }.Parse("--name name1"); + + result.Errors.Should().BeEmpty(); + } + + [Fact] + public void AcceptOnlyFromAmong_with_comparer_rejects_invalid_values() + { + Option option = new("--name"); + option.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2"); + + var result = new RootCommand { option }.Parse("--name NAME3"); + + result.Errors + .Select(e => e.Message) + .Should() + .BeEquivalentTo(new[] { $"Argument 'NAME3' not recognized. Must be one of:\n\t'NAME1'\n\t'NAME2'" }); + } + [Fact] public void Option_result_provides_identifier_token_if_name_was_provided() { diff --git a/src/System.CommandLine/ArgumentValidation.cs b/src/System.CommandLine/ArgumentValidation.cs index 80a94a7bb5..42b996387f 100644 --- a/src/System.CommandLine/ArgumentValidation.cs +++ b/src/System.CommandLine/ArgumentValidation.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.CommandLine.Parsing; using System.IO; +using System.Linq; namespace System.CommandLine { @@ -131,7 +132,21 @@ public static Argument AcceptOnlyFromAmong( this Argument argument, params string[] values) { - if (values is not null && values.Length > 0) + return AcceptOnlyFromAmong(argument, StringComparer.Ordinal, values); + } + + /// + /// Configures the argument to accept only the specified values using the specified comparer, and to suggest them as command line completions. + /// + /// The argument to configure. + /// The comparer used to match argument values against the allowed values. + /// The values that are allowed for the argument. + public static Argument AcceptOnlyFromAmong( + this Argument argument, + StringComparer comparer, + params string[] values) + { + if (values?.Length > 0) { argument.Validators.Clear(); argument.Validators.Add(UnrecognizedArgumentError); @@ -140,7 +155,7 @@ public static Argument AcceptOnlyFromAmong( } return argument; - + void UnrecognizedArgumentError(ArgumentResult argumentResult) { for (var i = 0; i < argumentResult.Tokens.Count; i++) @@ -149,7 +164,7 @@ void UnrecognizedArgumentError(ArgumentResult argumentResult) if (token.Symbol is null || token.Symbol == argument) { - if (Array.IndexOf(values, token.Value) < 0) + if (!values.Contains(token.Value, comparer)) { argumentResult.AddError(LocalizationResources.UnrecognizedArgument(token.Value, values)); } diff --git a/src/System.CommandLine/Option{T}.cs b/src/System.CommandLine/Option{T}.cs index 4cd08d9833..e612dde011 100644 --- a/src/System.CommandLine/Option{T}.cs +++ b/src/System.CommandLine/Option{T}.cs @@ -57,6 +57,17 @@ public Option AcceptOnlyFromAmong(params string[] values) return this; } + /// + /// Configures the option to accept only the specified values using the specified comparer, and to suggest them as command line completions. + /// + /// The comparer used to match argument values against the allowed values. + /// The values that are allowed for the option. + public Option AcceptOnlyFromAmong(StringComparer comparer, params string[] values) + { + _argument.AcceptOnlyFromAmong(comparer, values); + return this; + } + /// /// Configures the option to accept only values representing legal file paths. ///