Skip to content

Commit ceda2c7

Browse files
committed
Update command lifecycle documentation and bump MyLittleContentEngine to v0.0.0-alpha.0.264
1 parent 62012c8 commit ceda2c7

2 files changed

Lines changed: 105 additions & 11 deletions

File tree

Spectre.Docs/Content/cli/explanation/command-lifecycle-and-execution-flow.md

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,105 @@ uid: "cli-command-lifecycle"
55
order: 3020
66
---
77

8-
An explanatory deep-dive into what happens from the moment `app.Run(args)` is called to when a command finishes execution. It describes:
9-
10-
* **Parsing Phase**: how the arguments array is parsed against the configured commands and settings, how Spectre.Console.Cli matches arguments to `CommandSettings` properties (and provides errors if something is wrong).
11-
* **Validation Phase**: how and when the library calls `Validate()` on settings or commands (e.g., does it validate CommandSettings by invoking an optional `Validate` method on settings class or just the command's override as documented).
12-
* **Execution Phase**: how the appropriate `Command` instance is constructed (using DI if available), then `Execute` or `ExecuteAsync` is called.
13-
* **Post-Execution**: handling the result (the int exit code) and any exception propagation or interception.
14-
* **Help invocation**: mention that if `--help` is detected, the above phases short-circuit to display help instead of executing a command.
15-
This explanation might include a simple flow diagram or description: Input args -> Parser selects Command -> Settings populated -> If parse errors, show error/help -> If ok, create Command -> (Interceptor before) -> Execute -> (Interceptor after) -> return exit code. By understanding this flow, users can reason about behaviors like why their code in `Execute` might not run (e.g. if parsing failed or validation failed first).
8+
When you call `app.Run(args)`, Spectre.Console.Cli orchestrates a multi-phase pipeline that parses your command line,
9+
validates input, and executes your command. Understanding this flow helps you reason about when your code runs, why
10+
certain errors appear, and how to debug unexpected behavior.
11+
12+
The library processes commands through distinct phases: parsing, help detection, settings binding, validation,
13+
interception, execution, and error handling. Each phase has specific responsibilities and can short-circuit the
14+
pipeline early if something goes wrong or if help is requested.
15+
16+
Execution Flow Overview
17+
-----------------------
18+
19+
The following diagram shows the high-level flow from input to exit code:
20+
21+
```mermaid
22+
flowchart TD
23+
A[app.Run args] --> B[Parse args]
24+
B --> C{Parse error?}
25+
C -->|Yes| D[Show error, return -1]
26+
C -->|No| E{Help requested?}
27+
E -->|Yes| F[Show help, return 0]
28+
E -->|No| G[Bind settings]
29+
G --> H[settings.Validate]
30+
H --> I{Validation failed?}
31+
I -->|Yes| D
32+
I -->|No| J[Interceptors before]
33+
J --> K[command.Validate]
34+
K --> L{Validation failed?}
35+
L -->|Yes| D
36+
L -->|No| M[Execute]
37+
M --> N[Interceptors after]
38+
N --> O[Return exit code]
39+
```
40+
41+
Parsing and Command Resolution
42+
------------------------------
43+
44+
The parser tokenizes the argument array and matches tokens against your configured command tree. For nested commands
45+
like `myapp add package Newtonsoft.Json`, the parser walks through each level: first matching `add`, then `package`,
46+
then capturing `Newtonsoft.Json` as an argument.
47+
48+
During parsing, the library builds a `CommandTree` representing the matched command hierarchy. Arguments that don't
49+
match any defined parameter are collected as remaining arguments, accessible via `IRemainingArguments` in your command.
50+
51+
Help detection happens during parsing, not after. When the parser encounters `-h`, `-?`, or `--help`, it sets a flag
52+
on the command node and stops processing further arguments. This means help short-circuits the entire pipeline before
53+
any binding or validation occurs. Branch commands (those with subcommands but no direct execution logic) automatically
54+
show help and return exit code 1.
55+
56+
Settings Binding and Validation
57+
-------------------------------
58+
59+
After parsing, the library creates an instance of your `CommandSettings` class and populates it with parsed values.
60+
If you have a dependency injection container configured, settings can be resolved through constructor injection.
61+
Otherwise, the library creates the instance directly and sets properties via reflection.
62+
63+
Type conversion happens automatically. If an option is declared as `int` and the user provides a string, the library
64+
converts it. Invalid conversions produce clear error messages before your code runs.
65+
66+
Immediately after binding, the library calls `settings.Validate()`. This is your first opportunity to check that the
67+
combination of values makes sense. The `Validate()` method on `CommandSettings` is virtual, returning
68+
`ValidationResult.Success()` by default. Override it to add custom validation logic that runs before command execution.
69+
70+
Command Validation and Execution
71+
--------------------------------
72+
73+
Once settings are validated, the library resolves your command class. If you have dependency injection configured,
74+
the command is resolved from the container, allowing constructor injection of services. Otherwise, it is instantiated
75+
directly.
76+
77+
Before calling `Execute`, the library invokes `command.Validate(context, settings)`. This is a separate validation
78+
point from settings validation. Use it when validation requires the full `CommandContext` or when you need to validate
79+
relationships between settings and external state.
80+
81+
The library then calls `Execute` (or `ExecuteAsync` for async commands). Your command returns an integer exit code.
82+
By convention, 0 indicates success and non-zero values indicate various error conditions.
83+
84+
Interceptors
85+
------------
86+
87+
Interceptors provide hooks before and after command execution. Any class implementing `ICommandInterceptor` registered
88+
in your DI container is automatically resolved and invoked.
89+
90+
The `Intercept` method runs after settings binding but before execution. Use it for cross-cutting concerns like
91+
logging, timing, or modifying settings. The `InterceptResult` method runs after execution and receives the exit code
92+
by reference, allowing you to modify it before it is returned to the caller.
93+
94+
Exit Codes and Error Handling
95+
-----------------------------
96+
97+
Spectre.Console.Cli uses standard exit code conventions: 0 for success, non-zero for errors. The library returns -1
98+
for unhandled exceptions by default.
99+
100+
Exception handling occurs at two levels. The `CommandExecutor` catches exceptions during execution and can invoke a
101+
custom `ExceptionHandler` if configured. If the exception propagates, `CommandApp` catches it and either re-throws
102+
(if `PropagateExceptions` is true) or renders the error and returns -1.
103+
104+
For cancellation scenarios, the library catches `OperationCanceledException` and returns a configurable cancellation
105+
exit code.
106+
107+
Understanding this lifecycle helps diagnose issues: if your `Execute` method never runs, the problem likely occurred
108+
during parsing, binding, or validation. If interceptors don't fire, check that they are registered in your DI
109+
container. The structured pipeline ensures errors are caught early and reported clearly.

Spectre.Docs/Spectre.Docs.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
library where if you don't reference it directly they don't include the BuildHost folders. Include for now.
1717
-->
1818
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="5.0.0" />
19-
<PackageReference Include="MyLittleContentEngine" Version="0.0.0-alpha.0.263" />
20-
<PackageReference Include="MyLittleContentEngine.MonorailCss" Version="0.0.0-alpha.0.263" />
21-
<PackageReference Include="MyLittleContentEngine.UI" Version="0.0.0-alpha.0.263" />
19+
<PackageReference Include="MyLittleContentEngine" Version="0.0.0-alpha.0.264" />
20+
<PackageReference Include="MyLittleContentEngine.MonorailCss" Version="0.0.0-alpha.0.264" />
21+
<PackageReference Include="MyLittleContentEngine.UI" Version="0.0.0-alpha.0.264" />
2222
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
2323
<PackageReference Include="Spectre.Console" Version="0.54.1-alpha.0.7" />
2424
<PackageReference Include="Spectre.Console.Cli" Version="1.0.0-alpha.0.11" />

0 commit comments

Comments
 (0)