Skip to content

Commit 105d563

Browse files
committed
default non-generic Option to bind to bool instead of string
1 parent a4f28f9 commit 105d563

File tree

11 files changed

+135
-62
lines changed

11 files changed

+135
-62
lines changed

src/System.CommandLine.Tests/Binding/TypeConversionTests.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public void When_command_argument_has_arity_greater_than_one_it_captures_argumen
341341
}
342342

343343
[Fact]
344-
public void The_default_value_of_an_option_with_no_arguments_is_null()
344+
public void The_default_value_of_an_option_with_no_arguments_is_false()
345345
{
346346
var option = new Option("-x");
347347

@@ -356,20 +356,22 @@ public void The_default_value_of_an_option_with_no_arguments_is_null()
356356
result.FindResultFor(option)
357357
.GetValueOrDefault()
358358
.Should()
359-
.BeNull();
359+
.Be(true);
360360
}
361361

362362
[Fact]
363363
public void By_default_an_option_without_arguments_parses_as_false_when_it_is_not_applied()
364364
{
365+
var option = new Option("-x");
366+
365367
var command = new Command("something")
366368
{
367-
new Option("-x")
369+
option
368370
};
369371

370372
var result = command.Parse("something");
371373

372-
result.ValueForOption<bool>("-x")
374+
result.ValueForOption<bool>(option)
373375
.Should()
374376
.BeFalse();
375377
}

src/System.CommandLine.Tests/OptionTests.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -503,11 +503,11 @@ public void Option_of_boolean_defaults_to_false_when_not_specified()
503503
var result = option.Parse("");
504504

505505
result.HasOption(option)
506-
.Should()
507-
.BeFalse();
506+
.Should()
507+
.BeFalse();
508508
result.ValueForOption(option)
509-
.Should()
510-
.BeFalse();
509+
.Should()
510+
.BeFalse();
511511
}
512512

513513
[Fact]
@@ -518,6 +518,14 @@ public void Arity_of_non_generic_option_defaults_to_zero()
518518
option.Arity.Should().BeEquivalentTo(ArgumentArity.Zero);
519519
}
520520

521+
[Fact]
522+
public void ArgumentType_of_non_generic_option_defaults_to_bool()
523+
{
524+
var option = new Option("-x");
525+
526+
option.ValueType.Should().Be(typeof(bool));
527+
}
528+
521529
protected override Symbol CreateSymbol(string name) => new Option(name);
522530
}
523531
}

src/System.CommandLine.Tests/ParserTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void The_portion_of_the_command_line_following_a_double_dash_is_accessibl
124124
[Fact]
125125
public void Short_form_options_can_be_specified_using_equals_delimiter()
126126
{
127-
var option = new Option("-x") { Arity = ArgumentArity.ExactlyOne };
127+
var option = new Option<string>("-x") { Arity = ArgumentArity.ExactlyOne };
128128

129129
var result = option.Parse("-x=some-value");
130130

src/System.CommandLine/Argument.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ internal TryConvertArgument? ConvertArguments
104104
/// <summary>
105105
/// Gets or sets the <see cref="Type" /> that the argument token(s) will be converted to.
106106
/// </summary>
107-
public Type ArgumentType
107+
public virtual Type ArgumentType
108108
{
109109
get => _argumentType;
110110
set => _argumentType = value ?? throw new ArgumentNullException(nameof(value));
@@ -196,7 +196,11 @@ public void SetDefaultValueFactory(Func<ArgumentResult, object?> getDefaultValue
196196
/// </summary>
197197
public bool HasDefaultValue => _defaultValueFactory != null;
198198

199-
internal static Argument None() => new Argument { Arity = ArgumentArity.Zero };
199+
internal static Argument None() => new()
200+
{
201+
Arity = ArgumentArity.Zero,
202+
ArgumentType = typeof(bool)
203+
};
200204

201205
internal void AddAllowedValues(IEnumerable<string> values)
202206
{

src/System.CommandLine/ArgumentArity.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ internal static IArgumentArity Default(Type type, Argument argument, ISymbolSet
118118

119119
if (type == typeof(void))
120120
{
121+
// FIX: (Default) delete
121122
return Zero;
122123
}
123124

src/System.CommandLine/Binding/ArgumentConverter.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace System.CommandLine.Binding
1515
{
1616
internal static class ArgumentConverter
1717
{
18-
private static readonly Dictionary<Type, Func<string, object>> _converters = new Dictionary<Type, Func<string, object>>
18+
private static readonly Dictionary<Type, Func<string, object>> _converters = new()
1919
{
2020
[typeof(FileSystemInfo)] = value =>
2121
{
@@ -39,6 +39,11 @@ internal static ArgumentConversionResult ConvertObject(
3939
Type type,
4040
object? value)
4141
{
42+
if (argument.Arity.MaximumNumberOfValues == 0)
43+
{
44+
return Success(argument, true);
45+
}
46+
4247
switch (value)
4348
{
4449
case string singleValue:
@@ -109,13 +114,24 @@ public static ArgumentConversionResult ConvertStrings(
109114
IReadOnlyList<string> tokens,
110115
ArgumentResult? argumentResult = null)
111116
{
112-
var itemType = type == typeof(string)
113-
? typeof(string)
114-
: Binder.GetItemTypeIfEnumerable(type);
117+
Type itemType;
118+
119+
if (type == typeof(string))
120+
{
121+
itemType = typeof(string);
122+
}
123+
else if (type == typeof(bool))
124+
{
125+
itemType = typeof(bool);
126+
}
127+
else
128+
{
129+
itemType = Binder.GetItemTypeIfEnumerable(type) ?? typeof(string);
130+
}
115131

116132
var (values, isArray) = type.IsArray
117-
? (CreateArray(itemType!, tokens.Count), true)
118-
: (CreateList(itemType!, tokens.Count), false);
133+
? (CreateArray(itemType, tokens.Count), true)
134+
: (CreateList(itemType, tokens.Count), false);
119135

120136
for (var i = 0; i < tokens.Count; i++)
121137
{

src/System.CommandLine/Binding/Binder.cs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,25 @@ static Array CreateEmptyArray(Type itemType)
109109
return type.GetElementType();
110110
}
111111

112-
var enumerableInterface =
113-
type.IsEnumerable()
114-
? type
115-
: type
116-
.GetInterfaces()
117-
.FirstOrDefault(IsEnumerable);
118-
119-
return enumerableInterface?.GenericTypeArguments switch
112+
Type enumerableInterface;
113+
114+
if (type.IsEnumerable())
115+
{
116+
enumerableInterface = type;
117+
}
118+
else
119+
{
120+
enumerableInterface = type
121+
.GetInterfaces()
122+
.FirstOrDefault(IsEnumerable);
123+
}
124+
125+
if (enumerableInterface is null)
126+
{
127+
return null;
128+
}
129+
130+
return enumerableInterface.GenericTypeArguments switch
120131
{
121132
{ Length: 1 } genericTypeArguments => genericTypeArguments[0],
122133
_ => null

src/System.CommandLine/Option.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class Option :
1313
IOption
1414
{
1515
private string? _implicitName;
16-
private protected readonly HashSet<string> _unprefixedAliases = new HashSet<string>();
16+
private protected readonly HashSet<string> _unprefixedAliases = new();
1717

1818
public Option(
1919
string alias,
@@ -114,7 +114,15 @@ public string ArgumentHelpName
114114
public IArgumentArity Arity
115115
{
116116
get => Argument.Arity;
117-
set => Argument.Arity = value;
117+
init
118+
{
119+
if (value.MaximumNumberOfValues > 0)
120+
{
121+
Argument.ArgumentType = typeof(string);
122+
}
123+
124+
Argument.Arity = value;
125+
}
118126
}
119127

120128
internal bool DisallowBinding { get; set; }
@@ -134,7 +142,7 @@ public override string Name
134142
}
135143
}
136144

137-
internal List<ValidateSymbol<OptionResult>> Validators { get; } = new List<ValidateSymbol<OptionResult>>();
145+
internal List<ValidateSymbol<OptionResult>> Validators { get; } = new();
138146

139147
public void AddAlias(string alias) => AddAliasInner(alias);
140148

src/System.CommandLine/Parsing/OptionResultExtensions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ internal static ArgumentConversionResult ConvertIfNeeded(
1414
optionResult.ArgumentConversionResult
1515
.ConvertIfNeeded(optionResult, type);
1616

17-
public static object? GetValueOrDefault(this OptionResult optionResult) => optionResult.GetValueOrDefault<object?>();
17+
public static object? GetValueOrDefault(this OptionResult optionResult) =>
18+
optionResult.Option.ValueType == typeof(bool)
19+
? optionResult.GetValueOrDefault<bool>()
20+
: optionResult.GetValueOrDefault<object?>();
1821

1922
[return: MaybeNull]
2023
public static T GetValueOrDefault<T>(this OptionResult optionResult) =>

src/System.CommandLine/Parsing/ParseResultExtensions.cs

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ private static void Diagram(
121121

122122
if (symbolResult is ArgumentResult argumentResult)
123123
{
124+
124125
var includeArgumentName =
125126
argumentResult.Argument is Argument argument &&
126127
argument.Parents[0] is ICommand command &&
@@ -133,40 +134,43 @@ argument.Parents[0] is ICommand command &&
133134
builder.Append(" ");
134135
}
135136

136-
switch (argumentResult.GetArgumentConversionResult())
137+
if (argumentResult.Argument.Arity.MaximumNumberOfValues > 0)
137138
{
138-
case SuccessfulArgumentConversionResult successful:
139-
140-
switch (successful.Value)
141-
{
142-
case string s:
143-
builder.Append($"<{s}>");
144-
break;
145-
146-
case IEnumerable items:
147-
builder.Append("<");
148-
builder.Append(
149-
string.Join("> <",
150-
items.Cast<object>().ToArray()));
151-
builder.Append(">");
152-
break;
153-
154-
default:
155-
builder.Append("<");
156-
builder.Append(successful.Value);
157-
builder.Append(">");
158-
break;
159-
}
160-
161-
break;
162-
163-
case FailedArgumentConversionResult _:
164-
165-
builder.Append("<");
166-
builder.Append(string.Join("> <", symbolResult.Tokens.Select(t => t.Value)));
167-
builder.Append(">");
168-
169-
break;
139+
switch (argumentResult.GetArgumentConversionResult())
140+
{
141+
case SuccessfulArgumentConversionResult successful:
142+
143+
switch (successful.Value)
144+
{
145+
case string s:
146+
builder.Append($"<{s}>");
147+
break;
148+
149+
case IEnumerable items:
150+
builder.Append("<");
151+
builder.Append(
152+
string.Join("> <",
153+
items.Cast<object>().ToArray()));
154+
builder.Append(">");
155+
break;
156+
157+
default:
158+
builder.Append("<");
159+
builder.Append(successful.Value);
160+
builder.Append(">");
161+
break;
162+
}
163+
164+
break;
165+
166+
case FailedArgumentConversionResult _:
167+
168+
builder.Append("<");
169+
builder.Append(string.Join("> <", symbolResult.Tokens.Select(t => t.Value)));
170+
builder.Append(">");
171+
172+
break;
173+
}
170174
}
171175

172176
if (includeArgumentName)
@@ -182,6 +186,13 @@ argument.Parents[0] is ICommand command &&
182186
for (var i = 0; i < symbolResult.Children.Count; i++)
183187
{
184188
var child = symbolResult.Children[i];
189+
190+
if (child is ArgumentResult arg &&
191+
arg.Argument.Arity.MaximumNumberOfValues == 0)
192+
{
193+
continue;
194+
}
195+
185196
builder.Append(" ");
186197
builder.Diagram(child, parseResult);
187198
}

0 commit comments

Comments
 (0)