|
| 1 | +# System.CommandLine |
| 2 | + |
| 3 | +System.CommandLine provides robust support for command-line parsing, invocation, and shell completions in .NET applications. It supports both POSIX and Windows conventions, making it easy to build professional command-line interfaces. |
| 4 | + |
| 5 | +## Getting Started |
| 6 | + |
| 7 | +### Basic Command |
| 8 | + |
| 9 | +Here's a simple "Hello World" command-line application: |
| 10 | + |
| 11 | +```csharp |
| 12 | +using System.CommandLine; |
| 13 | + |
| 14 | +RootCommand rootCommand = new("Sample command-line app"); |
| 15 | + |
| 16 | +Option<string> nameOption = new("--name", "-n") |
| 17 | +{ |
| 18 | + Description = "Your name" |
| 19 | +}; |
| 20 | + |
| 21 | +rootCommand.Options.Add(nameOption); |
| 22 | + |
| 23 | +rootCommand.SetAction(parseResult => |
| 24 | +{ |
| 25 | + string name = parseResult.GetValue(nameOption); |
| 26 | + Console.WriteLine($"Hello, {name ?? "World"}!"); |
| 27 | +}); |
| 28 | + |
| 29 | +return rootCommand.Parse(args).Invoke(); |
| 30 | +``` |
| 31 | + |
| 32 | +In this example, we create a `RootCommand`, add an option for the user's name, and define an action that prints a greeting. The `RootCommand` is a special kind of `Command` that comes with a few predefined behaviors: |
| 33 | + |
| 34 | +* It discovers its name automatically from the currently-running application |
| 35 | +* It automatically provides `--help` and `--version` options and default behaviors for them |
| 36 | +* It provides a default integration with `dotnet-suggest` for dynamic [shell completions](#shell-completions) |
| 37 | + |
| 38 | +You can always override or customize these behaviors as needed on a `RootCommand`, or create your own top-level `Command` instead. |
| 39 | + |
| 40 | +### Commands with Arguments |
| 41 | + |
| 42 | +Arguments are values passed directly to commands without option names: |
| 43 | + |
| 44 | +```csharp |
| 45 | +var fileArgument = new Argument<FileInfo>("file") |
| 46 | +{ |
| 47 | + Description = "The file to process" |
| 48 | +}; |
| 49 | + |
| 50 | +var processCommand = new Command("process", "Process a file"); |
| 51 | +processCommand.Arguments.Add(fileArgument); |
| 52 | + |
| 53 | +processCommand.SetAction(parseResult => |
| 54 | +{ |
| 55 | + FileInfo file = parseResult.GetValue(fileArgument); |
| 56 | + Console.WriteLine($"Processing {file.FullName}"); |
| 57 | +}); |
| 58 | + |
| 59 | +var rootCommand = new RootCommand(); |
| 60 | + |
| 61 | +rootCommand.Subcommands.Add(processCommand); |
| 62 | +``` |
| 63 | + |
| 64 | +### Options with Default Values |
| 65 | + |
| 66 | +Options can have default values and validation: |
| 67 | + |
| 68 | +```csharp |
| 69 | +var rootCommand = new RootCommand(); |
| 70 | + |
| 71 | +var delayOption = new Option<int>("--delay", "-d") |
| 72 | +{ |
| 73 | + Description = "Delay in milliseconds", |
| 74 | + DefaultValueFactory = _ => 1000 |
| 75 | +}; |
| 76 | + |
| 77 | +delayOption.Validators.Add(result => |
| 78 | +{ |
| 79 | + if (result.GetValueOrDefault<int>() < 0) |
| 80 | + { |
| 81 | + result.AddError("Delay must be non-negative"); |
| 82 | + } |
| 83 | +}); |
| 84 | + |
| 85 | +rootCommand.Options.Add(delayOption); |
| 86 | +``` |
| 87 | + |
| 88 | +### Subcommands |
| 89 | + |
| 90 | +Build complex CLI applications with nested commands: |
| 91 | + |
| 92 | +```csharp |
| 93 | +var rootCommand = new RootCommand("My application"); |
| 94 | + |
| 95 | +var configCommand = new Command("config", "Configure the application"); |
| 96 | +var configSetCommand = new Command("set", "Set a configuration value"); |
| 97 | +var configGetCommand = new Command("get", "Get a configuration value"); |
| 98 | + |
| 99 | +var keyOption = new Option<string>("--key") |
| 100 | +{ |
| 101 | + Description = "Configuration key" |
| 102 | +}; |
| 103 | +var valueOption = new Option<string>("--value") |
| 104 | +{ |
| 105 | + Description = "Configuration value" |
| 106 | +}; |
| 107 | + |
| 108 | +configSetCommand.Options.Add(keyOption); |
| 109 | +configSetCommand.Options.Add(valueOption); |
| 110 | +configGetCommand.Options.Add(keyOption); |
| 111 | + |
| 112 | +configCommand.Subcommands.Add(configSetCommand); |
| 113 | +configCommand.Subcommands.Add(configGetCommand); |
| 114 | +rootCommand.Subcommands.Add(configCommand); |
| 115 | + |
| 116 | +// Usage: myapp config set --key "apiUrl" --value "https://api.example.com" |
| 117 | +// Usage: myapp config get --key "apiUrl" |
| 118 | +``` |
| 119 | + |
| 120 | +### Using Options in Command Actions |
| 121 | + |
| 122 | +Access option values through the ParseResult: |
| 123 | + |
| 124 | +```csharp |
| 125 | +var connectionOption = new Option<string>("--connection") |
| 126 | +{ |
| 127 | + Description = "Database connection string" |
| 128 | +}; |
| 129 | +var timeoutOption = new Option<int>("--timeout") |
| 130 | +{ |
| 131 | + Description = "Timeout in seconds", |
| 132 | + DefaultValueFactory = _ => 30 |
| 133 | +}; |
| 134 | +var verboseOption = new Option<bool>("--verbose") |
| 135 | +{ |
| 136 | + Description = "Enable verbose output" |
| 137 | +}; |
| 138 | + |
| 139 | +rootCommand.Options.Add(connectionOption); |
| 140 | +rootCommand.Options.Add(timeoutOption); |
| 141 | +rootCommand.Options.Add(verboseOption); |
| 142 | + |
| 143 | +rootCommand.SetAction(parseResult => |
| 144 | +{ |
| 145 | + var connection = parseResult.GetValue(connectionOption); |
| 146 | + var timeout = parseResult.GetValue(timeoutOption); |
| 147 | + var verbose = parseResult.GetValue(verboseOption); |
| 148 | + |
| 149 | + Console.WriteLine($"Connection: {connection}"); |
| 150 | + Console.WriteLine($"Timeout: {timeout}"); |
| 151 | + Console.WriteLine($"Verbose: {verbose}"); |
| 152 | +}); |
| 153 | +``` |
| 154 | + |
| 155 | +### Shell Completions |
| 156 | + |
| 157 | +Enable tab completion for your CLI: |
| 158 | + |
| 159 | +```csharp |
| 160 | +// Completions are automatically available for all commands, options, and arguments |
| 161 | +var rootCommand = new RootCommand("My app with completions"); |
| 162 | + |
| 163 | +var fileOption = new Option<FileInfo>("--file") |
| 164 | +{ |
| 165 | + Description = "The file to process" |
| 166 | +}; |
| 167 | + |
| 168 | +// Add custom completions using CompletionSources |
| 169 | +fileOption.CompletionSources.Add(ctx => |
| 170 | + // hard-coded list of files |
| 171 | + ["file1.txt", "file2.txt", "file3.txt" ] |
| 172 | +); |
| 173 | + |
| 174 | +// Or add simple string suggestions |
| 175 | +fileOption.CompletionSources.Add("option1", "option2", "option3"); |
| 176 | + |
| 177 | +rootCommand.Options.Add(fileOption); |
| 178 | +``` |
| 179 | + |
| 180 | +Users can then easily trigger your completions using `dotnet-suggest`: |
| 181 | + |
| 182 | +```shell |
| 183 | +> dotnet tool install -g dotnet-suggest |
| 184 | +> dotnet suggest script bash > ~/.bashrc |
| 185 | +``` |
| 186 | + |
| 187 | +```powershell |
| 188 | +> dotnet tool install -g dotnet-suggest |
| 189 | +> dotnet suggest script powershell >> $PROFILE |
| 190 | +``` |
| 191 | + |
| 192 | +Once `dotnet-suggest` is installed, you can register your app with it for completions support: |
| 193 | + |
| 194 | +```shell |
| 195 | +> dotnet-suggest register --command-path /path/to/myapp |
| 196 | +``` |
| 197 | + |
| 198 | +Alternatively, you can create your own commands for completion generation and instruct users on how to set them up. |
| 199 | + |
| 200 | +### Async Command Handlers |
| 201 | + |
| 202 | +Support for asynchronous operations: |
| 203 | + |
| 204 | +```csharp |
| 205 | +var urlOption = new Option<string>("--url") |
| 206 | +{ |
| 207 | + Description = "The URL to fetch" |
| 208 | +}; |
| 209 | +rootCommand.Options.Add(urlOption); |
| 210 | + |
| 211 | +rootCommand.SetAction(async (parseResult, cancellationToken) => |
| 212 | +{ |
| 213 | + var url = parseResult.GetValue(urlOption); |
| 214 | + if (url != null) |
| 215 | + { |
| 216 | + using var client = new HttpClient(); |
| 217 | + var response = await client.GetStringAsync(url, cancellationToken); |
| 218 | + Console.WriteLine(response); |
| 219 | + } |
| 220 | +}); |
| 221 | + |
| 222 | +// Or return an exit code: |
| 223 | +rootCommand.SetAction(async (parseResult, cancellationToken) => |
| 224 | +{ |
| 225 | + // Your async logic here |
| 226 | + return await Task.FromResult(0); // Return exit code |
| 227 | +}); |
| 228 | +``` |
| 229 | + |
| 230 | +## Notable Changes Since v2.0.0-beta7 |
| 231 | + |
| 232 | +### New Features |
| 233 | +- **Improved Help System**: Enhanced `HelpAction` to allow users to provide custom `MaxWidth` for help text formatting ([#2635](https://github.com/dotnet/command-line-api/pull/2635)). Note that if you create custom Help or Version actions, you'll want to set `ClearsParseErrors` to `true` to ensure that invoking those features isn't treated like an error by the parser. |
| 234 | +- **`Task<int>` Support**: Added `SetAction` overload for `Task<int>` return types ([#2634](https://github.com/dotnet/command-line-api/issues/2634)) |
| 235 | +- **Detect Implicit Arguments**: Added the `ArgumentResult.Implicit` property for better argument handling ([#2622](https://github.com/dotnet/command-line-api/issues/2622), [#2625](https://github.com/dotnet/command-line-api/pull/2625)) |
| 236 | +- **Performance Improvements**: Reduced reflection usage throughout the library for better performance ([#2662](https://github.com/dotnet/command-line-api/pull/2662)) |
| 237 | + |
| 238 | +### Bug Fixes |
| 239 | +- Fixed issue [#2128](https://github.com/dotnet/command-line-api/issues/2128): Resolved command parsing edge cases ([#2656](https://github.com/dotnet/command-line-api/pull/2656)) |
| 240 | +- Fixed issue [#2257](https://github.com/dotnet/command-line-api/issues/2257): Corrected argument validation behavior |
| 241 | +- Fixed issue [#2589](https://github.com/dotnet/command-line-api/issues/2589): Improved error message clarity ([#2654](https://github.com/dotnet/command-line-api/pull/2654)) |
| 242 | +- Fixed issue [#2591](https://github.com/dotnet/command-line-api/issues/2591): Resolved option parsing inconsistencies ([#2644](https://github.com/dotnet/command-line-api/pull/2644)) |
| 243 | +- Fixed issue [#2622](https://github.com/dotnet/command-line-api/issues/2622): Enhanced implicit argument support ([#2625](https://github.com/dotnet/command-line-api/pull/2625)) |
| 244 | +- Fixed issue [#2628](https://github.com/dotnet/command-line-api/issues/2628): Corrected help text formatting issues |
| 245 | +- Fixed issue [#2634](https://github.com/dotnet/command-line-api/issues/2634): Added missing Task<int> action support |
| 246 | +- Fixed issue [#2640](https://github.com/dotnet/command-line-api/issues/2640): Resolved completion suggestions for nested commands ([#2646](https://github.com/dotnet/command-line-api/pull/2646)) |
| 247 | + |
| 248 | +### Breaking Changes |
| 249 | +- Default value handling for `ProcessTerminationTimeout` has been re-added ([#2672](https://github.com/dotnet/command-line-api/pull/2672)) |
| 250 | +- Some internal APIs have been refactored to reduce reflection usage ([#2662](https://github.com/dotnet/command-line-api/pull/2662)) |
| 251 | + |
| 252 | +### Other Improvements |
| 253 | +- Updated to .NET 10.0 RC1 compatibility |
| 254 | +- Improved memory usage and performance optimizations |
| 255 | +- Better handling of complex command hierarchies |
| 256 | + |
| 257 | +## Documentation |
| 258 | + |
| 259 | +For comprehensive documentation, tutorials, and API reference, visit: |
| 260 | +- **[Microsoft Learn Documentation](https://learn.microsoft.com/en-us/dotnet/standard/commandline/)** - Complete guides and API reference |
| 261 | +- **[GitHub Repository](https://github.com/dotnet/command-line-api)** - Source code, samples, and issues |
| 262 | + |
| 263 | +## Framework Support |
| 264 | + |
| 265 | +- **.NET 8.0+** - Full feature support with trimming and AOT compilation |
| 266 | +- **.NET Standard 2.0** - Compatible with .NET Framework 4.6.1+, .NET Core 2.0+ |
| 267 | + |
| 268 | +## License |
| 269 | + |
| 270 | +This package is licensed under the [MIT License](https://opensource.org/licenses/MIT). |
| 271 | + |
| 272 | +## Contributing |
| 273 | + |
| 274 | +We welcome contributions! Please see our [Contributing Guide](https://github.com/dotnet/command-line-api/blob/main/CONTRIBUTING.md) for details. |
| 275 | + |
| 276 | +## Support |
| 277 | + |
| 278 | +- **Issues**: [GitHub Issues](https://github.com/dotnet/command-line-api/issues) |
| 279 | +- **Discussions**: [GitHub Discussions](https://github.com/dotnet/command-line-api/discussions) |
0 commit comments