Arguments

Go back


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:

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:

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:

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:

  1. First parser will try to read first free argument from command line
  2. If there is no first free argument, parser will try to parse environment variable HOSTNAME.
  3. If HOSTNAME variable is either not set or contains an invalid value, parser will use hostname 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:

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):

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:

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.