Create Options Class (aka ComplexTypes)
Bug Alert
As of
System.CommandLine
version2.0.0-beta1.21216.1
trying to provide adecimal
in a ComplexType that is optional throws an exception. For more information and a sample see project that demonstrates the exception.
When using ComplexTypes
When you have a lot of command line arguments it can be a bit messy trying to pass them all to a method. When a method accepts more than a few arguments it becomes difficult to read the code. It would be easier if you could just pass an object instead. A single class that has properties for each of the possible options and can be passed instead.
This is supported by System.Commandline
with the use of Complex Types.
Building from the existing options we can create a RootOptions class that represents each of the current options with a property. The RootOptions is then added as a parameter to the command handler.
The command handler will now have access to the RootOptions object and related properties.
There are two ways to allow System.CommandLine to intialize the RootOptions. You can just use auto properties with a getter and setter. In this type of implementation there is no need to include a constructor.
The other method is to provide a constructor with a parameter for each option to be set. This can be useful if you only want to set specific properties.
You can now reference the relevant options by accessing the property in the RootOptions class.
public class RootOptions
{
public RootOptions(string name, int optInt, /*decimal optDecimal, */bool optBool, string[] optString)
{
Name = name;
OptInt = optInt;
OptBool = optBool;
// OptDecimal = optDecimal; // Avoid a bug https://github.com/dotnet/command-line-api/issues/1284
OptString = optString;
}
public void SetOptDecimal(decimal optDecimal)
{
OptDecimal = optDecimal;
}
public string Name { get; }
public int OptInt { get; }
public decimal OptDecimal { get; private set; }
public bool OptBool { get; }
public string[] OptString { get; }
}
Subcommands
Bug Alert
As of
System.CommandLine
version2.0.0-beta1.21216.1
trying to provide a option that requires a list of integers via the ArgumentArity.OneOrMore throws an exception. You must declare it as an option of int[] and not int.
Sometimes you need to provide multiple functionality with an application and you may need to use different options for each type of functionality. And example of this would be the dotnet
application. With dotnet
you provide a subcommand or verb that tells dotnet what you want to accomplish.
Some common examples of verbs used with dotnet are add
and new
. Each of these verbs has a set of options that are relevant to that verb.
System.CommandLine
supports the use of subcommands. Let's add two new subcommands. add
and subtract
to demonstrate how to use these.
public static RootCommand AddOptions(string[] args)
{
...
var addCommand = new Command("add");
var subtractCommand = new Command("subtract");
rootCommand.Add(addCommand);
rootCommand.Add(subtractCommand);
return rootCommand;
}
If we runt dotnet run -- --help
we can now see that these two subcommands are now available.
$ dotnet run -- --help
SimpleCmdLine
Console app to demonstrate System.CommandLine
Usage:
SimpleCmdLine [options] [command]
Options:
-n, --name <name> (REQUIRED) Name of person to greet
-i, --opt-int <opt-int> An integer option [default: 47]
--opt-decimal <opt-decimal> A decimal option
--opt-bool A boolean option
--opt-string <opt-string> A string option
--version Show version information
-?, -h, --help Show help and usage information
Commands:
add
subtract
We can also get help specific to the commands by using the --help
option.
$ dotnet run -- add --help
add
Usage:
SimpleCmdLine [options] add
Options:
-?, -h, --help Show help and usage information
These commands currently don't do much. We want the add
command to add a series of numbers. We need to configure the command to accept some numbers.
We can pass values to this command in a couple of ways. We can use the now familiar option value. Another option is to use an argument.
Arguments can be added to a command by using the AddArgument()
method. This method does not contain an Arity. If you need an undetermined or variable number of arguments then this will not be effective. To indicate that we want to have 2 arguments for the add
command we need to add both arguments to the command.
addCommand.AddArgument(new Argument<int>("Operand1"));
addCommand.AddArgument(new Argument<int>("Operand2"));
This adds two arguments. The first argument is called Operand1
and the second argument is called Operand2
. Both operands are integers and are the two values that will be added together.
Subcommands can also have options just like those added to the root command. Adding an optional argument for operands
with an airy of One or more will allow us to pass any number of operands to the add command.
addCommand.Add(new Option<int[]>("--operands", description: "A list of numbers to sum.", arity: ArgumentArity.OneOrMore));
addCommand.Handler = CommandHandler.Create((ParseResult parseResult, IConsole console, int operand1, int operand2, IList<int> operands) =>
{
addHandler(parseResult, console, operand1, operand2, operands);
});
The command handler for the add command takes the arguments and options provided and adds them together. If arguments are provided then those are summed. If the operands
option and values are provided then those are summed as well as a unique sum.
public static void addHandler(ParseResult parseResult, IConsole console, int operand1, int operand2, IList<int> operands)
{
console.Out.WriteLine("Processing add");
console.Out.WriteLine((operands.Sum()).ToString());
console.Out.WriteLine((operand1+operand2).ToString());
}
When using arguments and options for a command the order that they are provided on the command line can be important. It all depends on the airy of the options.
The add
command is expecting two arguments and exactly two arguments. If the option is provided then it is expecting at least one value. The option can accept more than one argument though. Changing the order that tokens are provided on the command line can affect the way that they are parsed and the results of how the program uses them.
$ dotnet run --project SimpleCmdLine -- add 1 2 --operands 5 6
Processing add
11 # <== Sum of operands
3 # <== Sum of arguments
If we provide the option flag before the arguments then we get a different result.
$ dotnet run --project SimpleCmdLine -- add --operands 5 6 1 2
Required argument missing for command: add
Required argument missing for command: add
Required argument missing for command: add
add
Usage:
SimpleCmdLine [options] add <Operand1> <Operand2>
Arguments:
<Operand1>
<Operand2>
Options:
--operands <operands> A list of numbers to sum.
-?, -h, --help Show help and usage information
The reason we get an error is because the arguments are required. With an airy of OneOrMore for the --operands
option all of the values provided after the --operands
option are provided to the option instead of being assigned to the arguments.
If we were to change the airy for the --operands
option to new ArgumentAirty(2, 2)
then we would get the expected result.
$ dotnet run --project SimpleCmdLine -- add --operands 5 6 1 2
Processing add
11 # <== The first two numbers after --operands
3 # <== Assigned as the arguments to add
Implementing the subtract command is relatively simple. If no --operands
flag is provided we will get the help message because the option is required.
If we provide only a single number then the handler will presume that the starting sum is zero. This will result subtracting the first operand from zero.
If more than one operand is provided then the first operand is considered the starting sum and the remaining items are subtracted from the first number.
public static void subtractHandler(ParseResult parseResult, IConsole console, IList<int> operands)
{
console.Out.WriteLine("Processing subtraction");
if(operands.Count == 0)
return;
if(operands.Count == 1)
{
operands = operands.Prepend(0).ToList();
}
var difference = operands.Aggregate<int>((total, next) => total - next);
console.Out.WriteLine(difference.ToString());
}
Conclusion
We have covered a lot of topics in this post. Hopefully this helps to better understand how you can use System.CommandLine
in your own projects.
Footnotes
1: Computers were "slow" and had limited memory. Also, programmers are notoriously "lazy". They want to type as little as possible to get to where they need to go.↩
2: Not to be confused with the empty double dash --
which is used to indicate the end of command line processing.↩
You must be logged in to see the comments. Log in now!