Skip to content

Commit 8b18d6e

Browse files
Merge pull request #2472 from KathleenDollard/m-depends-clidatasymbol-pipelineerrors-valueprovider
Changed ValueSubsystem to ValueProvider
2 parents 66e9399 + 1d060be commit 8b18d6e

File tree

11 files changed

+134
-157
lines changed

11 files changed

+134
-157
lines changed

src/System.CommandLine.Subsystems.Tests/ValueSubsystemTests.cs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,63 @@
55
using System.CommandLine.Directives;
66
using System.CommandLine.Parsing;
77
using Xunit;
8+
using static System.CommandLine.Subsystems.Tests.TestData;
89

910
namespace System.CommandLine.Subsystems.Tests;
1011

1112
public class ValueSubsystemTests
1213
{
1314
[Fact]
14-
public void ValueSubsystem_is_activated_by_default()
15+
public void Values_that_are_entered_are_retrieved()
1516
{
16-
CliRootCommand rootCommand = [
17-
new CliCommand("x")
18-
{
19-
new CliOption<string>("--opt1")
20-
}];
17+
var option = new CliOption<int>("--intOpt");
18+
var rootCommand = new CliRootCommand { option };
2119
var configuration = new CliConfiguration(rootCommand);
22-
var subsystem = new ValueSubsystem();
23-
var input = "x --opt1 Kirk";
24-
var args = CliParser.SplitCommandLine(input).ToList();
20+
var pipeline = Pipeline.Create();
21+
var consoleHack = new ConsoleHack().RedirectToBuffer(true);
22+
var input = "--intOpt 42";
2523

26-
Subsystem.Initialize(subsystem, configuration, args);
27-
var parseResult = CliParser.Parse(rootCommand, args[0], configuration);
28-
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);
24+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
25+
var pipelineResult = new PipelineResult(parseResult, input, pipeline);
2926

30-
isActive.Should().BeTrue();
27+
pipelineResult.Should().NotBeNull();
28+
var optionValueResult = pipelineResult.GetValueResult(option);
29+
var optionValue = pipelineResult.GetValue<int>(option);
30+
optionValueResult.Should().NotBeNull();
31+
optionValue.Should().Be(42);
3132
}
33+
34+
[Fact]
35+
public void Values_that_are_not_entered_are_type_default_with_no_default_values()
36+
{
37+
var stringOption = new CliOption<string>("--stringOption");
38+
var intOption = new CliOption<int>("--intOption");
39+
var dateOption = new CliOption<DateTime>("--dateOption");
40+
var nullableIntOption = new CliOption<int?>("--nullableIntOption");
41+
var guidOption = new CliOption<Guid>("--guidOption");
42+
var rootCommand = new CliRootCommand { stringOption, intOption, dateOption, nullableIntOption, guidOption };
43+
var configuration = new CliConfiguration(rootCommand);
44+
var pipeline = Pipeline.Create();
45+
var input = "";
46+
47+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
48+
var pipelineResult = new PipelineResult(parseResult, input, pipeline);
49+
50+
pipelineResult.Should().NotBeNull();
51+
var stringOptionValue = pipelineResult.GetValue<string>(stringOption);
52+
var intOptionValue = pipelineResult.GetValue<int>(intOption);
53+
var dateOptionValue = pipelineResult.GetValue<DateTime>(dateOption);
54+
var nullableIntOptionValue = pipelineResult.GetValue<int?>(nullableIntOption);
55+
var guidOptionValue = pipelineResult.GetValue<Guid>(guidOption);
56+
stringOptionValue.Should().BeNull();
57+
intOptionValue.Should().Be(0);
58+
dateOptionValue.Should().Be(DateTime.MinValue);
59+
nullableIntOptionValue.Should().BeNull();
60+
guidOptionValue.Should().Be(Guid.Empty);
61+
}
62+
63+
// TODO: Add various default value tests
64+
3265
/* Hold these tests until we determine if ValueSubsystem is replaceable
3366
[Fact]
3467
public void ValueSubsystem_returns_values_that_are_entered()

src/System.CommandLine.Subsystems/Directives/DiagramSubsystem.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,16 @@ private static void Diagram(
6767
{
6868
builder.Append('!');
6969
}
70-
*/
71-
// TODO: Directives
72-
/*
70+
*/
71+
72+
/* Directives
7373
switch (symbolResult)
7474
{
7575
case DirectiveResult { Directive: not DiagramDirective }:
7676
break;
7777
*/
7878

79-
// TODO: This logic is deeply tied to internal types/properties. These aren't things we probably want to expose like SymbolNode. See #2349 for alternatives
80-
/*
79+
/* TODO: This logic is deeply tied to internal types/properties. These aren't things we probably want to expose like SymbolNode. See #2349 for alternatives
8180
case ArgumentResult argumentResult:
8281
{
8382
var includeArgumentName =

src/System.CommandLine.Subsystems/Pipeline.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public partial class Pipeline
2828
/// <param name="errorReporting">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
2929
/// <returns>A new pipeline.</returns>
3030
/// <remarks>
31-
/// Currently, the standard <see cref="ValueSubsystem"/>, <see cref="ValidationSubsystem"/> , and <see cref="ResponseSubsystem"/> cannot be replaced. <see cref="ResponseSubsystem"/> is disabled by default.
31+
/// The <see cref="ValueProvider"/>, <see cref="Directives.ResponseSubystem"/>, <see cref="InvocationSubsystem"/>, and <see cref="ValidationSubsystem"/> cannot be replaced.
3232
/// </remarks>
3333
public static Pipeline Create(HelpSubsystem? help = null,
3434
VersionSubsystem? version = null,
@@ -56,7 +56,6 @@ public static Pipeline CreateEmpty()
5656

5757
private Pipeline()
5858
{
59-
Value = new ValueSubsystem();
6059
Response = new ResponseSubsystem();
6160
Invocation = new InvocationSubsystem();
6261
Validation = new ValidationSubsystem();
@@ -172,21 +171,16 @@ public ErrorReportingSubsystem? ErrorReporting
172171

173172
// TODO: Consider whether replacing the validation subsystem is valuable
174173
/// <summary>
175-
/// Sets or gets the validation subsystem
174+
/// Gets the validation subsystem
176175
/// </summary>
177176
public ValidationSubsystem? Validation { get; }
178177

179178
// TODO: Consider whether replacing the invocation subsystem is valuable
180179
/// <summary>
181-
/// Sets or gets the invocation subsystem
180+
/// Gets the invocation subsystem
182181
/// </summary>
183182
public InvocationSubsystem? Invocation { get; }
184183

185-
/// <summary>
186-
/// Gets the value subsystem which manages entered and default values.
187-
/// </summary>
188-
public ValueSubsystem Value { get; }
189-
190184
/// <summary>
191185
/// Gets the response file subsystem
192186
/// </summary>

src/System.CommandLine.Subsystems/PipelineResult.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,31 @@
55

66
namespace System.CommandLine;
77

8-
public class PipelineResult(ParseResult? parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null)
8+
public class PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null)
99
{
10+
// TODO: Try to build workflow so it is illegal to create this without a ParseResult
1011
private readonly List<ParseError> errors = [];
11-
public ParseResult? ParseResult { get; } = parseResult;
12+
public ParseResult ParseResult { get; } = parseResult;
13+
private ValueProvider valueProvider { get; } = new ValueProvider(parseResult);
1214
public string RawInput { get; } = rawInput;
15+
16+
// TODO: Consider behavior when pipeline is null - this is probably a core user accessing some subsystems
1317
public Pipeline Pipeline { get; } = pipeline ?? Pipeline.CreateEmpty();
1418
public ConsoleHack ConsoleHack { get; } = consoleHack ?? new ConsoleHack();
1519

1620
public bool AlreadyHandled { get; set; }
1721
public int ExitCode { get; set; }
1822

23+
public T? GetValue<T>(CliValueSymbol dataSymbol)
24+
=> valueProvider.GetValue<T>(dataSymbol);
25+
26+
public object? GetValue(CliValueSymbol option)
27+
=> valueProvider.GetValue<object?>(option);
28+
29+
public CliValueResult? GetValueResult(CliValueSymbol dataSymbol)
30+
=> ParseResult.GetValueResult(dataSymbol);
31+
32+
1933
public void AddErrors(IEnumerable<ParseError> errors)
2034
{
2135
if (errors is not null)

src/System.CommandLine.Subsystems/Subsystems/PipelinePhase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ private List<CliSubsystem> CreateAfterIfNeeded()
4141
return after;
4242
}
4343

44-
public IEnumerable<CliSubsystem> GetSubsystems()
44+
public IEnumerable<CliSubsystem> GetSubsystems()
4545
=> [
4646
.. (before is null ? [] : before),
4747
.. (CliSubsystem is null ? new List<CliSubsystem> { } : [CliSubsystem]),

src/System.CommandLine.Subsystems/ValueAnnotationExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static void SetDefaultValue<TValue>(this CliOption<TValue> option, TValue
3939
/// <param name="option">The option</param>
4040
/// <returns>The option's default value annotation if any, otherwise <see langword="null"/></returns>
4141
/// <remarks>
42-
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="ValueSubsystem.GetValue{T}(CliOption)"/>,
42+
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="PipelineResult.GetValue{T}(CliOption)"/>,
4343
/// which calculates the actual default value, based on the default value annotation and default value calculation,
4444
/// whether directly stored on the symbol or from the subsystem's <see cref="IAnnotationProvider"/>.
4545
/// </remarks>
@@ -84,7 +84,7 @@ public static void SetDefaultValue<TValue>(this CliArgument<TValue> argument, TV
8484
/// <param name="argument">The argument</param>
8585
/// <returns>The argument's default value annotation if any, otherwise <see langword="null"/></returns>
8686
/// <remarks>
87-
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="ValueSubsystem.GetValue{T}(CliArgument)"/>,
87+
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="PipelineResult.GetValue{T}(CliArgument)"/>,
8888
/// which calculates the actual default value, based on the default value annotation and default value calculation,
8989
/// whether directly stored on the symbol or from the subsystem's <see cref="IAnnotationProvider"/>.
9090
/// </remarks>
@@ -128,7 +128,7 @@ public static void SetDefaultValueCalculation<TValue>(this CliOption<TValue> opt
128128
/// <param name="option">The option</param>
129129
/// <returns>The option's default value calculation if any, otherwise <see langword="null"/></returns>
130130
/// <remarks>
131-
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="ValueSubsystem.GetValue{T}(CliOption)"/>,
131+
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="PipelineResult.GetValue{T}(CliOption)"/>,
132132
/// which calculates the actual default value, based on the default value annotation and default value calculation,
133133
/// whether directly stored on the symbol or from the subsystem's <see cref="IAnnotationProvider"/>.
134134
/// </remarks>
@@ -173,7 +173,7 @@ public static void SetDefaultValueCalculation<TValue>(this CliArgument<TValue> a
173173
/// <param name="argument">The argument</param>
174174
/// <returns>The argument's default value calculation if any, otherwise <see langword="null"/></returns>
175175
/// <remarks>
176-
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="ValueSubsystem.GetValue{T}(CliArgument)"/>,
176+
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="PipelineResult.GetValue{T}(CliArgument)"/>,
177177
/// which calculates the actual default value, based on the default value annotation and default value calculation,
178178
/// whether directly stored on the symbol or from the subsystem's <see cref="IAnnotationProvider"/>.
179179
/// </remarks>

src/System.CommandLine.Subsystems/ValueSubsystem.cs renamed to src/System.CommandLine.Subsystems/ValueProvider.cs

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,17 @@
66

77
namespace System.CommandLine;
88

9-
public class ValueSubsystem(IAnnotationProvider? annotationProvider = null)
10-
: CliSubsystem(ValueAnnotations.Prefix, SubsystemKind.Value, annotationProvider)
9+
internal class ValueProvider
1110
{
1211
private Dictionary<CliSymbol, object?> cachedValues = [];
1312
private ParseResult? parseResult = null;
1413

15-
// It is possible that another subsystems GetIsActivated method will access a value.
16-
// If this is called from a GetIsActivated method of a subsystem in the early termination group,
17-
// it will fail. That is not an expected scenario.
18-
/// <inheritdoc cref="CliSubsystem.GetIsActivated"/>
19-
/// <remarks>
20-
/// Note to inheritors: Call base for all ValueSubsystem methods that you override to ensure correct behavior
21-
/// </remarks>
22-
protected internal override bool GetIsActivated(ParseResult? parseResult)
14+
public ValueProvider(ParseResult parseResult)
2315
{
2416
this.parseResult = parseResult;
25-
return true;
2617
}
2718

28-
/// <inheritdoc cref="CliSubsystem.Execute"/>
29-
/// <remarks>
30-
/// Note to inheritors: Call base for all ValueSubsystem methods that you override to ensure correct behavior
31-
/// </remarks>
32-
protected internal override void Execute(PipelineResult pipelineResult)
33-
{
34-
parseResult ??= pipelineResult.ParseResult;
35-
base.Execute(pipelineResult);
36-
}
37-
38-
private void SetValue<T>(CliSymbol symbol, object? value)
19+
private void SetValue(CliSymbol symbol, object? value)
3920
{
4021
cachedValues[symbol] = value;
4122
}
@@ -53,10 +34,8 @@ private bool TryGetValue<T>(CliSymbol symbol, out T? value)
5334
return false;
5435
}
5536

56-
public T? GetValue<T>(CliOption option)
57-
=> GetValueInternal<T>(option);
58-
public T? GetValue<T>(CliArgument argument)
59-
=> GetValueInternal<T>(argument);
37+
public T? GetValue<T>(CliValueSymbol valueSymbol)
38+
=> GetValueInternal<T>(valueSymbol);
6039

6140
private T? GetValueInternal<T>(CliSymbol? symbol)
6241
{
@@ -74,17 +53,17 @@ not null when TryGetValue<T>(symbol, out var value)
7453
// configuration values go here in precedence
7554
//not null when GetDefaultFromEnvironmentVariable<T>(symbol, out var envName)
7655
// => UseValue(symbol, GetEnvByName(envName)),
77-
not null when TryGetAnnotation(symbol, ValueAnnotations.DefaultValueCalculation, out Func<T?>? defaultValueCalculation)
56+
not null when symbol.TryGetAnnotation(ValueAnnotations.DefaultValueCalculation, out Func<T?>? defaultValueCalculation)
7857
=> UseValue(symbol, CalculatedDefault<T>(symbol, (Func<T?>)defaultValueCalculation)),
79-
not null when TryGetAnnotation(symbol, ValueAnnotations.DefaultValue, out T? explicitValue)
58+
not null when symbol.TryGetAnnotation(ValueAnnotations.DefaultValue, out T? explicitValue)
8059
=> UseValue(symbol, explicitValue),
8160
null => throw new ArgumentNullException(nameof(symbol)),
8261
_ => UseValue(symbol, default(T))
8362
};
8463

8564
TValue? UseValue<TValue>(CliSymbol symbol, TValue? value)
8665
{
87-
SetValue<TValue>(symbol, value);
66+
SetValue(symbol, value);
8867
return value;
8968
}
9069
}

0 commit comments

Comments
 (0)