Skip to content

Commit 71cfaed

Browse files
KathleenDollardmhutch
authored andcommitted
Add diagram subsystem and tests
1 parent 8f7918d commit 71cfaed

File tree

8 files changed

+289
-6
lines changed

8 files changed

+289
-6
lines changed
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

+1
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

+24
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 =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
switch (symbolResult)
70+
{
71+
// TODO: Directives
72+
/*
73+
case DirectiveResult { Directive: not DiagramDirective }:
74+
break;
75+
*/
76+
77+
// 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
78+
/*
79+
case ArgumentResult argumentResult:
80+
{
81+
var includeArgumentName =
82+
argumentResult.Argument.FirstParent!.Symbol is CliCommand { HasArguments: true, Arguments.Count: > 1 };
83+
84+
if (includeArgumentName)
85+
{
86+
builder.Append("[ ");
87+
builder.Append(argumentResult.Argument.Name);
88+
builder.Append(' ');
89+
}
90+
91+
if (argumentResult.Argument.Arity.MaximumNumberOfValues > 0)
92+
{
93+
ArgumentConversionResult conversionResult = argumentResult.GetArgumentConversionResult();
94+
switch (conversionResult.Result)
95+
{
96+
case ArgumentConversionResultType.NoArgument:
97+
break;
98+
case ArgumentConversionResultType.Successful:
99+
switch (conversionResult.Value)
100+
{
101+
case string s:
102+
builder.Append($"<{s}>");
103+
break;
104+
105+
case IEnumerable items:
106+
builder.Append('<');
107+
builder.Append(
108+
string.Join("> <",
109+
items.Cast<object>().ToArray()));
110+
builder.Append('>');
111+
break;
112+
113+
default:
114+
builder.Append('<');
115+
builder.Append(conversionResult.Value);
116+
builder.Append('>');
117+
break;
118+
}
119+
120+
break;
121+
122+
default: // failures
123+
builder.Append('<');
124+
builder.Append(string.Join("> <", symbolResult.Tokens.Select(t => t.Value)));
125+
builder.Append('>');
126+
127+
break;
128+
}
129+
}
130+
131+
if (includeArgumentName)
132+
{
133+
builder.Append(" ]");
134+
}
135+
136+
break;
137+
}
138+
139+
default:
140+
{
141+
OptionResult? optionResult = symbolResult as OptionResult;
142+
143+
if (optionResult is { Implicit: true })
144+
{
145+
builder.Append('*');
146+
}
147+
148+
builder.Append("[ ");
149+
150+
if (optionResult is not null)
151+
{
152+
builder.Append(optionResult.IdentifierToken?.Value ?? optionResult.Option.Name);
153+
}
154+
else
155+
{
156+
builder.Append(((CommandResult)symbolResult).IdentifierToken.Value);
157+
}
158+
159+
foreach (SymbolResult child in symbolResult.SymbolResultTree.GetChildren(symbolResult))
160+
{
161+
if (child is ArgumentResult arg &&
162+
(arg.Argument.ValueType == typeof(bool) ||
163+
arg.Argument.Arity.MaximumNumberOfValues == 0))
164+
{
165+
continue;
166+
}
167+
168+
builder.Append(' ');
169+
170+
Diagram(builder, child, parseResult);
171+
}
172+
173+
builder.Append(" ]");
174+
break;
175+
}
176+
}
177+
*/
178+
}
179+
}
180+
}

src/System.CommandLine.Subsystems/Pipeline.cs

+20-4
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)
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
}
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

+1
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)