diff options
-rw-r--r-- | lib/pure/parseopt.nim | 85 | ||||
-rw-r--r-- | tests/misc/tparseopt.nim | 20 |
2 files changed, 83 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 diff --git a/tests/misc/tparseopt.nim b/tests/misc/tparseopt.nim index 1f8375dfd..badbc59ad 100644 --- a/tests/misc/tparseopt.nim +++ b/tests/misc/tparseopt.nim @@ -9,6 +9,17 @@ kind: cmdLongOption key:val -- left: kind: cmdLongOption key:val -- debug:3 kind: cmdShortOption key:val -- l:4 kind: cmdShortOption key:val -- r:2 +parseoptNoVal +kind: cmdLongOption key:val -- left: +kind: cmdLongOption key:val -- debug:3 +kind: cmdShortOption key:val -- l: +kind: cmdShortOption key:val -- r:2 +kind: cmdLongOption key:val -- debug:2 +kind: cmdLongOption key:val -- debug:1 +kind: cmdShortOption key:val -- r:1 +kind: cmdShortOption key:val -- r:0 +kind: cmdShortOption key:val -- l: +kind: cmdShortOption key:val -- r:4 parseopt2 first round kind: cmdLongOption key:val -- left: @@ -40,6 +51,15 @@ block: echo "kind: ", kind, "\tkey:val -- ", key, ":", val block: + echo "parseoptNoVal" + # test NoVal mode with custom cmdline arguments + var argv = "--left --debug:3 -l -r:2 --debug 2 --debug=1 -r1 -r=0 -lr4" + var p = parseopt.initOptParser(argv, + shortNoVal = {'l'}, longNoVal = @["left"]) + for kind, key, val in parseopt.getopt(p): + echo "kind: ", kind, "\tkey:val -- ", key, ":", val + +block: echo "parseopt2" for kind, key, val in parseopt2.getopt(): echo "kind: ", kind, "\tkey:val -- ", key, ":", val |