diff options
Diffstat (limited to 'lib/pure/parseopt.nim')
-rw-r--r-- | lib/pure/parseopt.nim | 315 |
1 files changed, 266 insertions, 49 deletions
diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 06f32f032..0f8f8197c 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -11,23 +11,141 @@ ## It supports one convenience iterator over all command line options and some ## lower-level features. ## -## Supported syntax with default empty ``shortNoVal``/``longNoVal``: +## 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 +## The following syntax is supported when arguments for the ``shortNoVal`` and +## ``longNoVal`` parameters, which are +## `described later<#shortnoval-and-longnoval>`_, are not provided: ## -## 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. +## 1. Short options: ``-abcd``, ``-e:5``, ``-e=5`` +## 2. Long options: ``--foo:bar``, ``--foo=bar``, ``--foo`` +## 3. Arguments: everything that does not start with a ``-`` ## -## When option values begin with ':' or '=' they need to be doubled up (as in +## These three kinds of tokens are enumerated in the +## `CmdLineKind enum<#CmdLineKind>`_. +## +## 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. +## The ``--`` option, commonly used to denote that every token that follows is +## an argument, is interpreted as a long option, and its name is the empty +## string. +## +## Parsing +## ======= +## +## Use an `OptParser<#OptParser>`_ to parse command line options. It can be +## created with `initOptParser<#initOptParser,string,set[char],seq[string]>`_, +## and `next<#next,OptParser>`_ advances the parser by one token. +## +## For each token, the parser's ``kind``, ``key``, and ``val`` fields give +## information about that token. If the token is a long or short option, ``key`` +## is the option's name, and ``val`` is either the option's value, if provided, +## or the empty string. For arguments, the ``key`` field contains the argument +## itself, and ``val`` is unused. To check if the end of the command line has +## been reached, check if ``kind`` is equal to ``cmdEnd``. +## +## Here is an example: +## +## .. code-block:: +## import parseopt +## +## var p = initOptParser("-ab -e:5 --foo --bar=20 file.txt") +## while true: +## p.next() +## case p.kind +## of cmdEnd: break +## of cmdShortOption, cmdLongOption: +## if p.val == "": +## echo "Option: ", p.key +## else: +## echo "Option and value: ", p.key, ", ", p.val +## of cmdArgument: +## echo "Argument: ", p.key +## +## # Output: +## # Option: a +## # Option: b +## # Option and value: e, 5 +## # Option: foo +## # Option and value: bar, 20 +## # Argument: file.txt +## +## The `getopt iterator<#getopt.i,OptParser>`_, which is provided for +## convenience, can be used to iterate through all command line options as well. +## +## ``shortNoVal`` and ``longNoVal`` +## ================================ +## +## The optional ``shortNoVal`` and ``longNoVal`` parameters present in +## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ are for +## specifying which short and long options do not accept values. +## +## When ``shortNoVal`` is non-empty, users are not required to separate short +## options and their values with a ':' or '=' since the parser knows which +## options accept values and which ones do not. This behavior also applies for +## long options if ``longNoVal`` is non-empty. For short options, ``-j4`` +## becomes supported syntax, and for long options, ``--foo bar`` becomes +## supported. This is in addition to the `previously mentioned +## syntax<#supported-syntax>`_. Users can still separate options and their +## values with ':' or '=', but that becomes optional. +## +## As more options which do not accept values are added to your program, +## remember to amend ``shortNoVal`` and ``longNoVal`` accordingly. +## +## The following example illustrates the difference between having an empty +## ``shortNoVal`` and ``longNoVal``, which is the default, and providing +## arguments for those two parameters: +## +## .. code-block:: +## import parseopt +## +## proc printToken(kind: CmdLineKind, key: string, val: string) = +## case kind +## of cmdEnd: doAssert(false) # Doesn't happen with getopt() +## of cmdShortOption, cmdLongOption: +## if val == "": +## echo "Option: ", key +## else: +## echo "Option and value: ", key, ", ", val +## of cmdArgument: +## echo "Argument: ", key +## +## let cmdLine = "-j4 --first bar" +## +## var emptyNoVal = initOptParser(cmdLine) +## for kind, key, val in emptyNoVal.getopt(): +## printToken(kind, key, val) +## +## # Output: +## # Option: j +## # Option: 4 +## # Option: first +## # Argument: bar +## +## var withNoVal = initOptParser(cmdLine, shortNoVal = {'c'}, +## longNoVal = @["second"]) +## for kind, key, val in withNoVal.getopt(): +## printToken(kind, key, val) +## +## # Output: +## # Option and value: j, 4 +## # Option and value: first, bar +## +## See also +## ======== +## +## * `os module<os.html>`_ for lower-level command line parsing procs +## * `parseutils module<parseutils.html>`_ for helpers that parse tokens, +## numbers, identifiers, etc. +## * `strutils module<strutils.html>`_ for common string handling operations +## * `json module<json.html>`_ for a JSON parser +## * `parsecfg module<parsecfg.html>`_ for a configuration file parser +## * `parsecsv module<parsecsv.html>`_ for a simple CSV (comma separated value) +## parser +## * `parsexml module<parsexml.html>`_ for a XML / HTML parser +## * `other parsers<lib.html#pure-libraries-parsers>`_ for more parsers {.push debugger: off.} @@ -37,23 +155,26 @@ import os, strutils type - CmdLineKind* = enum ## the detected command line token - cmdEnd, ## end of command line reached - cmdArgument, ## argument detected - cmdLongOption, ## a long option ``--option`` detected - cmdShortOption ## a short option ``-c`` detected + CmdLineKind* = enum ## The detected command line token. + cmdEnd, ## End of command line reached + cmdArgument, ## An argument such as a filename + cmdLongOption, ## A long option such as --option + cmdShortOption ## A short option such as -c OptParser* = - object of RootObj ## this object implements the command line parser - pos*: int # ..empty key or subcmd cmdArg & handle specially + object of RootObj ## Implementation of the command line parser. + ## + ## To initialize it, use the + ## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_. + pos*: int inShortState: bool allowWhitespaceAfterColon: bool shortNoVal: set[char] longNoVal: seq[string] cmds: seq[string] idx: int - 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 + kind*: CmdLineKind ## The detected command line token + key*, val*: TaintedString ## Key and value pair; the key is the option + ## or the argument, and the value is not "" if ## the option was given a value proc parseWord(s: string, i: int, w: var string, @@ -79,13 +200,24 @@ when declared(os.paramCount): proc initOptParser*(cmdline = "", shortNoVal: set[char]={}, longNoVal: seq[string] = @[]; allowWhitespaceAfterColon = true): OptParser = - ## inits the option parser. If ``cmdline == ""``, the real command line - ## (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. + ## Initializes the command line parser. + ## + ## If ``cmdline == ""``, the real command line as provided by the + ## ``os`` module is retrieved instead. + ## + ## ``shortNoVal`` and ``longNoVal`` are used to specify which options + ## do not take values. See the `documentation about these + ## parameters<#shortnoval-and-longnoval>`_ for more information on + ## how this affects parsing. + ## + ## See also: + ## * `getopt iterator<#getopt.i,OptParser>`_ + runnableExamples: + var p = initOptParser() + p = initOptParser("--left --debug:3 -l -r:2") + p = initOptParser("--left --debug:3 -l -r:2", + shortNoVal = {'l'}, longNoVal = @["left"]) + result.pos = 0 result.idx = 0 result.inShortState = false @@ -106,9 +238,21 @@ when declared(os.paramCount): proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char]={}, longNoVal: seq[string] = @[]; allowWhitespaceAfterColon = true): OptParser = - ## inits the option parser. If ``cmdline.len == 0``, the real command line - ## (as provided by the ``OS`` module) is taken. ``shortNoVal`` and - ## ``longNoVal`` behavior is the same as for ``initOptParser(string,...)``. + ## Initializes the command line parser. + ## + ## If ``cmdline.len == 0``, the real command line as provided by the + ## ``os`` module is retrieved instead. Behavior of the other parameters + ## remains the same as in `initOptParser(string, ...) + ## <#initOptParser,string,set[char],seq[string]>`_. + ## + ## See also: + ## * `getopt iterator<#getopt.i,seq[TaintedString],set[char],seq[string]>`_ + runnableExamples: + var p = initOptParser() + p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"]) + p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"], + shortNoVal = {'l'}, longNoVal = @["left"]) + result.pos = 0 result.idx = 0 result.inShortState = false @@ -153,8 +297,21 @@ proc handleShortOption(p: var OptParser; cmd: string) = inc p.idx proc next*(p: var OptParser) {.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. + ## Parses the next token. + ## + ## ``p.kind`` describes what kind of token has been parsed. ``p.key`` and + ## ``p.val`` are set accordingly. + runnableExamples: + var p = initOptParser("--left -r:2 file.txt") + p.next() + doAssert p.kind == cmdLongOption and p.key == "left" + p.next() + doAssert p.kind == cmdShortOption and p.key == "r" and p.val == "2" + p.next() + doAssert p.kind == cmdArgument and p.key == "file.txt" + p.next() + doAssert p.kind == cmdEnd + if p.idx >= p.cmds.len: p.kind = cmdEnd return @@ -209,20 +366,61 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = when declared(os.paramCount): proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = - ## retrieves the rest of the command line that has not been parsed yet. + ## Retrieves the rest of the command line that has not been parsed yet. + ## + ## See also: + ## * `remainingArgs proc<#remainingArgs,OptParser>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var p = initOptParser("--left -r:2 -- foo.txt bar.txt") + ## while true: + ## p.next() + ## if p.kind == cmdLongOption and p.key == "": # Look for "--" + ## break + ## else: continue + ## doAssert p.cmdLineRest == "foo.txt bar.txt" result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} = - ## retrieves the rest of the command line that has not been parsed yet. + ## Retrieves a sequence of the arguments that have not been parsed yet. + ## + ## See also: + ## * `cmdLineRest proc<#cmdLineRest,OptParser>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var p = initOptParser("--left -r:2 -- foo.txt bar.txt") + ## while true: + ## p.next() + ## if p.kind == cmdLongOption and p.key == "": # Look for "--" + ## break + ## else: continue + ## doAssert p.remainingArgs == @["foo.txt", "bar.txt"] result = @[] for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i]) iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] = - ## This is an convenience iterator for iterating over the given OptParser object. - ## Example: + ## Convenience iterator for iterating over the given + ## `OptParser<#OptParser>`_. + ## + ## There is no need to check for ``cmdEnd`` while iterating. ## - ## .. code-block:: nim + ## See also: + ## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## # these are placeholders, of course + ## proc writeHelp() = discard + ## proc writeVersion() = discard + ## + ## var filename: string ## var p = initOptParser("--left --debug:3 -l -r:2") + ## ## for kind, key, val in p.getopt(): ## case kind ## of cmdArgument: @@ -233,7 +431,7 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedSt ## of "version", "v": writeVersion() ## of cmdEnd: assert(false) # cannot happen ## if filename == "": - ## # no filename has been given, so we show the help: + ## # no filename has been given, so we show the help ## writeHelp() p.pos = 0 p.idx = 0 @@ -246,15 +444,34 @@ when declared(initOptParser): 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: + ## Convenience iterator for iterating over command line arguments. + ## + ## This creates a new `OptParser<#OptParser>`_. If no command line + ## arguments are provided, the real command line as provided by the + ## ``os`` module is retrieved instead. + ## + ## ``shortNoVal`` and ``longNoVal`` are used to specify which options + ## do not take values. See the `documentation about these + ## parameters<#shortnoval-and-longnoval>`_ for more information on + ## how this affects parsing. + ## + ## There is no need to check for ``cmdEnd`` while iterating. ## - ## .. code-block:: nim - ## for kind, key, val in getopt(shortNoVal = { 'l' }, - ## longNoVal = @[ "left" ]): + ## See also: + ## * `initOptParser proc<#initOptParser,seq[TaintedString],set[char],seq[string]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## + ## # these are placeholders, of course + ## proc writeHelp() = discard + ## proc writeVersion() = discard + ## + ## var filename: string + ## let params = @["--left", "--debug:3", "-l", "-r:2"] + ## + ## for kind, key, val in getopt(params): ## case kind ## of cmdArgument: ## filename = key @@ -264,8 +481,8 @@ when declared(initOptParser): ## of "version", "v": writeVersion() ## of cmdEnd: assert(false) # cannot happen ## if filename == "": + ## # no filename has been written, so we show the help ## writeHelp() - ## var p = initOptParser(cmdline, shortNoVal=shortNoVal, longNoVal=longNoVal) while true: next(p) |