Skip to content

Commit 9d3cbed

Browse files
KathleenDollardmhutch
authored andcommitted
Add diagram subsystem and tests
1 parent 8f7918d commit 9d3cbed

File tree

8 files changed

+287
-6
lines changed

8 files changed

+287
-6
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using FluentAssertions;
5+
using System.CommandLine.Directives;
6+
using System.CommandLine.Parsing;
7+
using Xunit;
8+
9+
namespace System.CommandLine.Subsystems.Tests;
10+
11+
public class DiagramSubsystemTests
12+
{
13+
14+
[Theory]
15+
[ClassData(typeof(TestData.Diagram))]
16+
public void Diagram_is_activated_only_when_requested(string input, bool expectedIsActive)
17+
{
18+
CliRootCommand rootCommand = [new CliCommand("x")];
19+
var configuration = new CliConfiguration(rootCommand);
20+
var subsystem = new DiagramSubsystem();
21+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
22+
23+
Subsystem.Initialize(subsystem, configuration, args);
24+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
25+
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);
26+
27+
isActive.Should().Be(expectedIsActive);
28+
}
29+
30+
[Theory]
31+
[ClassData(typeof(TestData.Diagram))]
32+
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(string input, bool expectedIsActive)
33+
{
34+
CliRootCommand rootCommand = [new CliCommand("x")];
35+
var configuration = new CliConfiguration(rootCommand);
36+
var subsystem = new DiagramSubsystem();
37+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
38+
39+
Subsystem.Initialize(subsystem, configuration, args);
40+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
41+
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);
42+
43+
isActive.Should().Be(expectedIsActive);
44+
}
45+
}

src/System.CommandLine.Subsystems.Tests/System.CommandLine.Subsystems.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
<Compile Include="AlternateSubsystems.cs" />
3333
<Compile Include="Constants.cs" />
3434
<Compile Include="DirectiveSubsystemTests.cs" />
35+
<Compile Include="DiagramSubsystemTests.cs" />
3536
<Compile Include="PipelineTests.cs" />
3637
<Compile Include="TestData.cs" />
3738
<Compile Include="VersionFunctionalTests.cs" />

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,30 @@ internal class Version : IEnumerable<object[]>
3131
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
3232
}
3333

34+
internal class Diagram : IEnumerable<object[]>
35+
{
36+
// The tests define an x command, but -o and -v are just random values
37+
private readonly List<object[]> _data =
38+
[
39+
["[diagram]", true],
40+
["[diagram] x", true],
41+
["[diagram] -o", true],
42+
["[diagram] -v", true],
43+
["[diagram] x -v", true],
44+
["[diagramX]", false],
45+
["[diagram] [other]", true],
46+
["x", false],
47+
["-o", false],
48+
["x -x", false],
49+
[null, false],
50+
["", false]
51+
];
52+
53+
public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
54+
55+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
56+
}
57+
3458
internal class Directive : IEnumerable<object[]>
3559
{
3660
private readonly List<object[]> _data =
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.CommandLine.Subsystems;
5+
using System.Text;
6+
using System.CommandLine.Parsing;
7+
8+
namespace System.CommandLine.Directives;
9+
10+
public class DiagramSubsystem( IAnnotationProvider? annotationProvider = null)
11+
: DirectiveSubsystem("diagram", SubsystemKind.Diagram, annotationProvider)
12+
{
13+
//protected internal override bool GetIsActivated(ParseResult? parseResult)
14+
// => parseResult is not null && option is not null && parseResult.GetValue(option);
15+
16+
protected internal override CliExit Execute(PipelineContext pipelineContext)
17+
{
18+
// Gather locations
19+
//var locations = pipelineContext.ParseResult.LocationMap
20+
// .Concat(Map(pipelineContext.ParseResult.Configuration.PreProcessedLocations));
21+
22+
pipelineContext.ConsoleHack.WriteLine("Output diagram");
23+
return CliExit.SuccessfullyHandled(pipelineContext.ParseResult);
24+
}
25+
26+
27+
// TODO: Capture logic in previous diagramming, shown below
28+
/// <summary>
29+
/// Formats a string explaining a parse result.
30+
/// </summary>
31+
/// <param name="parseResult">The parse result to be diagrammed.</param>
32+
/// <returns>A string containing a diagram of the parse result.</returns>
33+
internal static StringBuilder Diagram(ParseResult parseResult)
34+
{
35+
var builder = new StringBuilder(100);
36+
37+
38+
Diagram(builder, parseResult.RootCommandResult, parseResult);
39+
40+
// TODO: Unmatched tokens
41+
/*
42+
var unmatchedTokens = parseResult.UnmatchedTokens;
43+
if (unmatchedTokens.Count > 0)
44+
{
45+
builder.Append(" ???-->");
46+
47+
for (var i = 0; i < unmatchedTokens.Count; i++)
48+
{
49+
var error = unmatchedTokens[i];
50+
builder.Append(' ');
51+
builder.Append(error);
52+
}
53+
}
54+
*/
55+
56+
return builder;
57+
}
58+
59+
private static void Diagram(
60+
StringBuilder builder,
61+
SymbolResult symbolResult,
62+
ParseResult parseResult)
63+
{
64+
if (parseResult.Errors.Any(e => e.SymbolResult == symbolResult))
65+
{
66+
builder.Append('!');
67+
}
68+
69+
/*
70+
switch (symbolResult)
71+
{
72+
// TODO: Directives
73+
case DirectiveResult { Directive: not DiagramDirective }:
74+
break;
75+
76+
// 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
77+
case ArgumentResult argumentResult:
78+
{
79+
var includeArgumentName =
80+
argumentResult.Argument.FirstParent!.Symbol is CliCommand { HasArguments: true, Arguments.Count: > 1 };
81+
82+
if (includeArgumentName)
83+
{
84+
builder.Append("[ ");
85+
builder.Append(argumentResult.Argument.Name);
86+
builder.Append(' ');
87+
}
88+
89+
if (argumentResult.Argument.Arity.MaximumNumberOfValues > 0)
90+
{
91+
ArgumentConversionResult conversionResult = argumentResult.GetArgumentConversionResult();
92+
switch (conversionResult.Result)
93+
{
94+
case ArgumentConversionResultType.NoArgument:
95+
break;
96+
case ArgumentConversionResultType.Successful:
97+
switch (conversionResult.Value)
98+
{
99+
case string s:
100+
builder.Append($"<{s}>");
101+
break;
102+
103+
case IEnumerable items:
104+
builder.Append('<');
105+
builder.Append(
106+
string.Join("> <",
107+
items.Cast<object>().ToArray()));
108+
builder.Append('>');
109+
break;
110+
111+
default:
112+
builder.Append('<');
113+
builder.Append(conversionResult.Value);
114+
builder.Append('>');
115+
break;
116+
}
117+
118+
break;
119+
120+
default: // failures
121+
builder.Append('<');
122+
builder.Append(string.Join("> <", symbolResult.Tokens.Select(t => t.Value)));
123+
builder.Append('>');
124+
125+
break;
126+
}
127+
}
128+
129+
if (includeArgumentName)
130+
{
131+
builder.Append(" ]");
132+
}
133+
134+
break;
135+
}
136+
137+
default:
138+
{
139+
OptionResult? optionResult = symbolResult as OptionResult;
140+
141+
if (optionResult is { Implicit: true })
142+
{
143+
builder.Append('*');
144+
}
145+
146+
builder.Append("[ ");
147+
148+
if (optionResult is not null)
149+
{
150+
builder.Append(optionResult.IdentifierToken?.Value ?? optionResult.Option.Name);
151+
}
152+
else
153+
{
154+
builder.Append(((CommandResult)symbolResult).IdentifierToken.Value);
155+
}
156+
157+
foreach (SymbolResult child in symbolResult.SymbolResultTree.GetChildren(symbolResult))
158+
{
159+
if (child is ArgumentResult arg &&
160+
(arg.Argument.ValueType == typeof(bool) ||
161+
arg.Argument.Arity.MaximumNumberOfValues == 0))
162+
{
163+
continue;
164+
}
165+
166+
builder.Append(' ');
167+
168+
Diagram(builder, child, parseResult);
169+
}
170+
171+
builder.Append(" ]");
172+
break;
173+
}
174+
}
175+
}
176+
*/
177+
}
178+
}

src/System.CommandLine.Subsystems/Pipeline.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
// Copyright (c) .NET Foundation and contributors. All rights reserved.
1+
// 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.Directives;
45
using System.CommandLine.Parsing;
56
using System.CommandLine.Subsystems;
67

@@ -10,8 +11,9 @@ public class Pipeline
1011
{
1112
public HelpSubsystem? Help { get; set; }
1213
public VersionSubsystem? Version { get; set; }
13-
public ErrorReportingSubsystem? ErrorReporting { get; set; }
1414
public CompletionSubsystem? Completion { get; set; }
15+
public DiagramSubsystem? Diagram { get; set; }
16+
public ErrorReportingSubsystem? ErrorReporting { get; set; }
1517

1618
public ParseResult Parse(CliConfiguration configuration, string rawInput)
1719
=> Parse(configuration, CliParser.SplitCommandLine(rawInput).ToArray());
@@ -48,6 +50,9 @@ protected virtual void InitializeVersion(InitializationContext context)
4850
protected virtual void InitializeCompletion(InitializationContext context)
4951
=> Completion?.Initialize(context);
5052

53+
protected virtual void InitializeDiagram(InitializationContext context)
54+
=> Diagram?.Initialize(context);
55+
5156
protected virtual void InitializeErrorReporting(InitializationContext context)
5257
=> ErrorReporting?.Initialize(context);
5358

@@ -66,6 +71,11 @@ protected virtual CliExit TearDownCompletion(CliExit cliExit)
6671
? cliExit
6772
: Completion.TearDown(cliExit);
6873

74+
protected virtual CliExit TearDownDiagram(CliExit cliExit)
75+
=> Diagram is null
76+
? cliExit
77+
: Diagram.TearDown(cliExit);
78+
6979
protected virtual CliExit TearDownErrorReporting(CliExit cliExit)
7080
=> ErrorReporting is null
7181
? cliExit
@@ -80,6 +90,9 @@ protected virtual void ExecuteVersion(PipelineContext context)
8090
protected virtual void ExecuteCompletion(PipelineContext context)
8191
=> ExecuteIfNeeded(Completion, context);
8292

93+
protected virtual void ExecuteDiagram(PipelineContext context)
94+
=> ExecuteIfNeeded(Diagram, context);
95+
8396
protected virtual void ExecuteErrorReporting(PipelineContext context)
8497
=> ExecuteIfNeeded(ErrorReporting, context);
8598

@@ -99,6 +112,7 @@ protected virtual void InitializeSubsystems(InitializationContext context)
99112
InitializeHelp(context);
100113
InitializeVersion(context);
101114
InitializeCompletion(context);
115+
InitializeDiagram(context);
102116
InitializeErrorReporting(context);
103117
}
104118

@@ -113,8 +127,9 @@ protected virtual void InitializeSubsystems(InitializationContext context)
113127
/// </remarks>
114128
protected virtual CliExit TearDownSubsystems(CliExit cliExit)
115129
{
116-
TearDownCompletions(cliExit);
117130
TearDownErrorReporting(cliExit);
131+
TearDownDiagram(cliExit);
132+
TearDownCompletion(cliExit);
118133
TearDownVersion(cliExit);
119134
TearDownHelp(cliExit);
120135
return cliExit;
@@ -124,8 +139,9 @@ protected virtual void ExecuteSubsystems(PipelineContext pipelineContext)
124139
{
125140
ExecuteHelp(pipelineContext);
126141
ExecuteVersion(pipelineContext);
142+
ExecuteCompletion(pipelineContext);
143+
ExecuteDiagram(pipelineContext);
127144
ExecuteErrorReporting(pipelineContext);
128-
ExecuteCompletions(pipelineContext);
129145
}
130146

131147
protected static void ExecuteIfNeeded(CliSubsystem? subsystem, PipelineContext pipelineContext)
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
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.Directives;
5+
46
namespace System.CommandLine;
57

68
public class StandardPipeline : Pipeline
7-
{
9+
{
810
public StandardPipeline() {
911
Help = new HelpSubsystem();
1012
Version = new VersionSubsystem();
11-
ErrorReporting = new ErrorReportingSubsystem();
1213
Completion = new CompletionSubsystem();
14+
Diagram = new DiagramSubsystem();
15+
ErrorReporting = new ErrorReportingSubsystem();
1316
}
1417
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace System.CommandLine.Subsystems.Annotations;
5+
6+
/// <summary>
7+
/// IDs for well-known diagram annotations.
8+
/// </summary>
9+
public static class DiagramAnnotations
10+
{
11+
public static string Prefix { get; } = nameof(SubsystemKind.Diagram);
12+
13+
}

src/System.CommandLine.Subsystems/Subsystems/SubsystemKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ public enum SubsystemKind
1010
Version,
1111
ErrorReporting,
1212
Completion,
13+
Diagram,
1314
}

0 commit comments

Comments
 (0)