Reimplement parseopt which parses arguments given as a sequence of strings, not single string.

This commit is contained in:
Michał Zieliński 2013-12-09 22:22:06 +01:00
commit d1f3512aba
3 changed files with 103 additions and 91 deletions

1
.gitignore vendored
View file

@ -180,3 +180,4 @@ examples/cross_calculator/android/tags
/tests/caas/main /tests/caas/main
/tests/caasdriver /tests/caasdriver
/tools/nimgrep /tools/nimgrep
/tests/system/params

View file

@ -10,123 +10,117 @@
## This module provides the standard Nimrod command line parser. ## This module provides the standard Nimrod command line parser.
## 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:
##
## 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.} {.push debugger: off.}
include "system/inclrtl" include "system/inclrtl"
import import
os, strutils os, strutils
type type
TCmdLineKind* = enum ## the detected command line token TCmdLineKind* = enum ## the detected command line token
cmdEnd, ## end of command line reached cmdEnd, ## end of command line reached
cmdArgument, ## argument detected cmdArgument, ## argument detected
cmdLongoption, ## a long option ``--option`` detected cmdLongOption, ## a long option ``--option`` detected
cmdShortOption ## a short option ``-c`` detected cmdShortOption ## a short option ``-c`` detected
TOptParser* = TOptParser* =
object of TObject ## this object implements the command line parser object of TObject ## this object implements the command line parser
cmd: string cmd: seq[string]
pos: int pos: int
inShortState: bool remainingShortOptions: string
kind*: TCmdLineKind ## the dected command line token kind*: TCmdLineKind ## the dected command line token
key*, val*: TaintedString ## key and value pair; ``key`` is the option key*, val*: TaintedString ## key and value pair; ``key`` is the option
## or the argument, ``value`` is not "" if ## or the argument, ``value`` is not "" if
## the option was given a value ## the option was given a value
when defined(os.ParamCount): proc initOptParser*(cmdline: seq[string]): TOptParser {.rtl.} =
# we cannot provide this for NimRtl creation on Posix, because we can't ## Initalizes option parses with cmdline. cmdline should not contain
# access the command line arguments then! ## 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."
proc initOptParser*(cmdline = ""): TOptParser = result.cmd = @cmdline
## 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, proc initOptParser*(cmdline: string): TOptParser {.rtl, deprecated.} =
delim: TCharSet = {'\x09', ' ', '\0'}): int = ## Initalizes option parses with cmdline. Splits cmdline in on spaces
result = i ## and calls initOptParser(openarray[string])
if s[result] == '\"': ## Do not use.
inc(result) if cmdline == "": # backward compatibilty
while not (s[result] in {'\0', '\"'}): return initOptParser(seq[string](nil))
add(w, s[result]) else:
inc(result) return initOptParser(cmdline.split)
if s[result] == '\"': inc(result)
else:
while not (s[result] in delim):
add(w, s[result])
inc(result)
proc handleShortOption(p: var TOptParser) = when not defined(createNimRtl):
var i = p.pos proc initOptParser*(): TOptParser =
p.kind = cmdShortOption ## Initializes option parser from current command line arguments.
add(p.key.string, p.cmd[i]) return initOptParser(commandLineParams())
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) {. proc next*(p: var TOptParser) {.rtl, extern: "npo$1".}
rtl, extern: "npo$1".} =
## parses the first or next option; ``p.kind`` describes what token has been proc nextOption(p: var TOptParser, token: string, allowEmpty: bool) =
## parsed. ``p.key`` and ``p.val`` are set accordingly. for splitchar in ['=', ':']:
var i = p.pos if splitchar in token:
while p.cmd[i] in {'\x09', ' '}: inc(i) let pos = token.find(splitchar)
p.pos = i p.key = token[0..pos-1]
setlen(p.key.string, 0) p.val = token[pos+1..token.len-1]
setlen(p.val.string, 0) return
if p.inShortState:
handleShortOption(p) p.key = token
return if allowEmpty:
case p.cmd[i] p.val = ""
of '\0': 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 p.kind = cmdEnd
of '-': return
inc(i)
if p.cmd[i] == '-': let token = p.cmd[p.pos]
p.kind = cmdLongOption p.pos += 1
inc(i)
i = parseWord(p.cmd, i, p.key.string, {'\0', ' ', '\x09', ':', '='}) if token.startswith("--"):
while p.cmd[i] in {'\x09', ' '}: inc(i) p.kind = cmdLongOption
if p.cmd[i] in {':', '='}: nextOption(p, token[2..token.len-1], allowEmpty=true)
inc(i) elif token.startswith("-"):
while p.cmd[i] in {'\x09', ' '}: inc(i) p.kind = cmdShortOption
p.pos = parseWord(p.cmd, i, p.val.string) nextOption(p, token[1..token.len-1], allowEmpty=true)
else:
p.pos = i
else:
p.pos = i
handleShortOption(p)
else: else:
p.kind = cmdArgument p.kind = cmdArgument
p.pos = parseWord(p.cmd, i, p.key.string) p.key = token
p.val = ""
proc cmdLineRest*(p: TOptParser): TaintedString {. proc cmdLineRest*(p: TOptParser): TaintedString {.rtl, extern: "npo$1", deprecated.} =
rtl, extern: "npo$1".} = ## Returns part of command line string that has not been parsed yet.
## retrieves the rest of the command line that has not been parsed yet. ## Do not use - does not correctly handle whitespace.
result = strip(substr(p.cmd, p.pos, len(p.cmd) - 1)).TaintedString 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 is an convenience iterator for iterating over the command line.
## This uses the TOptParser object. Example: ## This uses the TOptParser object. Example:
## ##
@ -135,7 +129,7 @@ when defined(initOptParser):
## filename = "" ## filename = ""
## for kind, key, val in getopt(): ## for kind, key, val in getopt():
## case kind ## case kind
## of cmdArgument: ## of cmdArgument:
## filename = key ## filename = key
## of cmdLongOption, cmdShortOption: ## of cmdLongOption, cmdShortOption:
## case key ## case key

17
tests/system/params.nim Normal file
View file

@ -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 == ""