Skip to content

Commit 3bbc68e

Browse files
authored
add GetRequiredValue to avoid null checks for required options and arguments (#2564)
1 parent f6052a8 commit 3bbc68e

File tree

5 files changed

+121
-2
lines changed

5 files changed

+121
-2
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@
124124
public System.Collections.Generic.IReadOnlyList<System.String> UnmatchedTokens { get; }
125125
public System.CommandLine.Completions.CompletionContext GetCompletionContext()
126126
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.Nullable<System.Int32> position = null)
127+
public T GetRequiredValue<T>(Argument<T> argument)
128+
public T GetRequiredValue<T>(Option<T> option)
129+
public T GetRequiredValue<T>(System.String name)
127130
public System.CommandLine.Parsing.ArgumentResult GetResult(Argument argument)
128131
public System.CommandLine.Parsing.CommandResult GetResult(Command command)
129132
public System.CommandLine.Parsing.OptionResult GetResult(Option option)
@@ -234,6 +237,9 @@ System.CommandLine.Parsing
234237
public SymbolResult Parent { get; }
235238
public System.Collections.Generic.IReadOnlyList<Token> Tokens { get; }
236239
public System.Void AddError(System.String errorMessage)
240+
public T GetRequiredValue<T>(Argument<T> argument)
241+
public T GetRequiredValue<T>(Option<T> option)
242+
public T GetRequiredValue<T>(System.String name)
237243
public ArgumentResult GetResult(System.CommandLine.Argument argument)
238244
public CommandResult GetResult(System.CommandLine.Command command)
239245
public OptionResult GetResult(System.CommandLine.Option option)

src/System.CommandLine.Tests/OptionTests.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,8 +215,14 @@ public void When_options_use_different_prefixes_they_still_work(string prefix)
215215
var result = rootCommand.Parse(prefix + "c value-for-c " + prefix + "a value-for-a");
216216

217217
result.GetValue(optionA).Should().Be("value-for-a");
218+
result.GetRequiredValue(optionA).Should().Be("value-for-a");
219+
result.GetRequiredValue<string>(optionA.Name).Should().Be("value-for-a");
218220
result.GetResult(optionB).Should().BeNull();
221+
result.Invoking(result => result.GetRequiredValue(optionB)).Should().Throw<InvalidOperationException>();
222+
result.Invoking(result => result.GetRequiredValue<string>(optionB.Name)).Should().Throw<InvalidOperationException>();
219223
result.GetValue(optionC).Should().Be("value-for-c");
224+
result.GetRequiredValue(optionC).Should().Be("value-for-c");
225+
result.GetRequiredValue<string>(optionC.Name).Should().Be("value-for-c");
220226
}
221227

222228
[Fact]
@@ -243,12 +249,22 @@ public void Option_T_default_value_can_be_set_after_instantiation()
243249
DefaultValueFactory = (_) => 123
244250
};
245251

246-
new RootCommand { option }
252+
var result = new RootCommand { option }
247253
.Parse("")
248-
.GetResult(option)
254+
.GetResult(option);
255+
256+
result
249257
.GetValueOrDefault<int>()
250258
.Should()
251259
.Be(123);
260+
261+
result.GetRequiredValue(option)
262+
.Should()
263+
.Be(123);
264+
265+
result.GetRequiredValue<int>(option.Name)
266+
.Should()
267+
.Be(123);
252268
}
253269

254270
[Fact]
@@ -390,6 +406,8 @@ public void Multiple_identifier_token_instances_without_argument_tokens_can_be_p
390406
var result = root.Parse("-v -v -v");
391407

392408
result.GetValue(option).Should().BeTrue();
409+
result.GetRequiredValue(option).Should().BeTrue();
410+
result.GetRequiredValue<bool>(option.Name).Should().BeTrue();
393411
}
394412

395413
[Fact]

src/System.CommandLine.Tests/ParserTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,33 @@ public void Commands_can_have_default_argument_values()
826826
GetValue(result, argument)
827827
.Should()
828828
.Be("default");
829+
830+
result.GetRequiredValue(argument)
831+
.Should()
832+
.Be("default");
833+
}
834+
835+
[Fact]
836+
public void GetRequiredValue_throws_when_argument_without_default_value_was_not_provided()
837+
{
838+
Argument<int> argument = new("the-arg");
839+
Option<bool> option = new("--option");
840+
841+
Command command = new("command")
842+
{
843+
argument,
844+
option
845+
};
846+
847+
ParseResult result = command.Parse("command --option");
848+
849+
result.Invoking(result => result.GetRequiredValue(argument))
850+
.Should()
851+
.Throw<InvalidOperationException>();
852+
853+
result.Invoking(result => result.GetRequiredValue<int>(argument.Name))
854+
.Should()
855+
.Throw<InvalidOperationException>();
829856
}
830857

831858
[Fact]
@@ -925,6 +952,11 @@ public void Command_default_argument_value_does_not_override_parsed_value()
925952
.Name
926953
.Should()
927954
.Be("the-directory");
955+
956+
result.GetRequiredValue(argument)
957+
.Name
958+
.Should()
959+
.Be("the-directory");
928960
}
929961

930962
[Fact]
@@ -1160,6 +1192,10 @@ public void Arguments_can_match_subcommands()
11601192
GetValue(result, argument)
11611193
.Should()
11621194
.BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four");
1195+
1196+
result.GetRequiredValue(argument)
1197+
.Should()
1198+
.BeEquivalentSequenceTo("one", "two", "three", "subcommand", "four");
11631199
}
11641200

11651201
[Theory]

src/System.CommandLine/ParseResult.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,35 @@ CommandLineText is null
133133
public T? GetValue<T>(string name)
134134
=> RootCommandResult.GetValue<T>(name);
135135

136+
/// <summary>
137+
/// Gets the parsed or default value for the specified required argument or throws.
138+
/// </summary>
139+
/// <param name="argument">The argument for which to get a value.</param>
140+
/// <returns>The parsed value or a configured default.</returns>
141+
/// <exception cref="InvalidOperationException">Thrown when required argument was not parsed or has no default value configured.</exception>
142+
public T GetRequiredValue<T>(Argument<T> argument)
143+
=> RootCommandResult.GetRequiredValue(argument);
144+
145+
/// <summary>
146+
/// Gets the parsed or default value for the specified required option or throws.
147+
/// </summary>
148+
/// <param name="option">The option for which to get a value.</param>
149+
/// <returns>The parsed value or a configured default.</returns>
150+
/// <exception cref="InvalidOperationException">Thrown when required option was not parsed or has no default value configured.</exception>
151+
public T GetRequiredValue<T>(Option<T> option)
152+
=> RootCommandResult.GetRequiredValue(option);
153+
154+
/// <summary>
155+
/// Gets the parsed or default value for the specified required symbol name, in the context of parsed command (not entire symbol tree).
156+
/// </summary>
157+
/// <param name="name">The name of the required Symbol for which to get a value.</param>
158+
/// <returns>The parsed value or a configured default.</returns>
159+
/// <exception cref="InvalidOperationException">Thrown when parsing resulted in parse error(s) or required symbol was not parsed or has no default value configured.</exception>
160+
/// <exception cref="ArgumentException">Thrown when there was no symbol defined for given name for the parsed command.</exception>
161+
/// <exception cref="InvalidCastException">Thrown when parsed result can not be cast to <typeparamref name="T"/>.</exception>
162+
public T GetRequiredValue<T>(string name)
163+
=> RootCommandResult.GetRequiredValue<T>(name);
164+
136165
/// <inheritdoc />
137166
public override string ToString() => ParseDiagramAction.Diagram(this).ToString();
138167

src/System.CommandLine/Parsing/SymbolResult.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ public IEnumerable<ParseError> Errors
122122
return Argument<T>.CreateDefaultValue();
123123
}
124124

125+
/// <inheritdoc cref="ParseResult.GetRequiredValue{T}(Argument{T})"/>
126+
public T GetRequiredValue<T>(Argument<T> argument)
127+
=> GetResult(argument) switch
128+
{
129+
ArgumentResult argumentResult => argumentResult.GetValueOrDefault<T>(),
130+
null => throw new InvalidOperationException($"{argument.Name} is required but was not provided."),
131+
};
132+
133+
/// <inheritdoc cref="ParseResult.GetRequiredValue{T}(Option{T})"/>
134+
public T GetRequiredValue<T>(Option<T> option)
135+
=> GetResult(option) switch
136+
{
137+
OptionResult optionResult => optionResult.GetValueOrDefault<T>(),
138+
null => throw new InvalidOperationException($"{option.Name} is required but was not provided."),
139+
};
140+
125141
/// <summary>
126142
/// Gets the value for a symbol having the specified name anywhere in the parse tree.
127143
/// </summary>
@@ -147,6 +163,20 @@ public IEnumerable<ParseError> Errors
147163
return Argument<T>.CreateDefaultValue();
148164
}
149165

166+
/// <summary>
167+
/// Gets the value for a symbol having the specified name anywhere in the parse tree.
168+
/// </summary>
169+
/// <param name="name">The name of the symbol for which to find a result.</param>
170+
/// <returns>An argument result if the argument was matched by the parser or has a default value; otherwise, <c>null</c>.</returns>
171+
public T GetRequiredValue<T>(string name)
172+
=> GetResult(name) switch
173+
{
174+
OptionResult optionResult => optionResult.GetValueOrDefault<T>(),
175+
ArgumentResult argumentResult => argumentResult.GetValueOrDefault<T>(),
176+
SymbolResult _ => throw new InvalidOperationException($"{name} is not an option or argument."),
177+
_ => throw new InvalidOperationException($"{name} is required but was not provided."),
178+
};
179+
150180
internal virtual bool UseDefaultValueFor(ArgumentResult argumentResult) => false;
151181
}
152182
}

0 commit comments

Comments
 (0)