summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorMichał Zieliński <michal@zielinscy.org.pl>2013-12-09 22:22:06 +0100
committerMichał Zieliński <michal@zielinscy.org.pl>2013-12-09 23:29:16 +0100
commitd1f3512aba83814146583538c46b7f0c84175423 (patch)
tree3425909ffab4a4fa5fc2d1f6c7130c65465296ae
parent8dae66415966e2cba50fd5295c514a97ff187fc2 (diff)
downloadNim-d1f3512aba83814146583538c46b7f0c84175423.tar.gz
Reimplement parseopt which parses arguments given as a sequence of strings, not single string.
-rw-r--r--.gitignore1
-rw-r--r--lib/pure/parseopt.nim184
-rw-r--r--tests/system/params.nim17
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 == ""