Skip to content

Commit b92fbfa

Browse files
Implmented aggregate value and updated ValueProvider
1 parent 1e120e3 commit b92fbfa

File tree

6 files changed

+147
-36
lines changed

6 files changed

+147
-36
lines changed

src/System.CommandLine.Subsystems/PipelineResult.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,35 @@
55

66
namespace System.CommandLine;
77

8-
public class PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null)
8+
public class PipelineResult
99
{
1010
// TODO: Try to build workflow so it is illegal to create this without a ParseResult
1111
private readonly List<ParseError> errors = [];
12-
public ParseResult ParseResult { get; } = parseResult;
13-
private ValueProvider valueProvider { get; } = new ValueProvider(parseResult);
14-
public string RawInput { get; } = rawInput;
12+
private ValueProvider valueProvider { get; }
13+
14+
public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null)
15+
{
16+
ParseResult = parseResult;
17+
RawInput = rawInput;
18+
Pipeline = pipeline ?? Pipeline.CreateEmpty();
19+
ConsoleHack = consoleHack ?? new ConsoleHack();
20+
valueProvider = new ValueProvider(this);
21+
}
22+
23+
public ParseResult ParseResult { get; }
24+
public string RawInput { get; }
1525

1626
// TODO: Consider behavior when pipeline is null - this is probably a core user accessing some subsystems
17-
public Pipeline Pipeline { get; } = pipeline ?? Pipeline.CreateEmpty();
18-
public ConsoleHack ConsoleHack { get; } = consoleHack ?? new ConsoleHack();
27+
public Pipeline Pipeline { get; }
28+
public ConsoleHack ConsoleHack { get; }
1929

2030
public bool AlreadyHandled { get; set; }
2131
public int ExitCode { get; set; }
2232

23-
public T GetValue<T>(CliValueSymbol dataSymbol)
33+
public T? GetValue<T>(CliValueSymbol dataSymbol)
2434
=> valueProvider.GetValue<T>(dataSymbol);
2535

26-
public object GetValue(CliValueSymbol option)
36+
public object? GetValue(CliValueSymbol option)
2737
=> valueProvider.GetValue<object>(option);
2838

2939
public CliValueResult? GetValueResult(CliValueSymbol dataSymbol)

src/System.CommandLine.Subsystems/Subsystems/Annotations/ValueAnnotations.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ public static class ValueAnnotations
1010
{
1111
internal static string Prefix { get; } = nameof(SubsystemKind.Value);
1212

13+
/// <summary>
14+
/// Default value source, which may be an aggregate source, for an option or argument
15+
/// </summary>
16+
public static AnnotationId DefaultValueSource { get; } = new(Prefix, nameof(DefaultValueSource));
17+
1318
/// <summary>
1419
/// Default value for an option or argument
1520
/// </summary>
@@ -19,6 +24,7 @@ public static class ValueAnnotations
1924
/// </remarks>
2025
public static AnnotationId DefaultValue { get; } = new(Prefix, nameof(DefaultValue));
2126

27+
2228
/// <summary>
2329
/// Default value calculation for an option or argument
2430
/// </summary>

src/System.CommandLine.Subsystems/ValueAnnotationExtensions.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,37 @@
33

44
using System.CommandLine.Subsystems;
55
using System.CommandLine.Subsystems.Annotations;
6+
using System.CommandLine.ValueSources;
7+
using System.Diagnostics.CodeAnalysis;
68

79
namespace System.CommandLine;
810

911
public static class ValueAnnotationExtensions
1012
{
11-
13+
14+
/// <summary>
15+
/// Get the default value annotation for the <paramref name="option"/>
16+
/// </summary>
17+
/// <typeparam name="TValue">The type of the option value</typeparam>
18+
/// <param name="option">The option</param>
19+
/// <returns>The option's default value annotation if any, otherwise <see langword="null"/></returns>
20+
/// <remarks>
21+
/// This is intended to be called by CLI authors. Subsystems should instead call <see cref="PipelineResult.GetValue{T}(CliOption)"/>,
22+
/// which calculates the actual default value, based on the default value annotation and default value calculation,
23+
/// whether directly stored on the symbol or from the subsystem's <see cref="IAnnotationProvider"/>.
24+
/// </remarks>
25+
public static bool TryGetDefaultValueSource(this CliValueSymbol valueSymbol, [NotNullWhen(true)] out ValueSource? defaultValueSource)
26+
=> valueSymbol.TryGetAnnotation(ValueAnnotations.DefaultValueSource, out defaultValueSource);
27+
28+
/// <summary>
29+
/// Sets the default value annotation on the <paramref name="option"/>
30+
/// </summary>
31+
/// <typeparam name="TValue">The type of the option value</typeparam>
32+
/// <param name="option">The option</param>
33+
/// <param name="defaultValue">The default value for the option</param>
34+
public static void SetDefaultValueSource(this CliValueSymbol valueSymbol, ValueSource defaultValue)
35+
=> valueSymbol.SetAnnotation(ValueAnnotations.DefaultValue, defaultValue);
36+
1237
/// <summary>
1338
/// Get the default value annotation for the <paramref name="option"/>
1439
/// </summary>

src/System.CommandLine.Subsystems/ValueProvider.cs

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.CommandLine.Subsystems.Annotations;
5+
using System.CommandLine.ValueSources;
6+
using System.Diagnostics.CodeAnalysis;
57

68
namespace System.CommandLine;
79

810
internal class ValueProvider
911
{
1012
private Dictionary<CliSymbol, object?> cachedValues = [];
11-
private ParseResult? parseResult = null;
13+
private PipelineResult pipelineResult;
1214

13-
public ValueProvider(ParseResult parseResult)
15+
public ValueProvider(PipelineResult pipelineResult)
1416
{
15-
this.parseResult = parseResult;
17+
this.pipelineResult = pipelineResult;
1618
}
1719

1820
private void SetValue(CliSymbol symbol, object? value)
@@ -33,32 +35,55 @@ private bool TryGetValue<T>(CliSymbol symbol, out T? value)
3335
return false;
3436
}
3537

36-
public T GetValue<T>(CliValueSymbol valueSymbol)
38+
public T? GetValue<T>(CliValueSymbol valueSymbol)
3739
=> GetValueInternal<T>(valueSymbol);
3840

39-
private T GetValueInternal<T>(CliValueSymbol? valueSymbol)
41+
private T? GetValueInternal<T>(CliValueSymbol valueSymbol)
4042
{
41-
// TODO: This method is definitely WRONG. If there is a relative or env variable, it does not continue if it is not found
43+
var _ = valueSymbol ?? throw new ArgumentNullException(nameof(valueSymbol));
44+
if (TryGetValue<T>(valueSymbol, out var value))
45+
{
46+
return value;
47+
}
48+
if (pipelineResult.ParseResult?.GetValueResult(valueSymbol) is { } valueResult)
49+
{
50+
return UseValue(valueSymbol, valueResult.GetValue<T>());
51+
}
52+
if (valueSymbol.TryGetDefaultValueSource(out ValueSource? defaultValueSource))
53+
{
54+
if (defaultValueSource is not ValueSource<T> typedValueSource)
55+
{
56+
throw new InvalidOperationException("Unexpected ValueSource type");
57+
}
58+
(var success, var defaultValue) = typedValueSource.GetTypedValue(pipelineResult);
59+
if (success)
60+
{
61+
return UseValue(valueSymbol, defaultValue);
62+
}
63+
}
64+
return UseValue(valueSymbol, default(T));
65+
66+
// TODO: The following logic is definitely WRONG. If there is a relative or env variable, it does not continue if it is not found
4267
// TODO: Replace this method with an AggregateValueSource
4368
// NOTE: We use the subsystem's TryGetAnnotation here instead of the GetDefaultValue etc
4469
// extension methods, as the subsystem's TryGetAnnotation respects its annotation provider
45-
return valueSymbol switch
46-
{
47-
{ } when TryGetValue<T>(valueSymbol, out var value)
48-
=> value, // It has already been retrieved at least once
49-
{ } when parseResult?.GetValueResult(valueSymbol) is { } valueResult // GetValue not used because it would always return a value
50-
=> UseValue(valueSymbol, valueResult.GetValue<T>()), // Value was supplied during parsing,
51-
// Value was not supplied during parsing, determine default now
52-
// configuration values go here in precedence
53-
//not null when GetDefaultFromEnvironmentVariable<T>(symbol, out var envName)
54-
// => UseValue(symbol, GetEnvByName(envName)),
55-
{ } when valueSymbol.TryGetAnnotation(ValueAnnotations.DefaultValueCalculation, out Func<T?>? defaultValueCalculation)
56-
=> UseValue(valueSymbol, CalculatedDefault<T>(valueSymbol, (Func<T?>)defaultValueCalculation)),
57-
{ } when valueSymbol.TryGetAnnotation(ValueAnnotations.DefaultValue, out T? explicitValue)
58-
=> UseValue(valueSymbol, explicitValue),
59-
null => throw new ArgumentNullException(nameof(valueSymbol)),
60-
_ => UseValue(valueSymbol, default(T))
61-
};
70+
//return valueSymbol switch
71+
//{
72+
// { } when TryGetValue<T>(valueSymbol, out var value)
73+
// => value, // It has already been retrieved at least once
74+
// { } when parseResult?.GetValueResult(valueSymbol) is { } valueResult // GetValue not used because it would always return a value
75+
// => UseValue(valueSymbol, valueResult.GetValue<T>()), // Value was supplied during parsing,
76+
// // Value was not supplied during parsing, determine default now
77+
// // configuration values go here in precedence
78+
// //not null when GetDefaultFromEnvironmentVariable<T>(symbol, out var envName)
79+
// // => UseValue(symbol, GetEnvByName(envName)),
80+
// { } when valueSymbol.TryGetAnnotation(ValueAnnotations.DefaultValueCalculation, out Func<T?>? defaultValueCalculation)
81+
// => UseValue(valueSymbol, CalculatedDefault<T>(valueSymbol, (Func<T?>)defaultValueCalculation)),
82+
// { } when valueSymbol.TryGetAnnotation(ValueAnnotations.DefaultValue, out T? explicitValue)
83+
// => UseValue(valueSymbol, explicitValue),
84+
// null => throw new ArgumentNullException(nameof(valueSymbol)),
85+
// _ => UseValue(valueSymbol, default(T))
86+
//};
6287

6388
TValue UseValue<TValue>(CliSymbol symbol, TValue value)
6489
{

src/System.CommandLine.Subsystems/ValueSources/AggregateValueSource.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,55 @@
33

44
namespace System.CommandLine.ValueSources;
55

6-
public class AggregateValueSource : ValueSource
6+
public class AggregateValueSource<T> : ValueSource<T>
77
{
8-
private List<ValueSource> valueSources = [];
8+
private List<ValueSource<T>> valueSources = [];
9+
10+
public AggregateValueSource(ValueSource<T> firstSource,
11+
ValueSource<T> secondSource,
12+
string? description = null,
13+
params ValueSource<T>[] otherSources)
14+
{
15+
valueSources.AddRange([firstSource, secondSource, .. otherSources]);
16+
Description = description;
17+
}
18+
919

1020
public override string? Description { get; }
1121

12-
public override (bool success, object? value) GetValue(PipelineResult pipelineResult)
22+
public bool PrecedenceAsEntered { get; set; }
23+
24+
public override (bool success, T? value) GetTypedValue(PipelineResult pipelineResult)
25+
=> ValueFromSources(pipelineResult);
26+
27+
private (bool success, T? value) ValueFromSources(PipelineResult pipelineResult)
1328
{
14-
throw new NotImplementedException();
29+
var orderedSources = PrecedenceAsEntered
30+
? valueSources
31+
: [.. valueSources.OrderBy(GetPrecedence)];
32+
foreach (var source in orderedSources)
33+
{
34+
(var success, var value) = source.GetTypedValue(pipelineResult);
35+
if (success)
36+
{
37+
return (true, value);
38+
}
39+
}
40+
return (false, default);
41+
}
42+
43+
internal static int GetPrecedence(ValueSource<T> source)
44+
{
45+
return source switch
46+
{
47+
SimpleValueSource<T> => 0,
48+
CalculatedValueSource<T> => 1,
49+
RelativeToSymbolValueSource<T> => 2,
50+
//RelativeToConfigurationValueSource<T> => 3,
51+
RelativeToEnvironmentVariableValueSource<T> => 4,
52+
_ => 5
53+
};
1554
}
1655
}
1756

57+

src/System.CommandLine.Subsystems/ValueSources/ValueSource.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ public static ValueSource<T> Create<T>(Func<(bool success, T? value)> calculatio
1818
public static ValueSource<T> Create<T>(CliValueSymbol otherSymbol, Func<object, (bool success, T? value)>? calculation = null, string? description = null)
1919
=> new RelativeToSymbolValueSource<T>(otherSymbol, calculation, description);
2020

21+
public static ValueSource<T> Create<T>(ValueSource<T> firstSource, ValueSource<T> secondSource, string? description = null, params ValueSource<T>[] otherSources)
22+
{
23+
return new AggregateValueSource<T>(firstSource, secondSource, description, otherSources);
24+
}
25+
2126
public static ValueSource<T> CreateFromEnvironmentVariable<T>(string environmentVariableName, Func<string?, (bool success, T? value)>? calculation = null, string? description = null)
2227
=> new RelativeToEnvironmentVariableValueSource<T>(environmentVariableName, calculation, description);
2328
}

0 commit comments

Comments
 (0)