Skip to content

Commit 8f7918d

Browse files
KathleenDollardmhutch
authored andcommitted
Add abstract DirectiveSubsystem
The core parser no longer supports the special "directive" syntax, but this abstract class provides a reusable implementation of handling directive syntax in a subsystem using preprocessing.
1 parent 0746536 commit 8f7918d

File tree

5 files changed

+137
-0
lines changed

5 files changed

+137
-0
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
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;
45
using System.CommandLine.Subsystems;
6+
using System.CommandLine.Subsystems.Annotations;
57

68
namespace System.CommandLine.Subsystems.Tests
79
{
@@ -65,5 +67,13 @@ protected override CliExit TearDown(CliExit cliExit)
6567
}
6668
}
6769

70+
internal class StringDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
71+
: DirectiveSubsystem("other",SubsystemKind.Other, annotationProvider)
72+
{ }
73+
74+
internal class BooleanDirectiveSubsystem(IAnnotationProvider? annotationProvider = null)
75+
: DirectiveSubsystem("diagram", SubsystemKind.Other, annotationProvider)
76+
{ }
77+
6878
}
6979
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 DirectiveSubsystemTests
12+
{
13+
14+
// For Boolean tests see DiagramSubsystemTests
15+
16+
[Theory]
17+
[ClassData(typeof(TestData.Directive))]
18+
// TODO: Not sure why these tests are passing
19+
public void String_directive_supplies_string_or_default_and_is_activated_only_when_requested(
20+
string input, bool expectedBoolIsActive, bool expectedStringIsActive, string? expectedValue)
21+
{
22+
CliRootCommand rootCommand = [new CliCommand("x")];
23+
var configuration = new CliConfiguration(rootCommand);
24+
var stringSubsystem = new AlternateSubsystems.StringDirectiveSubsystem();
25+
var boolSubsystem = new AlternateSubsystems.BooleanDirectiveSubsystem();
26+
var args = CliParser.SplitCommandLine(input).ToList().AsReadOnly();
27+
28+
Subsystem.Initialize(stringSubsystem, configuration, args);
29+
Subsystem.Initialize(boolSubsystem, configuration, args);
30+
31+
var parseResult = CliParser.Parse(rootCommand, input, configuration);
32+
var stringIsActive = Subsystem.GetIsActivated(stringSubsystem, parseResult);
33+
var boolIsActive = Subsystem.GetIsActivated(boolSubsystem, parseResult);
34+
var actualValue = stringSubsystem.Value;
35+
36+
boolIsActive.Should().Be(expectedBoolIsActive);
37+
stringIsActive.Should().Be(expectedStringIsActive);
38+
actualValue.Should().Be(expectedValue);
39+
40+
}
41+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
-->
3232
<Compile Include="AlternateSubsystems.cs" />
3333
<Compile Include="Constants.cs" />
34+
<Compile Include="DirectiveSubsystemTests.cs" />
3435
<Compile Include="PipelineTests.cs" />
3536
<Compile Include="TestData.cs" />
3637
<Compile Include="VersionFunctionalTests.cs" />

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

+25
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,29 @@ internal class Version : IEnumerable<object[]>
3030

3131
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
3232
}
33+
34+
internal class Directive : IEnumerable<object[]>
35+
{
36+
private readonly List<object[]> _data =
37+
[
38+
["[diagram]", true, false, null],
39+
["[other:Hello]", false, true, "Hello"],
40+
["[diagram] x", true, false, null],
41+
["[diagram] -o", true, false, null],
42+
["[diagram] -v", true, false, null],
43+
["[diagram] x -v", true, false, null],
44+
["[diagramX]", false, false, null],
45+
["[diagram] [other:Hello]", true, true, "Hello"],
46+
["x", false, false, null],
47+
["-o", false, false, null],
48+
["x -x", false, false, null],
49+
[null, false, false, null],
50+
["", false, false, null],
51+
//["[diagram] [other Goodbye]", true, true, "Goodbye"],This is a new test that demos new feature, but is also broken
52+
];
53+
54+
public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
55+
56+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
57+
}
3358
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.Parsing;
5+
using System.CommandLine.Subsystems;
6+
7+
namespace System.CommandLine.Directives;
8+
9+
public abstract class DirectiveSubsystem : CliSubsystem
10+
{
11+
public string? Value { get; private set; }
12+
public bool Found { get; private set; }
13+
public string Id { get; }
14+
public Location? Location { get; private set; }
15+
16+
public DirectiveSubsystem(string name, SubsystemKind kind, IAnnotationProvider? annotationProvider = null, string? id = null)
17+
: base(name, kind, annotationProvider: annotationProvider)
18+
{
19+
Id = id ?? name;
20+
}
21+
22+
protected internal override CliConfiguration Initialize(InitializationContext context)
23+
{
24+
for (int i = 0; i < context.Args.Count; i++)
25+
{
26+
var arg = context.Args[i];
27+
if (arg[0] == '[') // It looks like a directive, see if it is the one we want
28+
{
29+
var start = arg.IndexOf($"[{Id}");
30+
// Protect against matching substrings, such as "diagramX" matching "diagram" - but longer string may be valid for a different directive and we may still find the one we want
31+
if (start >= 0)
32+
{
33+
var end = arg.IndexOf("]", start) + 1;
34+
var nextChar = arg[start + Id.Length + 1];
35+
if (nextChar is ']' or ':')
36+
{
37+
Found = true;
38+
if (nextChar == ':')
39+
{
40+
Value = arg[(start + Id.Length + 2)..(end - 1)];
41+
}
42+
Location = new Location(arg.Substring(start, end - start), Location.User, i, null, start);
43+
context.Configuration.AddPreprocessedLocation(Location);
44+
break;
45+
}
46+
}
47+
}
48+
else if (i > 0) // First position might be ExeName, but directives are not legal after other tokens appear
49+
{
50+
break;
51+
}
52+
}
53+
54+
return context.Configuration;
55+
}
56+
57+
protected internal override bool GetIsActivated(ParseResult? parseResult)
58+
=> Found;
59+
60+
}

0 commit comments

Comments
 (0)