nupp.cli

Command-line arguments, from raw tokens up to a whole application.

The usual way in is a record: the types decide the conversion, the doc comments become the help text.

local cli = require("nupp.cli")

@derive(cli.Arguments)
local record Options
    --- Print more detail.
    @cli(short = "v")
    verbose: boolean = false

    --- Output path.
    @cli(short = "o", value = "PATH")
    output: string?

    --- Input files.
    @cli(positional = "FILE")
    files: {string} = {}
end

const options, problem = Options.fromCLI(arg)

assert(options ~= nil, problem and problem.message)

@derive(cli.Command) goes further and adds metadata, help, and shell completion, with a parent naming its children in subcommands so the tree reads from the top down. Underneath both, optParser and getopt are the syntax-only foundation: a token stream with no types attached, for when the shape of the command line is not a record.

This module also holds the terminal side of a command-line program -- color modes, styles, and tables -- so what a command prints lines up with what it parsed.

See Command-line applications.

Module contents

Types

TypeKindDescription
AlignmenttypeHow a cell sits in its column.
ApplicationrecordA finite tree of derived command types.
ApplicationOptionstypeRuntime-only settings for a CLI application.
ArgumentRecordinterfaceMarker implemented by a record deriving Arguments.
CellrecordOne cell.
ColorModetypeHow terminal styling is selected.
ColumnrecordOne column.
CommandTypetypeA record type deriving Command.
CompleterinterfaceA lazily invoked source of dynamic values for one CLI field.
CompletionCandidaterecordOne shell-neutral completion candidate.
CompletionRequestrecordContext supplied to a dynamic field completion provider.
DecodeErrortype
InvocationrecordA command type resolved and decoded without executing it.
OptParserrecordAn incremental command-line option parser.
OptParserConfigtypeSyntax policy supplied to optParser.
PatterntypeOne whole-word option form, such as -O2.
RunnableinterfaceThe instance contract of a derived executable command.
StylerecordGeneric terminal styles shared by CLI applications and their commands.
SyntaxErrorrecordA syntax failure found while consuming argv.
TableOptionsrecordWhat to render.
TokenKindtypeThe kind of one classified argv word.
ValueAritytypeHow an option obtains a value.

Functions

FunctionKindDescription
cliModule.annotatedCellfunctionA table cell carrying an aside, measured with the cell and painted apart from it.
cliModule.applicationfunctionBuilds an application from a command type and its subcommands tree.
cliModule.Argumentscomptime functionDerives typed argv decoding for a record.
cliModule.cellfunctionA plain table cell.
cliModule.colorEnabledfunctionReports whether escapes should be written to one stream.
cliModule.columnsfunctionReports how many columns one stream's terminal has, when it has one.
cliModule.Commandcomptime functionDerives typed argv decoding and a self-describing executable command.
cliModule.commandfunctionReturns the descriptor generated for a command type.
cliModule.decodeAsfunctionDecodes argv as a record deriving Arguments or Command.
cliModule.isTerminalfunctionReports whether one stream is connected to a terminal.
cliModule.paintedCellfunctionA table cell that paints itself, whatever its column says.
cliModule.schemafunctionReturns the CLI schema carried by a derived type.
cliModule.setColorModefunctionSets the process-wide default color policy.
cliModule.severityfunctionSelects the color associated with a diagnostic severity.
cliModule.stylefunctionReturns the generic styles selected for one stream.
cliModule.tablefunctionRenders aligned columns, painting each cell after it is padded.
cliModule.withColorModefunctionRuns a body under one color policy and restores the previous policy.
cliModule.writeTablefunctionRenders aligned columns straight to a stream.

Types#

Alignmenttype#

type Alignment = "left" | "right"

How a cell sits in its column.

Applicationrecord#

record Application ...

A finite tree of derived command types.

Members

NameKindDescription
rootfield
commandsfield
byParentfield
optionsfield
resolvemethodResolves and decodes argv, without writing output or invoking run.
helpmethodRenders the application's top-level help.
commandHelpmethodRenders help for a command path, or returns nil when it is unknown.
completemethodProduces shell-neutral candidates for one cursor position.
completionmethodRenders a Bash, Zsh, or Fish completion script from this schema.
mainmethodHandles framework options, renders failures, and invokes a typed command.

rootfield#

root: Descriptor
@private

commandsfield#

commands: {Descriptor}
@private

byParentfield#

byParent: {[any]: {[string]: Descriptor}}
@private

optionsfield#

@private

resolvemethod#

resolve: function resolve(self, argv: {string}): Invocation?, any?

Resolves and decodes argv, without writing output or invoking run.

Arguments
NameTypeDescription
selfany
argv{string}
Returns
TypeDescription
Invocation?
any?

helpmethod#

help: function help(self): string

Renders the application's top-level help.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string

commandHelpmethod#

commandHelp: function commandHelp(self, path: {string}): string?

Renders help for a command path, or returns nil when it is unknown.

Arguments
NameTypeDescription
selfany
path{string}
Returns
TypeDescription
string?

completemethod#

complete: function complete(self, request: any): {any}, string?

Produces shell-neutral candidates for one cursor position.

The second result is "files" or "dirs" when the slot is a path the shell should complete itself.

Arguments
NameTypeDescription
selfany
requestany
Returns
TypeDescription
{any}
string?

completionmethod#

completion: function completion(self, shell: "bash" | "zsh" | "fish"): string

Renders a Bash, Zsh, or Fish completion script from this schema.

The script asks the program itself, through the hidden __complete command, so what it offers depends on where the cursor is: one command's options rather than every command's, and a value's choices rather than every word the grammar knows. A reply whose first line is :files or :dirs hands the word to the shell's own path completion.

Arguments
NameTypeDescription
selfany
shell"bash" | "zsh" | "fish"
Returns
TypeDescription
string

mainmethod#

main: function main(self, argv: {string}): integer

Handles framework options, renders failures, and invokes a typed command.

Arguments
NameTypeDescription
selfany
argv{string}
Returns
TypeDescription
integer

ApplicationOptionstype#

type ApplicationOptions = {
    name: string?,
    version: string?,
    stdout: LuaFile?,
    stderr: LuaFile?,
    cwd: string?,
    environment: {[string]: string}?,
    width: integer?,
    indent: string?,
    labelLimit: integer?
}

Runtime-only settings for a CLI application.

ArgumentRecordinterface#

interface cliModule.ArgumentRecord

Marker implemented by a record deriving Arguments.

Cellrecord#

record Cell ...

One cell.

Members

NameKindDescription
textfieldWhat the cell says.
paintfieldPaints the text, overriding the column's painter for this cell alone.
notefieldAn aside after the text, in its own color: a default a project moved off, a unit, a "(default)" beside the name it...
notePaintfieldPaints the note.

textfield#

text: string

What the cell says.

paintfield#

paint: (function(string): string)?

Paints the text, overriding the column's painter for this cell alone. How one row reports a level in its own severity color while its neighbors do not.

notefield#

note: string?

An aside after the text, in its own color: a default a project moved off, a unit, a "(default)" beside the name it belongs to. Written separately because it is painted separately, and measured with the text rather than after it, so a column carrying one still lines up with the column beside it.

notePaintfield#

notePaint: (function(string): string)?

Paints the note. Defaults to the style's faint, which is what an aside is.

ColorModetype#

type ColorMode = "auto" | "always" | "never"

How terminal styling is selected.

Columnrecord#

record Column ...

One column.

Members

NameKindDescription
headingfieldWhat prints above it.
alignfieldWhich side the padding goes on.
paintfieldPaints every cell in this column that does not paint itself.
minimumWidthfieldA floor on the width, for a column whose contents are shorter than what they will be once a row appears that fills it.

headingfield#

heading: string

What prints above it. An empty heading still reserves the column's width.

alignfield#

align: Alignment?

Which side the padding goes on. Numbers read as a column when they are right aligned and as noise when they are not.

paintfield#

paint: (function(string): string)?

Paints every cell in this column that does not paint itself.

minimumWidthfield#

minimumWidth: integer?

A floor on the width, for a column whose contents are shorter than what they will be once a row appears that fills it.

CommandTypetype#

type cliModule.CommandType = Type<cliModule.Runnable>

A record type deriving Command.

Completerinterface#

interface cliModule.Completer ...

A lazily invoked source of dynamic values for one CLI field.

Members

NameKindDescription
completemethod

completemethod#

complete: function(self, request: cliModule.CompletionRequest): {cliModule.CompletionCandidate}
Arguments
NameTypeDescription
?self
requestcliModule.CompletionRequest
Returns
TypeDescription
{cliModule.CompletionCandidate}

CompletionCandidaterecord#

record cliModule.CompletionCandidate ...

One shell-neutral completion candidate.

Members

NameKindDescription
valuefield
descriptionfield
kindfield

valuefield#

value: string

descriptionfield#

description: string?

kindfield#

kind: "value" | "file" | "directory"?

CompletionRequestrecord#

record cliModule.CompletionRequest ...

Context supplied to a dynamic field completion provider.

Members

NameKindDescription
prefixfield
argvfield
wordIndexfield
commandPathfield
fieldfield
cwdfield
environmentfield

prefixfield#

prefix: string

argvfield#

argv: {string}

wordIndexfield#

wordIndex: integer

commandPathfield#

commandPath: {string}

fieldfield#

field: string

cwdfield#

cwd: string

environmentfield#

environment: {[string]: string}

DecodeErrortype#

type DecodeError = {
    @readonly kind: string,
    @readonly message: string,
    @readonly index: integer?,
    @readonly raw: string?,
    @readonly option: string?,
    @readonly field: string?,
    @readonly destination: string?
}

Invocationrecord#

record Invocation ...

A command type resolved and decoded without executing it.

Members

NameKindDescription
commandTypefield
optionsfield
argvfield
commandPathfield

commandTypefield#

commandType: any

optionsfield#

options: any

argvfield#

argv: {string}

commandPathfield#

commandPath: {string}

OptParserrecord#

record OptParser ...

An incremental command-line option parser.

Members

NameKindDescription
argvfield
configfield
nextIndexfield
literalfield
currentKindfield
currentKeyfield
currentValuefield
currentRawfield
currentIndexfield
currentAttachedfield
currentPatternfield
nextmethodAdvances to the next token and returns its kind, or a syntax error.
kindmethodReturns the current token kind.
keymethodReturns the current option's normalized key.
valuemethodReturns the current option's value, when it has one.
rawmethodReturns the original argv word for the current token.
indexmethodReturns the one-based argv index of the current token.
attachedmethodReports whether the current option carried an attached value.
patternmethodReports whether the current option matched a configured whole-word form.
remainingmethodReturns the unclassified suffix in its original order.
originalmethodReturns the original argv table.

argvfield#

argv: {string}
@private

configfield#

@private

nextIndexfield#

nextIndex: integer
@private

literalfield#

literal: boolean
@private

currentKindfield#

currentKind: TokenKind?
@private

currentKeyfield#

currentKey: string?
@private

currentValuefield#

currentValue: string?
@private

currentRawfield#

currentRaw: string?
@private

currentIndexfield#

currentIndex: integer
@private

currentAttachedfield#

currentAttached: boolean
@private

currentPatternfield#

currentPattern: boolean
@private

nextmethod#

next: function next(self): TokenKind?, SyntaxError?

Advances to the next token and returns its kind, or a syntax error.

Token data remains on this parser until the next call to next.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
TokenKind?
SyntaxError?

kindmethod#

kind: function kind(self): TokenKind

Returns the current token kind.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
TokenKind
Raises
TypeCondition
string

when the parser has no current token

keymethod#

key: function key(self): string

Returns the current option's normalized key.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string
Raises
TypeCondition
string

when the parser has no current token or it is not an option

valuemethod#

value: function value(self): string?

Returns the current option's value, when it has one.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string?
Raises
TypeCondition
string

when the parser has no current token or it is not an option

rawmethod#

raw: function raw(self): string

Returns the original argv word for the current token.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
string
Raises
TypeCondition
string

when the parser has no current token

indexmethod#

index: function index(self): integer

Returns the one-based argv index of the current token.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
integer
Raises
TypeCondition
string

when the parser has no current token

attachedmethod#

attached: function attached(self): boolean

Reports whether the current option carried an attached value.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
boolean
Raises
TypeCondition
string

when the parser has no current token or it is not an option

patternmethod#

pattern: function pattern(self): boolean

Reports whether the current option matched a configured whole-word form.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
boolean
Raises
TypeCondition
string

when the parser has no current token or it is not an option

remainingmethod#

remaining: function remaining(self): {string}

Returns the unclassified suffix in its original order.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
{string}

originalmethod#

original: function original(self): {string}

Returns the original argv table.

Arguments
NameTypeDescription
selfany
Returns
TypeDescription
{string}

OptParserConfigtype#

type OptParserConfig = {
    --- Value arity by normalized option name, without leading hyphens.
    arity: {[string]: ValueArity}?,

    --- Whole-word forms tried before ordinary short-option classification.
    patterns: {Pattern}?,

    --- Once the first argument is seen, expose every later word as an argument.
    stopAtFirstArgument: boolean?,

    --- Whether `--` itself is returned before its following arguments.
    exposeTerminator: boolean?
}

Syntax policy supplied to optParser.

Patterntype#

type Pattern = {
    --- Lua pattern matched against the complete argv word.
    pattern: string,

    --- The normalized key exposed by the token.
    key: string
}

One whole-word option form, such as -O2.

Runnableinterface#

interface cliModule.Runnable ...

The instance contract of a derived executable command.

Members

NameKindDescription
runmethod

runmethod#

run: function(self): integer
Arguments
NameTypeDescription
?self
Returns
TypeDescription
integer

Stylerecord#

record Style ...

Generic terminal styles shared by CLI applications and their commands.

Members

NameKindDescription
strongmethod
faintmethod
headingmethodA column heading, a section title: text that names what is beneath it rather than saying anything itself.
redmethod
yellowmethod
greenmethod
cyanmethod
bluemethod
pathmethod
guttermethod
severityfield

strongmethod#

strong: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

faintmethod#

faint: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

headingmethod#

heading: function(string): string

A column heading, a section title: text that names what is beneath it rather than saying anything itself. Named as a role rather than as a colour so the one decision lives here instead of at every table that prints one.

Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

redmethod#

red: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

yellowmethod#

yellow: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

greenmethod#

green: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

cyanmethod#

cyan: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

bluemethod#

blue: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

pathmethod#

path: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

guttermethod#

gutter: function(string): string
Arguments
NameTypeDescription
?string
Returns
TypeDescription
string

severityfield#

severity: {[string]: function(string): string}

SyntaxErrorrecord#

record SyntaxError ...

A syntax failure found while consuming argv.

Members

NameKindDescription
kindfield
indexfield
rawfield
keyfield
messagefield

kindfield#

kind: "missingValue" | "needsAttachedValue"

indexfield#

index: integer

rawfield#

raw: string

keyfield#

key: string

messagefield#

message: string

TableOptionsrecord#

record TableOptions ...

What to render.

Members

NameKindDescription
columnsfield
rowsfieldRows, each as many cells as there are columns.
stylefieldWhere the heading color and any cell colors come from.
headingPaintfieldPaints the heading row.
gapfieldPrinted between columns.
indentfieldPrinted before every line, heading included.
showHeadingfieldWhether to print the heading row at all.
widthfieldColumns available on screen.

columnsfield#

rowsfield#

rows: {{Cell}}

Rows, each as many cells as there are columns. A short row is padded with blanks rather than refused, so a caller may leave a trailing cell out.

stylefield#

Where the heading color and any cell colors come from. Supply the style selected for the stream being written to; omit it and nothing is painted.

headingPaintfield#

headingPaint: (function(string): string)?

Paints the heading row. Defaults to the style's heading role.

gapfield#

gap: string?

Printed between columns. Two spaces unless a caller says otherwise.

indentfield#

indent: string?

Printed before every line, heading included.

showHeadingfield#

showHeading: boolean?

Whether to print the heading row at all.

widthfield#

width: integer?

Columns available on screen. The last column is cut to fit rather than left to wrap. Nil cuts nothing, which is what a pipe wants.

TokenKindtype#

type TokenKind = "shortOption" | "longOption" | "argument" | "terminator"

The kind of one classified argv word.

ValueAritytype#

type ValueArity = "none" | "required" | "optionalAttached" | "attachedOnly"

How an option obtains a value.

Functions#

cliModule.annotatedCellfunction#

function cliModule.annotatedCell(value: string, note: string, paint: (function(string): string)?): cliModule.Cell

A table cell carrying an aside, measured with the cell and painted apart from it.

Arguments

NameTypeDescription
valuestring
notestring
paint(function(string): string)?

Returns

TypeDescription
cliModule.Cell

cliModule.applicationfunction#

function cliModule.application<T is cliModule.Runnable>(rootType: Type<T>, options: cliModule.ApplicationOptions?): cliModule.Application

Builds an application from a command type and its subcommands tree.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
rootTypeType<T>
optionscliModule.ApplicationOptions?

Returns

TypeDescription
cliModule.Application

cliModule.Argumentscomptime function#

@comptime function cliModule.Arguments(info: nupp.derive.Info): nupp.derive.Result<cliModule.ArgumentRecord>
@comptime

Derives typed argv decoding for a record.

Arguments

NameTypeDescription
infonupp.derive.Info

Returns

TypeDescription
nupp.derive.Result<cliModule.ArgumentRecord>

cliModule.cellfunction#

function cliModule.cell(value: string): cliModule.Cell

A plain table cell.

Arguments

NameTypeDescription
valuestring

Returns

TypeDescription
cliModule.Cell

cliModule.colorEnabledfunction#

function cliModule.colorEnabled(stream: LuaFile): boolean

Reports whether escapes should be written to one stream.

Arguments

NameTypeDescription
streamLuaFile

Returns

TypeDescription
boolean

cliModule.columnsfunction#

function cliModule.columns(stream: LuaFile): integer?

Reports how many columns one stream's terminal has, when it has one.

Arguments

NameTypeDescription
streamLuaFile

Returns

TypeDescription
integer?

cliModule.Commandcomptime function#

@comptime function cliModule.Command(info: nupp.derive.Info): nupp.derive.Result<cliModule.Runnable>
@comptime

Derives typed argv decoding and a self-describing executable command.

Arguments

NameTypeDescription
infonupp.derive.Info

Returns

TypeDescription
nupp.derive.Result<cliModule.Runnable>

cliModule.commandfunction#

function cliModule.command<T is table>(subject: Type<T>): table?, string?

Returns the descriptor generated for a command type.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
subjectType<T>

Returns

TypeDescription
table?
string?

cliModule.decodeAsfunction#

function cliModule.decodeAs<T is table>(subject: Type<T>, argv: {string}): T?, cliModule.DecodeError?

Decodes argv as a record deriving Arguments or Command.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
subjectType<T>
argv{string}

Returns

TypeDescription
T?
cliModule.DecodeError?

cliModule.isTerminalfunction#

function cliModule.isTerminal(stream: LuaFile): boolean

Reports whether one stream is connected to a terminal.

Arguments

NameTypeDescription
streamLuaFile

Returns

TypeDescription
boolean

cliModule.paintedCellfunction#

function cliModule.paintedCell(value: string, paint: function(string): string): cliModule.Cell

A table cell that paints itself, whatever its column says.

Arguments

NameTypeDescription
valuestring
paintfunction(string): string

Returns

TypeDescription
cliModule.Cell

cliModule.schemafunction#

function cliModule.schema<T is table>(subject: Type<T>): table?

Returns the CLI schema carried by a derived type.

Type parameters

NameDescription
T

Arguments

NameTypeDescription
subjectType<T>

Returns

TypeDescription
table?

cliModule.setColorModefunction#

function cliModule.setColorMode(wanted: cliModule.ColorMode): nil

Sets the process-wide default color policy.

Arguments

NameTypeDescription
wantedcliModule.ColorMode

Returns

TypeDescription
nil

cliModule.severityfunction#

function cliModule.severity(styles: cliModule.Style, name: string?): function(string): string

Selects the color associated with a diagnostic severity.

Arguments

NameTypeDescription
stylescliModule.Style
namestring?

Returns

TypeDescription
function(string): string

cliModule.stylefunction#

function cliModule.style(stream: LuaFile): cliModule.Style

Returns the generic styles selected for one stream.

Arguments

NameTypeDescription
streamLuaFile

Returns

TypeDescription
cliModule.Style

cliModule.tablefunction#

function cliModule.table(options: cliModule.TableOptions): string

Renders aligned columns, painting each cell after it is padded.

Arguments

NameTypeDescription
optionscliModule.TableOptions

Returns

TypeDescription
string

cliModule.withColorModefunction#

function cliModule.withColorMode(wanted: cliModule.ColorMode, body: function(): nil): nil

Runs a body under one color policy and restores the previous policy.

Arguments

NameTypeDescription
wantedcliModule.ColorMode
bodyfunction(): nil

Returns

TypeDescription
nil

cliModule.writeTablefunction#

function cliModule.writeTable(stream: LuaFile, options: cliModule.TableOptions): nil

Renders aligned columns straight to a stream.

Arguments

NameTypeDescription
streamLuaFile
optionscliModule.TableOptions

Returns

TypeDescription
nil