diff options
author | Michał Zieliński <michal@zielinscy.org.pl> | 2013-12-09 22:22:06 +0100 |
---|---|---|
committer | Michał Zieliński <michal@zielinscy.org.pl> | 2013-12-09 23:29:16 +0100 |
commit | d1f3512aba83814146583538c46b7f0c84175423 (patch) | |
tree | 3425909ffab4a4fa5fc2d1f6c7130c65465296ae | |
parent | 8dae66415966e2cba50fd5295c514a97ff187fc2 (diff) | |
download | Nim-d1f3512aba83814146583538c46b7f0c84175423.tar.gz |
Reimplement parseopt which parses arguments given as a sequence of strings, not single string.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | lib/pure/parseopt.nim | 184 | ||||
-rw-r--r-- | tests/system/params.nim | 17 |
3 files changed, 107 insertions, 95 deletions
diff --git a/.gitignore b/.gitignore index 536ec9d24..d67d9f2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,4 @@ examples/cross_calculator/android/tags /tests/caas/main /tests/caasdriver /tools/nimgrep +/tests/system/params diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 6d9d16bc9..81a1fd1f0 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -10,123 +10,117 @@ ## This module provides the standard Nimrod command line parser. ## It supports one convenience iterator over all command line options and some ## lower-level features. +## +## Supported syntax: +## +## 1. short options - ``-abcd``, where a, b, c, d are names +## 2. long option - ``--foo:bar``, ``--foo=bar`` or ``--foo`` +## 3. argument - everything else {.push debugger: off.} include "system/inclrtl" -import +import os, strutils -type +type TCmdLineKind* = enum ## the detected command line token cmdEnd, ## end of command line reached cmdArgument, ## argument detected - cmdLongoption, ## a long option ``--option`` detected + cmdLongOption, ## a long option ``--option`` detected cmdShortOption ## a short option ``-c`` detected - TOptParser* = - object of TObject ## this object implements the command line parser - cmd: string + TOptParser* = + object of TObject ## this object implements the command line parser + cmd: seq[string] pos: int - inShortState: bool + remainingShortOptions: string kind*: TCmdLineKind ## the dected command line token key*, val*: TaintedString ## key and value pair; ``key`` is the option ## or the argument, ``value`` is not "" if ## the option was given a value -when defined(os.ParamCount): - # we cannot provide this for NimRtl creation on Posix, because we can't - # access the command line arguments then! - - proc initOptParser*(cmdline = ""): TOptParser = - ## inits the option parser. If ``cmdline == ""``, the real command line - ## (as provided by the ``OS`` module) is taken. - result.pos = 0 - result.inShortState = false - if cmdline != "": - result.cmd = cmdline - else: - result.cmd = "" - for i in countup(1, ParamCount()): - result.cmd = result.cmd & quoteIfContainsWhite(paramStr(i).string) & ' ' - result.kind = cmdEnd - result.key = TaintedString"" - result.val = TaintedString"" - -proc parseWord(s: string, i: int, w: var string, - delim: TCharSet = {'\x09', ' ', '\0'}): int = - result = i - if s[result] == '\"': - inc(result) - while not (s[result] in {'\0', '\"'}): - add(w, s[result]) - inc(result) - if s[result] == '\"': inc(result) - else: - while not (s[result] in delim): - add(w, s[result]) - inc(result) - -proc handleShortOption(p: var TOptParser) = - var i = p.pos - p.kind = cmdShortOption - add(p.key.string, p.cmd[i]) - inc(i) - p.inShortState = true - while p.cmd[i] in {'\x09', ' '}: - inc(i) - p.inShortState = false - 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) - if p.cmd[i] == '\0': p.inShortState = false - p.pos = i - -proc next*(p: var TOptParser) {. - rtl, extern: "npo$1".} = - ## parses the first or next option; ``p.kind`` describes what token has been - ## parsed. ``p.key`` and ``p.val`` are set accordingly. - var i = p.pos - while p.cmd[i] in {'\x09', ' '}: inc(i) - p.pos = i - setlen(p.key.string, 0) - setlen(p.val.string, 0) - if p.inShortState: - handleShortOption(p) - return - case p.cmd[i] - of '\0': +proc initOptParser*(cmdline: seq[string]): TOptParser {.rtl.} = + ## Initalizes option parses with cmdline. cmdline should not contain + ## argument 0 - program name. + ## If cmdline == nil default to current command line arguments. + result.remainingShortOptions = "" + when not defined(createNimRtl): + if cmdline == nil: + result.cmd = commandLineParams() + return + else: + assert cmdline != nil, "Cannot determine command line arguments." + + result.cmd = @cmdline + +proc initOptParser*(cmdline: string): TOptParser {.rtl, deprecated.} = + ## Initalizes option parses with cmdline. Splits cmdline in on spaces + ## and calls initOptParser(openarray[string]) + ## Do not use. + if cmdline == "": # backward compatibilty + return initOptParser(seq[string](nil)) + else: + return initOptParser(cmdline.split) + +when not defined(createNimRtl): + proc initOptParser*(): TOptParser = + ## Initializes option parser from current command line arguments. + return initOptParser(commandLineParams()) + +proc next*(p: var TOptParser) {.rtl, extern: "npo$1".} + +proc nextOption(p: var TOptParser, token: string, allowEmpty: bool) = + for splitchar in ['=', ':']: + if splitchar in token: + let pos = token.find(splitchar) + p.key = token[0..pos-1] + p.val = token[pos+1..token.len-1] + return + + p.key = token + if allowEmpty: + p.val = "" + else: + p.remainingShortOptions = token[0..token.len-1] + p.next() + +proc next(p: var TOptParser) = + if p.remainingShortOptions.len != 0: + p.kind = cmdShortOption + p.key = TaintedString(p.remainingShortOptions[0..0]) + p.val = "" + p.remainingShortOptions = p.remainingShortOptions[1..p.remainingShortOptions.len-1] + return + + if p.pos >= p.cmd.len: p.kind = cmdEnd - of '-': - inc(i) - if p.cmd[i] == '-': - 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) - while p.cmd[i] in {'\x09', ' '}: inc(i) - p.pos = parseWord(p.cmd, i, p.val.string) - else: - p.pos = i - else: - p.pos = i - handleShortOption(p) + return + + let token = p.cmd[p.pos] + p.pos += 1 + + if token.startswith("--"): + p.kind = cmdLongOption + nextOption(p, token[2..token.len-1], allowEmpty=true) + elif token.startswith("-"): + p.kind = cmdShortOption + nextOption(p, token[1..token.len-1], allowEmpty=true) else: p.kind = cmdArgument - p.pos = parseWord(p.cmd, i, p.key.string) + p.key = token + p.val = "" -proc cmdLineRest*(p: TOptParser): TaintedString {. - rtl, extern: "npo$1".} = - ## retrieves the rest of the command line that has not been parsed yet. - result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString +proc cmdLineRest*(p: TOptParser): TaintedString {.rtl, extern: "npo$1", deprecated.} = + ## Returns part of command line string that has not been parsed yet. + ## Do not use - does not correctly handle whitespace. + return p.cmd[p.pos..p.cmd.len-1].join(" ") -when defined(initOptParser): +type + TGetoptResult* = tuple[kind: TCmdLineKind, key, val: TaintedString] - iterator getopt*(): tuple[kind: TCmdLineKind, key, val: TaintedString] = +when defined(paramCount): + iterator getopt*(): TGetoptResult = ## This is an convenience iterator for iterating over the command line. ## This uses the TOptParser object. Example: ## @@ -135,7 +129,7 @@ when defined(initOptParser): ## filename = "" ## for kind, key, val in getopt(): ## case kind - ## of cmdArgument: + ## of cmdArgument: ## filename = key ## of cmdLongOption, cmdShortOption: ## case key diff --git a/tests/system/params.nim b/tests/system/params.nim new file mode 100644 index 000000000..1e8385dc8 --- /dev/null +++ b/tests/system/params.nim @@ -0,0 +1,17 @@ +import os +import osproc +import parseopt +import sequtils + +let argv = commandLineParams() + +if argv == @[]: + # this won't work with spaces + assert execShellCmd(getAppFilename() & " \"foo bar\" --aa:bar --ab -c --a[baz]:doo") == 0 +else: + let f = toSeq(getopt()) + echo f.repr + assert f[0].kind == cmdArgument and f[0].key == "foo bar" and f[0].val == "" + assert f[1].kind == cmdLongOption and f[1].key == "aa" and f[1].val == "bar" + assert f[2].kind == cmdLongOption and f[2].key == "ab" and f[2].val == "" + assert f[3].kind == cmdShortOption and f[3].key == "c" and f[3].val == "" |