Reimplement parseopt which parses arguments given as a sequence of strings, not single string.
This commit is contained in:
parent
8dae664159
commit
d1f3512aba
3 changed files with 103 additions and 91 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@
|
||||||
## 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.}
|
||||||
|
|
||||||
|
|
@ -22,111 +28,99 @@ 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.
|
||||||
proc initOptParser*(cmdline = ""): TOptParser =
|
result.remainingShortOptions = ""
|
||||||
## inits the option parser. If ``cmdline == ""``, the real command line
|
when not defined(createNimRtl):
|
||||||
## (as provided by the ``OS`` module) is taken.
|
if cmdline == nil:
|
||||||
result.pos = 0
|
result.cmd = commandLineParams()
|
||||||
result.inShortState = false
|
return
|
||||||
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:
|
else:
|
||||||
while not (s[result] in delim):
|
assert cmdline != nil, "Cannot determine command line arguments."
|
||||||
add(w, s[result])
|
|
||||||
inc(result)
|
|
||||||
|
|
||||||
proc handleShortOption(p: var TOptParser) =
|
result.cmd = @cmdline
|
||||||
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) {.
|
proc initOptParser*(cmdline: string): TOptParser {.rtl, deprecated.} =
|
||||||
rtl, extern: "npo$1".} =
|
## Initalizes option parses with cmdline. Splits cmdline in on spaces
|
||||||
## parses the first or next option; ``p.kind`` describes what token has been
|
## and calls initOptParser(openarray[string])
|
||||||
## parsed. ``p.key`` and ``p.val`` are set accordingly.
|
## Do not use.
|
||||||
var i = p.pos
|
if cmdline == "": # backward compatibilty
|
||||||
while p.cmd[i] in {'\x09', ' '}: inc(i)
|
return initOptParser(seq[string](nil))
|
||||||
p.pos = i
|
else:
|
||||||
setlen(p.key.string, 0)
|
return initOptParser(cmdline.split)
|
||||||
setlen(p.val.string, 0)
|
|
||||||
if p.inShortState:
|
when not defined(createNimRtl):
|
||||||
handleShortOption(p)
|
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
|
return
|
||||||
case p.cmd[i]
|
|
||||||
of '\0':
|
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:
|
||||||
##
|
##
|
||||||
|
|
|
||||||
17
tests/system/params.nim
Normal file
17
tests/system/params.nim
Normal 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 == ""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue