Skip to content

Commit 2956e1f

Browse files
jonsequiturJon Sequeira
authored andcommitted
Fix #1726
1 parent 4d32027 commit 2956e1f

5 files changed

Lines changed: 112 additions & 0 deletions

File tree

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
public static Argument<T> AcceptLegalFileNamesOnly<T>(this Argument<T> argument)
3636
public static Argument<T> AcceptLegalFilePathsOnly<T>(this Argument<T> argument)
3737
public static Argument<T> AcceptOnlyFromAmong<T>(this Argument<T> argument, System.String[] values)
38+
public static Argument<T> AcceptOnlyFromAmong<T>(this Argument<T> argument, System.StringComparer comparer, System.String[] values)
3839
public class Command : Symbol, System.Collections.IEnumerable
3940
.ctor(System.String name, System.String description = null)
4041
public System.CommandLine.Invocation.CommandLineAction Action { get; set; }
@@ -99,6 +100,7 @@
99100
public Option<T> AcceptLegalFileNamesOnly()
100101
public Option<T> AcceptLegalFilePathsOnly()
101102
public Option<T> AcceptOnlyFromAmong(System.String[] values)
103+
public Option<T> AcceptOnlyFromAmong(System.StringComparer comparer, System.String[] values)
102104
public static class OptionValidation
103105
public static Option<System.IO.FileInfo> AcceptExistingOnly(this Option<System.IO.FileInfo> option)
104106
public static Option<System.IO.DirectoryInfo> AcceptExistingOnly(this Option<System.IO.DirectoryInfo> option)

src/System.CommandLine.Tests/ArgumentTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,39 @@ public void Argument_of_enum_can_limit_enum_members_as_valid_values()
107107
.Should()
108108
.BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" });
109109
}
110+
111+
[Fact]
112+
public void AcceptOnlyFromAmong_with_comparer_is_case_insensitive()
113+
{
114+
var argument = new Argument<string>("name");
115+
argument.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2");
116+
117+
Command command = new("run")
118+
{
119+
argument
120+
};
121+
122+
var result = command.Parse("run name1");
123+
124+
result.Errors.Should().BeEmpty();
125+
}
126+
127+
[Fact]
128+
public void AcceptOnlyFromAmong_with_comparer_rejects_invalid_values()
129+
{
130+
var argument = new Argument<string>("name");
131+
argument.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2");
132+
133+
Command command = new("run")
134+
{
135+
argument
136+
};
137+
138+
var result = command.Parse("run NAME3");
139+
140+
result.Errors
141+
.Select(e => e.Message)
142+
.Should()
143+
.BeEquivalentTo(new[] { $"Argument 'NAME3' not recognized. Must be one of:\n\t'NAME1'\n\t'NAME2'" });
144+
}
110145
}

src/System.CommandLine.Tests/OptionTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,31 @@ public void Option_of_enum_can_limit_enum_members_as_valid_values()
408408
.BeEquivalentTo(new[] { $"Argument 'Fuschia' not recognized. Must be one of:\n\t'Red'\n\t'Green'" });
409409
}
410410

411+
[Fact]
412+
public void AcceptOnlyFromAmong_with_comparer_is_case_insensitive()
413+
{
414+
Option<string> option = new("--name");
415+
option.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2");
416+
417+
var result = new RootCommand { option }.Parse("--name name1");
418+
419+
result.Errors.Should().BeEmpty();
420+
}
421+
422+
[Fact]
423+
public void AcceptOnlyFromAmong_with_comparer_rejects_invalid_values()
424+
{
425+
Option<string> option = new("--name");
426+
option.AcceptOnlyFromAmong(StringComparer.OrdinalIgnoreCase, "NAME1", "NAME2");
427+
428+
var result = new RootCommand { option }.Parse("--name NAME3");
429+
430+
result.Errors
431+
.Select(e => e.Message)
432+
.Should()
433+
.BeEquivalentTo(new[] { $"Argument 'NAME3' not recognized. Must be one of:\n\t'NAME1'\n\t'NAME2'" });
434+
}
435+
411436
[Fact]
412437
public void Option_result_provides_identifier_token_if_name_was_provided()
413438
{

src/System.CommandLine/ArgumentValidation.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.CommandLine.Parsing;
66
using System.IO;
7+
using System.Linq;
78

89
namespace System.CommandLine
910
{
@@ -158,6 +159,44 @@ void UnrecognizedArgumentError(ArgumentResult argumentResult)
158159
}
159160
}
160161

162+
/// <summary>
163+
/// Configures the argument to accept only the specified values using the specified comparer, and to suggest them as command line completions.
164+
/// </summary>
165+
/// <param name="argument">The argument to configure.</param>
166+
/// <param name="comparer">The comparer used to match argument values against the allowed values.</param>
167+
/// <param name="values">The values that are allowed for the argument.</param>
168+
public static Argument<T> AcceptOnlyFromAmong<T>(
169+
this Argument<T> argument,
170+
StringComparer comparer,
171+
params string[] values)
172+
{
173+
if (values is not null && values.Length > 0)
174+
{
175+
argument.Validators.Clear();
176+
argument.Validators.Add(UnrecognizedArgumentError);
177+
argument.CompletionSources.Clear();
178+
argument.CompletionSources.Add(values);
179+
}
180+
181+
return argument;
182+
183+
void UnrecognizedArgumentError(ArgumentResult argumentResult)
184+
{
185+
for (var i = 0; i < argumentResult.Tokens.Count; i++)
186+
{
187+
var token = argumentResult.Tokens[i];
188+
189+
if (token.Symbol is null || token.Symbol == argument)
190+
{
191+
if (!values.Contains(token.Value, comparer))
192+
{
193+
argumentResult.AddError(LocalizationResources.UnrecognizedArgument(token.Value, values));
194+
}
195+
}
196+
}
197+
}
198+
}
199+
161200
private static void FileOrDirectoryExists<T>(ArgumentResult result)
162201
where T : FileSystemInfo
163202
{

src/System.CommandLine/Option{T}.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ public Option<T> AcceptOnlyFromAmong(params string[] values)
5757
return this;
5858
}
5959

60+
/// <summary>
61+
/// Configures the option to accept only the specified values using the specified comparer, and to suggest them as command line completions.
62+
/// </summary>
63+
/// <param name="comparer">The comparer used to match argument values against the allowed values.</param>
64+
/// <param name="values">The values that are allowed for the option.</param>
65+
public Option<T> AcceptOnlyFromAmong(StringComparer comparer, params string[] values)
66+
{
67+
_argument.AcceptOnlyFromAmong(comparer, values);
68+
return this;
69+
}
70+
6071
/// <summary>
6172
/// Configures the option to accept only values representing legal file paths.
6273
/// </summary>

0 commit comments

Comments
 (0)