Skip to content

Commit f5473a9

Browse files
Cleanup and fixes
- CommandValueResult ctor internal - ValueResult ctor restricted to CliOption and CliArgument - Added XML comments - Removed dead code I was confident had been replaced
1 parent 262b6fc commit f5473a9

File tree

5 files changed

+133
-137
lines changed

5 files changed

+133
-137
lines changed

src/System.CommandLine/ParseResult.cs

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -82,44 +82,7 @@ internal ParseResult(
8282
Errors = errors is not null ? errors : Array.Empty<ParseError>();
8383
}
8484

85-
//private Dictionary<string, CliSymbol> PopulateSymbolByName()
86-
//{
87-
// var commands = GetSelfAndAncestors(CommandResult);
88-
// var ret = new Dictionary<string, CliSymbol> { };
89-
90-
// foreach (var command in commands)
91-
// {
92-
// if (command.HasOptions)
93-
// {
94-
// foreach (var option in command.Options)
95-
// {
96-
// ret[option.Name] = option;
97-
// }
98-
// }
99-
// if (command.HasArguments)
100-
// {
101-
// foreach (var argument in command.Arguments)
102-
// {
103-
// ret[argument.Name] = argument;
104-
// }
105-
// }
106-
// }
107-
// return ret;
108-
109-
// static IEnumerable<CliCommand> GetSelfAndAncestors(CommandResult commandResult)
110-
// {
111-
// var ret = new List<CliCommand> { commandResult.Command };
112-
// while (commandResult.Parent is CommandResult parent)
113-
// {
114-
// commandResult = parent;
115-
// ret.Add(parent.Command);
116-
// }
117-
// ret.Reverse();
118-
// return ret;
119-
// }
120-
//}
121-
122-
public CliSymbol GetSymbolByName(string name, bool valuesOnly = false)
85+
public CliSymbol? GetSymbolByName(string name, bool valuesOnly = false)
12386
{
12487

12588
symbolLookupByName ??= new SymbolLookupByName(this);
@@ -232,10 +195,22 @@ CommandLineText is null
232195
public override string ToString() => ParseDiagramAction.Diagram(this).ToString();
233196
*/
234197

198+
/// <summary>
199+
/// Gets the ValueResult, if any, for the specified option.
200+
/// </summary>
201+
/// <param name="option">The option for which to find a result.</param>
202+
/// <returns>A result for the specified option, or <see langword="null"/> if it was not entered by the user.</returns>
235203
public ValueResult? GetValueResult(CliOption option)
236204
=> GetValueResultInternal(option);
205+
206+
/// <summary>
207+
/// Gets the result, if any, for the specified argument.
208+
/// </summary>
209+
/// <param name="argument">The argument for which to find a result.</param>
210+
/// <returns>A result for the specified argument, or <see langword="default"/> if it was not entered by the user.</returns>
237211
public ValueResult? GetValueResult(CliArgument argument)
238212
=> GetValueResultInternal(argument);
213+
239214
private ValueResult? GetValueResultInternal(CliSymbol symbol)
240215
=> valueResultDictionary.TryGetValue(symbol, out var result)
241216
? result

src/System.CommandLine/Parsing/CommandValueResult.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,38 @@
55

66
namespace System.CommandLine.Parsing;
77

8+
/// <summary>
9+
/// Provides the publicly facing command result
10+
/// </summary>
11+
/// <remarks>
12+
/// The name is temporary as we expect to later name this CommandResult and the previous one to CommandResultInternal
13+
/// </remarks>
814
public class CommandValueResult
915
{
10-
public CommandValueResult(CliCommand command, CommandValueResult parent)
16+
/// <summary>
17+
/// Creates a CommandValueResult instance
18+
/// </summary>
19+
/// <param name="command">The CliCommand that the result is for.</param>
20+
/// <param name="parent">The parent command in the case of a CLI hierarchy, or null if there is no parent.</param>
21+
internal CommandValueResult(CliCommand command, CommandValueResult parent = null)
1122
{
1223
Command = command;
1324
Parent = parent;
1425
}
26+
27+
/// <summary>
28+
/// The ValueResult instances for user entered data. This is a sparse list.
29+
/// </summary>
1530
public IEnumerable<ValueResult> ValueResults { get; } = new List<ValueResult>();
31+
32+
/// <summary>
33+
/// The CliCommand that the result is for.
34+
/// </summary>
1635
public CliCommand Command { get; }
17-
public CommandValueResult Parent { get; }
36+
37+
/// <summary>
38+
/// The command's parent if one exists, otherwise, null
39+
/// </summary>
40+
public CommandValueResult? Parent { get; }
1841

1942
}

src/System.CommandLine/Parsing/SymbolLookupByName.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ namespace System.CommandLine.Parsing;
1212
// give correct results for many CLIs, and also, it built a dictionary for the full CLI tree, rather than just the
1313
// current commands and its ancestors, so in many cases, this will be faster.
1414
//
15-
// Most importantly, that approach fails for options, like the previous global options, that appear on multiple
15+
// Most importantly, the previous approach fails for options, like the previous global options, that appear on multiple
1616
// commands, since we are now explicitly putting them on all commands.
1717

18+
/// <summary>
19+
/// Provides a mechanism to lookup symbols by their name. This searches the symbols corresponding to the current command and its ancestors.
20+
/// </summary>
1821
public class SymbolLookupByName
1922
{
2023
private class CommandCache(CliCommand command)
@@ -25,6 +28,11 @@ private class CommandCache(CliCommand command)
2528

2629
private List<CommandCache> cache;
2730

31+
/// <summary>
32+
/// Creates a new symbol lookup tied to a specific parseResult.
33+
/// </summary>
34+
/// <param name="parseResult"></param>
35+
// TODO: If needed, consider a static list/dictionary of ParseResult to make general use easier.
2836
public SymbolLookupByName(ParseResult parseResult)
2937
=> cache = BuildCache(parseResult);
3038

@@ -121,12 +129,31 @@ private bool TryGetSymbolAndParentInternal(string name,
121129
return false;
122130
}
123131

132+
/// <summary>
133+
/// Gets the symbol with the requested name that appears nearest to the starting command, which defaults to the current or leaf command.
134+
/// </summary>
135+
/// <param name="name">The name to search for</param>
136+
/// <param name="startCommand">The command to start searching up from, which defaults to the current command.</param>
137+
/// <param name="skipAncestors">If true, only the starting command and no ancestors are searched.</param>
138+
/// <param name="valuesOnly">If true, commands are ignored and only options and arguments are found.</param>
139+
/// <returns>A tuple of the found symbol and its parent command. Throws if the name is not found.</returns>
140+
/// <exception cref="InvalidOperationException">Thrown if the name is not found.</exception>
124141
public (CliSymbol symbol, CliCommand parent) GetSymbolAndParent(string name, CliCommand? startCommand = null, bool skipAncestors = false, bool valuesOnly = false)
125142
=> TryGetSymbolAndParentInternal(name, out var symbol, out var parent, out var errorMessage, startCommand, skipAncestors, valuesOnly)
126143
? (symbol, parent)
127144
: throw new InvalidOperationException(errorMessage);
128145

129-
public bool TryGetSymbol(string name, out CliSymbol symbol, CliCommand? startCommand = null, bool skipAncestors = false, bool valuesOnly = false)
146+
147+
/// <summary>
148+
/// Returns true if the symbol is found, and provides the symbols as the `out` symbol parameter.
149+
/// </summary>
150+
/// <param name="name">The name to search for</param>
151+
/// <param name="symbol">An out parameter to receive the symbol if it is found.</param>
152+
/// <param name="startCommand">The command to start searching up from, which defaults to the current command.</param>
153+
/// <param name="skipAncestors">If true, only the starting command and no ancestors are searched.</param>
154+
/// <param name="valuesOnly">If true, commands are ignored and only options and arguments are found.</param>
155+
/// <returns>True if a symbol with the requested name is found</returns>
156+
public bool TryGetSymbol(string name, [NotNullWhen(true)] out CliSymbol? symbol, CliCommand? startCommand = null, bool skipAncestors = false, bool valuesOnly = false)
130157
=> TryGetSymbolAndParentInternal(name, out symbol, out var _, out var _, startCommand, skipAncestors, valuesOnly);
131158

132159

src/System.CommandLine/Parsing/SymbolResultTree.cs

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ internal sealed class SymbolResultTree : Dictionary<CliSymbol, SymbolResult>
1818
// TODO: Looks like this is a SymboNode/linked list because a symbol may appear multiple
1919
// places in the tree and multiple symbols will have the same short name. The question is
2020
// whether creating the multiple node instances is faster than just using lists. Could well be.
21-
//private Dictionary<string, SymbolNode>? _symbolsByName;
2221
internal SymbolResultTree(
2322
CliCommand rootCommand,
2423
List<string>? tokenizeErrors)
@@ -47,11 +46,6 @@ internal SymbolResultTree(
4746
internal OptionResult? GetResult(CliOption option)
4847
=> TryGetValue(option, out SymbolResult? result) ? (OptionResult)result : default;
4948

50-
//TODO: directives
51-
/*
52-
internal DirectiveResult? GetResult(CliDirective directive)
53-
=> TryGetValue(directive, out SymbolResult? result) ? (DirectiveResult)result : default;
54-
*/
5549
// TODO: Determine how this is used. It appears to be O^n in the size of the tree and so if it is called multiple times, we should reconsider to avoid O^(N*M)
5650
internal IEnumerable<SymbolResult> GetChildren(SymbolResult parent)
5751
{
@@ -109,91 +103,5 @@ internal void AddUnmatchedToken(CliToken token, CommandResult commandResult, Com
109103
// }
110104
}
111105

112-
/* No longer used
113-
public SymbolResult? GetResult(string name)
114-
{
115-
if (_symbolsByName is null)
116-
{
117-
_symbolsByName = new();
118-
// TODO: See if we can avoid populating the entire tree and just populate the portion/cone we need
119-
PopulateSymbolsByName(_rootCommand);
120-
}
121-
122-
if (!_symbolsByName.TryGetValue(name, out SymbolNode? node))
123-
{
124-
throw new ArgumentException($"No symbol result found with name \"{name}\".");
125-
}
126-
127-
while (node is not null)
128-
{
129-
if (TryGetValue(node.Symbol, out var result))
130-
{
131-
return result;
132-
}
133-
134-
node = node.Next;
135-
}
136-
137-
return null;
138-
}
139-
140-
// TODO: symbolsbyname - this is inefficient
141-
// results for some values may not be queried at all, dependent on other options
142-
// so we could avoid using their value factories and adding them to the dictionary
143-
// could we sort by name allowing us to do a binary search instead of allocating a dictionary?
144-
// could we add codepaths that query for specific kinds of symbols so they don't have to search all symbols?
145-
// Additional Note: Couldn't commands know their children, and thus this involves querying the active command, and possibly the parents
146-
private void PopulateSymbolsByName(CliCommand command)
147-
{
148-
if (command.HasArguments)
149-
{
150-
for (var i = 0; i < command.Arguments.Count; i++)
151-
{
152-
AddToSymbolsByName(command.Arguments[i]);
153-
}
154-
}
155-
156-
if (command.HasOptions)
157-
{
158-
for (var i = 0; i < command.Options.Count; i++)
159-
{
160-
AddToSymbolsByName(command.Options[i]);
161-
}
162-
}
163-
164-
if (command.HasSubcommands)
165-
{
166-
for (var i = 0; i < command.Subcommands.Count; i++)
167-
{
168-
var childCommand = command.Subcommands[i];
169-
AddToSymbolsByName(childCommand);
170-
PopulateSymbolsByName(childCommand);
171-
}
172-
}
173-
174-
// TODO: Explore removing closure here
175-
void AddToSymbolsByName(CliSymbol symbol)
176-
{
177-
if (_symbolsByName!.TryGetValue(symbol.Name, out var node))
178-
{
179-
if (symbol.Name == node.Symbol.Name &&
180-
symbol.FirstParent?.Symbol is { } parent &&
181-
parent == node.Symbol.FirstParent?.Symbol)
182-
{
183-
throw new InvalidOperationException($"Command {parent.Name} has more than one child named \"{symbol.Name}\".");
184-
}
185-
186-
_symbolsByName[symbol.Name] = new(symbol)
187-
{
188-
Next = node
189-
};
190-
}
191-
else
192-
{
193-
_symbolsByName[symbol.Name] = new(symbol);
194-
}
195-
}
196-
}
197-
*/
198106
}
199107
}

src/System.CommandLine/Parsing/ValueResult.cs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
namespace System.CommandLine.Parsing;
77

8+
/// <summary>
9+
/// The publicly facing class for argument and option data.
10+
/// </summary>
811
public class ValueResult
912
{
10-
internal ValueResult(
13+
private ValueResult(
1114
CliSymbol valueSymbol,
1215
object? value,
1316
IEnumerable<Location> locations,
@@ -22,22 +25,82 @@ internal ValueResult(
2225
Error = error;
2326
}
2427

28+
/// <summary>
29+
/// Creates a new ValueResult instance
30+
/// </summary>
31+
/// <param name="argument">The CliArgument the value is for.</param>
32+
/// <param name="value">The entered value.</param>
33+
/// <param name="locations">The locations list.</param>
34+
/// <param name="outcome">True if parsing and converting the value was successful.</param>
35+
/// <param name="error">The CliError if parsing or converting failed, otherwise null.</param>
36+
internal ValueResult(
37+
CliArgument argument,
38+
object? value,
39+
IEnumerable<Location> locations,
40+
ValueResultOutcome outcome,
41+
// TODO: Error should be an Enumerable<Error> and perhaps should not be here at all, only on ParseResult
42+
string? error = null)
43+
:this((CliSymbol)argument, value, locations, outcome,error)
44+
{ }
45+
46+
/// <summary>
47+
/// Creates a new ValueResult instance
48+
/// </summary>
49+
/// <param name="option">The CliOption the value is for.</param>
50+
/// <param name="value">The entered value.</param>
51+
/// <param name="locations">The locations list.</param>
52+
/// <param name="outcome">True if parsing and converting the value was successful.</param>
53+
/// <param name="error">The CliError if parsing or converting failed, otherwise null.</param>
54+
internal ValueResult(
55+
CliOption option,
56+
object? value,
57+
IEnumerable<Location> locations,
58+
ValueResultOutcome outcome,
59+
// TODO: Error should be an Enumerable<Error> and perhaps should not be here at all, only on ParseResult
60+
string? error = null)
61+
: this((CliSymbol)option, value, locations, outcome, error)
62+
{ }
63+
64+
/// <summary>
65+
/// The CliSymbol the value is for. This is always a CliOption or CliArgument.
66+
/// </summary>
2567
public CliSymbol ValueSymbol { get; }
68+
2669
internal object? Value { get; }
2770

71+
/// <summary>
72+
/// Returns the value, or the default for the type.
73+
/// </summary>
74+
/// <typeparam name="T">The type to return</typeparam>
75+
/// <returns>The value, cast to the requested type.</returns>
2876
public T? GetValue<T>()
2977
=> Value is null
3078
? default
3179
: (T?)Value;
3280

33-
// This needs to be a collection because collection types have multiple tokens and they will not be simple offsets when response files are used
34-
// TODO: Consider more efficient ways to do this in the case where there is a single location
81+
/// <summary>
82+
/// Gets the locations at which the tokens that made up the value appeared.
83+
/// </summary>
84+
/// <remarks>
85+
/// This needs to be a collection because collection types have multiple tokens and they will not be simple offsets when response files are used.
86+
/// </remarks>
3587
public IEnumerable<Location> Locations { get; }
3688

89+
/// <summary>
90+
/// True when parsing and converting the value was successful
91+
/// </summary>
3792
public ValueResultOutcome Outcome { get; }
3893

94+
/// <summary>
95+
/// Parsing and conversion errors when parsing or converting failed.
96+
/// </summary>
3997
public string? Error { get; }
4098

99+
/// <summary>
100+
/// Returns test suitable for display.
101+
/// </summary>
102+
/// <returns></returns>
103+
/// <exception cref="NotImplementedException"></exception>
41104
public IEnumerable<string> TextForDisplay()
42105
{
43106
throw new NotImplementedException();

0 commit comments

Comments
 (0)