Overview
- System.CommandLine is
pre-release . - Documentation: https://learn.microsoft.com/en-us/dotnet/standard/commandline/
Installation:
dotnet add package system.commandline --pre-release
Model Binding
The process of parsing arguments and providing them to command handler code.
Of note, any type with a constructor that accepts a single string parameter can be bound (like FileInfo
).
Root Command
A root command, represented by RootCommand
, represents the app executable itself:
RootCommand rootCommand = new("app description");
Adding Commands to RootCommand
rootCommand.Add(someCommand);
Commands
Commands are actions performed by the program. They should be verbs.
Commands are represented by the Command
class:
Command actionCommand = new Command("actionCommand", "Perform an action.");
Subcommands
Subcommands also use the Command
class and can be added to a Command or RootCommand.
When commands have subcommands, the commands above them in the hierarchy do nothing:
dotnet add # does nothing
dotnet add package # does something
Example:
rootCommand.Add(actionCommand);
Command subCommand = new Command("subcmd");
actionCommand.Add(subCommand);
> myapp cmd subcmd
Equivalent using collection initializer syntax:
RootCommand rootCommand = new RootCommand()
{
new Command("actionCommand")
{
new Command("subcmd")
}
};
Options
Options are named parameters that can be passed to commands.
They are represented by the Option
class:
Option<int> delayOption = new Option<int>(
name: "--delay",
description: "The delay",
getDefaultValue: () => 3);
Options That Accept Multiple Strings
Use AllowMultipleArgumentsPerToken
:
Option<string[]> myOption = new Option<string[]>( … ) { AllowMultipleArgumentsPerToken = true };
Other Parameters
isDefault
- If true, if no input is provided for the option, the ParseArgumentdelegate is called.
Properties
IsHidden
- This option will not be displayed in help or tab-completed.IsRequired
- Boolean if the option is required.AllowMultipleArgumentsPerToken
- Boolean if multiple arguments should be allowed for the option.- This is required when an option accepts multiple strings as an argument.
Adding Options to Commands
rootCommand.Add(delayOption)
//or
rootCommand.AddOption(delayOption)
Global Options
Global options are available to the command it’s assigned and all its subcommands.
Style
The default style is POSIX, using --
prefixes.
Windows-based prefixes of /
are also supported.
Delimiters
A space or :
or =
can delimit an option from an argument:
myapp --option 123
myapp --option:123
myapp --option=123
Building Commands with Options
Command actionCommand = new Command( “actionCommand”, “Performs an action.”) { delayOption };
Aliases
Both Commands and Options support aliases:
var command = new Command("serialize");
command.AddAlias("serialise");
var option = new Option("--verbose");
option.AddAlias("-v");
Arguments
Arguments are values passed to an option or a command.
Arguments without default values are treated as required arguments.
Built-in validation errors when arguments don’t match default values, expected types, or arity.
// This argument will be parsed into an int:
Argument<int> delayArgument = new(
name: "delay",
description: "The delay in seconds.",
getDefaultValue: () => 3); // Arguments with no default value are made required.
Arguments are added to Commands, just like Options:
rootCommand.AddArgument(delayArgument);
Listing Valid Argument Values
To specify of a list of valid values for an Argument or an Option, either:
- Use an
Enum
as theOption
type, or; - use
FromAmong
:
var languageOption = new Option<string>(
"--language",
"An option that that must be one of the values of a static list.")
.FromAmong("csharp", "fsharp", "vb", "pwsh", "sql");
Arity
Arity is expressed with a minimum and maximum value. The ArgumentArity struct has these values:
Zero
- No values allowed.ZeroOrOne
- May have one, may have zero.ExactlyOne
- Must have one.ZeroOrMore
- May have multiple, may have zero.OneOrMore
- May have multiple, must have one.
Arity is inferred from the type:
- An int option has arity of
ExactlyOne
- A
List<int>
option has arity ofOneOrMore
Custom Validation of Options and Arguments
Use .AddValidator
:
Option<int> delayOption = new("delay");
delayOption.AddValidator(result =>
{
if (result.GetValueForOption(delayOption) < 1)
{
result.ErrorMessage = "error";
}
};
Custom Validation and Parsing
Use the ParseArgument<T>
delegate:
var delayOption = new Option<int>(
name: "--delay",
description: "An option whose argument is parsed as an int.",
isDefault: true, // This must be set to true for parseArgument to be called when no value is sent.
parseArgument: result =>
{
if (!result.Tokens.Any())
return 42;
if (int.TryParse(result.Tokens.Single().Value, out var delay))
if (delay < 1)
result.ErrorMessage = "Must be greater than 0";
return delay;
else
{
result.ErrorMessage = "Not an int.";
return 0; // Ignored.
}
});
SetHandler
The SetHandler
is what binds options to command handlers.
A root command has a SetHandler
if there are no commands.
If there are commands, each Command
has a SetHandler
:
actionCommand.SetHandler(async (int delayOptionValue)
{
await PerformAction(delayOptionValue); // The command calls this method.
return Task.FromResult(int) // Where int is an exit code. If the handler exits normally,
// it will exit with 0, not this code.
}, delayOption);
All options and arguments must be declared in the same order in the lambda and in the parameters that follow the lambda.
Starting Execution
After defining SetHandler
:
rootCommand.Invoke(args);
// or
return rootCommand.InvokeAsync(args).Result;