Skip to content

Commit 20fc56f

Browse files
Add XML docs, adjust scope, remove some unused stuff
1 parent bd98d30 commit 20fc56f

File tree

11 files changed

+182
-55
lines changed

11 files changed

+182
-55
lines changed

OpenQuestions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ However, the point of conditions is that they are a statement about the symbol a
5454

5555
Suggestion: Use internal constructors and leave conditions public
5656

57+
## Should `ValueCondition` be called `Condition`?
58+
59+
They may apply to commands.

src/System.CommandLine.Subsystems/Pipeline.cs

Lines changed: 1 addition & 1 deletion
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-
/// The <see cref="ValueProvider"/>, <see cref="Directives.ResponseSubystem"/>, <see cref="InvocationSubsystem"/>, and <see cref="ValidationSubsystem"/> cannot be replaced.
31+
/// The <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,

src/System.CommandLine.Subsystems/PipelineResult.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ public PipelineResult(ParseResult parseResult, string rawInput, Pipeline? pipeli
3030
public bool AlreadyHandled { get; set; }
3131
public int ExitCode { get; set; }
3232

33-
public T? GetValue<T>(CliValueSymbol dataSymbol)
34-
=> valueProvider.GetValue<T>(dataSymbol);
33+
public T? GetValue<T>(CliValueSymbol valueSymbol)
34+
=> valueProvider.GetValue<T>(valueSymbol);
3535

3636
public object? GetValue(CliValueSymbol option)
3737
=> valueProvider.GetValue<object>(option);
3838

39-
public CliValueResult? GetValueResult(CliValueSymbol dataSymbol)
40-
=> ParseResult.GetValueResult(dataSymbol);
39+
public CliValueResult? GetValueResult(CliValueSymbol valueSymbol)
40+
=> ParseResult.GetValueResult(valueSymbol);
4141

4242

4343
public void AddErrors(IEnumerable<ParseError> errors)

src/System.CommandLine.Subsystems/Validation/InclusiveGroupValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public override void Validate(CliCommandResult commandResult,
4444
// TODO: Rework to allow localization
4545
var pluralToBe = "are";
4646
var singularToBe = "is";
47-
validationContext.PipelineResult.AddError(new ParseError( $"The members {string.Join(", ", groupMembers.Select(m => m.Name))} " +
47+
validationContext.AddError(new ParseError( $"The members {string.Join(", ", groupMembers.Select(m => m.Name))} " +
4848
$"must all be used if one is used. {string.Join(", ", missingMembers.Select(m => m.Name))} " +
4949
$"{(missingMembers.Skip(1).Any() ? pluralToBe : singularToBe)} missing."));
5050
}

src/System.CommandLine.Subsystems/Validation/RangeValidator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public override void Validate(object? value, CliValueSymbol valueSymbol,
2424
}
2525
if (valueCondition.MustHaveValidator)
2626
{
27-
validationContext.PipelineResult.AddError(new ParseError($"Range validator missing for {valueSymbol.Name}"));
27+
validationContext.AddError(new ParseError($"Range validator missing for {valueSymbol.Name}"));
2828
}
2929
}
3030

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,57 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.CommandLine.Parsing;
5+
using System.CommandLine.ValueSources;
6+
using static System.Runtime.InteropServices.JavaScript.JSType;
7+
48
namespace System.CommandLine.Validation;
59

6-
// TODO: Remove this class. All of the things it contains are in the PipelineResult, except the ValidationSubsystem currently
7-
// running, if there are multiple. The scenario where that is needed seems unlikely.
10+
/// <summary>
11+
/// Provides the context for IValidator implementations
12+
/// </summary>
813
public class ValidationContext
914
{
10-
public ValidationContext(PipelineResult pipelineResult, ValidationSubsystem validationSubsystem)
15+
private PipelineResult pipelineResult { get; }
16+
17+
internal ValidationContext(PipelineResult pipelineResult, ValidationSubsystem validationSubsystem)
1118
{
12-
PipelineResult = pipelineResult;
19+
this.pipelineResult = pipelineResult;
1320
ValidationSubsystem = validationSubsystem;
1421
}
1522

16-
public PipelineResult PipelineResult { get; }
17-
public Pipeline Pipeline => PipelineResult.Pipeline;
18-
public ValidationSubsystem ValidationSubsystem { get; }
19-
public ParseResult? ParseResult => PipelineResult.ParseResult;
23+
/// <summary>
24+
/// Adds an error to the PipelineContext.
25+
/// </summary>
26+
/// <param name="error">The <see cref="ParseError"/> to add</param>
27+
public void AddError(ParseError error)
28+
=> pipelineResult.AddError(error);
29+
30+
/// <summary>
31+
/// Gets the value for an option or argument.
32+
/// </summary>
33+
/// <param name="valueSymbol">The symbol to get the value for.</param>
34+
/// <returns></returns>
35+
public object? GetValue(CliValueSymbol valueSymbol)
36+
=> pipelineResult.GetValue<object>(valueSymbol);
37+
38+
/// <summary>
39+
/// Gets the <see cref="ValueResult"/> for the option or argument, if the user entered a value.
40+
/// </summary>
41+
/// <param name="valueSymbol">The symbol to get the ValueResult for.</param>
42+
/// <returns>The ValueResult for the option or argument, or null if the user did not enter a value.</returns>
43+
public CliValueResult? GetValueResult(CliValueSymbol valueSymbol)
44+
=> pipelineResult.GetValueResult(valueSymbol);
45+
46+
/// <summary>
47+
/// Tries to get the value for a <see cref="ValueSource"/> and returns it a an `out` parameter.
48+
/// </summary>
49+
/// <typeparam name="T">The type of the value to retrieve</typeparam>
50+
/// <param name="valueSource">The <see cref="ValueSource"/> to query for its result.</param>
51+
/// <param name="value">An output parameter that contains the value, if it is found.</param>
52+
/// <returns>True if the <see cref="ValueSource"/> succeeded, otherwise false.</returns>
53+
public bool TryGetTypedValue<T>(ValueSource<T> valueSource, out T? value)
54+
=> valueSource.TryGetTypedValue(pipelineResult, out value);
55+
56+
internal ValidationSubsystem ValidationSubsystem { get; }
2057
}

src/System.CommandLine.Subsystems/ValidationSubsystem.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ private void ValidateValue(CliValueSymbol valueSymbol, ValidationContext validat
7474
return; // nothing to do
7575
}
7676

77-
var value = validationContext.PipelineResult.GetValue(valueSymbol);
78-
var valueResult = validationContext.ParseResult?.GetValueResult(valueSymbol);
77+
var value = validationContext.GetValue(valueSymbol);
78+
var valueResult = validationContext.GetValueResult(valueSymbol);
7979
foreach (var condition in valueConditions)
8080
{
8181
ValidateValueCondition(value, valueSymbol, valueResult, condition, validationContext);
@@ -120,7 +120,7 @@ private void ValidateValueCondition(object? value, CliValueSymbol valueSymbol, C
120120
{
121121
if (condition.MustHaveValidator)
122122
{
123-
validationContext.PipelineResult.AddError(new ParseError($"{valueSymbol.Name} must have {condition.Name} validator."));
123+
validationContext.AddError(new ParseError($"{valueSymbol.Name} must have {condition.Name} validator."));
124124
}
125125
return;
126126
}
@@ -166,7 +166,7 @@ private void ValidateCommandCondition(CliCommandResult commandResult, CommandCon
166166
{
167167
if (condition.MustHaveValidator)
168168
{
169-
validationContext.PipelineResult.AddError(new ParseError($"{commandResult.Command.Name} must have {condition.Name} validator."));
169+
validationContext.AddError(new ParseError($"{commandResult.Command.Name} must have {condition.Name} validator."));
170170
}
171171
return;
172172
}

src/System.CommandLine.Subsystems/ValueCondition.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,22 @@
33

44
namespace System.CommandLine;
55

6+
/// <summary>
7+
/// The base class for all conditions. Conditions describe aspects of
8+
/// symbol results, including restrictions used for validation.
9+
/// </summary>
10+
/// <param name="name"></param>
611
public abstract class ValueCondition(string name)
712
{
13+
/// <summary>
14+
/// Whether a diagnostic should be reported if there is no validator.
15+
/// Conditions may be used for other purposes, such as completions and
16+
/// not require validation.
17+
/// </summary>
818
public virtual bool MustHaveValidator { get; } = true;
19+
20+
/// <summary>
21+
/// The name of the ValueCondition.
22+
/// </summary>
923
public string Name { get; } = name;
1024
}

src/System.CommandLine.Subsystems/ValueConditionAnnotationExtensions.cs

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@
44

55
namespace System.CommandLine;
66

7+
/// <summary>
8+
/// Contains the extension methods that are used to create value conditions
9+
/// </summary>
710
public static class ValueConditionAnnotationExtensions
811
{
12+
/// <summary>
13+
/// Set the upper and/or lower bound values of the range.
14+
/// </summary>
15+
/// <typeparam name="T">The type of the bounds.</typeparam>
16+
/// <param name="symbol">The option or argument the range applies to.</param>
17+
/// <param name="lowerBound">The lower bound of the range.</param>
18+
/// <param name="upperBound">The upper bound of the range.</param>
19+
// TODO: Add RangeBounds
20+
// TODO: You should not have to set both...why not nullable?
921
public static void SetRange<T>(this CliValueSymbol symbol, T lowerBound, T upperBound)
1022
where T : IComparable<T>
1123
{
@@ -14,33 +26,35 @@ public static void SetRange<T>(this CliValueSymbol symbol, T lowerBound, T upper
1426
symbol.SetValueCondition(range);
1527
}
1628

17-
public static void SetRange<T>(this CliValueSymbol symbol, ValueSource<T> lowerBound, T upperBound)
18-
where T : IComparable<T>
19-
{
20-
var range = new Range<T>(lowerBound, upperBound);
21-
22-
symbol.SetValueCondition(range);
23-
}
24-
25-
public static void SetRange<T>(this CliValueSymbol symbol, T lowerBound, ValueSource<T> upperBound)
26-
where T : IComparable<T>
27-
{
28-
var range = new Range<T>(lowerBound, upperBound);
29-
30-
symbol.SetValueCondition(range);
31-
}
32-
29+
/// <summary>
30+
/// Set the upper and/or lower bound via ValueSource. Implicit conversions means this
31+
/// generally just works with any <see cref="ValueSource">.
32+
/// </summary>
33+
/// <typeparam name="T">The type of the bounds.</typeparam>
34+
/// <param name="symbol">The option or argument the range applies to.</param>
35+
/// <param name="lowerBound">The <see cref="ValueSource"> that is the lower bound of the range.</param>
36+
/// <param name="upperBound">The <see cref="ValueSource"> that is the upper bound of the range.</param>
37+
// TODO: Add RangeBounds
38+
// TODO: You should not have to set both...why not nullable?
3339
public static void SetRange<T>(this CliValueSymbol symbol, ValueSource<T> lowerBound, ValueSource<T> upperBound)
34-
where T : IComparable<T>
40+
where T : IComparable<T>
41+
// TODO: You should not have to set both...why not nullable?
3542
{
3643
var range = new Range<T>(lowerBound, upperBound);
3744

3845
symbol.SetValueCondition(range);
3946
}
4047

41-
public static void SetInclusiveGroup(this CliCommand symbol, IEnumerable<CliValueSymbol> group)
42-
=> symbol.SetValueCondition(new InclusiveGroup(group));
48+
/// <summary>
49+
/// Indicates that there is an inclusive group of options and arguments for the command. All
50+
/// members of an inclusive must be present, or none can be present.
51+
/// </summary>
52+
/// <param name="command">The command the inclusive group applies to.</param>
53+
/// <param name="group">The group of options and arguments that must all be present, or none can be present.</param>
54+
public static void SetInclusiveGroup(this CliCommand command, IEnumerable<CliValueSymbol> group)
55+
=> command.SetValueCondition(new InclusiveGroup(group));
4356

57+
// TODO: This should not be public if ValueConditions are not public
4458
public static void SetValueCondition<TValueSymbol, TValueCondition>(this TValueSymbol symbol, TValueCondition valueCondition)
4559
where TValueSymbol : CliValueSymbol
4660
where TValueCondition : ValueCondition
@@ -53,33 +67,63 @@ public static void SetValueCondition<TValueSymbol, TValueCondition>(this TValueS
5367
valueConditions.Add(valueCondition);
5468
}
5569

56-
public static void SetValueCondition<TValueCondition>(this CliCommand symbol, TValueCondition valueCondition)
57-
where TValueCondition : CommandCondition
70+
// TODO: This should not be public if ValueConditions are not public
71+
public static void SetValueCondition<TCommandCondition>(this CliCommand symbol, TCommandCondition commandCondition)
72+
where TCommandCondition : CommandCondition
5873
{
5974
if (!symbol.TryGetAnnotation<List<CommandCondition>>(ValueConditionAnnotations.ValueConditions, out var valueConditions))
6075
{
6176
valueConditions = [];
6277
symbol.SetAnnotation(ValueConditionAnnotations.ValueConditions, valueConditions);
6378
}
64-
valueConditions.Add(valueCondition);
79+
valueConditions.Add(commandCondition);
6580
}
6681

82+
/// <summary>
83+
/// Gets a list of conditions on an option or argument.
84+
/// </summary>
85+
/// <param name="command">The option or argument to get the conditions for.</param>
86+
/// <returns>The conditions that have been applied to the option or argument.</returns>
87+
///
88+
// TODO: This is public because it will be used by other subsystems we might not own. It could be an extension method the subsystem namespace
6789
public static List<ValueCondition>? GetValueConditions(this CliValueSymbol symbol)
6890
=> symbol.TryGetAnnotation<List<ValueCondition>>(ValueConditionAnnotations.ValueConditions, out var valueConditions)
6991
? valueConditions
7092
: null;
7193

72-
public static List<CommandCondition>? GetCommandConditions(this CliCommand symbol)
73-
=> symbol.TryGetAnnotation<List<CommandCondition>>(ValueConditionAnnotations.ValueConditions, out var valueConditions)
94+
/// <summary>
95+
/// Gets a list of conditions on a command.
96+
/// </summary>
97+
/// <param name="command">The command to get the conditions for.</param>
98+
/// <returns>The conditions that have been applied to the command.</returns>
99+
///
100+
// TODO: This is public because it will be used by other subsystems we might not own. It could be an extension method the subsystem namespace
101+
public static List<CommandCondition>? GetCommandConditions(this CliCommand command)
102+
=> command.TryGetAnnotation<List<CommandCondition>>(ValueConditionAnnotations.ValueConditions, out var valueConditions)
74103
? valueConditions
75104
: null;
76105

106+
/// <summary>
107+
/// Gets the condition that matches the type, if it exists on this option or argument.
108+
/// </summary>
109+
/// <typeparam name="TCondition">The type of condition to return.</typeparam>
110+
/// <param name="symbol">The option or argument that may contain the condition.</param>
111+
/// <returns>The condition if it exists on the option or argument, otherwise null.</returns>
112+
// This method feels useful because it clarifies that last should win and returns one, when only one should be applied
113+
// TODO: Consider removing user facing naming, other than the base type, that is Value or CommandCondition and just use Condition
77114
public static TCondition? GetValueCondition<TCondition>(this CliValueSymbol symbol)
78115
where TCondition : ValueCondition
79116
=> !symbol.TryGetAnnotation(ValueConditionAnnotations.ValueConditions, out List<ValueCondition>? valueConditions)
80117
? null
81118
: valueConditions.OfType<TCondition>().LastOrDefault();
82119

120+
/// <summary>
121+
/// Gets the condition that matches the type, if it exists on this command.
122+
/// </summary>
123+
/// <typeparam name="TCondition">The type of condition to return.</typeparam>
124+
/// <param name="symbol">The command that may contain the condition.</param>
125+
/// <returns>The condition if it exists on the command, otherwise null.</returns>
126+
// This method feels useful because it clarifies that last should win and returns one, when only one should be applied
83127
public static TCondition? GetCommandCondition<TCondition>(this CliCommand symbol)
84128
where TCondition : CommandCondition
85129
=> !symbol.TryGetAnnotation(ValueConditionAnnotations.ValueConditions, out List<CommandCondition>? valueConditions)

0 commit comments

Comments
 (0)