cligen

Search:
Group by:

This is an include file used by cligen.nim proper to initialize the clCfg global. Here we use only a parsecfg config file to do so.

Types

ClAlias = tuple[long: string, short: char, helpStr: string,
                dfl: seq[seq[string]]]
User CL aliases
ClCfg = object
  version*: string
  hTabCols*: seq[ClHelpCol]  ## selects columns to format
  hTabRowSep*: string        ## separates rows, e.g. "\n" double spaces
  hTabColGap*: int           ## number of spaces to separate cols by
  hTabMinLast*: int          ## narrowest rightmost col no matter term width
  hTabVal4req*: string       ## ``"REQUIRED"`` (or ``"NEEDED"``, etc.).
  reqSep*: bool              ## ``parseopt3.initOptParser`` parameter
  sepChars*: set[char]       ## ``parseopt3.initOptParser`` parameter
  opChars*: set[char]        ## ``parseopt3.initOptParser`` parameter
  longPfxOk*: bool           ## ``parseopt3.initOptParser`` parameter
  stopPfxOk*: bool           ## ``parseopt3.initOptParser`` parameter
  hTabSuppress*: string      ## Magic val for per-param help to suppress
  helpAttr*: Table[string, string] ## Text attrs for each help area
  helpAttrOff*: Table[string, string] ## Text attr offs for each help area
  noHelpHelp*: bool          ## Elide --help, --help-syntax from help table
  useHdr*: string            ## Override of const usage header template
  use*: string               ## Override of const usage template
  useMulti*: string          ## Override of const subcmd table template
  helpSyntax*: string        ## Override of const syntaxHelp string
  render*: proc (s: string): string ## string->string help transformer
  widthEnv*: string          ## name of environment var for width override
  sigPIPE*: ClSIGPIPE        ## `dispatch` use allows end-user SIGPIPE ctrl
Settings tending to be program- or CLI-author-global
ClHelpCol = enum
  clOptKeys, clValType, clDflVal, clDescrip
ClParse = tuple[paramName: string, ## Parse status for param
## Param name/long opt key
                unparsedVal: string, ## Unparsed val ("" for missing)
                message: string, ## default error message
                status: ClStatus]
Parse status for param
ClSIGPIPE = enum
  spRaise = "raise", spPass = "pass", spIsOk = "isOk"
ClStatus = enum
  clBadKey,                 ## Unknown long key
  clBadVal,                 ## Unparsable value
  clNonOption,              ## Unexpected non-option
  clMissing,                ## Required but missing
  clParseOptErr,            ## parseopt error
  clOk,                     ## Option parse part ok
  clPositional,             ## Expected non-option
  clHelpOnly, clVersionOnly  ## Early Exit requests
HelpError = object of CatchableError
User-Syntax/Semantic Err; ${HELP}
HelpOnly = object of CatchableError
Ok Ctl Flow Only For --help
ParseError = object of CatchableError
CL-Syntax Err from generated code
VersionOnly = object of CatchableError
Ok Ctl Flow Only For --version

Vars

clCfg = ClCfg(version: "",
              hTabCols: @[clOptKeys, clValType, clDflVal, clDescrip],
              hTabRowSep: "", hTabColGap: 2, hTabMinLast: 16,
              hTabVal4req: "REQUIRED", reqSep: false, sepChars: {'=', ':'}, opChars: {
    '+', '-', '*', '/', '%', '@', ',', '.', '&', '|', '~', '^', '$', '#', '<',
    '>', '?'}, longPfxOk: true, stopPfxOk: true, hTabSuppress: "CLIGEN-NOHELP",
              helpAttr: initTable(32), helpAttrOff: initTable(32),
              helpSyntax: syntaxHelp, render: descape, widthEnv: "CLIGEN_WIDTH",
              sigPIPE: spIsOk)

Lets

cgSetByParseNil = addr(setByParseDum)
cgVarSeqStrNil = addr(varSeqStrDum)

Consts

ClErrors = {clBadKey, clBadVal, clNonOption, clMissing}
ClExit = {clHelpOnly, clVersionOnly}
ClNoCall = {clBadKey..clMissing, clHelpOnly..clVersionOnly}
clUse = "$command $args\n${doc}Options:\n$options"
clUseHdr = "Usage:\n  "
clUseMulti = "${doc}Usage:\n  $command {SUBCMD}  [sub-command options & parameters]\nwhere {SUBCMD} is one of:\n$subcmds\n$command {-h|--help} or with no args at all prints this message.\n$command --help-syntax gives general cligen syntax help.\nRun \"$command {help SUBCMD|SUBCMD --help}\" to see help for just SUBCMD.\nRun \"$command help\" to get *comprehensive* help.${ifVersion}"
clUseMultiGeneral = """$command {-h|--help} or with no args at all prints this message.
$command --help-syntax gives general cligen syntax help.
Run "$command {help SUBCMD|SUBCMD --help}" to see help for just SUBCMD.
Run "$command help" to get *comprehensive* help.${ifVersion}"""
clUseMultiPerlish = "NAME\n  ${doc}USAGE\n  $command {SUBCMD}  [sub-command options & parameters]\n\nSUBCOMMANDS\n$subcmds\n$command {-h|--help} or with no args at all prints this message.\n$command --help-syntax gives general cligen syntax help.\nRun \"$command {help SUBCMD|SUBCMD --help}\" to see help for just SUBCMD.\nRun \"$command help\" to get *comprehensive* help.${ifVersion}"

Procs

proc contains(x: openArray[ClParse]; paramName: string): bool {....raises: [],
    tags: [], forbids: [].}
Test if the seq updated via setByParse contains a parameter.
proc contains(x: openArray[ClParse]; status: ClStatus): bool {....raises: [],
    tags: [], forbids: [].}
Test if the seq updated via setByParse contains a certain status.
proc ha0(key: string; c = clCfg): string {....raises: [], tags: [], forbids: [].}
Internal routine to access c.helpAttr
proc ha1(key: string; c = clCfg): string {....raises: [], tags: [], forbids: [].}
Internal routine to access c.helpAttrOff
proc mergeParams(cmdNames: seq[string]; cmdLine = commandLineParams()): seq[
    string] {....raises: [], tags: [], forbids: [].}
This is a pass-through parameter merge to provide a hook for CLI authors to create the seq[string] to be parsed from any run-time sources (likely based on cmdNames) that they would like. In a single dispatch context, cmdNames[0] is the cmdName while in a dispatchMulti context it is @[ <mainCommand>, <subCommand> ].
proc next(x: openArray[ClParse]; stati: set[ClStatus]; start = 0): int {.
    ...raises: [], tags: [], forbids: [].}
First index after startIx in setByParse seq w/parse status in stati.
proc numOfStatus(x: openArray[ClParse]; stati: set[ClStatus]): int {....raises: [],
    tags: [], forbids: [].}
Count elements in the setByParse seq with parse status in stati.
proc offCols(c: ClCfg): seq[string] {....raises: [], tags: [], forbids: [].}
Internal routine to map help table color specs to strings for alignTable.
proc onCols(c: ClCfg): seq[string] {....raises: [], tags: [], forbids: [].}
Internal routine to map help table color specs to strings for alignTable.
proc quits(s: int) {....raises: [], tags: [], forbids: [].}
quits)afe|s)aturating is for non-literal/maybe big input for compatibility w/older Nim stdlibs. Value is clipped to -128..127. Note that Unix shells often map a signaled exit to SIGNUM-128, e.g. 2-128 for SIGINT.
proc SIGPIPE_isOk() {....raises: [], tags: [], forbids: [].}
Install signal handler to exit success upon OS posting SIGPIPE. This is more or less what (non-network) programs/users "expect".
proc SIGPIPE_pass() {....raises: [], tags: [], forbids: [].}
Restore default signal disposition to allow OS to post SIGPIPE and likely terminate with non-zero exit status (typically 141=128+signo13). This optimizes for "no surprises" behavior of exec()d code reasonably expecting to inherit a near default SIGPIPE disposition.
proc toInts(x: seq[ClHelpCol]): seq[int] {....raises: [], tags: [], forbids: [].}
Internal routine to convert help column enums to just ints for alignTable.
proc topLevelHelp(doc: auto; use: auto; cmd: auto; subCmds: auto; subDocs: auto): string

Macros

macro cligenQuitAux(cmdLine: seq[string]; dispatchName: string; cmdName: string;
                    pro: untyped; echoResult: bool; noAutoEcho: bool;
                    mergeNames: seq[string] = @[]): untyped
macro dispatchGen(pro: typed{nkSym}; cmdName: string = ""; doc: string = "";
                  help: typed = {}; short: typed = {}; usage: string = clUse;
                  cf: ClCfg = clCfg; echoResult = false; noAutoEcho = false;
                  positional: static string = AUTO;
                  suppress: seq[string] = @[];
                  implicitDefault: seq[string] = @[]; vars: seq[string] = @[];
                  dispatchName = ""; mergeNames: seq[string] = @[];
                  alias: seq[ClAlias] = @[]; stopWords: seq[string] = @[];
                  noHdr = false; docs: ptr var seq[string] = cgVarSeqStrNil;
                  setByParse: ptr var seq[ClParse] = cgSetByParseNil): untyped

Generate command-line dispatcher for proc pro named dispatchName (defaulting to dispatchPro) with generated help/syntax guided by cmdName, doc, and cf. Parameters with no explicit default in the proc become required command arguments while those with default values become command options. Each proc parameter type needs in-scope argParse & argHelp procs. cligen/argcvt defines them for basic types & basic collections (int, string, enum, .., seq[T], set[T], ..).

help is a {(paramNm, str)} of per-param help, eg. {"quiet": "be quiet"}. Often, only these help strings are needed for a decent CLI. A row of the help table can be suppressed from showing by setting str to a magic clCfg.hTabSuppress value (defaults to "CLIGEN-NOHELP", but is customizable).

short is a {(paramNm, char)} of per-param single-char option keys. Setting a parameter value to '\0' suppresses the assignment of a short option. Suppress all short options by passing an empty key: { "": ' ' }.

help & short definitions outside a call require explicit toTable (from std/tables) conversions.

usage is a help template interpolating $command $args $doc $options.

cf controls whole program aspects of generated CLIs. See ClCfg docs.

Default exit protocol is: quits(int(result)) or (echo $result or discard; quit(0)) depending on what compiles. True echoResult forces echo while noAutoEcho blocks it (=>int()||discard). Technically, cligenQuit implements this behavior.

By default, cligen maps the first non-defaulted seq[] proc parameter to any non-option/positional command args. positional selects another. Set positional to the empty string ("") to disable this entirely.

suppress is a list of formal parameter names to exclude from the parse- assign system. Such names are effectively pinned to their default values.

implicitDefault is a list of formal parameter names allowed to default to the Nim default value for a type, rather than becoming required, when they lack an explicit initializer.

vars is a seq[string] of outer scope-declared variables bound to the CLI (e.g., on CL, --logLvl=X converts & assigns to outer.logLvl = X).

stopWords is a seq[string] of words beyond which -.* no longer signifies an option (like the common sole -- command argument).

noHdr is a (mostly internal) flag to suppress emitting "Usage:" header in generated help tables (e.g. in a multi-command).

mergeNames gives the cmdNames param passed to mergeParams, which defaults to @[cmdName] if mergeNames is @[].

alias is @[] | 2-seq of (string,char,string) 3-tuples specifying (Long, Short opt keys, Help) to {Define,Reference} aliases. This lets CL users define aliases early in mergeParams sources (e.g. cfg files/evars) & reference them later. Eg. if alias[0][0]=="alias" and alias[1][1]=='a' then --alias:k='-a -b' -ak expands to @["-a", "-b"].

docs is addr(some var seq[string]) to which to append each main doc comment or its replacement doc=text. Default of nil means do nothing.

setByParse is addr(some var seq[ClParse]). When non-nil, this var collects each parameter seen, keyed under its long/param name (i.e., parsed but not converted to native types). Wrapped procs can inspect this or even convert args themselves to revive parseopt-like iterative interpreting. cligen provides convenience procs to use setByParse: contains, numOfStatus & next. Note that ordinary Nim procs, from inside calls, do not know how params got their values (positional, keyword, defaulting). Wrapped procs accessing setByParse are inherently command-line only. So, this var seq needing to be declared before such procs for such access is ok. Ideally, keep important functionality Nim-callable. setByParse may also be useful combined with the parseOnly arg of generated dispatchers.

macro dispatchMulti(procBrackets: varargs[untyped]): untyped

A wrapper to generate a multi-command dispatcher, call it, and quit. The argument is a list of bracket expressions passed to dispatchGen for each subcommand.

The VERY FIRST bracket can be the special string literal "multi" to adjust dispatchGen settings for the top-level proc that dispatches to subcommands. In particular, top-level usage is a string template interpolating $command $doc $subcmds $ifVersion (args & options dropped and subcmd & ifVersion added relative to an ordinary dispatchGen usage template.).

macro dispatchMultiDG(procBkts: varargs[untyped]): untyped
An internal macro to generate the call to dispatchGen(itsMulti).
macro dispatchMultiGen(procBkts: varargs[untyped]): untyped
Generate multi-cmd dispatch. procBkts are argLists for dispatchGen. Eg., dispatchMultiGen([foo, short={"dryRun": "n"}], [bar, doc="Um"]).
macro initDispatchGen(dispName, obName: untyped; default: typed;
                      positional = ""; suppress: seq[string] = @[];
                      body: untyped): untyped
Create a proc with signature from default that calls initGen and initializes var obName by calling to the generated initializer. It puts body inside an appropriate try/except so that you can just say:
initDispatchGen(cmdProc, cfg, cfgDfl):
  cfg.callAPI()
  quit(min(127, cfg.nError))
dispatch(cmdProc)
macro initGen(default: typed; T: untyped; positional = "";
              suppress: seq[string] = @[]; name = ""): untyped
This macro generates an init proc for object|tuples of type T with param names equal to top-level field names & default values from default like init(field1=default.field1,...): T = result.field1=field1; .., except if fieldN==positional fieldN: typeN is used instead which in turn makes dispatchGen bind that seq to catch positional CL args.

Templates

template ambigSubcommand(cb: CritBitTree[string]; attempt: string)
template cligenHelp(p: untyped; hlp: untyped; use: untyped; pfx: untyped;
                    skipHlp: untyped; noUHdr = false): auto
template cligenQuit(p: untyped; echoResult = false; noAutoEcho = false): auto
template dispatch(pro: typed{nkSym}; cmdName = ""; doc = ""; help: typed = {};
                  short: typed = {}; usage = clUse; echoResult = false;
                  noAutoEcho = false; positional = AUTO;
                  suppress: seq[string] = @[];
                  implicitDefault: seq[string] = @[]; vars: seq[string] = @[];
                  dispatchName = ""; mergeNames: seq[string] = @[];
                  alias: seq[ClAlias] = @[]; stopWords: seq[string] = @[];
                  noHdr = false): untyped
Convenience dispatchCf wrapper to silence bogus GcUnsafe warnings at verbosity:2. Parameters are the same as dispatchCf (except for no cf).
template dispatchCf(pro: typed{nkSym}; cmdName = ""; doc = ""; help: typed = {};
                    short: typed = {}; usage = clUse; cf: ClCfg = clCfg;
                    echoResult = false; noAutoEcho = false; positional = AUTO;
                    suppress: seq[string] = @[];
                    implicitDefault: seq[string] = @[];
                    vars: seq[string] = @[]; dispatchName = "";
                    mergeNames: seq[string] = @[]; alias: seq[ClAlias] = @[];
                    stopWords: seq[string] = @[]; noHdr = false;
                    cmdLine = commandLineParams()): untyped
A convenience wrapper to both generate a command-line dispatcher and then call the dispatcher & exit; Params are same as the dispatchGen macro.
template initFromCL[T](default: T; cmdName: string = ""; doc: string = "";
                       help: typed = {}; short: typed = {};
                       usage: string = clUse; positional = "";
                       suppress: seq[string] = @[];
                       mergeNames: seq[string] = @[]; alias: seq[ClAlias] = @[]): T
Convenience initFromCLcf wrapper to silence bogus GcUnsafe warnings at verbosity:2. Parameters are as for initFromCLcf (except for no cf).
template initFromCLcf[T](default: T; cmdName: string = ""; doc: string = "";
                         help: typed = {}; short: typed = {};
                         usage: string = clUse; cf: ClCfg = clCfg;
                         positional = ""; suppress: seq[string] = @[];
                         mergeNames: seq[string] = @[];
                         alias: seq[ClAlias] = @[]): T
Like dispatchCf but only quit when user gave bad CL, --help, or --version. On success, returns T populated from object|tuple default and then from the mergeNames/the command-line. Top-level fields must have types with argParse & argHelp overloads. Params to this common to dispatchGen mean the same thing. The inability here to distinguish between syntactically explicit assigns & Nim type defaults eliminates several features and makes the positional default be empty.
template unknownSubcommand(cmd: string; subCmds: seq[string])