Skip to content

Commit

Permalink
Add diagram subsystem and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KathleenDollard authored and mhutch committed Apr 4, 2024
1 parent 708181b commit 39afcc0
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 6 deletions.
45 changes: 45 additions & 0 deletions src/System.CommandLine.Subsystems.Tests/DiagramSubsystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using FluentAssertions;
using System.CommandLine.Directives;
using System.CommandLine.Parsing;
using Xunit;

namespace System.CommandLine.Subsystems.Tests;

public class DiagramSubsystemTests
{

[Theory]
[ClassData(typeof(TestData.Diagram))]
public void Diagram_is_activated_only_when_requested(string input, bool expectedIsActive)
{
CliRootCommand rootCommand = [new CliCommand("x")];
var configuration = new CliConfiguration(rootCommand);
var subsystem = new DiagramSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();

Subsystem.Initialize(subsystem, configuration, args);
var parseResult = CliParser.Parse(rootCommand, input, configuration);
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);

isActive.Should().Be(expectedIsActive);
}

[Theory]
[ClassData(typeof(TestData.Diagram))]
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(string input, bool expectedIsActive)
{
CliRootCommand rootCommand = [new CliCommand("x")];
var configuration = new CliConfiguration(rootCommand);
var subsystem = new DiagramSubsystem();
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();

Subsystem.Initialize(subsystem, configuration, args);
var parseResult = CliParser.Parse(rootCommand, input, configuration);
var isActive = Subsystem.GetIsActivated(subsystem, parseResult);

isActive.Should().Be(expectedIsActive);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<Compile Include="AlternateSubsystems.cs" />
<Compile Include="Constants.cs" />
<Compile Include="DirectiveSubsystemTests.cs" />
<Compile Include="DiagramSubsystemTests.cs" />
<Compile Include="PipelineTests.cs" />
<Compile Include="TestData.cs" />
<Compile Include="VersionFunctionalTests.cs" />
Expand Down
24 changes: 24 additions & 0 deletions src/System.CommandLine.Subsystems.Tests/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,30 @@ internal class Version : IEnumerable<object[]>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

internal class Diagram : IEnumerable<object[]>
{
// The tests define an x command, but -o and -v are just random values
private readonly List<object[]> _data =
[
["[diagram]", true],
["[diagram] x", true],
["[diagram] -o", true],
["[diagram] -v", true],
["[diagram] x -v", true],
["[diagramX]", false],
["[diagram] [other]", true],
["x", false],
["-o", false],
["x -x", false],
[null, false],
["", false]
];

public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

internal class Directive : IEnumerable<object[]>
{
private readonly List<object[]> _data =
Expand Down
180 changes: 180 additions & 0 deletions src/System.CommandLine.Subsystems/Directives/DiagramSubsystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Subsystems;
using System.Text;
using System.CommandLine.Parsing;

namespace System.CommandLine.Directives;

public class DiagramSubsystem( IAnnotationProvider? annotationProvider = null)
: DirectiveSubsystem("diagram", SubsystemKind.Diagram, annotationProvider)
{
//protected internal override bool GetIsActivated(ParseResult? parseResult)
// => parseResult is not null && option is not null && parseResult.GetValue(option);

protected internal override CliExit Execute(PipelineContext pipelineContext)
{
// Gather locations
//var locations = pipelineContext.ParseResult.LocationMap
// .Concat(Map(pipelineContext.ParseResult.Configuration.PreProcessedLocations));

pipelineContext.ConsoleHack.WriteLine("Output diagram");
return CliExit.SuccessfullyHandled(pipelineContext.ParseResult);
}


// TODO: Capture logic in previous diagramming, shown below
/// <summary>
/// Formats a string explaining a parse result.
/// </summary>
/// <param name="parseResult">The parse result to be diagrammed.</param>
/// <returns>A string containing a diagram of the parse result.</returns>
internal static StringBuilder Diagram(ParseResult parseResult)
{
var builder = new StringBuilder(100);


Diagram(builder, parseResult.RootCommandResult, parseResult);

// TODO: Unmatched tokens
/*
var unmatchedTokens = parseResult.UnmatchedTokens;
if (unmatchedTokens.Count > 0)
{
builder.Append(" ???-->");
for (var i = 0; i < unmatchedTokens.Count; i++)
{
var error = unmatchedTokens[i];
builder.Append(' ');
builder.Append(error);
}
}
*/

return builder;
}

private static void Diagram(
StringBuilder builder,
SymbolResult symbolResult,
ParseResult parseResult)
{
if (parseResult.Errors.Any(e => e.SymbolResult == symbolResult))
{
builder.Append('!');
}

switch (symbolResult)
{
// TODO: Directives
/*
case DirectiveResult { Directive: not DiagramDirective }:
break;
*/

// 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
/*
case ArgumentResult argumentResult:
{
var includeArgumentName =
argumentResult.Argument.FirstParent!.Symbol is CliCommand { HasArguments: true, Arguments.Count: > 1 };
if (includeArgumentName)
{
builder.Append("[ ");
builder.Append(argumentResult.Argument.Name);
builder.Append(' ');
}
if (argumentResult.Argument.Arity.MaximumNumberOfValues > 0)
{
ArgumentConversionResult conversionResult = argumentResult.GetArgumentConversionResult();
switch (conversionResult.Result)
{
case ArgumentConversionResultType.NoArgument:
break;
case ArgumentConversionResultType.Successful:
switch (conversionResult.Value)
{
case string s:
builder.Append($"<{s}>");
break;
case IEnumerable items:
builder.Append('<');
builder.Append(
string.Join("> <",
items.Cast<object>().ToArray()));
builder.Append('>');
break;
default:
builder.Append('<');
builder.Append(conversionResult.Value);
builder.Append('>');
break;
}
break;
default: // failures
builder.Append('<');
builder.Append(string.Join("> <", symbolResult.Tokens.Select(t => t.Value)));
builder.Append('>');
break;
}
}
if (includeArgumentName)
{
builder.Append(" ]");
}
break;
}
default:
{
OptionResult? optionResult = symbolResult as OptionResult;
if (optionResult is { Implicit: true })
{
builder.Append('*');
}
builder.Append("[ ");
if (optionResult is not null)
{
builder.Append(optionResult.IdentifierToken?.Value ?? optionResult.Option.Name);
}
else
{
builder.Append(((CommandResult)symbolResult).IdentifierToken.Value);
}
foreach (SymbolResult child in symbolResult.SymbolResultTree.GetChildren(symbolResult))
{
if (child is ArgumentResult arg &&
(arg.Argument.ValueType == typeof(bool) ||
arg.Argument.Arity.MaximumNumberOfValues == 0))
{
continue;
}
builder.Append(' ');
Diagram(builder, child, parseResult);
}
builder.Append(" ]");
break;
}
}
*/
}
}
}
24 changes: 20 additions & 4 deletions src/System.CommandLine.Subsystems/Pipeline.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Directives;
using System.CommandLine.Parsing;
using System.CommandLine.Subsystems;

Expand All @@ -10,8 +11,9 @@ public class Pipeline
{
public HelpSubsystem? Help { get; set; }
public VersionSubsystem? Version { get; set; }
public ErrorReportingSubsystem? ErrorReporting { get; set; }
public CompletionSubsystem? Completion { get; set; }
public DiagramSubsystem? Diagram { get; set; }
public ErrorReportingSubsystem? ErrorReporting { get; set; }

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

protected virtual void InitializeDiagram(InitializationContext context)
=> Diagram?.Initialize(context);

protected virtual void InitializeErrorReporting(InitializationContext context)
=> ErrorReporting?.Initialize(context);

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

protected virtual CliExit TearDownDiagram(CliExit cliExit)
=> Diagram is null
? cliExit
: Diagram.TearDown(cliExit);

protected virtual CliExit TearDownErrorReporting(CliExit cliExit)
=> ErrorReporting is null
? cliExit
Expand All @@ -80,6 +90,9 @@ protected virtual void ExecuteVersion(PipelineContext context)
protected virtual void ExecuteCompletion(PipelineContext context)
=> ExecuteIfNeeded(Completion, context);

protected virtual void ExecuteDiagram(PipelineContext context)
=> ExecuteIfNeeded(Diagram, context);

protected virtual void ExecuteErrorReporting(PipelineContext context)
=> ExecuteIfNeeded(ErrorReporting, context);

Expand All @@ -99,6 +112,7 @@ protected virtual void InitializeSubsystems(InitializationContext context)
InitializeHelp(context);
InitializeVersion(context);
InitializeCompletion(context);
InitializeDiagram(context);
InitializeErrorReporting(context);
}

Expand All @@ -113,8 +127,9 @@ protected virtual void InitializeSubsystems(InitializationContext context)
/// </remarks>
protected virtual CliExit TearDownSubsystems(CliExit cliExit)
{
TearDownCompletions(cliExit);
TearDownErrorReporting(cliExit);
TearDownDiagram(cliExit);
TearDownCompletion(cliExit);
TearDownVersion(cliExit);
TearDownHelp(cliExit);
return cliExit;
Expand All @@ -124,8 +139,9 @@ protected virtual void ExecuteSubsystems(PipelineContext pipelineContext)
{
ExecuteHelp(pipelineContext);
ExecuteVersion(pipelineContext);
ExecuteCompletion(pipelineContext);
ExecuteDiagram(pipelineContext);
ExecuteErrorReporting(pipelineContext);
ExecuteCompletions(pipelineContext);
}

protected static void ExecuteIfNeeded(CliSubsystem? subsystem, PipelineContext pipelineContext)
Expand Down
7 changes: 5 additions & 2 deletions src/System.CommandLine.Subsystems/StandardPipeline.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.CommandLine.Directives;

namespace System.CommandLine;

public class StandardPipeline : Pipeline
{
{
public StandardPipeline() {
Help = new HelpSubsystem();
Version = new VersionSubsystem();
ErrorReporting = new ErrorReportingSubsystem();
Completion = new CompletionSubsystem();
Diagram = new DiagramSubsystem();
ErrorReporting = new ErrorReportingSubsystem();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace System.CommandLine.Subsystems.Annotations;

/// <summary>
/// IDs for well-known diagram annotations.
/// </summary>
public static class DiagramAnnotations
{
public static string Prefix { get; } = nameof(SubsystemKind.Diagram);

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public enum SubsystemKind
Version,
ErrorReporting,
Completion,
Diagram,
}

0 comments on commit 39afcc0

Please sign in to comment.