A simple, intuitive library for building CLI applications in D.
- Nested subcommands — Supports hierarchical command structures, allowing commands to contain other commands.
- Flexible arguments — Required (
<>
) and optional ([]
) arguments with variadic support (...
). - Rich option handling — Supports
--option=value
and--option value
forms. - Automatic help generation — Built-in help and usage text generation.
- Array collection — Repeated options are automatically collected as arrays.
- Type-safe parsing — Clean API for accessing parsed arguments and options.
Here’s a minimal example that splits a string:
import std.stdio;
import std.string;
import cmd.program;
void main(string[] argv)
{
auto args = new Program("split")
.description("Split a string")
.versionString("1.0.0")
.versionOption("--version", "Show version information")
.helpOption("-h, --help", "Show help for command")
.argument("<string>", "String to split")
.option("-s, --separator <char>", "Separator character")
.option("--first", "Return only the first element")
.parse(argv);
auto parts = args.argument("string").split(args.option("separator"));
if (args.flag("first"))
writeln(parts[0]);
else
writeln(parts);
}
$ ./split "hello,world,foo" --separator ","
["hello", "world", "foo"]
$ ./split "hello,world,foo" --separator "," --first
hello
Note: versionOption()
enables a version flag for your program (e.g., --version
), and helpOption()
enables a help flag (e.g., -h
or --help
). When specified, these flags are handled automatically: the library will print the version or help message and exit.
Use option()
to add named parameters to your commands:
ParsedArgs args = new Program("example")
// Required option - user must provide a value
.option("-t, --target <name>", "Target name")
// Optional with default value
.option("-p, --port [number]", "Port number", "8080")
// Optional without default
.option("-c, --config [file]", "Configuration file")
// Boolean flag - without a parameter
.option("--verbose", "Enable verbose output")
.option("-q, --quiet", "Suppress output")
.parse(argv);
Please see the API reference page for ParsedArgs
.
// Get option value (first occurrence)
const string target = args.option("target");
const string port = args.option("port");
// Get all values for repeated options
// const string[] targets = args.optionList("target");
// Check if optional option is present
if (args.hasOption("--config")) {
const string config = args.option("--config");
// ...
}
// Check boolean flags
if (args.flag("verbose"))
writeln("Verbose mode enabled");
const bool quiet = args.flag("-q");
You can access options using:
option("target")
- access by long name (explicit for long names)option("t")
- access by short name (implicit, could be ambiguous)option("-t")
- access by short name (explicit)option("--target")
- access by long name (explicit, stylistic)
(also applies for hasOption()
, flag()
, etc.)
Use argument()
to add positional arguments that users provide in order:
new Program("file-processor")
.argument("<input>", "Input file path") // Required
.argument("[output]", "Output file path") // Optional
Variadic (rest) arguments can be added as the last argument in your command to accept multiple values. Optional variadic [files...]
accepts ≥ 0 values, required variadic <files...>
needs ≥ 1.
new Program("search")
// …
.argument("<files...>", "Files to search")
const string inputFile = args.argument("input");
// Check if optional argument was provided
if (args.hasArgument("output")) {
const string outputFile = args.argument("output");
}
// Get all values of variadic
const string[] files = args.argumentList("files");
Note: Required arguments after optional ones are technically allowed, but behave counter-intuitively and ambiguously for the user. It is strongly recommended not to use optional positional arguments before required ones—consider re-ordering the arguments, or using named parameters (options).
Build complex CLI applications with nested subcommands using command()
. Here’s an example with basic subcommands.
import std.stdio;
import std.string;
import cmd.program;
import cmd.command;
void main(string[] args)
{
new Program("subcommands")
.description("A CLI application with subcommands")
.versionString("1.0.0")
.helpOption("-h, --help", "Show help for command")
.command(new Command("split")
.description("Split a string")
.argument("<string>", "String to split")
.option("-s, --separator <char>", "Separator character")
.action((args) {
auto parts = args.argument("string").split(args.option("separator"));
writeln(parts);
return 0;
})
)
.command(new Command("greet")
.description("Greet someone")
.argument("<name>", "Name of the person to greet")
.option("--excited", "Add excitement to the greeting")
.action((args) {
auto msg = "Hello, " ~ args.argument("name");
if (args.flag("excited"))
msg ~= "!!!";
writeln(msg);
return 0;
})
)
.run(args);
}
$ ./subcommands split "hello,world" --separator ","
$ ./subcommands greet Alice --excited
$ ./subcommands split --help # Shows help for the split subcommand
Important: A command cannot have both subcommands and arguments. Choose one pattern per command level.
Commands can be nested endlessly.
Create modular command implementations by extending Command
(or Program
):
module modular.commands.greet;
import std.stdio;
import cmd.command;
import cmd.parsed_args;
class GreetCommand : Command
{
public this()
{
super("greet")
.description("Greet someone")
.argument("<name>", "Name of the person to greet")
.option("--excited", "Add excitement to the greeting")
.action(&execute);
}
private int execute(ParsedArgs args)
{
auto msg = "Hello, " ~ args.argument("name");
if (args.flag("excited"))
msg ~= "!!!";
writeln(msg);
return 0;
}
}
// …
import modular.commands.greet;
import cmd.program;
void main(string[] args)
{
new Program("modular")
.command(new GreetCommand())
.run(args);
}
Enable help
as a subcommand using HelpCommand
:
// …
import cmd.help_command;
new Program("myapp")
.description("My CLI application")
.command(new Command("deploy").description("Deploy the application"))
.command(new Command("status").description("Check application status"))
.command(new HelpCommand()) // Adds the built-in ‘help’ subcommand
.run(args);
$ myapp help # Show help for program
$ myapp help deploy # Show help for deploy command
The library provides clear error messages for common mistakes during args parsing and exits with status code 2
:
error: unknown command 'unknown-command'
error: missing value for option 'target'
error: unknown option 'invalid-option'
error: unexpected argument 'extra'
error: missing required option '-t, --target <name>'
error: missing required argument 'input'
You can also trigger custom errors in your command actions using the error(string)
and noreturn error(string, int)
methods on Command
:
.action((args) {
if (someCondition) {
args.command.error("error message"); // Does not exit (only prints error)
// or
args.command.error("error message", 1); // Exits with status
}
return 0;
});
The error
methods can be overridden to customise the error format.
Copyright © 2025 Zefir Kirilov.
This project is licenced under the GPL-3.0 licence.