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.nim410
1 files changed, 220 insertions, 190 deletions
diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim
index a95a5b48d..03f151b66 100644
--- a/lib/pure/parseopt.nim
+++ b/lib/pure/parseopt.nim
@@ -14,21 +14,21 @@
 ## Supported Syntax
 ## ================
 ##
-## The following syntax is supported when arguments for the ``shortNoVal`` and
-## ``longNoVal`` parameters, which are
-## `described later<#shortnoval-and-longnoval>`_, are not provided:
+## The following syntax is supported when arguments for the `shortNoVal` and
+## `longNoVal` parameters, which are
+## `described later<#nimshortnoval-and-nimlongnoval>`_, are not provided:
 ##
-## 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 ``-``
+## 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 `-`
 ##
 ## 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=:``).
+## `--delim::`) or alternated (as in `--delim=:`).
 ##
-## The ``--`` option, commonly used to denote that every token that follows is
+## 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.
 ##
@@ -39,17 +39,17 @@
 ## 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``.
+## 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
+##   ```Nim
+##   import std/parseopt
 ##
 ##   var p = initOptParser("-ab -e:5 --foo --bar=20 file.txt")
 ##   while true:
@@ -71,35 +71,59 @@
 ##   # 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``
-## ================================
+## To set a default value for a variable assigned through `getopt` and accept arguments from the cmd line.
+## Assign the default value to a variable before parsing.
+## Then set the variable to the new value while parsing.
 ##
-## The optional ``shortNoVal`` and ``longNoVal`` parameters present in
+## Here is an example:
+##
+##   ```Nim
+##   import std/parseopt
+##
+##   var varName: string = "defaultValue"
+##
+##   for kind, key, val in getopt():
+##     case kind
+##     of cmdArgument:
+##       discard
+##     of cmdLongOption, cmdShortOption:
+##       case key:
+##       of "varName": # --varName:<value> in the console when executing
+##         varName = val # do input sanitization in production systems
+##     of cmdEnd:
+##       discard
+##   ```
+##
+## `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
+## 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
+## 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.
+## 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
+## `shortNoVal` and `longNoVal`, which is the default, and providing
 ## arguments for those two parameters:
 ##
-## .. code-block::
-##   import parseopt
+##   ```Nim
+##   import std/parseopt
 ##
 ##   proc printToken(kind: CmdLineKind, key: string, val: string) =
 ##     case kind
@@ -132,6 +156,7 @@
 ##   # Output:
 ##   # Option and value: j, 4
 ##   # Option and value: first, bar
+##   ```
 ##
 ## See also
 ## ========
@@ -151,7 +176,8 @@
 
 include "system/inclrtl"
 
-import os
+import std/strutils
+import std/os
 
 type
   CmdLineKind* = enum ## The detected command line token.
@@ -164,7 +190,7 @@ type
     ##
     ## To initialize it, use the
     ## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_.
-    pos*: int
+    pos: int
     inShortState: bool
     allowWhitespaceAfterColon: bool
     shortNoVal: set[char]
@@ -172,7 +198,7 @@ type
     cmds: seq[string]
     idx: int
     kind*: CmdLineKind           ## The detected command line token
-    key*, val*: TaintedString    ## Key and value pair; the key is the option
+    key*, val*: string           ## Key and value pair; the key is the option
                                  ## or the argument, and the value is not "" if
                                  ## the option was given a value
 
@@ -192,101 +218,104 @@ proc parseWord(s: string, i: int, w: var string,
       add(w, s[result])
       inc(result)
 
-when declared(os.paramCount):
-  # we cannot provide this for NimRtl creation on Posix, because we can't
-  # access the command line arguments then!
-
-  proc initOptParser*(cmdline = "", shortNoVal: set[char] = {},
-                      longNoVal: seq[string] = @[];
-                      allowWhitespaceAfterColon = true): OptParser =
-    ## 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"])
+proc initOptParser*(cmdline: seq[string], shortNoVal: set[char] = {},
+                    longNoVal: seq[string] = @[];
+                    allowWhitespaceAfterColon = true): OptParser =
+  ## Initializes the command line parser.
+  ##
+  ## If `cmdline.len == 0`, the real command line as provided by the
+  ## `os` module is retrieved instead if it is available. If the
+  ## command line is not available, a `ValueError` will be raised.
+  ## 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[string],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
-    result.shortNoVal = shortNoVal
-    result.longNoVal = longNoVal
-    result.allowWhitespaceAfterColon = allowWhitespaceAfterColon
-    if cmdline != "":
-      result.cmds = parseCmdLine(cmdline)
+  result.pos = 0
+  result.idx = 0
+  result.inShortState = false
+  result.shortNoVal = shortNoVal
+  result.longNoVal = longNoVal
+  result.allowWhitespaceAfterColon = allowWhitespaceAfterColon
+  if cmdline.len != 0:
+    result.cmds = newSeq[string](cmdline.len)
+    for i in 0..<cmdline.len:
+      result.cmds[i] = cmdline[i]
+  else:
+    when declared(paramCount):
+      when defined(nimscript):
+        var ctr = 0
+        var firstNimsFound = false
+        for i in countup(0, paramCount()):
+          if firstNimsFound: 
+            result.cmds[ctr] = paramStr(i)
+            inc ctr, 1
+          if paramStr(i).endsWith(".nims") and not firstNimsFound:
+            firstNimsFound = true 
+            result.cmds = newSeq[string](paramCount()-i)
+      else:
+        result.cmds = newSeq[string](paramCount())
+        for i in countup(1, paramCount()):
+          result.cmds[i-1] = paramStr(i)
     else:
-      result.cmds = newSeq[string](os.paramCount())
-      for i in countup(1, os.paramCount()):
-        result.cmds[i-1] = os.paramStr(i).string
+      # we cannot provide this for NimRtl creation on Posix, because we can't
+      # access the command line arguments then!
+      raiseAssert "empty command line given but" &
+        " real command line is not accessible"
+  result.kind = cmdEnd
+  result.key = ""
+  result.val = ""
 
-    result.kind = cmdEnd
-    result.key = TaintedString""
-    result.val = TaintedString""
-
-  proc initOptParser*(cmdline: seq[TaintedString], shortNoVal: set[char] = {},
-                      longNoVal: seq[string] = @[];
-                      allowWhitespaceAfterColon = true): OptParser =
-    ## 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"])
+proc initOptParser*(cmdline = "", shortNoVal: set[char] = {},
+                    longNoVal: seq[string] = @[];
+                    allowWhitespaceAfterColon = true): OptParser =
+  ## Initializes the command line parser.
+  ##
+  ## If `cmdline == ""`, the real command line as provided by the
+  ## `os` module is retrieved instead if it is available. If the
+  ## command line is not available, a `ValueError` will be raised.
+  ##
+  ## `shortNoVal` and `longNoVal` are used to specify which options
+  ## do not take values. See the `documentation about these
+  ## parameters<#nimshortnoval-and-nimlongnoval>`_ for more information on
+  ## how this affects parsing.
+  ##
+  ## This does not provide a way of passing default values to arguments.
+  ##
+  ## 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
-    result.shortNoVal = shortNoVal
-    result.longNoVal = longNoVal
-    result.allowWhitespaceAfterColon = allowWhitespaceAfterColon
-    if cmdline.len != 0:
-      result.cmds = newSeq[string](cmdline.len)
-      for i in 0..<cmdline.len:
-        result.cmds[i] = cmdline[i].string
-    else:
-      result.cmds = newSeq[string](os.paramCount())
-      for i in countup(1, os.paramCount()):
-        result.cmds[i-1] = os.paramStr(i).string
-    result.kind = cmdEnd
-    result.key = TaintedString""
-    result.val = TaintedString""
+  initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, allowWhitespaceAfterColon)
 
 proc handleShortOption(p: var OptParser; cmd: string) =
   var i = p.pos
   p.kind = cmdShortOption
   if i < cmd.len:
-    add(p.key.string, cmd[i])
+    add(p.key, cmd[i])
     inc(i)
   p.inShortState = true
   while i < cmd.len and cmd[i] in {'\t', ' '}:
     inc(i)
     p.inShortState = false
-  if i < cmd.len and cmd[i] in {':', '='} or
-      card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal:
+  if i < cmd.len and (cmd[i] in {':', '='} or
+      card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal):
     if i < cmd.len and cmd[i] in {':', '='}:
       inc(i)
     p.inShortState = false
     while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i)
-    p.val = TaintedString substr(cmd, i)
+    p.val = substr(cmd, i)
     p.pos = 0
     inc p.idx
   else:
@@ -299,8 +328,8 @@ proc handleShortOption(p: var OptParser; cmd: string) =
 proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
   ## Parses the next token.
   ##
-  ## ``p.kind`` describes what kind of token has been parsed. ``p.key`` and
-  ## ``p.val`` are set accordingly.
+  ## `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()
@@ -319,8 +348,8 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
   var i = p.pos
   while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
   p.pos = i
-  setLen(p.key.string, 0)
-  setLen(p.val.string, 0)
+  setLen(p.key, 0)
+  setLen(p.val, 0)
   if p.inShortState:
     p.inShortState = false
     if i >= p.cmds[p.idx].len:
@@ -338,7 +367,7 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
     if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
       p.kind = cmdLongOption
       inc(i)
-      i = parseWord(p.cmds[p.idx], i, p.key.string, {' ', '\t', ':', '='})
+      i = parseWord(p.cmds[p.idx], i, p.key, {' ', '\t', ':', '='})
       while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
       if i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {':', '='}:
         inc(i)
@@ -349,12 +378,12 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
           inc p.idx
           i = 0
         if p.idx < p.cmds.len:
-          p.val = TaintedString p.cmds[p.idx].substr(i)
-      elif len(p.longNoVal) > 0 and p.key.string notin p.longNoVal and p.idx+1 < p.cmds.len:
-        p.val = TaintedString p.cmds[p.idx+1]
+          p.val = p.cmds[p.idx].substr(i)
+      elif len(p.longNoVal) > 0 and p.key notin p.longNoVal and p.idx+1 < p.cmds.len:
+        p.val = p.cmds[p.idx+1]
         inc p.idx
       else:
-        p.val = TaintedString""
+        p.val = ""
       inc p.idx
       p.pos = 0
     else:
@@ -362,60 +391,60 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
       handleShortOption(p, p.cmds[p.idx])
   else:
     p.kind = cmdArgument
-    p.key = TaintedString p.cmds[p.idx]
+    p.key = p.cmds[p.idx]
     inc p.idx
     p.pos = 0
 
-proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} =
-  ## 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
+when declared(quoteShellCommand):
+  proc cmdLineRest*(p: OptParser): string {.rtl, extern: "npo$1".} =
+    ## Retrieves the rest of the command line that has not been parsed yet.
+    ##
+    ## See also:
+    ## * `remainingArgs proc<#remainingArgs,OptParser>`_
+    ##
+    ## **Examples:**
+    ##   ```Nim
+    ##   var p = initOptParser("--left -r:2 -- foo.txt bar.txt")
+    ##   while true:
+    ##     p.next()
+    ##     if p.kind == cmdLongOption and p.key == "":  # Look for "--"
+    ##       break
+    ##   doAssert p.cmdLineRest == "foo.txt bar.txt"
+    ##   ```
+    result = p.cmds[p.idx .. ^1].quoteShellCommand
 
-proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} =
+proc remainingArgs*(p: OptParser): seq[string] {.rtl, extern: "npo$1".} =
   ## Retrieves a sequence of the arguments that have not been parsed yet.
   ##
   ## See also:
   ## * `cmdLineRest proc<#cmdLineRest,OptParser>`_
   ##
   ## **Examples:**
-  ##
-  ## .. code-block::
+  ##   ```Nim
   ##   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])
+  for i in p.idx..<p.cmds.len: result.add p.cmds[i]
 
 iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
-    val: TaintedString] =
+    val: string] =
   ## Convenience iterator for iterating over the given
   ## `OptParser<#OptParser>`_.
   ##
-  ## There is no need to check for ``cmdEnd`` while iterating.
+  ## There is no need to check for `cmdEnd` while iterating. If using `getopt`
+  ## with case switching, checking for `cmdEnd` is required.
   ##
   ## See also:
   ## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_
   ##
   ## **Examples:**
   ##
-  ## .. code-block::
+  ##   ```Nim
   ##   # these are placeholders, of course
   ##   proc writeHelp() = discard
   ##   proc writeVersion() = discard
@@ -435,6 +464,7 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
   ##   if filename == "":
   ##     # no filename has been given, so we show the help
   ##     writeHelp()
+  ##   ```
   p.pos = 0
   p.idx = 0
   while true:
@@ -442,54 +472,54 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
     if p.kind == cmdEnd: break
     yield (p.kind, p.key, p.val)
 
-when declared(initOptParser):
-  iterator getopt*(cmdline: seq[TaintedString] = commandLineParams(),
-                   shortNoVal: set[char] = {}, longNoVal: seq[string] = @[]):
-             tuple[kind: CmdLineKind, key, val: TaintedString] =
-    ## 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.
-    ##
-    ## 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
-    ##     of cmdLongOption, cmdShortOption:
-    ##       case key
-    ##       of "help", "h": writeHelp()
-    ##       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)
-      if p.kind == cmdEnd: break
-      yield (p.kind, p.key, p.val)
+iterator getopt*(cmdline: seq[string] = @[],
+                  shortNoVal: set[char] = {}, longNoVal: seq[string] = @[]):
+            tuple[kind: CmdLineKind, key, val: string] =
+  ## 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<#nimshortnoval-and-nimlongnoval>`_ for more information on
+  ## how this affects parsing.
+  ##
+  ## There is no need to check for `cmdEnd` while iterating. If using `getopt`
+  ## with case switching, checking for `cmdEnd` is required.
+  ##
+  ## See also:
+  ## * `initOptParser proc<#initOptParser,seq[string],set[char],seq[string]>`_
+  ##
+  ## **Examples:**
+  ##
+  ##   ```Nim
+  ##   # 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
+  ##     of cmdLongOption, cmdShortOption:
+  ##       case key
+  ##       of "help", "h": writeHelp()
+  ##       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)
+    if p.kind == cmdEnd: break
+    yield (p.kind, p.key, p.val)
 
 {.pop.}