diff options
author | c-blake <c-blake@users.noreply.github.com> | 2018-03-08 02:12:34 -0500 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-03-08 08:12:34 +0100 |
commit | 551d7b7dc17d0eb05cbc8fd935c584739ce66bb2 (patch) | |
tree | 8613dafa8081713948352a9e1470e3264096c05e /lib/pure | |
parent | 566cec74b6a8d45ea20e71131b8e40792366d924 (diff) | |
download | Nim-551d7b7dc17d0eb05cbc8fd935c584739ce66bb2.tar.gz |
Add ability for users to elide ':' or '=' when CLI authors pass a (#7297)
* Add ability for users to elide ':' or '=' when CLI authors pass a non-empty partial symbol table. Behavior should be identical to the old behavior if empty partial symbol tables are passed. "Partialness" of the symbol table refers to the fact that one need only specify option keys that are toggles/booleans/do not take arguments, hence the "NoArg" suffixes in shortNoArg and longNoArg. commandLineParams() returns seq[TaintedString], so use that consistently in getopt() and initOptParser(seq[TaintedString]) dropping the taint at the quoting stage just as with the paramStr() logic. Fix capitalization inconsistency of cmdLongOption. Export OptParser.cmd and OptParser.pos so that, at least *in principle*, users of this API can handle "--" option processing termination or some "git-like" sub-command stop word with a separate option sub-syntax. { Eg., ``case p.key of "": echo "trailing non-option args: ", p.cmd[p.pos..^1]`` or ``case p.kind of cmdArgument: if p.key == "mysubcmd": ...``. } Really, searching for the last delimiter before p.pos is probably needed to frame the trailing text..Not the nicest API, but still possible with effort. * Make requested changes from string to seq[char] (see https://github.com/nim-lang/Nim/pull/7297) * Document new behavior and elaborate on some special cases. * NoArg => NoVal to be less ambiguous/more clear. * Add more documentation and an example snippet. * Tweak language. Clarify still using ':'/'=' is ok. * Add a test case for new NoVal behavior.
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/parseopt.nim | 85 |
1 files changed, 63 insertions, 22 deletions
diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index cf275a88a..ffb69a72b 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -11,11 +11,23 @@ ## It supports one convenience iterator over all command line options and some ## lower-level features. ## -## Supported syntax: +## Supported syntax with default empty ``shortNoVal``/``longNoVal``: ## ## 1. short options - ``-abcd``, where a, b, c, d are names ## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo`` ## 3. argument - everything else +## +## When ``shortNoVal``/``longNoVal`` are non-empty then the ':' and '=' above +## are still accepted, but become optional. Note that these option key sets +## must be updated along with the set of option keys taking no value, but +## 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 +## ``--delim::``) or alternated (as in ``--delim=:``). +## +## The common ``--`` non-option argument delimiter appears as an empty string +## long option key. ``OptParser.cmd``, ``OptParser.pos``, and +## ``os.parseCmdLine`` may be used to complete parsing in that case. {.push debugger: off.} @@ -32,9 +44,11 @@ type cmdShortOption ## a short option ``-c`` detected OptParser* = object of RootObj ## this object implements the command line parser - cmd: string - pos: int + cmd*: string # cmd,pos exported so caller can catch "--" as.. + pos*: int # ..empty key or subcmd cmdArg & handle specially inShortState: bool + shortNoVal: set[char] + longNoVal: seq[string] kind*: CmdLineKind ## the dected command line token key*, val*: TaintedString ## key and value pair; ``key`` is the option ## or the argument, ``value`` is not "" if @@ -78,11 +92,19 @@ when declared(os.paramCount): # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! - proc initOptParser*(cmdline = ""): OptParser = + proc initOptParser*(cmdline = "", shortNoVal: set[char]={}, + longNoVal: seq[string] = @[]): OptParser = ## inits the option parser. If ``cmdline == ""``, the real command line - ## (as provided by the ``OS`` module) is taken. + ## (as provided by the ``OS`` module) is taken. If ``shortNoVal`` is + ## provided command users do not need to delimit short option keys and + ## values with a ':' or '='. If ``longNoVal`` is provided command users do + ## not need to delimit long option keys and values with a ':' or '=' + ## (though they still need at least a space). In both cases, ':' or '=' + ## may still be used if desired. They just become optional. result.pos = 0 result.inShortState = false + result.shortNoVal = shortNoVal + result.longNoVal = longNoVal if cmdline != "": result.cmd = cmdline else: @@ -94,15 +116,19 @@ when declared(os.paramCount): result.key = TaintedString"" result.val = TaintedString"" - proc initOptParser*(cmdline: seq[string]): OptParser = + proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={}, + longNoVal: seq[string] = @[]): OptParser = ## inits the option parser. If ``cmdline.len == 0``, the real command line - ## (as provided by the ``OS`` module) is taken. + ## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and + ## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``. result.pos = 0 result.inShortState = false + result.shortNoVal = shortNoVal + result.longNoVal = longNoVal result.cmd = "" if cmdline.len != 0: for i in 0..<cmdline.len: - result.cmd.add quote(cmdline[i]) + result.cmd.add quote(cmdline[i].string) result.cmd.add ' ' else: for i in countup(1, paramCount()): @@ -121,8 +147,9 @@ proc handleShortOption(p: var OptParser) = while p.cmd[i] in {'\x09', ' '}: inc(i) p.inShortState = false - if p.cmd[i] in {':', '='}: - inc(i) + if p.cmd[i] in {':', '='} or card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal: + if p.cmd[i] in {':', '='}: + inc(i) p.inShortState = false while p.cmd[i] in {'\x09', ' '}: inc(i) i = parseWord(p.cmd, i, p.val.string) @@ -146,12 +173,13 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = of '-': inc(i) if p.cmd[i] == '-': - p.kind = cmdLongoption + p.kind = cmdLongOption inc(i) i = parseWord(p.cmd, i, p.key.string, {'\0', ' ', '\x09', ':', '='}) while p.cmd[i] in {'\x09', ' '}: inc(i) - if p.cmd[i] in {':', '='}: - inc(i) + if p.cmd[i] in {':', '='} or len(p.longNoVal) > 0 and p.key.string notin p.longNoVal: + if p.cmd[i] in {':', '='}: + inc(i) while p.cmd[i] in {'\x09', ' '}: inc(i) p.pos = parseWord(p.cmd, i, p.val.string) else: @@ -172,7 +200,7 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt ## Example: ## ## .. code-block:: nim - ## var p = initOptParser("--left --debug:3 -l=4 -r:2") + ## var p = initOptParser("--left --debug:3 -l -r:2") ## for kind, key, val in p.getopt(): ## case kind ## of cmdArgument: @@ -192,17 +220,30 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt yield (p.kind, p.key, p.val) when declared(initOptParser): - iterator getopt*(): tuple[kind: CmdLineKind, key, val: TaintedString] = - ## This is an convenience iterator for iterating over the command line arguments. - ## This create a new OptParser object. - ## See above for a more detailed example + iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(), + shortNoVal: set[char]={}, longNoVal: seq[string] = @[]): + tuple[kind: CmdLineKind, key, val: TaintedString] = + ## This is an 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 - ## for kind, key, val in getopt(): - ## # this will iterate over all arguments passed to the cmdline. - ## continue + ## for kind, key, val in getopt(shortNoVal = { 'l' }, + ## longNoVal = @[ "left" ]): + ## case kind + ## of cmdArgument: + ## filename = key + ## of cmdLongOption, cmdShortOption: + ## case key + ## of "help", "h": writeHelp() + ## of "version", "v": writeVersion() + ## of cmdEnd: assert(false) # cannot happen + ## if filename == "": + ## writeHelp() ## - var p = initOptParser() + var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal) while true: next(p) if p.kind == cmdEnd: break |