better docs: parseopt (#10398)
This commit is contained in:
parent
458c827077
commit
a6a014a859
1 changed files with 266 additions and 49 deletions
|
|
@ -11,23 +11,141 @@
|
||||||
## It supports one convenience iterator over all command line options and some
|
## It supports one convenience iterator over all command line options and some
|
||||||
## lower-level features.
|
## lower-level features.
|
||||||
##
|
##
|
||||||
## Supported syntax with default empty ``shortNoVal``/``longNoVal``:
|
## Supported Syntax
|
||||||
|
## ================
|
||||||
##
|
##
|
||||||
## 1. short options - ``-abcd``, where a, b, c, d are names
|
## The following syntax is supported when arguments for the ``shortNoVal`` and
|
||||||
## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo``
|
## ``longNoVal`` parameters, which are
|
||||||
## 3. argument - everything else
|
## `described later<#shortnoval-and-longnoval>`_, are not provided:
|
||||||
##
|
##
|
||||||
## When ``shortNoVal``/``longNoVal`` are non-empty then the ':' and '=' above
|
## 1. Short options: ``-abcd``, ``-e:5``, ``-e=5``
|
||||||
## are still accepted, but become optional. Note that these option key sets
|
## 2. Long options: ``--foo:bar``, ``--foo=bar``, ``--foo``
|
||||||
## must be updated along with the set of option keys taking no value, but
|
## 3. Arguments: everything that does not start with a ``-``
|
||||||
## keys which do take values need no special updates as their set evolves.
|
|
||||||
##
|
##
|
||||||
## When option values begin with ':' or '=' they need to be doubled up (as in
|
## These three kinds of tokens are enumerated in the
|
||||||
|
## `CmdLineKind enum<#CmdLineKind>`_.
|
||||||
|
##
|
||||||
|
## When option values begin with ':' or '=', they need to be doubled up (as in
|
||||||
## ``--delim::``) or alternated (as in ``--delim=:``).
|
## ``--delim::``) or alternated (as in ``--delim=:``).
|
||||||
##
|
##
|
||||||
## The common ``--`` non-option argument delimiter appears as an empty string
|
## The ``--`` option, commonly used to denote that every token that follows is
|
||||||
## long option key. ``OptParser.cmd``, ``OptParser.pos``, and
|
## an argument, is interpreted as a long option, and its name is the empty
|
||||||
## ``os.parseCmdLine`` may be used to complete parsing in that case.
|
## string.
|
||||||
|
##
|
||||||
|
## Parsing
|
||||||
|
## =======
|
||||||
|
##
|
||||||
|
## Use an `OptParser<#OptParser>`_ to parse command line options. It can be
|
||||||
|
## created with `initOptParser<#initOptParser,string,set[char],seq[string]>`_,
|
||||||
|
## and `next<#next,OptParser>`_ advances the parser by one token.
|
||||||
|
##
|
||||||
|
## For each token, the parser's ``kind``, ``key``, and ``val`` fields give
|
||||||
|
## information about that token. If the token is a long or short option, ``key``
|
||||||
|
## is the option's name, and ``val`` is either the option's value, if provided,
|
||||||
|
## or the empty string. For arguments, the ``key`` field contains the argument
|
||||||
|
## itself, and ``val`` is unused. To check if the end of the command line has
|
||||||
|
## been reached, check if ``kind`` is equal to ``cmdEnd``.
|
||||||
|
##
|
||||||
|
## Here is an example:
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import parseopt
|
||||||
|
##
|
||||||
|
## var p = initOptParser("-ab -e:5 --foo --bar=20 file.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## case p.kind
|
||||||
|
## of cmdEnd: break
|
||||||
|
## of cmdShortOption, cmdLongOption:
|
||||||
|
## if p.val == "":
|
||||||
|
## echo "Option: ", p.key
|
||||||
|
## else:
|
||||||
|
## echo "Option and value: ", p.key, ", ", p.val
|
||||||
|
## of cmdArgument:
|
||||||
|
## echo "Argument: ", p.key
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option: a
|
||||||
|
## # Option: b
|
||||||
|
## # Option and value: e, 5
|
||||||
|
## # Option: foo
|
||||||
|
## # Option and value: bar, 20
|
||||||
|
## # Argument: file.txt
|
||||||
|
##
|
||||||
|
## The `getopt iterator<#getopt.i,OptParser>`_, which is provided for
|
||||||
|
## convenience, can be used to iterate through all command line options as well.
|
||||||
|
##
|
||||||
|
## ``shortNoVal`` and ``longNoVal``
|
||||||
|
## ================================
|
||||||
|
##
|
||||||
|
## The optional ``shortNoVal`` and ``longNoVal`` parameters present in
|
||||||
|
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ are for
|
||||||
|
## specifying which short and long options do not accept values.
|
||||||
|
##
|
||||||
|
## When ``shortNoVal`` is non-empty, users are not required to separate short
|
||||||
|
## options and their values with a ':' or '=' since the parser knows which
|
||||||
|
## options accept values and which ones do not. This behavior also applies for
|
||||||
|
## long options if ``longNoVal`` is non-empty. For short options, ``-j4``
|
||||||
|
## becomes supported syntax, and for long options, ``--foo bar`` becomes
|
||||||
|
## supported. This is in addition to the `previously mentioned
|
||||||
|
## syntax<#supported-syntax>`_. Users can still separate options and their
|
||||||
|
## values with ':' or '=', but that becomes optional.
|
||||||
|
##
|
||||||
|
## As more options which do not accept values are added to your program,
|
||||||
|
## remember to amend ``shortNoVal`` and ``longNoVal`` accordingly.
|
||||||
|
##
|
||||||
|
## The following example illustrates the difference between having an empty
|
||||||
|
## ``shortNoVal`` and ``longNoVal``, which is the default, and providing
|
||||||
|
## arguments for those two parameters:
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## import parseopt
|
||||||
|
##
|
||||||
|
## proc printToken(kind: CmdLineKind, key: string, val: string) =
|
||||||
|
## case kind
|
||||||
|
## of cmdEnd: doAssert(false) # Doesn't happen with getopt()
|
||||||
|
## of cmdShortOption, cmdLongOption:
|
||||||
|
## if val == "":
|
||||||
|
## echo "Option: ", key
|
||||||
|
## else:
|
||||||
|
## echo "Option and value: ", key, ", ", val
|
||||||
|
## of cmdArgument:
|
||||||
|
## echo "Argument: ", key
|
||||||
|
##
|
||||||
|
## let cmdLine = "-j4 --first bar"
|
||||||
|
##
|
||||||
|
## var emptyNoVal = initOptParser(cmdLine)
|
||||||
|
## for kind, key, val in emptyNoVal.getopt():
|
||||||
|
## printToken(kind, key, val)
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option: j
|
||||||
|
## # Option: 4
|
||||||
|
## # Option: first
|
||||||
|
## # Argument: bar
|
||||||
|
##
|
||||||
|
## var withNoVal = initOptParser(cmdLine, shortNoVal = {'c'},
|
||||||
|
## longNoVal = @["second"])
|
||||||
|
## for kind, key, val in withNoVal.getopt():
|
||||||
|
## printToken(kind, key, val)
|
||||||
|
##
|
||||||
|
## # Output:
|
||||||
|
## # Option and value: j, 4
|
||||||
|
## # Option and value: first, bar
|
||||||
|
##
|
||||||
|
## See also
|
||||||
|
## ========
|
||||||
|
##
|
||||||
|
## * `os module<os.html>`_ for lower-level command line parsing procs
|
||||||
|
## * `parseutils module<parseutils.html>`_ for helpers that parse tokens,
|
||||||
|
## numbers, identifiers, etc.
|
||||||
|
## * `strutils module<strutils.html>`_ for common string handling operations
|
||||||
|
## * `json module<json.html>`_ for a JSON parser
|
||||||
|
## * `parsecfg module<parsecfg.html>`_ for a configuration file parser
|
||||||
|
## * `parsecsv module<parsecsv.html>`_ for a simple CSV (comma separated value)
|
||||||
|
## parser
|
||||||
|
## * `parsexml module<parsexml.html>`_ for a XML / HTML parser
|
||||||
|
## * `other parsers<lib.html#pure-libraries-parsers>`_ for more parsers
|
||||||
|
|
||||||
{.push debugger: off.}
|
{.push debugger: off.}
|
||||||
|
|
||||||
|
|
@ -37,23 +155,26 @@ import
|
||||||
os, strutils
|
os, strutils
|
||||||
|
|
||||||
type
|
type
|
||||||
CmdLineKind* = enum ## the detected command line token
|
CmdLineKind* = enum ## The detected command line token.
|
||||||
cmdEnd, ## end of command line reached
|
cmdEnd, ## End of command line reached
|
||||||
cmdArgument, ## argument detected
|
cmdArgument, ## An argument such as a filename
|
||||||
cmdLongOption, ## a long option ``--option`` detected
|
cmdLongOption, ## A long option such as --option
|
||||||
cmdShortOption ## a short option ``-c`` detected
|
cmdShortOption ## A short option such as -c
|
||||||
OptParser* =
|
OptParser* =
|
||||||
object of RootObj ## this object implements the command line parser
|
object of RootObj ## Implementation of the command line parser.
|
||||||
pos*: int # ..empty key or subcmd cmdArg & handle specially
|
##
|
||||||
|
## To initialize it, use the
|
||||||
|
## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_.
|
||||||
|
pos*: int
|
||||||
inShortState: bool
|
inShortState: bool
|
||||||
allowWhitespaceAfterColon: bool
|
allowWhitespaceAfterColon: bool
|
||||||
shortNoVal: set[char]
|
shortNoVal: set[char]
|
||||||
longNoVal: seq[string]
|
longNoVal: seq[string]
|
||||||
cmds: seq[string]
|
cmds: seq[string]
|
||||||
idx: int
|
idx: int
|
||||||
kind*: CmdLineKind ## the dected command line token
|
kind*: CmdLineKind ## The detected command line token
|
||||||
key*, val*: TaintedString ## key and value pair; ``key`` is the option
|
key*, val*: TaintedString ## Key and value pair; the key is the option
|
||||||
## or the argument, ``value`` is not "" if
|
## or the argument, and the value is not "" if
|
||||||
## the option was given a value
|
## the option was given a value
|
||||||
|
|
||||||
proc parseWord(s: string, i: int, w: var string,
|
proc parseWord(s: string, i: int, w: var string,
|
||||||
|
|
@ -79,13 +200,24 @@ when declared(os.paramCount):
|
||||||
proc initOptParser*(cmdline = "", shortNoVal: set[char]={},
|
proc initOptParser*(cmdline = "", shortNoVal: set[char]={},
|
||||||
longNoVal: seq[string] = @[];
|
longNoVal: seq[string] = @[];
|
||||||
allowWhitespaceAfterColon = true): OptParser =
|
allowWhitespaceAfterColon = true): OptParser =
|
||||||
## inits the option parser. If ``cmdline == ""``, the real command line
|
## Initializes the command line parser.
|
||||||
## (as provided by the ``OS`` module) is taken. If ``shortNoVal`` is
|
##
|
||||||
## provided command users do not need to delimit short option keys and
|
## If ``cmdline == ""``, the real command line as provided by the
|
||||||
## values with a ':' or '='. If ``longNoVal`` is provided command users do
|
## ``os`` module is retrieved instead.
|
||||||
## not need to delimit long option keys and values with a ':' or '='
|
##
|
||||||
## (though they still need at least a space). In both cases, ':' or '='
|
## ``shortNoVal`` and ``longNoVal`` are used to specify which options
|
||||||
## may still be used if desired. They just become optional.
|
## do not take values. See the `documentation about these
|
||||||
|
## parameters<#shortnoval-and-longnoval>`_ for more information on
|
||||||
|
## how this affects parsing.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `getopt iterator<#getopt.i,OptParser>`_
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser()
|
||||||
|
p = initOptParser("--left --debug:3 -l -r:2")
|
||||||
|
p = initOptParser("--left --debug:3 -l -r:2",
|
||||||
|
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||||
|
|
||||||
result.pos = 0
|
result.pos = 0
|
||||||
result.idx = 0
|
result.idx = 0
|
||||||
result.inShortState = false
|
result.inShortState = false
|
||||||
|
|
@ -106,9 +238,21 @@ when declared(os.paramCount):
|
||||||
proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={},
|
proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={},
|
||||||
longNoVal: seq[string] = @[];
|
longNoVal: seq[string] = @[];
|
||||||
allowWhitespaceAfterColon = true): OptParser =
|
allowWhitespaceAfterColon = true): OptParser =
|
||||||
## inits the option parser. If ``cmdline.len == 0``, the real command line
|
## Initializes the command line parser.
|
||||||
## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and
|
##
|
||||||
## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``.
|
## If ``cmdline.len == 0``, the real command line as provided by the
|
||||||
|
## ``os`` module is retrieved instead. Behavior of the other parameters
|
||||||
|
## remains the same as in `initOptParser(string, ...)
|
||||||
|
## <#initOptParser,string,set[char],seq[string]>`_.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `getopt iterator<#getopt.i,seq[TaintedString],set[char],seq[string]>`_
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser()
|
||||||
|
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"])
|
||||||
|
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"],
|
||||||
|
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||||
|
|
||||||
result.pos = 0
|
result.pos = 0
|
||||||
result.idx = 0
|
result.idx = 0
|
||||||
result.inShortState = false
|
result.inShortState = false
|
||||||
|
|
@ -153,8 +297,21 @@ proc handleShortOption(p: var OptParser; cmd: string) =
|
||||||
inc p.idx
|
inc p.idx
|
||||||
|
|
||||||
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||||
## parses the first or next option; ``p.kind`` describes what token has been
|
## Parses the next token.
|
||||||
## parsed. ``p.key`` and ``p.val`` are set accordingly.
|
##
|
||||||
|
## ``p.kind`` describes what kind of token has been parsed. ``p.key`` and
|
||||||
|
## ``p.val`` are set accordingly.
|
||||||
|
runnableExamples:
|
||||||
|
var p = initOptParser("--left -r:2 file.txt")
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdLongOption and p.key == "left"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdShortOption and p.key == "r" and p.val == "2"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdArgument and p.key == "file.txt"
|
||||||
|
p.next()
|
||||||
|
doAssert p.kind == cmdEnd
|
||||||
|
|
||||||
if p.idx >= p.cmds.len:
|
if p.idx >= p.cmds.len:
|
||||||
p.kind = cmdEnd
|
p.kind = cmdEnd
|
||||||
return
|
return
|
||||||
|
|
@ -209,20 +366,61 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||||
|
|
||||||
when declared(os.paramCount):
|
when declared(os.paramCount):
|
||||||
proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} =
|
proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} =
|
||||||
## retrieves the rest of the command line that has not been parsed yet.
|
## Retrieves the rest of the command line that has not been parsed yet.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `remainingArgs proc<#remainingArgs,OptParser>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||||
|
## break
|
||||||
|
## else: continue
|
||||||
|
## doAssert p.cmdLineRest == "foo.txt bar.txt"
|
||||||
result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString
|
result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString
|
||||||
|
|
||||||
proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} =
|
proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} =
|
||||||
## retrieves the rest of the command line that has not been parsed yet.
|
## Retrieves a sequence of the arguments that have not been parsed yet.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `cmdLineRest proc<#cmdLineRest,OptParser>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
|
||||||
|
## while true:
|
||||||
|
## p.next()
|
||||||
|
## if p.kind == cmdLongOption and p.key == "": # Look for "--"
|
||||||
|
## break
|
||||||
|
## else: continue
|
||||||
|
## doAssert p.remainingArgs == @["foo.txt", "bar.txt"]
|
||||||
result = @[]
|
result = @[]
|
||||||
for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i])
|
for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i])
|
||||||
|
|
||||||
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] =
|
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||||
## This is an convenience iterator for iterating over the given OptParser object.
|
## Convenience iterator for iterating over the given
|
||||||
## Example:
|
## `OptParser<#OptParser>`_.
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## There is no need to check for ``cmdEnd`` while iterating.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
## # these are placeholders, of course
|
||||||
|
## proc writeHelp() = discard
|
||||||
|
## proc writeVersion() = discard
|
||||||
|
##
|
||||||
|
## var filename: string
|
||||||
## var p = initOptParser("--left --debug:3 -l -r:2")
|
## var p = initOptParser("--left --debug:3 -l -r:2")
|
||||||
|
##
|
||||||
## for kind, key, val in p.getopt():
|
## for kind, key, val in p.getopt():
|
||||||
## case kind
|
## case kind
|
||||||
## of cmdArgument:
|
## of cmdArgument:
|
||||||
|
|
@ -233,7 +431,7 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt
|
||||||
## of "version", "v": writeVersion()
|
## of "version", "v": writeVersion()
|
||||||
## of cmdEnd: assert(false) # cannot happen
|
## of cmdEnd: assert(false) # cannot happen
|
||||||
## if filename == "":
|
## if filename == "":
|
||||||
## # no filename has been given, so we show the help:
|
## # no filename has been given, so we show the help
|
||||||
## writeHelp()
|
## writeHelp()
|
||||||
p.pos = 0
|
p.pos = 0
|
||||||
p.idx = 0
|
p.idx = 0
|
||||||
|
|
@ -246,15 +444,34 @@ when declared(initOptParser):
|
||||||
iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(),
|
iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(),
|
||||||
shortNoVal: set[char]={}, longNoVal: seq[string] = @[]):
|
shortNoVal: set[char]={}, longNoVal: seq[string] = @[]):
|
||||||
tuple[kind: CmdLineKind, key, val: TaintedString] =
|
tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||||
## This is an convenience iterator for iterating over command line arguments.
|
## Convenience iterator for iterating over command line arguments.
|
||||||
## This creates a new OptParser. See the above ``getopt(var OptParser)``
|
|
||||||
## example for using default empty ``NoVal`` parameters. This example is
|
|
||||||
## for the same option keys as that example but here option key-value
|
|
||||||
## separators become optional for command users:
|
|
||||||
##
|
##
|
||||||
## .. code-block:: nim
|
## This creates a new `OptParser<#OptParser>`_. If no command line
|
||||||
## for kind, key, val in getopt(shortNoVal = { 'l' },
|
## arguments are provided, the real command line as provided by the
|
||||||
## longNoVal = @[ "left" ]):
|
## ``os`` module is retrieved instead.
|
||||||
|
##
|
||||||
|
## ``shortNoVal`` and ``longNoVal`` are used to specify which options
|
||||||
|
## do not take values. See the `documentation about these
|
||||||
|
## parameters<#shortnoval-and-longnoval>`_ for more information on
|
||||||
|
## how this affects parsing.
|
||||||
|
##
|
||||||
|
## There is no need to check for ``cmdEnd`` while iterating.
|
||||||
|
##
|
||||||
|
## See also:
|
||||||
|
## * `initOptParser proc<#initOptParser,seq[TaintedString],set[char],seq[string]>`_
|
||||||
|
##
|
||||||
|
## **Examples:**
|
||||||
|
##
|
||||||
|
## .. code-block::
|
||||||
|
##
|
||||||
|
## # these are placeholders, of course
|
||||||
|
## proc writeHelp() = discard
|
||||||
|
## proc writeVersion() = discard
|
||||||
|
##
|
||||||
|
## var filename: string
|
||||||
|
## let params = @["--left", "--debug:3", "-l", "-r:2"]
|
||||||
|
##
|
||||||
|
## for kind, key, val in getopt(params):
|
||||||
## case kind
|
## case kind
|
||||||
## of cmdArgument:
|
## of cmdArgument:
|
||||||
## filename = key
|
## filename = key
|
||||||
|
|
@ -264,8 +481,8 @@ when declared(initOptParser):
|
||||||
## of "version", "v": writeVersion()
|
## of "version", "v": writeVersion()
|
||||||
## of cmdEnd: assert(false) # cannot happen
|
## of cmdEnd: assert(false) # cannot happen
|
||||||
## if filename == "":
|
## if filename == "":
|
||||||
|
## # no filename has been written, so we show the help
|
||||||
## writeHelp()
|
## writeHelp()
|
||||||
##
|
|
||||||
var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal)
|
var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal)
|
||||||
while true:
|
while true:
|
||||||
next(p)
|
next(p)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue