Command-line applications#
nupp.cli parses raw option tokens or derives complete applications from typed records. Types control argument conversion, while doc comments supply help text.
local cli = require("nupp.cli")
(cli.Arguments)
local record Options
--- Print more detail.
(short = "v")
verbose: boolean = false
--- Output path.
(short = "o", value = "PATH")
output: string?
--- Input files.
(positional = "FILE")
files: {string} = {}
end
local options, problem = Options.fromCLI(arg)
assert(options ~= nil, problem and problem.message)Parse option tokens#
optParser is a mutable token cursor that does not allocate a token object on each advance. Configure which options take values, then advance it until it ends. next returns a token kind; the token's data remains on the parser until the following next call.
local cli = require("nupp.cli")
local parser = cli.optParser(arg, {
arity = {output = cli.required, verbose = cli.none},
})
while true do
local kind, problem = parser:next()
assert(problem == nil, problem and problem.message)
if kind == nil then break end
if kind == cli.shortOption or kind == cli.longOption then
print(kind, parser:key(), parser:value() or "")
else
print(kind, parser:raw())
end
endraw and index are valid for every current token. key, value, attached, and pattern are option-only and raise when the current token is another kind. Every accessor raises before the first token and after the end. cli.getopt exposes the token kind and current parser as an iterator, raising on syntax errors. -- makes every remaining word an argument.
Derive typed arguments#
@derive(cli.Arguments) adds fromCLI. Optional choice positionals are skipped until an argument matches, so a required positional can follow them.
local cli = require("nupp.cli")
(cli.Arguments)
local record Input
--- Execution mode.
(positional = "MODE", choices = {"fast", "thorough"})
mode: string?
--- Input file.
(positional = "FILE")
file: string
end
local input = assert(Input.fromCLI({"main.nupp"}))
assert(input.mode == nil and input.file == "main.nupp")Add commands#
@derive(cli.Command) adds typed parsing, metadata, help, and completion. A parent returns its children from subcommands, so the tree reads from the top down.
local cli = require("nupp.cli")
--- Search files.
(name = "gmatch")
(cli.Command)
local record Gmatch
--- Lua pattern to find.
(positional = "PATTERN")
pattern: string
--- Files to search.
(positional = "FILE")
files: {string} = {}
function run(self): integer
for _, path in ipairs(self.files) do
local file = assert(io.open(path))
local text = file:read("*a")
file:close()
for match in text:gmatch(self.pattern) do
print(path .. ": " .. match)
end
end
return 0
end
end
--- Project tools.
(name = "tool", group = true)
(cli.Command)
local record Tool
end
function Tool.subcommands(): {cli.CommandType}
return {Gmatch}
end
return cli.application(Tool):main(arg)Any command can be an application root. cli.application(Gmatch):main(arg) runs the same command without the tool gmatch prefix.
Complete dynamic values#
Literal choices complete automatically, and a value named FILE, PATH, HEADER, INPUT or ROCKSPEC (or DIR, DIRECTORY or ROOT) is handed to the shell's own file (or directory) completion. Use @cli(complete = Provider) when candidates depend on the machine or current project.
local record Files
function complete(
self,
request: cli.CompletionRequest
): {cli.CompletionCandidate}
local out: {cli.CompletionCandidate} = {}
local entries = require("nupp.io.files").list(request.cwd) or {}
for _, entry in ipairs(entries) do
if entry.name:sub(1, #request.prefix) == request.prefix then
out[#out + 1] = new cli.CompletionCandidate(
value = entry.name,
kind = "file"
)
end
end
return out
end
end
(cli.Arguments)
local record FileOptions
--- File to open.
(positional = "FILE", complete = Files)
file: string
endGenerate shell adapters with application:completion("bash" | "zsh" | "fish"). Each adapter asks the program for the candidates at the cursor through its hidden __complete command, so the answer is always the current grammar's.
Use terminal colors#
cli.style(stream) returns styles that become identity functions when color is disabled.
Automatic color respects the output stream, NO_COLOR, CLICOLOR_FORCE, and TERM=dumb; override it with cli.setColorMode("always" | "never" | "auto").