Arguments
- Defining arguments
- Configuration
- Value parsing
- Value validation
- Consuming parsed values
- Default values
- Nullable values
- Repeatable argument
Argument is a free command line parameter. Arguments are not named and are consumed by their positions.
However arguments have names but they are used only to generate help and do not affect parsing process in any way.
Defining arguments
Use a Argument<T>()
method of parser or command to add an argument:
// Create an argument that would consume first free argument
var argument = parser.Argument<string>("name", 0);
// Create an argument that would consume second free argument
var argument = parser.Argument("name", 1);
There are two versions of Argument()
method:
- a generic
Argument<T>()
for arbitrarily-typed arguments - a shorthand
Argument()
for string-typed arguments
Configuration
Help text
Use either helpText
parameter of Argument<T>()
method or a HelpText()
method of CliArgument<T>
to specify description for an autogenerated help:
var argument = parser.Argument<string>("argument", 0, helpText: "Help text");
// or
argument.HelpText("Help text");
Hidden argument
Use either hidden
parameter of Argument<T>()
method or a Hidden()
method of CliArgument<T>
to hide an argument from an autogenerated help:
var argument = parser.Argument<string>("argument", 0, hidden: true);
// or
argument.Hidden();
Value parsing
Use UseParser()
method of CliArgument<T>
to override argument value parsing:
var argument = parser.Argument<OptValue>("argument", 0);
argument.UseParser(new OptValueParser());
Read more about value parsing here
NOTE: you must specify a custom parser unless you use any of built-in types as an argument’s value.
Value validation
Not every value of an argument might be valid. Command line parser supports value validation via callback functions:
var argument = parser.Argument<OptValue>("argument", 0);
argument.Validate(ValidationFunction);
// This function will be called to validate a result of parsing
// It will be called even if argument is not set
// Function should return null if value is valid
// and an error message otherwise
string ValidationFunction(OptValue value, bool isSet)
{
if (!isSet)
{
return "value is not set";
}
if(!value.IsGood)
{
return "value is bad";
}
return null;
}
You can add more than one validation function - they will be called in a chain:
var argument = parser.Argument<OptValue>("argument", 0);
argument.Validate(Validate_IsSet);
argument.Validate(Validate_IsGood);
string Validate_IsSet(OptValue value, bool isSet)
{
if (!isSet)
{
return "value is not set";
}
return null;
}
string Validate_IsGood(OptValue value, bool isSet)
{
// No need to check "isSet" parameter here
// If value is not set - Validate_IsGood() will not be called,
// since Validate_IsSet() would return an error
if(!value.IsGood)
{
return "value is bad";
}
return null;
}
There is a helper method for required parameter validation - Required()
. It checks if value is set and fails if an argument is missing.
Also it affects autogenerated help, marking an argument as required one:
var argument = parser.Argument<OptValue>("argument", 0);
argument.Required();
argument.Validate(Validate_IsGood);
string Validate_IsGood(OptValue value, bool isSet)
{
// No need to check "isSet" parameter here since Required() will handle it
if(!value.IsGood)
{
return "value is bad";
}
return null;
}
Consuming parsed values
CliArgument<T>
class contains the following properties that might be used to access parsed values:
bool IsSet { get; }
- this property will be set totrue
if an argument has any value (including a default one)T Value { get; }
- this property provides access to parsed value
CliArgument<T>
is implicitly convertible to T
, so there is no need to access Value
property manually in most cases:
var argument = parser.Argument<string>("argument", 0);
// These two lines of code are identical
RunCommand(argument.Value);
RunCommand(argument);
// But these are not - x and y variables will have different types
var x = argument.Value;
var y = argument;
void RunCommand(string arg)
{
Console.WriteLine($"Argument is \"{arg}\"");
}
CliArgument<T>
is also implicitly convertible to bool
, simplifying access to IsSet
property:
var argument = parser.Argument<string>("argument", 0);
// These two lines of code are identical
RunCommand(argument.IsSet);
RunCommand(argument);
// But these are not - x and y variables will have different types
var x = argument.IsSet;
var y = argument;
void RunCommand(bool hasValue)
{
Console.WriteLine($"Argument is \"{(hasValue ? "set" : "not set")}\"");
}
Default values
There’s a way to provide default values for an argument. You may use:
- a plain value
- a value from an environment variable
- a value provided by a custom function
Constant default value
Use the DefaultValue()
method to set an argument’s default value:
var argument = parser.Argument<string>("hostname", 0);
argument.DefaultValue("localhost");
// This argument will have a value "localhost" if it's not set
Default value from an environment variable
Use the FromEnvironmentVariable()
method to bind argument’s default value to an environment variable:
var argument = parser.Argument<string>("hostname", 0);
argument.FromEnvironmentVariable("HOSTNAME");
// This argument will have a value getenv("HOSTNAME") if it's not set
Default value provided by a custom function
You may define a custom value provider function to set argument’s default value. For example, you may try to read default value from a config file:
var argument = parser.Argument<string>("hostname", 0);
argument.DefaultValue(TryReadHostnameFromConfigFile);
// This argument will have a value from a config file if it's not set
// If config file doesn't exists argument will have no value
bool TryReadHostnameFromConfigFile(out string value)
{
if(configFile.Exists)
{
value = configFile.GetString("hostname");
return true;
}
return false;
}
Using more than one default value
You may set more that one default value from various sources. Parser will try to take each of values in the same order as they were set.
var argument = parser.Argument<string>("hostname", 0);
argument.FromEnvironmentVariable("HOSTNAME");
argument.DefaultValue(TryReadHostnameFromConfigFile);
argument.DefaultValue("localhost");
bool TryReadHostnameFromConfigFile(out string value)
{
// ...
}
This example above will do the following:
- First parser will try to read first free argument from command line
- If there is no first free argument, parser will try to parse environment variable
HOSTNAME
. - If
HOSTNAME
variable is either not set or contains an invalid value, parser will usehostname
as an arguments’s value.
Default value and validation
Default values are applied before any validation takes place. So you should be careful when specifying a default value:
var argument = argument.Argument<string>("argument", 0);
argument.DefaultValue("not-empty");
// This validator could be omitted without any damage,
// it would never fire
argument.Required();
// ...
var argument = argument.Argument<string>("argument", 0);
// This default value could be omitted
// since it would never pass validation
argument.DefaultValue("1");
argument.Validate(Validate_IsGood);
string ValidationFunction(string value, bool isSet)
{
if(value == null || value.Length < 2)
{
return "value is too short or empty";
}
return null;
}
Nullable values
Non-mandatory arguments of struct types provide two helpers to handle them:
-
ToNullable()
extension method allows to get a value from an argument or(T?)null
if no value is set:var argument = parser.Argument<int>("argument", 0); // This line of code isn't valid since CliArgument<int> is implicilty convertible to int, // not to Nullable<int> int? value = argument; // This line of code is valid due to use of ToNullable() method // ToNullable() method returns a properly initialized Nullable<T> int? value = argument.ToNullable();
-
AsNullable()
extension method wraps aCliArgument<T>
with aNullableCliArgument<T>
.NullableCliArgument<T>
is an API-compatible version ofCliArgument<T>
, which is implicitly convertible toT?
:var argument = parser.Argument<int>("argument", 0).AsNullable(); // This line of code is valid since NullableCliArgument<int> is implicilty convertible to Nullable<int> int? value = argument; // This line of code is valid due to use of ToNullable() method // ToNullable() method returns a properly initialized Nullable<T> int? value = argument.ToNullable();
Repeatable argument
There are cases when you might need to have more than one value for an argument. Take a look, for an example, at docker exec
command:
docker exec [OPTIONS] container command [args...]
This command has the following arguments (options and switches are omitted for briefness):
- First argument - container name
- Second argument - command name
- Third argument - command arguments. This argument is called repeatable argument since it might be specified as many times as needed.
And here is a (preudo) code that implements the same arguments using CLI parser:
// First argument - container name
var containerNameArgument = parser.Argument<string>("contianer", 0);
// Second argument - command name
var commandNameArgument = parser.Argument<string>("command", 1);
// Third argument - command arguments
// This is a repeatable argument
var commandArgsArgument = parser.RepeatableArgument<string>("arg", 2);
CliRepeatableArgument<T>
has almost the same API as ordinary CliArgument<T>
.
Differencies are:
- It has no
Value
property but there is aValues
property instead. - It’s implicitly convertible to
T[]
, not toT
.
Warning! You should not try to use more than one repeatable argument per command - it simply would not work. First repeatable argument will consume all free arguments and the second one will always have no value.
Unfortunately, a this point parser doesn’t validate usage of repeatable arguments.