summary refs log tree commit diff stats
path: root/lib/pure/parseopt.nim
blob: a95a5b48d7a5d05a788d82d659a1bd3d56f0e143 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
Original prototype, last modified 2015-03-14

First install [Racket](http://racket-lang.org) (just for the initial
prototype). Then:

  $ cd mu/arc
  $ git clone http://github.com/arclanguage/anarki
  $ ./mu test mu.arc.t  # run tests

Example programs:

  $ ./mu factorial.mu  # computes factorial of 5
  $ ./mu fork.mu  # two threads print '33' and '34' forever
  $ ./mu channel.mu  # two threads in a producer/consumer relationship
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
#
#
#            Nim's Runtime Library
#        (c) Copyright 2015 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module provides the standard Nim command line parser.
## It supports one convenience iterator over all command line options and some
## lower-level features.
##
## 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:
##
## 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=:``).
##
## 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.}

include "system/inclrtl"

import os

type
  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 ## \
    ## 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 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,
               delim: set[char] = {'\t', ' '}): int =
  result = i
  if result < s.len and s[result] == '\"':
    inc(result)
    while result < s.len:
      if s[result] == '"':
        inc result
        break
      add(w, s[result])
      inc(result)
  else:
    while result < s.len and s[result] notin delim:
      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"])

    result.pos = 0
    result.idx = 0
    result.inShortState = false
    result.shortNoVal = shortNoVal
    result.longNoVal = longNoVal
    result.allowWhitespaceAfterColon = allowWhitespaceAfterColon
    if cmdline != "":
      result.cmds = parseCmdLine(cmdline)
    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""

  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"])

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

proc handleShortOption(p: var OptParser; cmd: string) =
  var i = p.pos
  p.kind = cmdShortOption
  if i < cmd.len:
    add(p.key.string, 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 {':', '='}:
      inc(i)
    p.inShortState = false
    while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i)
    p.val = TaintedString substr(cmd, i)
    p.pos = 0
    inc p.idx
  else:
    p.pos = i
  if i >= cmd.len:
    p.inShortState = false
    p.pos = 0
    inc p.idx

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.
  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

  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)
  if p.inShortState:
    p.inShortState = false
    if i >= p.cmds[p.idx].len:
      inc(p.idx)
      p.pos = 0
      if p.idx >= p.cmds.len:
        p.kind = cmdEnd
        return
    else:
      handleShortOption(p, p.cmds[p.idx])
      return

  if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
    inc(i)
    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', ':', '='})
      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)
        while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
        # if we're at the end, use the next command line option:
        if i >= p.cmds[p.idx].len and p.idx < p.cmds.len and
            p.allowWhitespaceAfterColon:
          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]
        inc p.idx
      else:
        p.val = TaintedString""
      inc p.idx
      p.pos = 0
    else:
      p.pos = i
      handleShortOption(p, p.cmds[p.idx])
  else:
    p.kind = cmdArgument
    p.key = TaintedString 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

proc remainingArgs*(p: OptParser): seq[TaintedString] {.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::
  ##   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] =
  ## Convenience iterator for iterating over the given
  ## `OptParser<#OptParser>`_.
  ##
  ## There is no need to check for ``cmdEnd`` while iterating.
  ##
  ## 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:
  ##       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 given, so we show the help
  ##     writeHelp()
  p.pos = 0
  p.idx = 0
  while true:
    next(p)
    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)

{.pop.}