import std.stdio;
import std.getopt;
int main(string[] args)
{
string name;
auto opts = getopt(
args,
"n|name", "Your Name", &name
);
if (opts.helpWanted) {
defaultGetoptPrinter("./mycli", opts.options);
return 1;
}
writefln("Hello %s", name);
return 0;
}
Implementing CLI Subcommands with D programming language
Never heard of D Programming?
D is a general-purpose programming language with static typing, systems-level access, and C-like syntax. With the D Programming Language, write fast, read fast, and run fast.
Fast code, fast. https://dlang.org
Parsing command-line arguments is very easy using the "getopt" module available in the standard library. In this blog, I am sharing my experience about how I added subcommand support to my command-line applications.
Below example shows a basic program which accepts --name
or -n
argument.
Once "getopt" parses the flags, args will contain only remaining arguments(positional arguments). For example,
import std.stdio;
import std.getopt;
int main(string[] args)
{
string lastName;
auto opts = getopt(
args,
"last-name", "Your Last Name", &lastName
);
if (opts.helpWanted || args.length != 2) {
defaultGetoptPrinter("./mycli <name> [OPTIONS]", opts.options);
return 1;
}
string name = args[1];
writefln("Hello %s %s", name, lastName);
return 0;
}
Now to handle sub-commands, we need to define multiple getopt. For Example,
import std.stdio;
import std.getopt;
const progHelp = "./mycli get|set [OPTIONS]";
int subcmdSet(string[] args)
{
string name;
bool isAdmin;
auto opts = getopt(
args,
std.getopt.config.required, // To make --name or -n as required field
"n|name", "Name", &name,
"admin", "Set Admin privileges", &isAdmin
);
if (opts.helpWanted)
{
defaultGetoptPrinter("./mycli set -n <Name>", opts.options);
return 1;
}
// Subcommand implementation
writef("Set name as %s", name);
if (isAdmin)
write("(admin)");
writeln();
return 0;
}
int subcmdGet(string[] args)
{
string name;
auto opts = getopt(
args,
std.getopt.config.required,
"n|name", "Name", &name
);
if (opts.helpWanted)
{
defaultGetoptPrinter("./mycli get --n <Name>", opts.options);
return 1;
}
// Subcommand implementation
writefln("Name is %s", name);
return 0;
}
int main(string[] args)
{
auto subcmds = [
"set": &subcmdSet,
"get": &subcmdGet,
];
if (args.length < 2)
{
writeln("subcommand not specified");
defaultGetoptPrinter(progHelp, globalOpts.options);
return 1;
}
auto func = (args[1] in subcmds);
if (func is null)
{
writeln("Unknown sub-command");
defaultGetoptPrinter(progHelp, globalOpts.options);
return 1;
}
return (*func)(args);
}
If the program needs global flags which are applicable for all
sub-commands, then make sure to add global flags getopt before parsing
sub-commands. Also, add std.getopt.config.passThrough
to avoid
failing to parse flags related to sub-commands.
struct globalFlags
{
string logLevel = "INFO";
bool debugEnabled;
}
globalFlags gflags;
int main(string[] args)
{
auto globalOpts = getopt(
args,
std.getopt.config.passThrough,
"l|log-level", "Log Level", &gflags.logLevel,
"debug", "Debug mode", &gflags.debugEnabled
);
if (args.length < 2)
{
if (!globalOpts.helpWanted)
writeln("subcommand not specified");
defaultGetoptPrinter(progHelp, globalOpts.options);
return 1;
}
// -h is already parsed during Global options parsing. Reinsert to args
// So that subcommands will work as usual
if (globalOpts.helpWanted)
args ~= "-h";
auto subcmds = [
"set": &subcmdSet,
"get": &subcmdGet,
];
auto func = (args[1] in subcmds);
if (func is null)
{
writeln("Unknown sub-command");
defaultGetoptPrinter(progHelp, globalOpts.options);
return 1;
}
return (*func)(args);
}
Notes:
-
getopt
errors can be handled by catchingGetoptException
and, -
Handle any datatype conversion errors by catching ConvException`.
-
Positional arguments start with index 2(Example:
args[2]
)
That’s it! Let me know if this blog post is useful.
About Aravinda Vishwanathapura
Co-Founder & CTO at Kadalu Technologies, Creator of Sanka, Creator of Chitra, GlusterFS core team member, Maintainer of Kadalu Storage