Skip to content

Commit 8585524

Browse files
Merge pull request #2444 from KathleenDollard/ph-kad-rearrange-commits
Reworked pipeline to introduce phases for ordering
2 parents 0449187 + dabfb02 commit 8585524

17 files changed

+359
-70
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public void Simple_response_file_contributes_to_parsing()
1919
var rootCommand = new CliRootCommand { option };
2020
var configuration = new CliConfiguration(rootCommand);
2121
var subsystem = new ResponseSubsystem();
22+
subsystem.Enabled = true;
2223
string[] args = ["@Response_1.rsp"];
2324

2425
Subsystem.Initialize(subsystem, configuration, args);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void ValueSubsystem_is_activated_by_default()
2929

3030
isActive.Should().BeTrue();
3131
}
32-
32+
/* Hold these tests until we determine if ValueSubsystem is replaceable
3333
[Fact]
3434
public void ValueSubsystem_returns_values_that_are_entered()
3535
{
@@ -90,4 +90,5 @@ public void ValueSubsystem_returns_calculated_default_value_when_no_value_is_ent
9090
9191
pipeline.Value.GetValue<int>(option).Should().Be(expected);
9292
}
93+
*/
9394
}

src/System.CommandLine.Subsystems/Directives/DiagramSubsystem.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace System.CommandLine.Directives;
99

10-
public class DiagramSubsystem( IAnnotationProvider? annotationProvider = null)
10+
public class DiagramSubsystem(IAnnotationProvider? annotationProvider = null)
1111
: DirectiveSubsystem("diagram", SubsystemKind.Diagram, annotationProvider)
1212
{
1313
//protected internal override bool GetIsActivated(ParseResult? parseResult)
@@ -68,8 +68,8 @@ private static void Diagram(
6868
builder.Append('!');
6969
}
7070
*/
71-
// TODO: Directives
72-
/*
71+
// TODO: Directives
72+
/*
7373
switch (symbolResult)
7474
{
7575
case DirectiveResult { Directive: not DiagramDirective }:

src/System.CommandLine.Subsystems/Directives/ResponseSubsystem.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@ namespace System.CommandLine.Directives;
99
public class ResponseSubsystem()
1010
: CliSubsystem("Response", SubsystemKind.Response, null)
1111
{
12-
protected internal override void Initialize(InitializationContext context)
12+
public bool Enabled { get; set; }
13+
14+
protected internal override void Initialize(InitializationContext context)
1315
=> context.Configuration.ResponseFileTokenReplacer = Replacer;
1416

15-
public static (List<string>? tokens, List<string>? errors) Replacer(string responseSourceName)
17+
public (List<string>? tokens, List<string>? errors) Replacer(string responseSourceName)
1618
{
19+
if (!Enabled)
20+
{
21+
return ([responseSourceName], null);
22+
}
1723
try
1824
{
1925
// TODO: Include checks from previous system.

src/System.CommandLine.Subsystems/HelpSubsystem.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class HelpSubsystem(IAnnotationProvider? annotationProvider = null)
2424
Arity = ArgumentArity.Zero
2525
};
2626

27-
protected internal override void Initialize(InitializationContext context)
27+
protected internal override void Initialize(InitializationContext context)
2828
=> context.Configuration.RootCommand.Add(HelpOption);
2929

3030
protected internal override bool GetIsActivated(ParseResult? parseResult)
@@ -37,6 +37,6 @@ protected internal override void Execute(PipelineResult pipelineResult)
3737
pipelineResult.SetSuccess();
3838
}
3939

40-
public bool TryGetDescription (CliSymbol symbol, out string? description)
41-
=> TryGetAnnotation (symbol, HelpAnnotations.Description, out description);
40+
public bool TryGetDescription(CliSymbol symbol, out string? description)
41+
=> TryGetAnnotation(symbol, HelpAnnotations.Description, out description);
4242
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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.CommandLine.Subsystems.Annotations;
6+
7+
namespace System.CommandLine;
8+
9+
public class InvocationSubsystem(IAnnotationProvider? annotationProvider = null)
10+
: CliSubsystem(InvocationAnnotations.Prefix, SubsystemKind.Invocation, annotationProvider)
11+
{}

src/System.CommandLine.Subsystems/Pipeline.cs

Lines changed: 185 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,190 @@
77

88
namespace System.CommandLine;
99

10-
public class Pipeline
10+
public partial class Pipeline
1111
{
12-
//TODO: When we allow adding subsystems, this code will change
13-
private IEnumerable<CliSubsystem?> Subsystems
14-
=> [Help, Version, Completion, Diagram, Value, ErrorReporting];
12+
private readonly PipelinePhase<DiagramSubsystem> diagramPhase = new(SubsystemKind.Diagram);
13+
private readonly PipelinePhase<CompletionSubsystem> completionPhase = new(SubsystemKind.Completion);
14+
private readonly PipelinePhase<HelpSubsystem> helpPhase = new(SubsystemKind.Help);
15+
private readonly PipelinePhase<VersionSubsystem> versionPhase = new(SubsystemKind.Version);
16+
private readonly PipelinePhase<ValidationSubsystem> validationPhase = new(SubsystemKind.Validation);
17+
private readonly PipelinePhase<InvocationSubsystem> invocationPhase = new(SubsystemKind.Invocation);
18+
private readonly PipelinePhase<ErrorReportingSubsystem> errorReportingPhase = new(SubsystemKind.ErrorReporting);
19+
private readonly IEnumerable<PipelinePhase> phases;
1520

21+
/// <summary>
22+
/// Creates an instance of the pipeline using standard features.
23+
/// </summary>
24+
/// <param name="help">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
25+
/// <param name="version">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
26+
/// <param name="completion">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
27+
/// <param name="diagram">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
28+
/// <param name="errorReporting">A help subsystem to replace the standard one. To add a subsystem, use <see cref="AddSubsystem"></param>
29+
/// <returns>A new pipeline.</returns>
30+
/// <remarks>
31+
/// Currently, the standard <see cref="ValueSubsystem"/>, <see cref="ValidationSubsystem"/> , and <see cref="ResponseSubsystem"/> cannot be replaced. <see cref="ResponseSubsystem"/> is disabled by default.
32+
/// </remarks>
1633
public static Pipeline Create(HelpSubsystem? help = null,
1734
VersionSubsystem? version = null,
1835
CompletionSubsystem? completion = null,
1936
DiagramSubsystem? diagram = null,
20-
ErrorReportingSubsystem? errorReporting = null,
21-
ValueSubsystem? value = null)
37+
ErrorReportingSubsystem? errorReporting = null)
2238
=> new()
2339
{
2440
Help = help ?? new HelpSubsystem(),
2541
Version = version ?? new VersionSubsystem(),
2642
Completion = completion ?? new CompletionSubsystem(),
2743
Diagram = diagram ?? new DiagramSubsystem(),
2844
ErrorReporting = errorReporting ?? new ErrorReportingSubsystem(),
29-
Value = value ?? new ValueSubsystem()
3045
};
3146

47+
/// <summary>
48+
/// Creates an instance of the pipeline with no features. Use this if you want to explicitly add features.
49+
/// </summary>
50+
/// <returns>A new pipeline.</returns>
51+
/// <remarks>
52+
/// The <cref>ValueSubsystem</cref> and <see cref="ResponseSubsystem"/> is always added and cannot be changed.
53+
/// </remarks>
3254
public static Pipeline CreateEmpty()
3355
=> new();
3456

35-
private Pipeline() { }
57+
private Pipeline()
58+
{
59+
Value = new ValueSubsystem();
60+
Response = new ResponseSubsystem();
61+
Invocation = new InvocationSubsystem();
62+
Validation = new ValidationSubsystem();
63+
64+
// This order is based on: if the user entered both, which should they get?
65+
// * It is reasonable to diagram help and completion. More reasonable than getting help on Diagram or Completion
66+
// * A future version of Help and Version may take arguments/options. In that case, help on version is reasonable.
67+
phases =
68+
[
69+
diagramPhase, completionPhase, helpPhase, versionPhase,
70+
validationPhase, invocationPhase, errorReportingPhase
71+
];
72+
}
73+
74+
/// <summary>
75+
/// Enables response files. They are disabled by default.
76+
/// </summary>
77+
public bool ResponseFilesEnabled
78+
{
79+
get => Response.Enabled;
80+
set => Response.Enabled = value;
81+
}
82+
83+
/// <summary>
84+
/// Adds a subsystem.
85+
/// </summary>
86+
/// <param name="subsystem">The subsystem to add.</param>
87+
/// <param name="timing"><see cref="PhaseTiming.Before"/> indicates that the subsystem should run before all other subsystems in the phase, and <see cref="PhaseTiming.After"/> indicates it should run after other subsystems. The default is <see cref="PhaseTiming.Before"/>.</param>
88+
/// <exception cref="InvalidOperationException"></exception>
89+
/// <remarks>
90+
/// The phase in which the subsystem runs is determined by the subsystem's 'Kind' property.
91+
/// <br/>
92+
/// To replace one of the standard subsystems, use the `Pipeline.(subsystem)` property, such as `myPipeline.Help = new OtherHelp();`
93+
/// </remarks>
94+
public void AddSubsystem(CliSubsystem subsystem, AddToPhaseBehavior timing = AddToPhaseBehavior.SubsystemRecommendation)
95+
{
96+
switch (subsystem.Kind)
97+
{
98+
case SubsystemKind.Other:
99+
case SubsystemKind.Response:
100+
case SubsystemKind.Value:
101+
throw new InvalidOperationException($"You cannot add subsystems to {subsystem.Kind}");
102+
case SubsystemKind.Diagram:
103+
diagramPhase.AddSubsystem(subsystem, timing);
104+
break;
105+
case SubsystemKind.Completion:
106+
completionPhase.AddSubsystem(subsystem, timing);
107+
break;
108+
case SubsystemKind.Help:
109+
helpPhase.AddSubsystem(subsystem, timing);
110+
break;
111+
case SubsystemKind.Version:
112+
versionPhase.AddSubsystem(subsystem, timing);
113+
break;
114+
// You can add Validation and Invocation subsystems, but you can't remove the core.
115+
// Other things may need to be run in the phase.
116+
case SubsystemKind.Validation:
117+
validationPhase.AddSubsystem(subsystem, timing);
118+
break;
119+
case SubsystemKind.Invocation:
120+
invocationPhase.AddSubsystem(subsystem, timing);
121+
break;
122+
case SubsystemKind.ErrorReporting:
123+
errorReportingPhase.AddSubsystem(subsystem, timing);
124+
break;
125+
}
126+
}
127+
128+
/// <summary>
129+
/// Sets or gets the diagramming subsystem.
130+
/// </summary>
131+
public DiagramSubsystem? Diagram
132+
{
133+
get => diagramPhase.Subsystem;
134+
set => diagramPhase.Subsystem = value;
135+
}
36136

37-
public HelpSubsystem? Help { get; set; }
38-
public VersionSubsystem? Version { get; set; }
39-
public CompletionSubsystem? Completion { get; set; }
40-
public DiagramSubsystem? Diagram { get; set; }
41-
public ErrorReportingSubsystem? ErrorReporting { get; set; }
42-
public ValueSubsystem? Value { get; set; }
137+
/// <summary>
138+
/// Sets or gets the completion subsystem.
139+
/// </summary>
140+
public CompletionSubsystem? Completion
141+
{
142+
get => completionPhase.Subsystem;
143+
set => completionPhase.Subsystem = value;
144+
}
145+
146+
/// <summary>
147+
/// Sets or gets the help subsystem.
148+
/// </summary>
149+
public HelpSubsystem? Help
150+
{
151+
get => helpPhase.Subsystem;
152+
set => helpPhase.Subsystem = value;
153+
}
154+
155+
/// <summary>
156+
/// Sets or gets the version subsystem.
157+
/// </summary>
158+
public VersionSubsystem? Version
159+
{
160+
get => versionPhase.Subsystem;
161+
set => versionPhase.Subsystem = value;
162+
}
163+
164+
/// <summary>
165+
/// Sets or gets the error reporting subsystem.
166+
/// </summary>
167+
public ErrorReportingSubsystem? ErrorReporting
168+
{
169+
get => errorReportingPhase.Subsystem;
170+
set => errorReportingPhase.Subsystem = value;
171+
}
172+
173+
// TODO: Consider whether replacing the validation subsystem is valuable
174+
/// <summary>
175+
/// Sets or gets the validation subsystem
176+
/// </summary>
177+
public ValidationSubsystem? Validation { get; }
178+
179+
// TODO: Consider whether replacing the invocation subsystem is valuable
180+
/// <summary>
181+
/// Sets or gets the invocation subsystem
182+
/// </summary>
183+
public InvocationSubsystem? Invocation { get; }
184+
185+
/// <summary>
186+
/// Gets the value subsystem which manages entered and default values.
187+
/// </summary>
188+
public ValueSubsystem Value { get; }
189+
190+
/// <summary>
191+
/// Gets the response file subsystem
192+
/// </summary>
193+
public ResponseSubsystem Response { get; }
43194

44195
public ParseResult Parse(CliConfiguration configuration, string rawInput)
45196
=> Parse(configuration, CliParser.SplitCommandLine(rawInput).ToArray());
@@ -55,16 +206,24 @@ public PipelineResult Execute(CliConfiguration configuration, string rawInput, C
55206
=> Execute(configuration, CliParser.SplitCommandLine(rawInput).ToArray(), rawInput, consoleHack);
56207

57208
public PipelineResult Execute(CliConfiguration configuration, string[] args, string rawInput, ConsoleHack? consoleHack = null)
58-
{
59-
var pipelineResult = Execute(Parse(configuration, args), rawInput, consoleHack);
60-
TearDownSubsystems(pipelineResult);
61-
return pipelineResult;
62-
}
209+
=> Execute(Parse(configuration, args), rawInput, consoleHack);
63210

64211
public PipelineResult Execute(ParseResult parseResult, string rawInput, ConsoleHack? consoleHack = null)
65212
{
66213
var pipelineResult = new PipelineResult(parseResult, rawInput, this, consoleHack ?? new ConsoleHack());
67-
ExecuteSubsystems(pipelineResult);
214+
foreach (var phase in phases)
215+
{
216+
// TODO: Allow subsystems to control short-circuiting
217+
foreach (var subsystem in phase.GetSubsystems())
218+
{
219+
// TODO: RunEvenIfAlreadyHandled needs more thought and laying out the scenarios
220+
if (subsystem is not null && (!pipelineResult.AlreadyHandled || subsystem.RunsEvenIfAlreadyHandled))
221+
{
222+
subsystem.ExecuteIfNeeded(pipelineResult);
223+
}
224+
}
225+
}
226+
TearDownSubsystems(pipelineResult);
68227
return pipelineResult;
69228
}
70229

@@ -81,9 +240,10 @@ public PipelineResult Execute(ParseResult parseResult, string rawInput, ConsoleH
81240
/// </remarks>
82241
protected virtual void InitializeSubsystems(InitializationContext context)
83242
{
84-
foreach (var subsystem in Subsystems)
243+
foreach (var phase in phases)
85244
{
86-
if (subsystem is not null)
245+
// TODO: Allow subsystems to control short-circuiting? Not sure we need that for initialization
246+
foreach (var subsystem in phase.GetSubsystems())
87247
{
88248
subsystem.Initialize(context);
89249
}
@@ -99,35 +259,13 @@ protected virtual void InitializeSubsystems(InitializationContext context)
99259
protected virtual void TearDownSubsystems(PipelineResult pipelineResult)
100260
{
101261
// TODO: Work on this design as the last pipelineResult wins and they may not all be well behaved
102-
var subsystems = Subsystems.Reverse();
103-
foreach (var subsystem in subsystems)
262+
foreach (var phase in phases)
104263
{
105-
if (subsystem is not null)
264+
// TODO: Allow subsystems to control short-circuiting? Not sure we need that for teardown
265+
foreach (var subsystem in phase.GetSubsystems())
106266
{
107267
subsystem.TearDown(pipelineResult);
108268
}
109269
}
110270
}
111-
112-
protected virtual void ExecuteSubsystems(PipelineResult pipelineResult)
113-
{
114-
// TODO: Consider redesign where pipelineResult is not modifiable.
115-
//
116-
foreach (var subsystem in Subsystems)
117-
{
118-
if (subsystem is not null)
119-
{
120-
subsystem.ExecuteIfNeeded(pipelineResult);
121-
}
122-
}
123-
}
124-
125-
protected static void ExecuteIfNeeded(CliSubsystem? subsystem, PipelineResult pipelineResult)
126-
{
127-
if (subsystem is not null && (!pipelineResult.AlreadyHandled || subsystem.RunsEvenIfAlreadyHandled))
128-
{
129-
subsystem.ExecuteIfNeeded(pipelineResult);
130-
}
131-
}
132-
133271
}

src/System.CommandLine.Subsystems/Subsystems/PipelineResult.cs renamed to src/System.CommandLine.Subsystems/PipelineResult.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
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-
5-
6-
namespace System.CommandLine.Subsystems;
4+
namespace System.CommandLine;
75

86
public class PipelineResult(ParseResult? parseResult, string rawInput, Pipeline? pipeline, ConsoleHack? consoleHack = null)
97
{

0 commit comments

Comments
 (0)