This guide covers advanced features of XenoAtom.CommandLine: the parse API for testing, shell completions, response files, configuration, and performance.
CommandApp.Parse(...) runs the full parsing pipeline — option callbacks, argument binding, constraint checks — but does not invoke the command action. This is ideal for unit testing:
string? name = null;
int port = 0;
var app = new CommandApp("myexe")
{
{ "n|name=", "Your {NAME}", v => name = v },
{ "p|port=", "Server {PORT}", (int v) => port = v },
new HelpOption(),
(ctx, _) => ValueTask.FromResult(0)
};
var result = app.Parse(["--name", "Alice", "--port", "8080"]);
// result.HasErrors → false
// result.ResolvedCommandPath → "myexe"
// result.OptionValues["name"][0] → "Alice"
// result.OptionValues["port"][0] → "8080"
// name → "Alice" (option callbacks are invoked)
// port → 8080
| Property | Type | Description |
|---|---|---|
ResolvedCommand |
Command |
The command that was resolved |
ResolvedCommandPath |
string |
Full command path (e.g. "myexe commit") |
OptionValues |
IReadOnlyDictionary<string, IReadOnlyList<string?>> |
Parsed option values by name |
ArgumentValues |
IReadOnlyList<string> |
Parsed positional argument values |
RemainingArguments |
IReadOnlyList<string> |
Remaining unbound arguments |
Errors |
IReadOnlyList<CommandException> |
Parse errors (empty on success) |
HasErrors |
bool |
Whether any errors occurred |
HelpRequested |
bool |
Whether --help was passed |
VersionRequested |
bool |
Whether --version was passed |
HelpRequested/VersionRequested.Errors instead of being written to stderr.runConfig is null, Out and Error default to TextWriter.Null to suppress output.WasSet tracking), so command graphs are intended for one invocation at a time.RunAsync/Parse calls on the same command tree.var result = app.Parse(["commit", "--message", "Hello"]);
// result.ResolvedCommandPath → "myexe commit"
// result.OptionValues["message"][0] → "Hello"
XenoAtom.CommandLine can generate shell completion scripts and provide completion candidates for partially typed command lines.
Add CompletionCommands to your app to expose completion commands:
var app = new CommandApp("myexe")
{
new CompletionCommands(),
{ "n|name=", "Your {NAME}", v => {} },
new HelpOption(),
new Command("build", "Build the project") { (ctx, _) => ValueTask.FromResult(0) },
(ctx, _) => ValueTask.FromResult(0)
};
This adds two sub-commands:
completion <shell> (hidden) — generates the shell completion script__complete (hidden) — handles completion requests from the shellBoth commands are hidden from default help output, but callable directly.
Generate and source the completion script for your shell:
# Bash (current session)
eval "$(myexe completion bash)"
# Zsh (current session)
source <(myexe completion zsh)
# Fish (current session)
myexe completion fish | source
# PowerShell (current session)
myexe completion powershell | Out-String | Invoke-Expression
You can also get completion candidates programmatically:
var candidates = app.GetCompletions("myexe --na");
// → ["--name"]
var candidates = app.GetCompletionsForTokens(["myexe", "buil"], tokenIndex: 1);
// → ["build"]
Provide completion candidates for option and argument values:
app.Options["name"].ValueCompleter = static (index, prefix) =>
["Alice", "Bob", "Charlie"];
app.Arguments[0].ValueCompleter = static (index, prefix) =>
["README.md", "src/", "tests/"];
The ValueCompleter delegate receives:
index — the 0-based value index being completedprefix — the partially typed textThe shell scripts call the hidden __complete sub-command using one of two modes:
myexe __complete --command-name <NAME> --index <N> --token <T1> --token <T2> ...myexe __complete --command-name <NAME> --line <LINE> --cursor <POS>Completion is non-executing — it does not invoke user option actions. It only inspects the declared command tree.
Response files let users put arguments in a file and reference it with @:
Add ResponseFileSource to your command:
var app = new CommandApp("myexe")
{
new HelpOption(),
new ResponseFileSource(),
{ "<>", "Extra arguments" },
(ctx, arguments) =>
{
foreach (var arg in arguments)
ctx.Out.WriteLine(arg);
return ValueTask.FromResult(0);
}
};
Help output includes:
@file Read response file for more options.
Create a response file (e.g. args.txt):
--name John
--port 8080
# This is a comment
"hello world"
Pass it with @:
myexe @args.txt
| Feature | Syntax | Example |
|---|---|---|
| Whitespace separation | spaces/tabs | --name John → --name, John |
| Quoted values | "..." or '...' |
"hello world" → hello world |
| Comments | # at start of line |
# This is a comment |
| Escaping (non-Windows) | \ |
c\ d → c d |
| Literal backslash (Windows) | \ is literal |
C:\Temp\file.txt → C:\Temp\file.txt |
You can create your own argument source by extending ArgumentSource:
public class EnvironmentSource : ArgumentSource
{
public override string Description => "Read arguments from environment";
public override string[] GetNames() => ["@env"];
public override bool TryGetArguments(string value, out IEnumerable<string>? arguments)
{
if (value.StartsWith("@env:"))
{
var envValue = Environment.GetEnvironmentVariable(value[5..]);
arguments = envValue?.Split(' ') ?? [];
return true;
}
arguments = null;
return false;
}
}
CommandConfig controls application-level behavior. It is set once when creating the CommandApp:
var config = new CommandConfig
{
StrictOptionParsing = true, // default
Localizer = s => s, // identity by default
EnvironmentVariableResolver = Environment.GetEnvironmentVariable, // default
OutputFactory = runConfig => new MyOutputRenderer(),
};
var app = new CommandApp("myexe", config: config);
| Property | Default | Description |
|---|---|---|
StrictOptionParsing |
true |
Fail on unknown -/-- tokens |
Localizer |
Identity | Transform all built-in strings (for localization) |
EnvironmentVariableResolver |
Environment.GetEnvironmentVariable |
Customize env-var lookup |
OutputFactory |
null (uses DefaultCommandOutput) |
Factory for custom output rendering |
CommandRunConfig controls runtime behavior for a specific invocation:
var runConfig = new CommandRunConfig(Width: 120, OptionWidth: 32)
{
Out = Console.Out,
Error = Console.Error,
ShowLicenseOnRun = true,
};
await app.RunAsync(args, runConfig);
| Property | Default | Description |
|---|---|---|
Width |
80 |
Terminal width for formatting |
OptionWidth |
29 |
Column width for option names in help |
Out |
Console.Out |
Standard output writer |
Error |
Console.Error |
Standard error writer |
ShowLicenseOnRun |
true |
Whether to print the license header |
Use CommandConfig.Localizer to translate all built-in strings:
var app = new CommandApp("myexe", config: new CommandConfig
{
Localizer = text => MyLocalizationService.Translate(text),
});
The localizer is applied before strings are written to Out/Error.
Override environment variable lookup for testing or custom scenarios:
var envVars = new Dictionary<string, string> { ["MY_PORT"] = "8080" };
var app = new CommandApp("myexe", config: new CommandConfig
{
EnvironmentVariableResolver = name => envVars.GetValueOrDefault(name),
});
EnumWrapper<T> provides AOT-friendly enum parsing for options:
var colors = new List<Color>();
var app = new CommandApp("myexe")
{
{ "c|color=", "Console {COLOR} (" + EnumWrapper<Color>.Names + ")",
(EnumWrapper<Color> v) => colors.Add(v) },
(ctx, _) => ValueTask.FromResult(0)
};
enum Color { Red, Green, Blue }
EnumWrapper<T>:
ISpanParsable<T> for seamless integration with the option parser.Names property returning a comma-separated list of valid values.The parser is optimized for minimal allocations:
string.Split arraysRun the included benchmarks:
dotnet run -c Release --project src/XenoAtom.CommandLine.Benchmarks
XenoAtom.CommandLine is fully compatible with NativeAOT publishing:
EnumWrapper<T> avoids runtime reflection for enum parsingPublish with NativeAOT:
dotnet publish -c Release -r win-x64 /p:PublishAot=true
The following diagram shows the main types and their relationships:

The design is intentionally simple. The main types are:
CommandApp — entry point, inherits from CommandCommand — executable command with options, arguments, and sub-commandsCommandGroup — conditional group of nodesOption — option with prototype, description, and callbackCommandArgument — positional argument with cardinalityHelpOption / VersionOption — built-in optionsResponseFileSource — @file argument expansionCompletionCommands — shell completion supportCommandUsage — usage text for helpAll these types inherit from CommandNode, the base class for all command tree nodes. Container types (Command, CommandApp, CommandGroup) inherit from CommandContainer and support collection initializers.