77
88namespace 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}
0 commit comments