Skip to content

Commit 8c2a3d2

Browse files
authored
feature: update CommandOption/Argument.value to return DefaultValue (#420)
1 parent 9ff8c22 commit 8c2a3d2

File tree

17 files changed

+341
-79
lines changed

17 files changed

+341
-79
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
<PropertyGroup>
3131
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
3232
<WarningsNotAsErrors>$(WarningsNotAsErrors);1591</WarningsNotAsErrors>
33-
<LangVersion>8.0</LangVersion>
33+
<LangVersion>9.0</LangVersion>
3434
<Nullable>annotations</Nullable>
3535
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src\StrongName.snk</AssemblyOriginatorKeyFile>
3636
<SignAssembly>true</SignAssembly>

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,19 @@ using McMaster.Extensions.CommandLineUtils;
8181
var app = new CommandLineApplication();
8282

8383
app.HelpOption();
84-
var optionSubject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
85-
var optionRepeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
84+
85+
var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
86+
subject.DefaultValue = "world";
87+
88+
var repeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
89+
repeat.DefaultValue = 1;
8690

8791
app.OnExecute(() =>
8892
{
89-
var subject = optionSubject.HasValue()
90-
? optionSubject.Value()
91-
: "world";
92-
93-
var count = optionRepeat.HasValue() ? optionRepeat.ParsedValue : 1;
94-
for (var i = 0; i < count; i++)
93+
for (var i = 0; i < repeat.ParsedValue; i++)
9594
{
96-
Console.WriteLine($"Hello {subject}!");
95+
Console.WriteLine($"Hello {subject.Value()}!");
9796
}
98-
return 0;
9997
});
10098

10199
return app.Execute(args);

docs/docs/intro.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,12 @@ public class Program
6464
var app = new CommandLineApplication();
6565

6666
app.HelpOption();
67-
var optionSubject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
67+
var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
68+
subject.DefaultValue = "world";
6869

6970
app.OnExecute(() =>
7071
{
71-
var subject = optionSubject.HasValue()
72-
? optionSubject.Value()
73-
: "world";
74-
75-
Console.WriteLine($"Hello {subject}!");
72+
Console.WriteLine($"Hello {subject.Value()}!");
7673
return 0;
7774
});
7875

docs/samples/custom-attribute/Program.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,10 @@ public void Apply(ConventionContext context, MemberInfo member)
4444
if (member is FieldInfo field)
4545
{
4646
var opt = context.Application.Option("--working-dir", "The working directory", CommandOptionType.SingleOrNoValue);
47+
opt.DefaultValue = context.Application.WorkingDirectory;
4748
context.Application.OnParsingComplete(_ =>
4849
{
49-
var cwd = opt.HasValue()
50-
? opt.Value()
51-
: context.Application.WorkingDirectory;
52-
field.SetValue(context.ModelAccessor.GetModel(), cwd);
50+
field.SetValue(context.ModelAccessor.GetModel(), opt.Value());
5351
});
5452
}
5553
}

docs/samples/helloworld-async/Program.cs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@
55
var app = new CommandLineApplication();
66

77
app.HelpOption();
8-
var optionSubject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
9-
var optionRepeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
8+
var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
9+
subject.DefaultValue = "world";
10+
11+
var repeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
12+
repeat.DefaultValue = 1;
1013

1114
app.OnExecuteAsync(async cancellationToken =>
1215
{
13-
var subject = optionSubject.HasValue()
14-
? optionSubject.Value()
15-
: "world";
16-
17-
var count = optionRepeat.HasValue() ? optionRepeat.ParsedValue : 1;
18-
for (var i = 0; i < count; i++)
16+
for (var i = 0; i < repeat.ParsedValue; i++)
1917
{
2018
Console.Write($"Hello");
2119

2220
// Pause for dramatic effect
2321
await Task.Delay(2000, cancellationToken);
2422

25-
Console.WriteLine($" {subject}!");
23+
Console.WriteLine($" {subject.Value()}!");
2624
}
2725
});
2826

docs/samples/helloworld/HelloWorld.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>net5.0</TargetFramework>
6+
<Nullable>enable</Nullable>
67
</PropertyGroup>
78

89
<ItemGroup>

docs/samples/helloworld/Program.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44
var app = new CommandLineApplication();
55

66
app.HelpOption();
7-
var optionSubject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
8-
var optionRepeat = app.Option<int>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
7+
var subject = app.Option("-s|--subject <SUBJECT>", "The subject", CommandOptionType.SingleValue);
8+
subject.DefaultValue = "world";
9+
var repeat = app.Option<int?>("-n|--count <N>", "Repeat", CommandOptionType.SingleValue);
10+
repeat.DefaultValue = 1;
911

1012
app.OnExecute(() =>
1113
{
12-
var subject = optionSubject.HasValue()
13-
? optionSubject.Value()
14-
: "world";
15-
16-
var count = optionRepeat.HasValue() ? optionRepeat.ParsedValue : 1;
17-
for (var i = 0; i < count; i++)
14+
for (var i = 0; i < repeat.ParsedValue; i++)
1815
{
19-
Console.WriteLine($"Hello {subject}!");
16+
Console.WriteLine($"Hello {subject.Value()}!");
2017
}
21-
return 0;
2218
});
2319

2420
return app.Execute(args);

src/CommandLineUtils/CommandArgument.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,25 @@ public class CommandArgument
3636
/// <summary>
3737
/// All values specified, if any.
3838
/// </summary>
39-
public IReadOnlyList<string?> Values => _values;
39+
public IReadOnlyList<string?> Values
40+
{
41+
get
42+
{
43+
if (_values.Count == 0 && DefaultValue != null)
44+
{
45+
return new List<string?> { DefaultValue };
46+
}
47+
return _values;
48+
}
49+
}
4050

4151
/// <summary>
4252
/// Allow multiple values.
4353
/// </summary>
4454
public bool MultipleValues { get; set; }
4555

4656
/// <summary>
47-
/// The first value from <see cref="Values"/>, if any.
57+
/// The first value from <see cref="Values"/>, if any, or <see cref="DefaultValue" />.
4858
/// </summary>
4959
public string? Value => Values.FirstOrDefault();
5060

@@ -54,7 +64,7 @@ public class CommandArgument
5464
public string? DefaultValue { get; set; }
5565

5666
/// <summary>
57-
/// True if this argument has been assigned values.
67+
/// True when <see cref="Values"/> is not empty or when a <see cref="DefaultValue" /> is given.
5868
/// </summary>
5969
public bool HasValue => Values.Any();
6070

src/CommandLineUtils/CommandArgument{T}.cs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public class CommandArgument<T> : CommandArgument, IInternalCommandParamOfT
2020
{
2121
private readonly List<T> _parsedValues = new List<T>();
2222
private readonly IValueParser<T> _valueParser;
23-
private T _defaultValue;
23+
private bool _hasBeenParsed;
24+
private bool _hasDefaultValue;
25+
private T? _defaultValue;
2426

2527
/// <summary>
2628
/// Initializes a new instance of <see cref="CommandArgument{T}" />
@@ -30,36 +32,53 @@ public CommandArgument(IValueParser<T> valueParser)
3032
{
3133
_valueParser = valueParser ?? throw new ArgumentNullException(nameof(valueParser));
3234
UnderlyingType = typeof(T);
33-
SetBaseDefaultValue(default);
3435
}
3536

3637
/// <summary>
3738
/// The parsed value.
3839
/// </summary>
39-
public T ParsedValue => _parsedValues.FirstOrDefault();
40+
public T ParsedValue => ParsedValues.FirstOrDefault();
4041

4142
/// <summary>
4243
/// All parsed values;
4344
/// </summary>
44-
public IReadOnlyList<T> ParsedValues => _parsedValues;
45+
public IReadOnlyList<T> ParsedValues
46+
{
47+
get
48+
{
49+
if (!_hasBeenParsed)
50+
{
51+
((IInternalCommandParamOfT)this).Parse(CultureInfo.CurrentCulture);
52+
}
53+
54+
if (_parsedValues.Count == 0 && _hasDefaultValue)
55+
{
56+
return new[] { DefaultValue };
57+
}
58+
59+
return _parsedValues;
60+
}
61+
}
4562

4663
/// <summary>
4764
/// The default value of the argument.
4865
/// </summary>
49-
public new T DefaultValue
66+
public new T? DefaultValue
5067
{
5168
get => _defaultValue;
5269
set
5370
{
71+
_hasDefaultValue = value != null;
5472
_defaultValue = value;
5573
SetBaseDefaultValue(value);
5674
}
5775
}
5876

5977
void IInternalCommandParamOfT.Parse(CultureInfo culture)
6078
{
79+
_hasBeenParsed = true;
6180
_parsedValues.Clear();
62-
foreach (var t in Values)
81+
foreach (var t in base._values)
6382
{
6483
_parsedValues.Add(_valueParser.Parse(Name, t, culture));
6584
}
@@ -79,5 +98,13 @@ void SetBaseDefaultValue(T value)
7998
}
8099
}
81100
}
101+
102+
/// <inheritdoc />
103+
public override void Reset()
104+
{
105+
_hasBeenParsed = false;
106+
_parsedValues.Clear();
107+
base.Reset();
108+
}
82109
}
83110
}

src/CommandLineUtils/CommandOption.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,17 @@ internal CommandOption(CommandOptionType type)
100100
/// <summary>
101101
/// Any values found during parsing, if any.
102102
/// </summary>
103-
public IReadOnlyList<string?> Values => _values;
103+
public IReadOnlyList<string?> Values
104+
{
105+
get
106+
{
107+
if (_values.Count == 0 && DefaultValue != null)
108+
{
109+
return new List<string?> { DefaultValue };
110+
}
111+
return _values;
112+
}
113+
}
104114

105115
/// <summary>
106116
/// The default value of the option.
@@ -171,7 +181,7 @@ public bool TryParse(string? value)
171181
}
172182

173183
/// <summary>
174-
/// True when <see cref="Values"/> is not empty.
184+
/// True when <see cref="Values"/> is not empty or when a <see cref="DefaultValue" /> is given.
175185
/// </summary>
176186
/// <returns></returns>
177187
public bool HasValue()
@@ -180,13 +190,10 @@ public bool HasValue()
180190
}
181191

182192
/// <summary>
183-
/// Returns the first element of <see cref="Values"/>, if any.
193+
/// Returns the first element of <see cref="Values"/>, if any, or <see cref="DefaultValue" />.
184194
/// </summary>
185195
/// <returns></returns>
186-
public string? Value()
187-
{
188-
return HasValue() ? Values[0] : null;
189-
}
196+
public string? Value() => Values.FirstOrDefault();
190197

191198
/// <summary>
192199
/// Generates the template string in the format "-{Symbol}|-{Short}|--{Long} &lt;{Value}&gt;" for display in help text.

0 commit comments

Comments
 (0)