A lua module to build command line interfaces declaratively.
To write a program with flags and positional arguments, you can do the following:
local cli = require "cli"
cli.locale "en_US"
cli.program {
"A description of the program",
cli.flag "o,one" {
"The first option",
type = cli.string
},
cli.flag "the-other" {
"The second option",
type = cli.number,
default = 17
},
cli.positional "file" {
"The input file",
type = cli.string
},
function(args)
print(string.format("One: %s.", args.one))
print(string.format("Two plus 1: %d.", args.the_other + 1))
print(string.format("File: %s.", args.file))
end
}
Supposing this is the code for a program called test-cli
, running test-cli -o value input.txt
will print:
One: value
Two plus 1: 18
File: input.txt
In the above example, running test-cli --help
or test-cli -h
will print:
A description of the program
Usage:
test-cli [options] file
Options and arguments without a default value are mandatory.
Options:
-o, --one <string>
The first option
--the-other <number> (default: 17)
The second option
Arguments:
file
The input file
Sticking with the previous example, calling it with test-cli -o value --the-other=dois input.txt
will print:
The following errors were found during the program execution:
∙ the option “--the-other” expects a number, but the given value was “dois”
You can run:
teste.lua --help
if you need some help.
Your program can be easily divided into subcommands:
local cli = require "cli"
cli.locale "en_US"
add = cli.command {
"Add all the given numbers",
function(args)
local sum = 0
for _, v in ipairs(args.numbers) do
sum = sum + v
end
print(sum)
end
}
max = cli.command {
"Find the maximum value",
function(args)
print(math.max(table.unpack(args.numbers)))
end
}
all_above = cli.command {
"Prints all numbers above the given value",
cli.flag "c,cutoff" {
"The value above which all numbers are retained",
type = cli.number
},
function(args)
for _, v in ipairs(args.numbers) do
if v > args.cutoff then
print(v)
end
end
end
}
cli.program {
"A program to compute numbers",
cli.positional "numbers" {
"The numbers to operate upon",
type = cli.number,
many = true,
default = {1, 3, 17}
}
}
Supposing this is the code for an hypothetical compute
, running compute --help
will print:
A program to compute numbers
Usage:
compute add numbers...
Add all the given numbers
compute all-above [options] numbers...
Prints all numbers above the given value
compute max numbers...
Find the maximum value
You can run
compute <command> --help
to get more details about a specific command.
And, then, running compute all-above --help
:
Prints all numbers above the given value
Usage:
compute all-above [options] numbers...
Options and arguments without a default value are mandatory.
Options:
-c, --cutoff <number>
The value above which all numbers are retained
Arguments:
numbers... (default: 1 3 17)
The numbers to operate upon
The start point of the cli
module are the functions cli.program
and cli.command
. Both have the same interface: a table with the following fields (all of them are optionals):
{
A string containing a description (used for help messages),
A sequence of definitions of command line arguments,
A function to be executed
}
The provided function receives as argument a table with all command line arguments filled with their values:
cli.program {
cli.flag "a-number" {
type = cli.number
},
function(args)
print(args.a_number)
end
}
If an argument has hyphens in its name, they are replaced with underscores, as in the above example.
Any value returned by the main function (the one present in the cli.program
table) is passed as an additional argument to the commands' functions:
a_command = cli.command {
cli.flag "do-it" {
type = cli.boolean
},
function(args, seventeen, nineteen)
print(args.do_it)
assert(seventeen == 17)
assert(nineteen == 19)
end
}
cli.program {
function()
return 17, 19
end
}
All global variables assined with a cli.command
are interpreted as subcommands of the program, and subcommands will be named after these global variables, replacing underscores with hyphens. So, for instance, the above example will generate a command called a-command
.
There are two kinds of arguments: flags and positional arguments.
Flags are defined with cli.flag
, passing to it a name and a table describing it:
cli.flag "first-flag" {
"This is the first flag",
type = cli.boolean
}
If the name contains a comma, the word preceding it is interpreted as a short variant of the word after it:
cli.flag "o,output" {}
The description table has the following fields:
- an optional string containing a description;
- a
type
key describing the value this flag accepts. Must be one ofcli.string
,cli.number
orcli.boolean
. It defaults tocli.string
. If it is set tocli.number
, the module will try to convert the value given at program invocation to a number and prints an error if it can not succeed; - an optional
default
key containing a default value for this flag. Flags without a default value are considered mandatory.
Positional arguments are defined with cli.positional
, passing to it a name and a table describing it:
cli.positional "file" {
"The file to be read",
type = cli.string
}
The description table has the following fields:
- an optional string containing a description;
- a
type
key describing the value this positional accepts. Must be one ofcli.string
orcli.number
. It defaults tocli.string
. If it is set tocli.number
, the module will try to convert the value given at program invocation to a number and prints an error if it can not succeed; - an optional
default
key containing a default value for this positional. Positionals without a default value are considered mandatory; - an optional
many
key telling whether this positional can receive many values at once.
The many
key is used this way:
cli.positional "files" {
"The files to be edited",
type = cli.string,
many = true,
default = { "README.md", "cli.lua" }
}
The function cli.locale
can be used to set the locale of help and error messages:
cli.locale "pt_BR"