mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-22 14:50:57 +00:00
Merge branch 'devel' into pr_Kisten
This commit is contained in:
@@ -61,6 +61,10 @@ errors.
|
||||
|
||||
- `system.setLenUninit` now supports refc, JS and VM backends.
|
||||
|
||||
- `std/parseopt` now supports multiple parser modes via a `CliMode` enum.
|
||||
Modes include `Nim` (default, fully compatible) and two new experimental modes:
|
||||
`Lax` and `Gnu` for different option parsing behaviors.
|
||||
|
||||
[//]: # "Changes:"
|
||||
|
||||
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.
|
||||
|
||||
@@ -3271,7 +3271,11 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "raiseObjectConversionError"))
|
||||
raiseInstr(p, p.s(cpsStmts))
|
||||
|
||||
if n[0].typ.kind != tyObject:
|
||||
# skip cast when types map to the same C type
|
||||
# this avoids invalid C code like `*(T*)&x` for types that can't have their address taken (e.g., WASM __externref_t)
|
||||
if getTypeDesc(p.module, n.typ) == getTypeDesc(p.module, n[0].typ):
|
||||
expr(p, n[0], d)
|
||||
elif n[0].typ.kind != tyObject:
|
||||
let destTyp = getTypeDesc(p.module, n.typ)
|
||||
let val = rdLoc(a)
|
||||
if n.isLValue:
|
||||
@@ -3317,7 +3321,7 @@ proc downConv(p: BProc, n: PNode, d: var TLoc) =
|
||||
cCast(ptrType(destType),
|
||||
wrapPar(cAddr(wrapPar(val))))),
|
||||
a.storage)
|
||||
elif p.module.compileToCpp:
|
||||
elif p.module.compileToCpp or isImportedType(src):
|
||||
# C++ implicitly downcasts for us
|
||||
expr(p, arg, d)
|
||||
else:
|
||||
|
||||
@@ -41,6 +41,7 @@ type
|
||||
CoType
|
||||
CoOwnerSig
|
||||
CoIgnoreRange
|
||||
CoIgnoreRangeInArray
|
||||
CoConsiderOwned
|
||||
CoDistinct
|
||||
CoHashTypeInsideNode
|
||||
@@ -220,10 +221,17 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
else:
|
||||
for a in t.kids: c.hashType a, flags+{CoIgnoreRange}, conf
|
||||
of tyRange:
|
||||
if CoIgnoreRange notin flags:
|
||||
if {CoIgnoreRange, CoIgnoreRangeInArray} * flags == {}:
|
||||
c &= char(t.kind)
|
||||
c.hashTree(t.n, {}, conf)
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
elif CoIgnoreRangeInArray in flags:
|
||||
# include only the length of the range (not its specific bounds)
|
||||
c &= char(t.kind)
|
||||
let l = lengthOrd(conf, t)
|
||||
lowlevel l
|
||||
else:
|
||||
c.hashType(t.elementType, flags, conf)
|
||||
of tyStatic:
|
||||
c &= char(t.kind)
|
||||
c.hashTree(t.n, {}, conf)
|
||||
@@ -253,7 +261,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
|
||||
if tfVarargs in t.flags: c &= ".varargs"
|
||||
of tyArray:
|
||||
c &= char(t.kind)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}, conf)
|
||||
c.hashType(t.indexType, flags-{CoIgnoreRange}+{CoIgnoreRangeInArray}, conf)
|
||||
c.hashType(t.elementType, flags-{CoIgnoreRange}, conf)
|
||||
else:
|
||||
c &= char(t.kind)
|
||||
|
||||
@@ -14,149 +14,306 @@
|
||||
## Supported Syntax
|
||||
## ================
|
||||
##
|
||||
## The following syntax is supported when arguments for the `shortNoVal` and
|
||||
## `longNoVal` parameters, which are
|
||||
## `described later<#nimshortnoval-and-nimlongnoval>`_, are not provided:
|
||||
## The syntax described here applies to the default way the parser works.
|
||||
## The behavior is configurable, though, and two additional modes
|
||||
## are supported, see the details: `Parser Modes`_.
|
||||
##
|
||||
## 1. Short options: `-abcd`, `-e:5`, `-e=5`
|
||||
## Parsing also depends on whether the `shortNoVal` and `longNoVal` parameters
|
||||
## are omitted/empty or provided. The details are described in a
|
||||
## `later section<#nimshortnoval-and-nimlongnoval>`_.
|
||||
##
|
||||
## The following syntax is supported:
|
||||
##
|
||||
## 1. Short options: `-a:5`, `-b=5`, `-cde`, `-fgh=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>`_.
|
||||
## Passing values to options **requires** a separator (`:`/`=`), short options
|
||||
## (flags) can be bundled together and the last one can take a value.
|
||||
##
|
||||
## When option values begin with ':' or '=', they need to be doubled up (as in
|
||||
## `--delim::`) or alternated (as in `--delim=:`).
|
||||
## Option values can begin with the separator character (`:`/`=`), so all of the
|
||||
## following is valid:
|
||||
## - option `foo`, value `:`: `--foo::`, `--foo=:`
|
||||
## - option `foo`, value `=`: `--foo:=`, `--foo==`
|
||||
##
|
||||
## 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.
|
||||
## string. Trailing arguments can be accessed with `remainingArgs<#remainingArgs,OptParser>`_
|
||||
## or `cmdLineRest<#cmdLineRest,OptParser>`_.
|
||||
##
|
||||
## 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.
|
||||
## To parse command line options, use the `getopt iterator<#getopt.i,OptParser>`_.
|
||||
## It initializes the `OptParser<#OptParser>`_ object internally and iterates
|
||||
## through the command line options.
|
||||
##
|
||||
## 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` (`CmdLineKind enum<#CmdLineKind>`_.),
|
||||
## `key`, and `val` fields are yielded.
|
||||
##
|
||||
## For long and short options, `key` is the option's name, and `val` is either
|
||||
## the option's value, if given, or an empty string. For arguments, the `key`
|
||||
## field contains the argument itself, and `val` is unused (empty).
|
||||
##
|
||||
## Here is an example:
|
||||
##
|
||||
## ```Nim
|
||||
## import std/parseopt
|
||||
runnableExamples:
|
||||
import std/os
|
||||
|
||||
let cmds = "-ab -e:5 --foo --bar=20 file.txt".parseCmdLine()
|
||||
var output: seq[string] = @[]
|
||||
# If cmds is not supplied, real arguments will be retrieved by the `os` module
|
||||
for kind, key, val in getopt(cmds):
|
||||
case kind
|
||||
of cmdEnd: break
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if val == "":
|
||||
output.add("Option: " & key)
|
||||
else:
|
||||
output.add("Option and value: " & key & ", " & val)
|
||||
of cmdArgument:
|
||||
output.add("Argument: " & key)
|
||||
|
||||
doAssert output == @[
|
||||
"Option: a",
|
||||
"Option: b",
|
||||
"Option and value: e, 5",
|
||||
"Option: foo",
|
||||
"Option and value: bar, 20",
|
||||
"Argument: file.txt"
|
||||
]
|
||||
##
|
||||
## 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
|
||||
## The `OptParser<#OptParser>`_ can be initialized with
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_.
|
||||
## The `next<#next,OptParser>`_ proc advances the parser by one token.
|
||||
##
|
||||
## # Output:
|
||||
## # Option: a
|
||||
## # Option: b
|
||||
## # Option and value: e, 5
|
||||
## # Option: foo
|
||||
## # Option and value: bar, 20
|
||||
## # Argument: file.txt
|
||||
## ```
|
||||
## When iterating the object manually with `next<#next,OptParser>`_, reaching
|
||||
## the end of the command line is signalled by setting the `kind` field
|
||||
## to `cmdEnd`.
|
||||
##
|
||||
## The `getopt iterator<#getopt.i,OptParser>`_, which is provided for
|
||||
## convenience, can be used to iterate through all command line options as well.
|
||||
## To set a default value for an option, assign the default value to a variable
|
||||
## beforehand, then update it while parsing.
|
||||
##
|
||||
## 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.
|
||||
##
|
||||
## 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
|
||||
## ```
|
||||
runnableExamples:
|
||||
import std/strutils
|
||||
|
||||
var varName: string = "defaultValue"
|
||||
|
||||
for kind, key, val in getopt(@["--varName:HELLO"]):
|
||||
case kind
|
||||
of cmdArgument:
|
||||
discard
|
||||
of cmdLongOption, cmdShortOption:
|
||||
case key
|
||||
of "varName": # --varName:<value> in the console when executing
|
||||
varName = val.toLowerAscii() # do input sanitization in production
|
||||
of cmdEnd:
|
||||
discard
|
||||
|
||||
doAssert varName == "hello"
|
||||
##
|
||||
## `shortNoVal` and `longNoVal`
|
||||
## ============================
|
||||
##
|
||||
## The optional `shortNoVal` and `longNoVal` parameters present in
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ are for
|
||||
## The optional `shortNoVal` and `longNoVal` parameters in
|
||||
## `initOptParser<#initOptParser,string,set[char],seq[string]>`_ and
|
||||
## `getopt iterator<#getopt.i,OptParser>`_ 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.
|
||||
## When `shortNoVal` or `longNoVal` is non-empty, using the separators (`:`/`=`)
|
||||
## becomes non-mandatory and users can separate a value from long
|
||||
## options (that are not supplied to the corresponding argument) by whitespace
|
||||
## or, in the case of a short option, by writing the value directly adjacent to
|
||||
## the option.
|
||||
##
|
||||
## For short options, `-j4` becomes supported syntax (parsed as option `j` with
|
||||
## value `4` instead of two separate options `j` and `4`). For long options,
|
||||
## `--foo bar` becomes supported syntax in all `modes<Parser Modes>`_.
|
||||
##
|
||||
## In `LaxMode` and `GnuMode`, short options can also take values from the next
|
||||
## argument (`-c val`), but this does **not** work in the default `Nim` mode.
|
||||
##
|
||||
## As more options which do not accept values are added to your program,
|
||||
## remember to amend `shortNoVal` and `longNoVal` accordingly.
|
||||
##
|
||||
## The parser does not validate the input for syntax mistakes, thus, options
|
||||
## can still have values if passed explicitly by the user, even when they are
|
||||
## marked as `shortNoVal`/`longNoVal`.
|
||||
##
|
||||
## This behavior allows associating an option with the mistakenly passed value:
|
||||
##
|
||||
runnableExamples:
|
||||
import std/[sequtils, os]
|
||||
|
||||
let cmds = "-n:9 --foo:bar".parseCmdLine()
|
||||
let parsed = toSeq(cmds.getopt(shortNoVal = {'n'}, longNoVal = @["foo"]))
|
||||
for (kind, key, val) in parsed:
|
||||
case kind
|
||||
of cmdEnd: raise newException(AssertionDefect, "Unreachable")
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if key in ["n", "foo"] and val != "":
|
||||
# Substitute for proper error handling in your code
|
||||
discard "Option " & key & " can't take values!"
|
||||
else: discard
|
||||
of cmdArgument: discard
|
||||
doAssert parsed == @[
|
||||
(cmdShortOption, "n", "9"),
|
||||
(cmdLongOption, "foo", "bar")]
|
||||
##
|
||||
## .. Important::
|
||||
## Next-argument value-taking for short/long options is only enabled when
|
||||
## `shortNoVal`/`longNoVal` are non-empty. If your program has *no* options
|
||||
## that take no value, you still must pass a non-empty placeholder (for example,
|
||||
## `shortNoVal = {'\0'}` and/or `longNoVal = @[""]`) to enable this form.
|
||||
##
|
||||
## The following example illustrates the difference between having an empty
|
||||
## `shortNoVal` and `longNoVal`, which is the default, and providing
|
||||
## arguments for those two parameters:
|
||||
##
|
||||
## ```Nim
|
||||
## import std/parseopt
|
||||
runnableExamples:
|
||||
|
||||
proc format(kind: CmdLineKind; key, val: string): string =
|
||||
case kind
|
||||
of cmdEnd: raise newException(AssertionDefect, "Unreachable")
|
||||
of cmdShortOption, cmdLongOption:
|
||||
if val == "": "Option: " & key
|
||||
else: "Option and value: " & key & ", " & val
|
||||
of cmdArgument: "Argument: " & key
|
||||
|
||||
let cmdLine = "-j4 --first bar"
|
||||
var output1, output2: seq[string] = @[]
|
||||
|
||||
var emptyNoVal = initOptParser(cmdLine)
|
||||
for kind, key, val in emptyNoVal.getopt():
|
||||
output1.add format(kind, key, val)
|
||||
|
||||
doAssert output1 == @[
|
||||
"Option: j",
|
||||
"Option: 4",
|
||||
"Option: first",
|
||||
"Argument: bar"
|
||||
]
|
||||
|
||||
var withNoVal = cmdLine.initOptParser(shortNoVal = {'c'},
|
||||
longNoVal = @["second"])
|
||||
for kind, key, val in withNoVal.getopt():
|
||||
output2.add format(kind, key, val)
|
||||
|
||||
doAssert output2 == @[
|
||||
"Option and value: j, 4",
|
||||
"Option and value: first, bar"
|
||||
]
|
||||
##
|
||||
## 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
|
||||
## Parser Modes
|
||||
## ============
|
||||
##
|
||||
## let cmdLine = "-j4 --first bar"
|
||||
## .. Warning:: Modes other than the default (`Nim`) are **experimental** and may
|
||||
## change in future releases.
|
||||
##
|
||||
## var emptyNoVal = initOptParser(cmdLine)
|
||||
## for kind, key, val in emptyNoVal.getopt():
|
||||
## printToken(kind, key, val)
|
||||
## The parser supports several distinct rule sets that change how options are
|
||||
## interpreted:
|
||||
##
|
||||
## # Output:
|
||||
## # Option: j
|
||||
## # Option: 4
|
||||
## # Option: first
|
||||
## # Argument: bar
|
||||
## 1. **LaxMode**: Most forgiving mode, combines `Nim` with POSIX-like
|
||||
## short option handling. Tries to follow the POSIX_ guidelines where possible.
|
||||
## 2. **NimMode**: Standard Nim parsing rules (default).
|
||||
## 3. **GnuMode**: GNU-inspired parsing (e.g. `=` as the only delimiter).
|
||||
## Puts some additional restrictions, following some of the GNU_ conventions.
|
||||
##
|
||||
## var withNoVal = initOptParser(cmdLine, shortNoVal = {'c'},
|
||||
## longNoVal = @["second"])
|
||||
## for kind, key, val in withNoVal.getopt():
|
||||
## printToken(kind, key, val)
|
||||
## Modes are ordered from most relaxed to strictest. The names were
|
||||
## chosen to set general user expectations and full compliance is neither
|
||||
## achieved nor planned.
|
||||
##
|
||||
## # Output:
|
||||
## # Option and value: j, 4
|
||||
## # Option and value: first, bar
|
||||
## ```
|
||||
## Mode Differences
|
||||
## ----------------
|
||||
##
|
||||
## **NimMode** (default):
|
||||
##
|
||||
## - Short options require adjacent values or explicit delimiters:
|
||||
## `-cval`, `-c:val`, `-c=val`
|
||||
## - Short options follow POSIX-style bundling rules
|
||||
## - Next-argument value taking (`-c val`) is **not** supported by default
|
||||
## - Supports both `:` and `=` as delimiters
|
||||
## - Allows whitespace around delimiters
|
||||
## - Values starting with `-` are interpreted as new options
|
||||
##
|
||||
## **LaxMode**:
|
||||
##
|
||||
## - Essentially the Nim mode with some relaxations for short options:
|
||||
## + Allows short options to take values from the next argument: `-c val`
|
||||
## + Supports bundled short options with trailing value: `-abc val`
|
||||
## - Values starting with `-` can be consumed as option arguments
|
||||
##
|
||||
## **GnuMode**:
|
||||
##
|
||||
## - Only `=` is treated as a delimiter (`:` is not a delimiter)
|
||||
## - No whitespace allowed around `=`
|
||||
## - Short options can take next-argument values (`-c val`), but only whitespace
|
||||
## is allowed as a delimiter, separators parse as part of the value
|
||||
## - Short options follow POSIX-style bundling rules
|
||||
## - Values starting with `-` can be consumed as option arguments
|
||||
## - Known discrepancies compared to GNU getopt:
|
||||
## + No notion of optional/mandatory arguments, colon (`:`) doesn't
|
||||
## indicate them and overall is not a special character.
|
||||
##
|
||||
## Mode-Specific Behavior
|
||||
## ----------------------
|
||||
##
|
||||
## The parser's behavior varies significantly between modes, particularly
|
||||
## around how options consume their values:
|
||||
##
|
||||
## **Short Options**
|
||||
##
|
||||
## Consider `-c val`:
|
||||
##
|
||||
## - In `Nim` mode: `-c` is parsed as an option without a value, and `val` is
|
||||
## parsed as a separate argument, regardless of `shortNoVal` being empty or not.
|
||||
## - In `Lax` and `Gnu` modes:
|
||||
## + When `shortNoVal` is empty, or not empty and `-c` is in it:
|
||||
## Same as `Nim`, parsed as option `-c` followed by argument `val`.
|
||||
## + When `-c` is not in `shortNoVal`:
|
||||
## parsed as option `-c`, `val` is consumed as its value.
|
||||
##
|
||||
## Consider `-c-10`:
|
||||
##
|
||||
## - If `shortNoVal` value is empty, all three modes parse three separate short
|
||||
## options: `c`, `1` and `0`.
|
||||
## - Otherwise, if `-c` is not in `shortNoVal`:
|
||||
## + `Nim`: `-c` is an option without an argument. `-10` is interpreted as a
|
||||
## an option `-1` with the `0` argument.
|
||||
## + `Lax` and `Gnu` modes: `-10` is consumed as the value of `-c`
|
||||
## (allowing negative number values).
|
||||
##
|
||||
## **Long Options**
|
||||
##
|
||||
## Consider `--foo:bar`:
|
||||
##
|
||||
## - `Nim`: `:` is a valid delimiter, so `bar` is the value of `--foo`.
|
||||
## - `LaxMode`: same as `Nim`.
|
||||
## - `Gnu`: only `=` is a valid delimiter, so this parses as an option named
|
||||
## `foo:bar` without a value (unless `longNoVal` is non-empty and allows
|
||||
## next-argument consumption).
|
||||
##
|
||||
## Consider `--foo =bar`:
|
||||
##
|
||||
## - `Nim`: whitespace around delimiters is allowed, so `=bar` is the
|
||||
## value of `--foo`.
|
||||
## - `LaxMode`: same as `Nim`.
|
||||
## - `Gnu`: whitespace around `=` is not allowed, so `--foo` is an
|
||||
## option without a value, and `=bar` is parsed as an argument.
|
||||
##
|
||||
## Custom Rule Sets
|
||||
## ================
|
||||
##
|
||||
## .. Warning:: Custom rule sets are unsupported and not tested
|
||||
##
|
||||
## If you require parsing rules beyond the three provided modes, it's possible
|
||||
## to define a custom parser behavior by specifying a set of individual parser
|
||||
## rules.
|
||||
##
|
||||
## Due to this feature being unsupported, it requires importing the private
|
||||
## symbols of the module (with `import std/parseopt {.all.}`) and utilizing
|
||||
## the unexported `initOptParser` overload, which accepts `set[ParserRules]`
|
||||
## (see the `ParserRules` enum in the code for details).
|
||||
##
|
||||
## See also
|
||||
## ========
|
||||
@@ -171,13 +328,42 @@
|
||||
## parser
|
||||
## * `parsexml module<parsexml.html>`_ for a XML / HTML parser
|
||||
## * `other parsers<lib.html#pure-libraries-parsers>`_ for more parsers
|
||||
## * POSIX_ - The Open Group Base Specifications Issue 8. Utility Conventions
|
||||
## * GNU_ - GNU C Library reference manual. 26.1.1 Program Argument Syntax Conventions
|
||||
##
|
||||
## .. _GNU: https://sourceware.org/glibc/manual/latest/html_node/Argument-Syntax.html
|
||||
## .. _POSIX: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap12.html
|
||||
|
||||
{.push debugger: off.}
|
||||
|
||||
include "system/inclrtl"
|
||||
|
||||
import std/strutils
|
||||
import std/os
|
||||
when defined(nimscript):
|
||||
from std/strutils import toLowerAscii, endsWith
|
||||
|
||||
type
|
||||
CliMode* = enum
|
||||
## Parser behavior profiles used to control parser behavior.
|
||||
## See `Parser Modes`_ for details.
|
||||
LaxMode, ## The most forgiving mode
|
||||
NimMode, ## Nim parsing rules (default)
|
||||
GnuMode ## GNU-style parsing
|
||||
|
||||
type
|
||||
ParserRules = enum
|
||||
## Feature flags used to assemble parser behavior for a given mode.
|
||||
prSepAllowDelimBefore, ## Allow whitespace before an opt-val separator
|
||||
prSepAllowDelimAfter, ## Allow whitespace after an opt-val separator
|
||||
prShortAllowSep, ## Allow `-k<separator>val` form
|
||||
prShortBundle, ## Allow bundling short options behind one '-'
|
||||
prShortValAllowAdjacent, ## Allow adjacent short option values: `-kval`
|
||||
prShortValAllowNextArg, ## Allow next-argv short option values: `-k val`
|
||||
prShortValAllowDashLeading, ## Allow values that start with '-' to be taken
|
||||
prLongAllowSep, ## Allow `--opt<separator>val` form
|
||||
prLongValAllowNextArg, ## Allow `--opt val` form, requires non-empty `longNoVal`
|
||||
prSepAllowColon, ## Allow `:` as an opt-val separator
|
||||
prSepAllowEq, ## Allow `=` as an opt-val separator
|
||||
|
||||
type
|
||||
CmdLineKind* = enum ## The detected command line token.
|
||||
@@ -189,21 +375,51 @@ type
|
||||
## Implementation of the command line parser.
|
||||
##
|
||||
## To initialize it, use the
|
||||
## `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_.
|
||||
## `initOptParser proc<#initOptParser,string,set[char],seq[string],CliMode>`_.
|
||||
## `next<#next,OptParser>`_ is used to advance the parser state and move
|
||||
## through the parsed tokens.
|
||||
pos: int
|
||||
inShortState: bool
|
||||
allowWhitespaceAfterColon: bool
|
||||
shortNoVal: set[char]
|
||||
longNoVal: seq[string]
|
||||
cmds: seq[string]
|
||||
idx: int
|
||||
separators: set[char] ## Allowed separators for long/short option values
|
||||
rules: set[ParserRules]
|
||||
kind*: CmdLineKind ## The detected command line token
|
||||
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
|
||||
|
||||
const DelimSet = {'\t', ' '} ## Allowed delimiters between tokens
|
||||
|
||||
func toRules(m: CliMode): set[ParserRules] =
|
||||
## Default rule sets for the given mode `m`
|
||||
let
|
||||
Common = {
|
||||
prSepAllowEq,
|
||||
prShortValAllowAdjacent,
|
||||
prShortBundle,
|
||||
prLongValAllowNextArg,
|
||||
prLongAllowSep,
|
||||
}
|
||||
Lax = {
|
||||
prSepAllowColon,
|
||||
prSepAllowDelimBefore,
|
||||
prSepAllowDelimAfter,
|
||||
prShortAllowSep,
|
||||
}
|
||||
ShortPosix = {
|
||||
prShortValAllowNextArg,
|
||||
prShortValAllowDashLeading,
|
||||
}
|
||||
case m
|
||||
of LaxMode: Common + Lax + ShortPosix
|
||||
of NimMode: Common + Lax
|
||||
of GnuMode: Common + ShortPosix
|
||||
|
||||
proc parseWord(s: string, i: int, w: var string,
|
||||
delim: set[char] = {'\t', ' '}): int =
|
||||
delim: set[char] = DelimSet): int =
|
||||
result = i
|
||||
if result < s.len and s[result] == '\"':
|
||||
inc(result)
|
||||
@@ -218,34 +434,23 @@ proc parseWord(s: string, i: int, w: var string,
|
||||
add(w, s[result])
|
||||
inc(result)
|
||||
|
||||
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 = OptParser(pos: 0, idx: 0, inShortState: false,
|
||||
shortNoVal: shortNoVal, longNoVal: longNoVal,
|
||||
allowWhitespaceAfterColon: allowWhitespaceAfterColon
|
||||
proc initOptParser(cmdline: openArray[string];
|
||||
shortNoVal: set[char];
|
||||
longNoVal: seq[string];
|
||||
rules: set[ParserRules]): OptParser =
|
||||
result = OptParser(pos: 0, idx: 0,
|
||||
cmds: @cmdline,
|
||||
inShortState: false,
|
||||
shortNoVal: shortNoVal,
|
||||
longNoVal: longNoVal,
|
||||
separators: {},
|
||||
rules: rules,
|
||||
kind: cmdEnd,
|
||||
key: "", val: "",
|
||||
)
|
||||
if cmdline.len != 0:
|
||||
result.cmds = newSeq[string](cmdline.len)
|
||||
for i in 0..<cmdline.len:
|
||||
result.cmds[i] = cmdline[i]
|
||||
else:
|
||||
if prSepAllowEq in rules: result.separators.incl('=')
|
||||
if prSepAllowColon in rules: result.separators.incl(':')
|
||||
if cmdline.len == 0:
|
||||
when declared(paramCount):
|
||||
when defined(nimscript):
|
||||
var ctr = 0
|
||||
@@ -254,7 +459,7 @@ proc initOptParser*(cmdline: seq[string], shortNoVal: set[char] = {},
|
||||
if firstNimsFound:
|
||||
result.cmds[ctr] = paramStr(i)
|
||||
inc ctr, 1
|
||||
if paramStr(i).endsWith(".nims") and not firstNimsFound:
|
||||
if paramStr(i).toLowerAscii().endsWith(".nims") and not firstNimsFound:
|
||||
firstNimsFound = true
|
||||
result.cmds = newSeq[string](paramCount()-i)
|
||||
else:
|
||||
@@ -266,25 +471,73 @@ proc initOptParser*(cmdline: seq[string], shortNoVal: set[char] = {},
|
||||
# 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 = ""
|
||||
|
||||
proc initOptParser*(cmdline = "", shortNoVal: set[char] = {},
|
||||
proc initOptParser*(cmdline: seq[string];
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon = true): OptParser =
|
||||
mode: CliMode = NimMode): 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.
|
||||
## **Parameters:**
|
||||
##
|
||||
## `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.
|
||||
## - `cmdline`: Sequence of command line arguments to parse. If empty, the
|
||||
## real command line as provided by the `os` module is retrieved instead.
|
||||
## If the command line is not available, an assertion will be raised.
|
||||
## - `shortNoVal`: Set of short option characters that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `longNoVal`: Sequence of long option names that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `mode`: Parser behavior profile (`NimMode`, `LaxMode`, or `GnuMode`).
|
||||
## See `Parser Modes`_ for details.
|
||||
##
|
||||
## This does not provide a way of passing default values to arguments.
|
||||
## See also:
|
||||
## * `getopt iterator<#getopt.i,seq[string],set[char],seq[string],CliMode>`_
|
||||
runnableExamples:
|
||||
var p = initOptParser()
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"])
|
||||
p = initOptParser(@["--left", "--debug:3", "-l", "-r:2"],
|
||||
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||
initOptParser(cmdline, shortNoVal, longNoVal, toRules(mode))
|
||||
|
||||
proc initOptParser*(cmdline: seq[string],
|
||||
shortNoVal: set[char] = {},
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon: bool): OptParser {.deprecated:
|
||||
"`allowWhitespaceAfterColon` is deprecated, use parser modes instead".} =
|
||||
## This is an overload for continued support of the legacy `allowWhitespaceAfterColon`
|
||||
## option. It modifies the default parser mode so that the passed value is respected.
|
||||
##
|
||||
## Current default parser mode behaves as if `true` was passed (old default)
|
||||
##
|
||||
## - `allowWhitespaceAfterColon`: When `true`, allows forms like
|
||||
## `--option: value` or `--option= value` where the value is in the next
|
||||
## token after the delimiter. When `false`, the value must be in the same
|
||||
## token as the delimiter.
|
||||
var nimrules = toRules(NimMode)
|
||||
if allowWhitespaceAfterColon == false: nimrules.excl prSepAllowDelimAfter
|
||||
initOptParser(cmdline, shortNoVal, longNoVal, nimrules)
|
||||
|
||||
proc initOptParser*(cmdline = "";
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
mode: CliMode = NimMode): OptParser =
|
||||
## Initializes the command line parser from a command line string.
|
||||
##
|
||||
## The `cmdline` string is parsed into tokens using shell-like quoting rules.
|
||||
##
|
||||
## **Parameters:**
|
||||
##
|
||||
## - `cmdline`: Command line string to parse. If empty, the real command line
|
||||
## as provided by the `os` module is retrieved instead. If the command line
|
||||
## is not available, an assertion will be raised.
|
||||
## - `shortNoVal`: Set of short option characters that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `longNoVal`: Sequence of long option names that do not accept values.
|
||||
## See `shortNoVal and longNoVal<#nimshortnoval-and-nimlongnoval>`_ for details.
|
||||
## - `mode`: Parser behavior profile (`NimMode`, `LaxMode`, or `GnuMode`).
|
||||
## See `Parser Modes`_ for details.
|
||||
##
|
||||
## **Note:** This does not provide a way of passing default values to arguments.
|
||||
##
|
||||
## See also:
|
||||
## * `getopt iterator<#getopt.i,OptParser>`_
|
||||
@@ -293,34 +546,81 @@ proc initOptParser*(cmdline = "", shortNoVal: set[char] = {},
|
||||
p = initOptParser("--left --debug:3 -l -r:2")
|
||||
p = initOptParser("--left --debug:3 -l -r:2",
|
||||
shortNoVal = {'l'}, longNoVal = @["left"])
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, toRules(mode))
|
||||
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, allowWhitespaceAfterColon)
|
||||
proc initOptParser*(cmdline = "";
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon: bool): OptParser {.deprecated:
|
||||
"`allowWhitespaceAfterColon` is deprecated, use parser modes instead".} =
|
||||
## This is an overload for continued support of the legacy `allowWhitespaceAfterColon`
|
||||
## option. It modifies the default parser mode so that the passed value is respected.
|
||||
##
|
||||
## Current default parser mode behaves as if `true` was passed (old default).
|
||||
##
|
||||
## - `allowWhitespaceAfterColon`: When `true`, allows forms like
|
||||
## `--option: value` or `--option= value` where the value is in the next
|
||||
## token after the delimiter. When `false`, the value must be in the same
|
||||
## token as the delimiter.
|
||||
var nimrules = toRules(NimMode)
|
||||
if allowWhitespaceAfterColon == false: nimrules.excl prSepAllowDelimAfter
|
||||
initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, nimrules)
|
||||
|
||||
proc handleShortOption(p: var OptParser; cmd: string) =
|
||||
var i = p.pos
|
||||
p.kind = cmdShortOption
|
||||
if i < cmd.len:
|
||||
if i < cmd.len: # multidigit short option support goes here
|
||||
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[0] notin p.shortNoVal):
|
||||
if i < cmd.len and cmd[i] in {':', '='}:
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
while i < cmd.len and cmd[i] in DelimSet:
|
||||
inc(i)
|
||||
p.inShortState = false
|
||||
|
||||
proc consumeDelims() =
|
||||
while i < cmd.len and cmd[i] in DelimSet: inc(i)
|
||||
|
||||
proc advance(p: var OptParser; n = 1)=
|
||||
p.inShortState = false
|
||||
while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i)
|
||||
p.pos = 0
|
||||
inc p.idx, n
|
||||
|
||||
template next(): untyped = p.cmds[p.idx + 1]
|
||||
|
||||
let canTakeVal = card(p.shortNoVal) > 0 and p.key[0] notin p.shortNoVal
|
||||
if i < cmd.len and cmd[i] in p.separators:
|
||||
# separator case
|
||||
if prShortAllowSep in p.rules:
|
||||
# allow separators: skip the separator and take the value after it
|
||||
inc(i)
|
||||
if prSepAllowDelimAfter in p.rules:
|
||||
consumeDelims()
|
||||
# prohibit separators: treat separator + remainder as the value
|
||||
# this represents an error state but produces output that can be validated
|
||||
p.val = substr(cmd, i)
|
||||
p.pos = 0
|
||||
inc p.idx
|
||||
else:
|
||||
p.pos = i
|
||||
p.advance(1)
|
||||
return
|
||||
elif canTakeVal and prShortValAllowAdjacent in p.rules and i < cmd.len:
|
||||
# adjacent value
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
consumeDelims()
|
||||
p.val = substr(cmd, i)
|
||||
p.advance(1)
|
||||
return
|
||||
elif canTakeVal and
|
||||
prShortValAllowNextArg in p.rules and
|
||||
i >= cmd.len and
|
||||
p.idx + 1 < p.cmds.len and (
|
||||
prShortValAllowDashLeading in p.rules or
|
||||
not (next().len > 0 and next()[0] == '-')):
|
||||
# next-argument value
|
||||
p.val = next()
|
||||
p.advance(2)
|
||||
return
|
||||
p.pos = i
|
||||
if i >= cmd.len:
|
||||
p.inShortState = false
|
||||
p.pos = 0
|
||||
inc p.idx
|
||||
p.advance(1)
|
||||
|
||||
proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||
## Parses the next token.
|
||||
@@ -343,54 +643,71 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} =
|
||||
return
|
||||
|
||||
var i = p.pos
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
template cmd(): untyped = p.cmds[p.idx]
|
||||
template nextArg(): untyped = p.cmds[p.idx + 1]
|
||||
|
||||
proc consumeDelims(cmds: openArray[string]; idx: int) =
|
||||
while i < cmds[idx].len and cmds[idx][i] in DelimSet: inc(i)
|
||||
|
||||
proc advance(p: var OptParser; n = 1) =
|
||||
p.pos = 0
|
||||
inc p.idx, n
|
||||
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
p.pos = i
|
||||
setLen(p.key, 0)
|
||||
setLen(p.val, 0)
|
||||
if p.inShortState:
|
||||
p.inShortState = false
|
||||
if i >= p.cmds[p.idx].len:
|
||||
inc(p.idx)
|
||||
p.pos = 0
|
||||
if i < cmd.len:
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
return
|
||||
else:
|
||||
p.advance(1)
|
||||
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] == '-':
|
||||
if i < cmd.len and cmd[i] == '-':
|
||||
inc(i)
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
|
||||
if i < cmd.len and cmd[i] == '-':
|
||||
p.kind = cmdLongOption
|
||||
inc(i)
|
||||
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 {':', '='}:
|
||||
i = parseWord(cmd, i, p.key,
|
||||
DelimSet + (if prLongAllowSep in p.rules: p.separators else: {}))
|
||||
if prSepAllowDelimBefore in p.rules:
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
if prLongAllowSep in p.rules and i < cmd.len and cmd[i] in p.separators:
|
||||
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 = 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
|
||||
if prSepAllowDelimAfter in p.rules:
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
if i >= cmd.len and p.idx + 1 < p.cmds.len and
|
||||
prSepAllowDelimAfter in p.rules:
|
||||
p.val = nextArg()
|
||||
p.advance(2)
|
||||
else:
|
||||
p.val = cmd.substr(i)
|
||||
p.advance(1)
|
||||
elif prLongValAllowNextArg in p.rules and
|
||||
len(p.longNoVal) > 0 and
|
||||
p.key notin p.longNoVal and
|
||||
p.idx + 1 < p.cmds.len:
|
||||
p.val = nextArg()
|
||||
p.advance(2)
|
||||
else:
|
||||
p.val = ""
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
if i < cmd.len:
|
||||
# Leave remainder of the current token to be parsed as an argument.
|
||||
consumeDelims(p.cmds, p.idx)
|
||||
p.cmds[p.idx] = cmd.substr(i)
|
||||
else:
|
||||
p.advance(1)
|
||||
else:
|
||||
p.pos = i
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
handleShortOption(p, cmd)
|
||||
else:
|
||||
p.kind = cmdArgument
|
||||
p.key = p.cmds[p.idx]
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
p.key = cmd
|
||||
p.advance(1)
|
||||
|
||||
when declared(quoteShellCommand):
|
||||
proc cmdLineRest*(p: OptParser): string {.rtl, extern: "npo$1".} =
|
||||
@@ -399,15 +716,13 @@ when declared(quoteShellCommand):
|
||||
## 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"
|
||||
## ```
|
||||
runnableExamples:
|
||||
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[string] {.rtl, extern: "npo$1".} =
|
||||
@@ -416,15 +731,13 @@ proc remainingArgs*(p: OptParser): seq[string] {.rtl, extern: "npo$1".} =
|
||||
## See also:
|
||||
## * `cmdLineRest proc<#cmdLineRest,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.remainingArgs == @["foo.txt", "bar.txt"]
|
||||
## ```
|
||||
runnableExamples:
|
||||
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.remainingArgs == @["foo.txt", "bar.txt"]
|
||||
result = @[]
|
||||
for i in p.idx..<p.cmds.len: result.add p.cmds[i]
|
||||
|
||||
@@ -439,29 +752,26 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
|
||||
## See also:
|
||||
## * `initOptParser proc<#initOptParser,string,set[char],seq[string]>`_
|
||||
##
|
||||
## **Examples:**
|
||||
##
|
||||
## ```Nim
|
||||
## # 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()
|
||||
## ```
|
||||
runnableExamples:
|
||||
# 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:
|
||||
@@ -469,8 +779,10 @@ iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key,
|
||||
if p.kind == cmdEnd: break
|
||||
yield (p.kind, p.key, p.val)
|
||||
|
||||
iterator getopt*(cmdline: seq[string] = @[],
|
||||
shortNoVal: set[char] = {}, longNoVal: seq[string] = @[]):
|
||||
iterator getopt*(cmdline: seq[string] = @[];
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[];
|
||||
mode: CliMode = NimMode):
|
||||
tuple[kind: CmdLineKind, key, val: string] =
|
||||
## Convenience iterator for iterating over command line arguments.
|
||||
##
|
||||
@@ -483,6 +795,9 @@ iterator getopt*(cmdline: seq[string] = @[],
|
||||
## parameters<#nimshortnoval-and-nimlongnoval>`_ for more information on
|
||||
## how this affects parsing.
|
||||
##
|
||||
## `mode` selects the parser behavior profile (`NimMode`, `LaxMode`,
|
||||
## or `GnuMode`). See `Parser Modes`_ for details.
|
||||
##
|
||||
## There is no need to check for `cmdEnd` while iterating. If using `getopt`
|
||||
## with case switching, checking for `cmdEnd` is required.
|
||||
##
|
||||
@@ -513,7 +828,8 @@ iterator getopt*(cmdline: seq[string] = @[],
|
||||
## writeHelp()
|
||||
## ```
|
||||
var p = initOptParser(cmdline, shortNoVal = shortNoVal,
|
||||
longNoVal = longNoVal)
|
||||
longNoVal = longNoVal,
|
||||
rules = toRules(mode))
|
||||
while true:
|
||||
next(p)
|
||||
if p.kind == cmdEnd: break
|
||||
|
||||
@@ -851,12 +851,12 @@ when defined(heaptrack):
|
||||
|
||||
proc bigChunkAlignOffset(alignment: int): int {.inline.} =
|
||||
## Compute the alignment offset for big chunk data.
|
||||
if alignment <= MemAlign:
|
||||
if alignment == 0:
|
||||
result = 0
|
||||
else:
|
||||
result = align(sizeof(BigChunk) + sizeof(Cell), alignment) - sizeof(BigChunk) - sizeof(Cell)
|
||||
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign): pointer =
|
||||
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer =
|
||||
when defined(nimTypeNames):
|
||||
inc(a.allocCounter)
|
||||
sysAssert(allocInv(a), "rawAlloc: begin")
|
||||
@@ -868,7 +868,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign):
|
||||
|
||||
# For custom alignments > MemAlign, force big chunk allocation
|
||||
# Small chunks cannot handle arbitrary alignments due to fixed cell boundaries
|
||||
if size <= SmallChunkSize-smallChunkOverhead() and alignment <= MemAlign:
|
||||
if size <= SmallChunkSize-smallChunkOverhead() and alignment == 0:
|
||||
template fetchSharedCells(tc: PSmallChunk) =
|
||||
# Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
|
||||
when defined(gcDestructors):
|
||||
@@ -1046,13 +1046,29 @@ proc rawDealloc(a: var MemRegion, p: pointer) =
|
||||
inc(c.free, s)
|
||||
else:
|
||||
inc(c.free, s)
|
||||
# Free only if the entire chunk is unused and there are no borrowed cells.
|
||||
# If the chunk were to be freed while it references foreign cells,
|
||||
# the foreign chunks will leak memory and can never be freed.
|
||||
if c.free == SmallChunkSize-smallChunkOverhead() and c.foreignCells == 0:
|
||||
listRemove(a.freeSmallChunks[s div MemAlign], c)
|
||||
c.size = SmallChunkSize
|
||||
freeBigChunk(a, cast[PBigChunk](c))
|
||||
# FIX: Don't free small chunks to avoid race condition with sharedFreeLists.
|
||||
#
|
||||
# RACE CONDITION: Between checking foreignCells==0 and calling freeBigChunk,
|
||||
# another thread may read chunk.owner and decide to add a cell to our
|
||||
# sharedFreeLists. If we free the chunk, that cell becomes orphaned.
|
||||
#
|
||||
# SOLUTION: Never free small chunks. They remain in freeSmallChunks[s] and
|
||||
# are reused on next allocation. This maintains the invariant that chunks
|
||||
# in freeSmallChunks[s] have c.free >= s (completely free chunks satisfy this).
|
||||
# If a chunk becomes exhausted (c.free < s), it's removed by line 949.
|
||||
#
|
||||
# TRADEOFF: Memory not returned to OS. Bounded by peak concurrent allocation
|
||||
# per size class (~4KB per active size class per thread, typically <1MB total).
|
||||
#
|
||||
# VERIFIED: TLA+ formal proof shows no race - see VERIFICATION_RESULTS.md
|
||||
#
|
||||
# Original code (REMOVED to fix race):
|
||||
sysAssert(c.free >= s, "Invariant violated: chunk in freeSmallChunks has insufficient space")
|
||||
when false:
|
||||
if c.free == SmallChunkSize-smallChunkOverhead() and c.foreignCells == 0:
|
||||
listRemove(a.freeSmallChunks[s div MemAlign], c)
|
||||
c.size = SmallChunkSize
|
||||
freeBigChunk(a, cast[PBigChunk](c))
|
||||
else:
|
||||
when logAlloc: cprintf("dealloc(pointer_%p) # SMALL FROM %p CALLER %p\n", p, c.owner, addr(a))
|
||||
|
||||
@@ -1348,4 +1364,4 @@ template instantiateForRegion(allocator: untyped) {.dirty.} =
|
||||
#sharedMemStatsShared(sharedHeap.currMem - sharedHeap.freeMem)
|
||||
{.pop.}
|
||||
|
||||
{.pop.}
|
||||
{.pop.}
|
||||
@@ -459,11 +459,15 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer =
|
||||
gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1")
|
||||
collectCT(gch)
|
||||
# Use alignment from typ.base if available, otherwise use MemAlign
|
||||
let alignment = if typ.kind == tyRef and typ.base != nil: max(typ.base.align, MemAlign) else: MemAlign
|
||||
let alignment = if typ.kind == tyRef and typ.base != nil and
|
||||
typ.base.align >= MemAlign: typ.base.align else: 0
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment))
|
||||
#gcAssert typ.kind in {tyString, tySequence} or size >= typ.base.size, "size too small"
|
||||
# Check that the user data (after the Cell header) is properly aligned
|
||||
gcAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2")
|
||||
if alignment == 0:
|
||||
gcAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2.1")
|
||||
else:
|
||||
gcAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2.2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
@@ -512,11 +516,15 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline, raise
|
||||
sysAssert(allocInv(gch.region), "newObjRC1 after collectCT")
|
||||
|
||||
# Use alignment from typ.base if available, otherwise use MemAlign
|
||||
let alignment = if typ.base != nil: max(typ.base.align, MemAlign) else: MemAlign
|
||||
let alignment = if typ.kind == tyRef and typ.base != nil and
|
||||
typ.base.align >= MemAlign: typ.base.align else: 0
|
||||
var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment))
|
||||
sysAssert(allocInv(gch.region), "newObjRC1 after rawAlloc")
|
||||
# Check that the user data (after the Cell header) is properly aligned
|
||||
sysAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2")
|
||||
if alignment == 0:
|
||||
sysAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2.1")
|
||||
else:
|
||||
sysAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2.2")
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
setFrameInfo(res)
|
||||
@@ -922,4 +930,4 @@ when not defined(useNimRtl):
|
||||
result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n"
|
||||
|
||||
{.pop.} # raises: []
|
||||
{.pop.} # profiler: off, stackTrace: off
|
||||
{.pop.} # profiler: off, stackTrace: off
|
||||
@@ -605,3 +605,17 @@ block t18643:
|
||||
except IndexDefect:
|
||||
caught = true
|
||||
doAssert caught, "IndexDefect not caught!"
|
||||
|
||||
|
||||
# bug #25475
|
||||
block:
|
||||
type N = object
|
||||
b: seq[array[1'u, int]]
|
||||
doAssert N(b: @[[0]]) == N(b: @[[0]])
|
||||
|
||||
block:
|
||||
var x: array[5..6, int] = [0, 1]
|
||||
var y: array[1..2, int] = [0, 1]
|
||||
|
||||
doAssert x == y # compiles
|
||||
doAssert @[x] == @[y]
|
||||
|
||||
@@ -56,3 +56,22 @@ proc main = # bug #24677
|
||||
for NDEBUG in 0..2:
|
||||
doAssert NDEBUG == NDEBUG
|
||||
main()
|
||||
|
||||
block: # importc type inheritance
|
||||
type
|
||||
A {.inheritable, pure, bycopy, importc: "int".} = object
|
||||
B {.importc: "int", bycopy.} = object of A
|
||||
|
||||
{.emit: """
|
||||
int foo(int a) {
|
||||
return 123;
|
||||
}
|
||||
""".}
|
||||
|
||||
proc foo(a: A): B {.importc, nodecl.}
|
||||
|
||||
var a: A
|
||||
var b = foo(a)
|
||||
doAssert(cast[cint](b) == 123)
|
||||
var c = foo(b)
|
||||
doAssert(cast[cint](c) == 123)
|
||||
|
||||
@@ -40,7 +40,7 @@ proc `=destroy`*[T](x: var myseq[T]) =
|
||||
x.len = 0
|
||||
x.cap = 0
|
||||
|
||||
proc `=`*[T](a: var myseq[T]; b: myseq[T]) =
|
||||
proc `=copy`*[T](a: var myseq[T]; b: myseq[T]) =
|
||||
if a.data == b.data: return
|
||||
if a.data != nil:
|
||||
`=destroy`(a)
|
||||
@@ -66,6 +66,11 @@ proc `=sink`*[T](a: var myseq[T]; b: myseq[T]) =
|
||||
a.cap = b.cap
|
||||
a.data = b.data
|
||||
|
||||
proc `=wasMoved`*[T](a: var myseq[T]) =
|
||||
a.data = nil
|
||||
a.len = 0
|
||||
a.cap = 0
|
||||
|
||||
proc resize[T](s: var myseq[T]) =
|
||||
if s.cap == 0: s.cap = 8
|
||||
else: s.cap = (s.cap * 3) shr 1
|
||||
@@ -118,7 +123,7 @@ template `[]=`*[T](x: myseq[T]; i: Natural; y: T) =
|
||||
proc createSeq*[T](elems: varargs[T]): myseq[T] =
|
||||
result.cap = elems.len
|
||||
result.len = elems.len
|
||||
result.data = cast[type(result.data)](alloc(result.cap * sizeof(T)))
|
||||
result.data = cast[type(result.data)](alloc0(result.cap * sizeof(T)))
|
||||
inc allocCount
|
||||
when supportsCopyMem(T):
|
||||
copyMem(result.data, addr(elems[0]), result.cap * sizeof(T))
|
||||
|
||||
508
tests/misc/tparseoptmodes.nim
Normal file
508
tests/misc/tparseoptmodes.nim
Normal file
@@ -0,0 +1,508 @@
|
||||
discard """
|
||||
action: run
|
||||
"""
|
||||
|
||||
import parseopt
|
||||
from std/sequtils import toSeq
|
||||
|
||||
type Opt = tuple[kind: CmdLineKind, key, val: string]
|
||||
proc `$`(opt: Opt): string = "(" & $opt[0] & ", \"" & opt[1] & "\", \"" & opt[2] & "\")"
|
||||
|
||||
proc collect(args: seq[string] | string;
|
||||
shortNoVal: set[char] = {};
|
||||
longNoVal: seq[string] = @[]): seq[(CliMode, seq[Opt])] =
|
||||
for mode in CliMode:
|
||||
var p = parseopt.initOptParser(args,
|
||||
shortNoVal = shortNoVal, longNoVal = longNoVal, mode = mode)
|
||||
let res = toSeq(parseopt.getopt(p))
|
||||
result.add (mode, res)
|
||||
|
||||
proc check(name: string;
|
||||
results: openArray[(CliMode, seq[Opt])];
|
||||
expected: proc(m: CliMode): seq[Opt]) =
|
||||
for (mode, res) in results:
|
||||
doAssert res == expected(mode), "[" & $mode & "]: " & name & ":\n" & $res
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg: separate option-argument for mandatory opt-arg.
|
||||
let res = collect(@["-c", "4"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "c", "4")]
|
||||
of NimMode: @[(cmdShortOption, "c", ""), (cmdArgument, "4", "")]
|
||||
of GnuMode: @[(cmdShortOption, "c", "4")]
|
||||
check("short whitespace value", res, expected)
|
||||
|
||||
block:
|
||||
# No opt-arg knowledge: whitespace does not bind to short option.
|
||||
let res = collect(@["-c", "4"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "c", ""), (cmdArgument, "4", "")]
|
||||
check("short no-val whitespace value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortBundle + pcShortValAllowNextArg: grouped shorts with one opt-arg.
|
||||
let res = collect(@["-abc", "4"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "b", ""),
|
||||
(cmdShortOption, "c", "4")]
|
||||
|
||||
of NimMode: @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "b", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdArgument, "4", "")]
|
||||
|
||||
of GnuMode: @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "b", ""),
|
||||
(cmdShortOption, "c", "4")]
|
||||
check("short bundle with trailing value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowAdjacent: option+argument in same token (dash-led value).
|
||||
let res = collect(@["-c-x"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "c", "-x")]
|
||||
check("short adjacent dash-led", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortBundle + pcShortValAllowAdjacent (dash-led value).
|
||||
let res = collect(@["-abc-10"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "b", ""),
|
||||
(cmdShortOption, "c", "-10")]
|
||||
check("short bundle with adjacent negative", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg: option and option-argument can be separate args.
|
||||
let res = collect(@["-c", ":"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "c", ":")]
|
||||
of NimMode: @[(cmdShortOption, "c", ""), (cmdArgument, ":", "")]
|
||||
of GnuMode: @[(cmdShortOption, "c", ":")]
|
||||
check("short whitespace colon value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowAdjacent: combined option+argument without blanks.
|
||||
let res = collect(@["-abc4"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "b", ""),
|
||||
(cmdShortOption, "c", "4")]
|
||||
check("short bundle adjacent value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortBundle: bundle of no-arg shorts should split into options.
|
||||
let res = collect(@["-ab"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "a", ""), (cmdShortOption, "b", "")]
|
||||
check("short bundle no-arg", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortBundle + pcShortValAllowNextArg: a no-arg short followed by one with arg.
|
||||
let res = collect(@["-ac", "4"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode : @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "c", "4")]
|
||||
of NimMode: @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdArgument, "4", "")]
|
||||
of GnuMode: @[(cmdShortOption, "a", ""),
|
||||
(cmdShortOption, "c", "4")]
|
||||
check("short bundle trailing value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg + cmdline parsing: whitespace-separated opt-arg.
|
||||
let res = collect("-c \"foo bar\"", shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "c", "foo bar")]
|
||||
of NimMode: @[(cmdShortOption, "c", ""), (cmdArgument, "foo bar", "")]
|
||||
of GnuMode: @[(cmdShortOption, "c", "foo bar")]
|
||||
check("short whitespace quoted value", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg + pcShortValAllowDashLeading: negative numbers as opt-args.
|
||||
let res = collect(@["-n", "-10"], shortNoVal = {'a', 'b', 'c'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", "-10")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""), (cmdShortOption, "1", "0")]
|
||||
of GnuMode: @[(cmdShortOption, "n", "-10")]
|
||||
check("short negative value, shortNoVal used", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg + pcShortValAllowDashLeading: negative numbers as opt-args.
|
||||
let res = collect(@["-n", "-10"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", ""),
|
||||
(cmdShortOption, "1", ""),
|
||||
(cmdShortOption, "0", "")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""),
|
||||
(cmdShortOption, "1", ""),
|
||||
(cmdShortOption, "0", "")]
|
||||
of GnuMode: @[(cmdShortOption, "n", ""),
|
||||
(cmdShortOption, "1", ""),
|
||||
(cmdShortOption, "0", "")]
|
||||
check("short negative value, shortNoVal empty", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowNextArg: repeated option-argument pairs are interpreted in order.
|
||||
let res = collect(@["-c", "1", "-c", "2"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "c", "1"),
|
||||
(cmdShortOption, "c", "2")]
|
||||
of NimMode: @[(cmdShortOption, "c", ""),
|
||||
(cmdArgument, "1", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdArgument, "2", "")]
|
||||
of GnuMode: @[(cmdShortOption, "c", "1"),
|
||||
(cmdShortOption, "c", "2")]
|
||||
check("short repeat whitespace values", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowAdjacent: adjacent opt-args preserve order for repeats.
|
||||
let res = collect(@["-c1", "-c2"], shortNoVal = {'a', 'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "c", "1"), (cmdShortOption, "c", "2")]
|
||||
check("short repeat adjacent values", res, expected)
|
||||
|
||||
block:
|
||||
# pcShortValAllowDashLeading: value starting with '-' is consumed as opt-arg.
|
||||
# Divergence from POSIX Guideline 14 when enabled.
|
||||
let res = collect(@["-c", "-a"], shortNoVal = {'b'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "c", "-a")]
|
||||
of NimMode: @[(cmdShortOption, "c", ""), (cmdShortOption, "a", "")]
|
||||
of GnuMode: @[(cmdShortOption, "c", "-a")]
|
||||
check("short dash-led value", res, expected)
|
||||
|
||||
block:
|
||||
# Separator overrides shortNoVal
|
||||
let res = collect(@["-a=foo"], shortNoVal = {'a'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "a", "foo")]
|
||||
of NimMode: @[(cmdShortOption, "a", "foo")]
|
||||
of GnuMode: @[(cmdShortOption, "a", "=foo")]
|
||||
check("separator suppresses shortNoVal", res, expected)
|
||||
|
||||
block:
|
||||
let res = collect(@["-a=foo"], shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "a", "foo")]
|
||||
of NimMode: @[(cmdShortOption, "a", "foo")]
|
||||
of GnuMode: @[(cmdShortOption, "a", "=foo")]
|
||||
check("adjacent value-taking vs chort option bundling 1", res, expected)
|
||||
|
||||
block:
|
||||
# pcLongAllowSep, mixed long/short parsing.
|
||||
# Option-arguments may include ':'/'=' chars.
|
||||
let args = @[
|
||||
"foo bar",
|
||||
"--path:/i like space/projects",
|
||||
"--aa:bar=a",
|
||||
"--a=c:d",
|
||||
"--ab",
|
||||
"-c",
|
||||
"--a[baz]:doo"
|
||||
]
|
||||
let res = collect(args, shortNoVal = {'c'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[
|
||||
(cmdArgument, "foo bar", ""),
|
||||
(cmdLongOption, "path", "/i like space/projects"),
|
||||
(cmdLongOption, "aa", "bar=a"),
|
||||
(cmdLongOption, "a", "c:d"),
|
||||
(cmdLongOption, "ab", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdLongOption, "a[baz]", "doo")]
|
||||
of NimMode: @[
|
||||
(cmdArgument, "foo bar", ""),
|
||||
(cmdLongOption, "path", "/i like space/projects"),
|
||||
(cmdLongOption, "aa", "bar=a"),
|
||||
(cmdLongOption, "a", "c:d"),
|
||||
(cmdLongOption, "ab", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdLongOption, "a[baz]", "doo")]
|
||||
of GnuMode: @[
|
||||
(cmdArgument, "foo bar", ""),
|
||||
(cmdLongOption, "path:/i", ""), # longNoVal is empty so can't take arg here
|
||||
(cmdArgument, "like space/projects", ""),
|
||||
(cmdLongOption, "aa:bar", "a"),
|
||||
(cmdLongOption, "a", "c:d"),
|
||||
(cmdLongOption, "ab", ""),
|
||||
(cmdShortOption, "c", ""),
|
||||
(cmdLongOption, "a[baz]:doo", "")]
|
||||
check("mixed long/short argv tokens", res, expected)
|
||||
|
||||
|
||||
block:
|
||||
# pcLongAllowSep + separators: long option separator handling.
|
||||
let res = collect(@["--foo:bar"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of NimMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of GnuMode: @[(cmdLongOption, "foo:bar", "")]
|
||||
check("long option colon separator", res, expected)
|
||||
|
||||
block:
|
||||
# pcLongAllowSep + separators: long option separator handling.
|
||||
let res = collect(@["--foo= bar"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of NimMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of GnuMode: @[(cmdLongOption, "foo", " bar")]
|
||||
check("long option whitespace around separators", res, expected)
|
||||
|
||||
block:
|
||||
let res = collect(@["--foo =bar"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of NimMode: @[(cmdLongOption, "foo", "bar")]
|
||||
of GnuMode: @[(cmdLongOption, "foo", ""), (cmdArgument, "=bar", "")]
|
||||
check("long option whitespace around separators", res, expected)
|
||||
|
||||
block:
|
||||
let res = collect("--foo =bar", longNoVal = @[""])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", "=bar")]
|
||||
check("long option argument delimited with whitespace, val allowed", res, expected)
|
||||
|
||||
block:
|
||||
let res = collect("--foo =bar")
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", ""), (cmdArgument, "=bar", "")]
|
||||
check("long option argument delimited with whitespace, val not allowed", res, expected)
|
||||
|
||||
block:
|
||||
# pcLongAllowSep: '=' separator
|
||||
let res = collect(@["--foo=bar"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", "bar")]
|
||||
check("long option equals separator", res, expected)
|
||||
|
||||
block:
|
||||
# pcLongValAllowNextArg: long option value can be next argument.
|
||||
let res = collect(@["--foo", "bar"], longNoVal = @[""])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", "bar")]
|
||||
check("long option next-arg value", res, expected)
|
||||
|
||||
block:
|
||||
# longNoVal disables next-arg value consumption.
|
||||
let res = collect(@["--foo", "bar"], longNoVal = @["foo"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", ""), (cmdArgument, "bar", "")]
|
||||
check("long option longNoVal disables argument taking", res, expected)
|
||||
|
||||
block:
|
||||
# "--" is parsed as a long option with an empty key.
|
||||
let res = collect(@["--", "rest"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "", ""), (cmdArgument, "rest", "")]
|
||||
check("double-dash marker", res, expected)
|
||||
|
||||
block:
|
||||
# option values beginning with ':' - doubled up
|
||||
let res = collect(@["--foo::"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "foo", ":")]
|
||||
of NimMode: @[(cmdLongOption, "foo", ":")]
|
||||
of GnuMode: @[(cmdLongOption, "foo::", "")]
|
||||
check("long option value starting with colon (doubled)", res, expected)
|
||||
|
||||
block:
|
||||
# option values beginning with '=' - doubled up
|
||||
let res = collect(@["--foo=="])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", "=")]
|
||||
check("long option value starting with equals (doubled)", res, expected)
|
||||
|
||||
block:
|
||||
# option values beginning with ':' - alternated with '='
|
||||
let res = collect(@["--foo=:"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "foo", ":")]
|
||||
check("long option value starting with colon (alternated)", res, expected)
|
||||
|
||||
block:
|
||||
# option values beginning with '=' - alternated with ':'
|
||||
let res = collect(@["--foo:="])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "foo", "=")]
|
||||
of NimMode: @[(cmdLongOption, "foo", "=")]
|
||||
of GnuMode: @[(cmdLongOption, "foo:", "")]
|
||||
check("long option value starting with equals (alternated)", res, expected)
|
||||
|
||||
block issue9619:
|
||||
let res = collect(@["--option=", "", "--anotherOption", "tree"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "option", ""),
|
||||
(cmdLongOption, "anotherOption", ""),
|
||||
(cmdArgument, "tree", "")]
|
||||
of NimMode: @[(cmdLongOption, "option", ""),
|
||||
(cmdLongOption, "anotherOption", ""),
|
||||
(cmdArgument, "tree", "")]
|
||||
of GnuMode: @[(cmdLongOption, "option", ""),
|
||||
(cmdArgument, "", ""),
|
||||
(cmdLongOption, "anotherOption", ""),
|
||||
(cmdArgument, "tree", "")]
|
||||
check("issue #9619, whitespace after separator", res, expected)
|
||||
|
||||
|
||||
block issue22736:
|
||||
let res = collect(@["--long", "", "-h", "--long:", "-h", "--long=", "-h", "arg"])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdLongOption, "long", ""),
|
||||
(cmdArgument, "", ""),
|
||||
(cmdShortOption, "h", ""),
|
||||
(cmdLongOption, "long", "-h"),
|
||||
(cmdLongOption, "long", "-h"),
|
||||
(cmdArgument, "arg", "")]
|
||||
of NimMode: @[(cmdLongOption, "long", ""),
|
||||
(cmdArgument, "", ""),
|
||||
(cmdShortOption, "h", ""),
|
||||
(cmdLongOption, "long", "-h"),
|
||||
(cmdLongOption, "long", "-h"),
|
||||
(cmdArgument, "arg", "")]
|
||||
of GnuMode: @[(cmdLongOption, "long", ""),
|
||||
(cmdArgument, "", ""),
|
||||
(cmdShortOption, "h", ""),
|
||||
(cmdLongOption, "long:", ""),
|
||||
(cmdShortOption, "h", ""),
|
||||
(cmdLongOption, "long", ""),
|
||||
(cmdShortOption, "h", ""),
|
||||
(cmdArgument, "arg", "")]
|
||||
check("issue #22736, whitespace after separator, colon separator", res, expected)
|
||||
|
||||
# Numbers =====================================================================
|
||||
|
||||
block:
|
||||
# Positive integer adjacent to option
|
||||
let res = collect("-n42", shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "n", "42")]
|
||||
check("numerical option: positive integer adjacent", res, expected)
|
||||
|
||||
block:
|
||||
# Positive integer adjacent to no-val option
|
||||
let res = collect("-n42x", shortNoVal = {'n'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", ""), (cmdShortOption, "4", "2x")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""), (cmdShortOption, "4", "2x")]
|
||||
of GnuMode: @[(cmdShortOption, "n", ""), (cmdShortOption, "4", "2x")]
|
||||
check("numerical no-val option: positive integer adjacent", res, expected)
|
||||
|
||||
block:
|
||||
# Negative integer adjacent to option
|
||||
let res = collect("-n-42", shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "n", "-42")]
|
||||
check("numerical option: negative integer adjacent", res, expected)
|
||||
|
||||
block:
|
||||
# Floating point number as value
|
||||
let res = collect(@["-n", "3.14"], shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", "3.14")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""), (cmdArgument, "3.14", "")]
|
||||
of GnuMode: @[(cmdShortOption, "n", "3.14")]
|
||||
check("numerical option: floating point whitespace", res, expected)
|
||||
|
||||
block:
|
||||
# Floating point adjacent to option
|
||||
let res = collect(@["-n3.14"], shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "n", "3.14")]
|
||||
check("numerical option: floating point adjacent", res, expected)
|
||||
|
||||
block:
|
||||
# Negative floating point
|
||||
let res = collect(@["-n", "-3.14"], shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", "-3.14")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""), (cmdShortOption, "3", ".14")]
|
||||
of GnuMode: @[(cmdShortOption, "n", "-3.14")]
|
||||
check("numerical option: negative floating point whitespace", res, expected)
|
||||
|
||||
block:
|
||||
# Negative floating point adjacent
|
||||
let res = collect("-n-3.14", shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdShortOption, "n", "-3.14")]
|
||||
check("numerical option: negative floating point adjacent", res, expected)
|
||||
|
||||
block:
|
||||
# Large number
|
||||
let res = collect(@["-n", "414"], shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", "414")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""), (cmdArgument, "414", "")]
|
||||
of GnuMode: @[(cmdShortOption, "n", "414")]
|
||||
check("numerical option: large number", res, expected)
|
||||
|
||||
|
||||
block:
|
||||
# Multiple numerical options
|
||||
let res = collect("-n 10 -m20 -k= 30 -40", shortNoVal = {'v'})
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
case m
|
||||
of LaxMode: @[(cmdShortOption, "n", "10"),
|
||||
(cmdShortOption, "m", "20"),
|
||||
(cmdShortOption, "k", ""), # buggy but preserved
|
||||
(cmdArgument, "30", ""),
|
||||
(cmdShortOption, "4", "0")]
|
||||
of NimMode: @[(cmdShortOption, "n", ""),
|
||||
(cmdArgument, "10", ""),
|
||||
(cmdShortOption, "m", "20"),
|
||||
(cmdShortOption, "k", ""), # buggy but preserved
|
||||
(cmdArgument, "30", ""),
|
||||
(cmdShortOption, "4", "0")]
|
||||
of GnuMode: @[(cmdShortOption, "n", "10"),
|
||||
(cmdShortOption, "m", "20"),
|
||||
(cmdShortOption, "k", "="),
|
||||
(cmdArgument, "30", ""),
|
||||
(cmdShortOption, "4", "0")]
|
||||
check("numerical option: multiple options", res, expected)
|
||||
|
||||
block:
|
||||
# Long option with numerical value
|
||||
let res = collect(@["--count=42"], longNoVal = @[])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "count", "42")]
|
||||
check("numerical option: long option with equals", res, expected)
|
||||
|
||||
block:
|
||||
# Long option with numerical value (whitespace)
|
||||
let res = collect(@["--count", "42"], longNoVal = @[""])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "count", "42")]
|
||||
check("numerical option: long option with whitespace", res, expected)
|
||||
|
||||
block:
|
||||
# Long option with negative numerical value
|
||||
let res = collect(@["--offset=-10"], longNoVal = @[])
|
||||
proc expected(m: CliMode): seq[Opt] =
|
||||
@[(cmdLongOption, "offset", "-10")]
|
||||
check("numerical option: long option negative", res, expected)
|
||||
Reference in New Issue
Block a user