summary refs log tree commit diff stats
path: root/lib/pure/parseopt.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/parseopt.nim')
-rw-r--r--lib/pure/parseopt.nim315
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)