Options are the core building block of any command-line interface. XenoAtom.CommandLine provides a rich option syntax inherited from Mono.Options with significant extensions.
Options are added to a CommandApp or Command using collection initializers or the Add method:
var app = new CommandApp("myexe")
{
{ "o|output=", "The target output {FILE}", v => target = v },
};
// Equivalent:
app.Add("o|output=", "The target output {FILE}", v => target = v);
The prototype string defines the option's names and value behavior. Each |-delimited segment is an alias:
prototype = alias ( '|' alias )*
alias = name [ '=' | ':' ] [ separator ]
= — the option requires a value (e.g. "n|name="): — the option has an optional value (e.g. "o:")"v|verbose")The suffix only needs to appear on one alias, but if it appears on multiple aliases it must be consistent.
| Prototype | Type | Command-line examples | Notes |
|---|---|---|---|
"v\|verbose" |
Flag | -v, --verbose, /v |
+/- suffix to explicitly enable/disable |
"n\|name=" |
Required value | --name John, --name=John, -nJohn |
Next token consumed as value |
"o:" |
Optional value | -o, -oVALUE, -o:VALUE |
Must be inline; -o VALUE does not attach VALUE |
"D:" (2 values) |
Key/value pair | -DKEY, -DKEY=VALUE |
Second value optional with : |
"I\|macro=" (2 values) |
Key/value pair | -IKEY=VALUE, --macro X=Y |
Second value required with = |
"P={->}" (2 values) |
Custom separator | -PKEY->VALUE |
Separator declared between {...} |
All options can be invoked with any of the three prefixes:
| Prefix | Example |
|---|---|
- (POSIX short) |
-v, -n John |
-- (GNU long) |
--verbose, --name=John |
/ (Windows) |
/v, /name:John |
Note:
/-prefixed tokens that look like absolute paths (e.g./mnt/home) are treated as positional arguments, not options.
A flag option has no = or : in its prototype. It receives a non-null value when present:
bool verbose = false;
var app = new CommandApp("myexe")
{
{ "v|verbose", "Enable verbose output", v => verbose = v is not null },
};
You can explicitly enable or disable a flag with + and -:
await app.RunAsync(["-v"]); // verbose == true
await app.RunAsync(["-v+"]); // verbose == true
await app.RunAsync(["-v-"]); // verbose == false
Append = to the prototype to require a value:
string? name = null;
var app = new CommandApp("myexe")
{
{ "n|name=", "Your {NAME}", v => name = v },
};
The value can be provided in several ways:
--name John (next argument)--name=John (inline with =)-nJohn (inline with short option)-n:John (inline with :)/name:John (Windows style)Append : to the prototype for an optional value:
string? output = null;
var app = new CommandApp("myexe")
{
{ "o:", "Output file (optional)", v => output = v },
};
When the value is optional, it must be provided inline. -o VALUE does not attach VALUE to -o — instead, VALUE becomes a positional argument and output is set to null (or empty string).
Valid: -ofile.txt, -o:file.txt, -o=file.txt
When an option callback has two parameters, values are automatically split into a key and a value:
var app = new CommandApp("myexe")
{
{ "D:", "Define {0:NAME} and optional {1:VALUE}", (key, value) =>
{
if (key is null) throw new CommandOptionException("Missing macro name", "D");
Console.WriteLine($"Macro: {key} = {value}");
}},
{ "I|macro=", "Define {0:NAME} and required {1:VALUE}", (key, value) =>
{
Console.WriteLine($"Macro: {key} = {value}");
}},
};
myexe -DA=B -DHello -IG=F --macro X=Y
Output:
Macro: A = B
Macro: Hello =
Macro: G = F
Macro: X = Y
The default key/value separators are = and :. You can use a custom separator:
{ "P={->}", "Define {0:NAME} and {1:VALUE}", (k, v) => Console.WriteLine($"{k} -> {v}") },
Now -PKey->Value splits into Key and Value.
Note: Key/value overloads intentionally do not support
envVarfallback orvalidatedelegates. If you need those behaviors, use a single-value option and parse the pair in your callback.
Any type that implements ISpanParsable<TSelf> can be used directly:
int port = 0;
var app = new CommandApp("myexe")
{
{ "p|port=", "Server {PORT}", (int v) => port = v },
};
This works with all built-in numeric types, DateTime, DateTimeOffset, Guid, TimeSpan, IPAddress, and any custom type implementing ISpanParsable<T>.
Instead of a callback, you can bind an option directly to a collection:
var names = new List<string>();
var ports = new List<int>();
var app = new CommandApp("myexe")
{
{ "n|name=", "A {NAME}", names },
{ "p|port=", "A {PORT}", ports },
};
Each time the option appears on the command line, the value is added to the list:
myexe --name Alice --name Bob --port 8080 --port 9090
Use EnumWrapper<T> for AOT-friendly enum parsing:
var colors = new List<Color>();
var app = new CommandApp("myexe")
{
{ "c|color=", "The {COLOR} (" + EnumWrapper<Color>.Names + ")", (EnumWrapper<Color> v) => colors.Add(v) },
};
EnumWrapper<T> implements ISpanParsable<T> and provides case-insensitive parsing. The .Names property returns a comma-separated list of valid values for use in descriptions.
Single-character options can be bundled with the - prefix (POSIX/tar style):
bool a = false, b = false, c = false;
var app = new CommandApp("myexe")
{
{ "a", "Flag A", v => a = v is not null },
{ "b", "Flag B", v => b = v is not null },
{ "c", "Flag C", v => c = v is not null },
};
await app.RunAsync(["-abc"]); // a == true, b == true, c == true
At most one option in the bundle can accept a value, and its value starts from the next character:
string? file = null;
var app = new CommandApp("myexe")
{
{ "x", "Extract", v => {} },
{ "f=", "Input {FILE}", v => file = v },
};
await app.RunAsync(["-xfarchive.tar"]); // file == "archive.tar"
Use {NAME} in the description to label the value in help output:
{ "n|name=", "Your {NAME}", v => name = v }
// Help: -n, --name=NAME Your NAME
For key/value options, use indexed placeholders:
{ "D:", "Define {0:KEY} and optional {1:VALUE}", (k, v) => {} }
// Help: -D[=KEY:VALUE] Define KEY and optional VALUE
--The -- token stops option parsing. Everything after -- is treated as a positional argument:
myexe --name John -- --not-an-option -x /mnt/home
This is useful for passing values that start with - without them being interpreted as options.
Options can be hidden from help output:
{ "secret=", "Secret option", v => {}, hidden: true }
Hidden options are still functional — they just don't appear in --help.
Set metadata (hidden, envVar, envVarDelimiter) while declaring options; these settings are part of construction-time configuration.
Options can fall back to environment variables when not provided on the command line:
int port = 0;
var includes = new List<string>();
var app = new CommandApp("myexe")
{
{ "p|port=", "Server {PORT}", (int v) => port = v, envVar: "MY_PORT" },
{ "i|include=", "Include {PATH}", includes, envVar: "MY_INCLUDES", envVarDelimiter: Path.PathSeparator },
(ctx, _) => ValueTask.FromResult(0)
};
The environment variable name appears in help output:
-p, --port=PORT Server PORT [env: MY_PORT]
Rules:
true/false, 1/0, yes/no (case-insensitive).envVarDelimiter parameter allows splitting a single environment variable value into multiple values (e.g. PATH-style lists).--help is requested.