Adds configurable parser modes to std/parseopt module. **Take two.**
Initially solved the issue of not being able to pass arguments to short
options as you do with most everyday CLI programs, but reading the tests
made me add more features so that some of the behaviour could be changed
and here we are.
**`std/parseopt` now supports three parser modes** via an optional
`mode` parameter in `initOptParser` and `getopt`.
Three modes are provided:
- `NimMode` (default, fully backward compatible),
- `LaxMode` (POSIX-inspired with relaxed short option handling),
- `GnuMode` (stricter GNU-style conventions).
The new modes are marked as experimental in the documentation.
The parser behaviour is controlled by a new `ParserRules` enum, which
provides granular feature flags that modes are built from. This makes it
possible for users with specific requirements to define custom rule sets
by importing private symbols, this is mentioned but clearly marked as
unsupported.
**Backward compatibility:**
The default mode preserves existing behaviour completely, with a single
exception: `allowWhitespaceAfterColon` is deprecated.
Now, `allowWhitespaceAfterColon` doesn't make much sense as a single
tuning knob. The `ParserRule.prSepAllowDelimAfter` controls this now.
As `allowWhitespaceAfterColon` had a default, most calls never mention
it so they will silently migrate to the new `initOptParser` overload. To
cover cases when the proc param was used at call-site, I added an
overload, which modifies the default parser mode to reflect the required
`allowWhitespaceAfterColon` value. Should be all smooth for most users,
except the deprecation warning.
The only thing I think can be classified as the breaking change is a
surprising **bug** of the old parser:
```nim
let p = initOptParser("-n 10 -m20 -k= 30 -40", shortNoVal = {'v'})
# ^-disappears
```
This is with the aforementioned `allowWhitespaceAfterColon` being true
by default, of course. In this case the `30` token is skipped
completely. I don't think that's right, so it's fixed.
Things I still don't like about how the old parser and the new default
mode behave:
1. **Parser behaviour is controlled by an emptiness of two containers**.
This is an interesting approach. It's also made more interesting because
the `shortNoVal`/`longNoVal` control both the namesakes, but *and also
how their opposites (value-taking opts) work*.
---
**Edit:**
2. `shortNoVal` is not mandatory:
```nim
let p = initOptParser(@["-a=foo"], shortNoVal = {'a'})
# Nim, Lax parses as: (cmdShortOption, "a", "foo")
# GnuMode parses as: (cmdShortOption, "a", "=foo")
```
In this case, even though the user specified `a` as no no-val, parser
ignores it, relying only on the syntax to decide the kind of the
argument. This is especially problematic with the modes that don't use
the rule `prShortAllowSep` (GnuMode), in this case the provided input is
twice invalid, regardless of the `shortNoVal`.
With the current parser architecture, parsing it this way **is
inevitable**, though. We don't have any way to signal the error state
detected with the input, so the user is expected to validate the input
for mistakes.
Bundling positional arguments is nonsensical and short option can't use
the separator character, so `[cmd "a", arg "=foo"]` and `[cmd "a", cmd
"=", cmd "f"...]` are both out of the question **and** would complicate
validating, requiring keeping track of a previous argument. Hope I'm
clear enough on the issue.
**Future work:**
1. Looks like the new modes are already usable, but from the discussions
elsewhere it looks like we might want to support special-casing
multi-digit short options (`-XX..`) to allow numerical options greater
than 9. This complicates bundling, though, so requires a bit of thinking
through.
2. Signaling error state?
---------
Co-authored-by: Andreas Rumpf <araq4k@proton.me>
(cherry picked from commit 7c873ca615)
5.8 KiB
v2.x.x - yyyy-mm-dd
Changes affecting backward compatibility
-
-d:nimPreviewFloatRoundtripbecomes the default.system.addFloatandsystem.$now can produce string representations of floating point numbers that are minimal in size and possess round-trip and correct rounding guarantees (via the Dragonbox algorithm). Use-d:nimLegacySprintfto emulate old behaviors. -
The
defaultparameter oftables.getOrDefaulthas been renamed todefto avoid conflicts withsystem.default, so named argument usage for this parameter likegetOrDefault(..., default = ...)will have to be changed. -
With
-d:nimPreviewCheckedClose, theclosefunction in thestd/synciomodule now raises an IO exception in case of an error. -
Unknown warnings and hints now gives warnings
warnUnknownNotesinstead of errors. -
With
-d:nimPreviewAsmSemSymbol, backticked symbols are type checked in theasm/emitstatements. -
The bare
except:now panics onDefect. Useexcept Exception:orexcept Defect:to catchDefect.--legacy:noPanicOnExceptis provided for a transition period. -
With
-d:nimPreviewCStringComparisons, comparsions (<,>,<=,>=) between cstrings switch from reference semantics to value semantics like==and!=. -
std/parsesqlhas been moved to a nimble package, usenimbleoratlasto install it. -
With
-d:nimPreviewDuplicateModuleError, importing two modules that share the same name becomes a compile-time error. This includes importing the same module more than once. Useimport foo as foo1(or other aliases) to avoid collisions. -
Adds the switch
--mangle:nim|cpp, which selectsnimorcppstyle name mangling when used withdebuginfoon, defaults tonim. The default is changed fromcpptonim. -
The second parameter of
succ,pred,inc, anddecinsystemnow acceptsSomeInteger(previouslyOrdinal). -
Bitshift operators (
shl,shr,ashr) now apply bitmasking to the right operand in the C/C++/VM/JS backends. -
Adds a new warning enabled by
--warning:ImplicitRangeConversionthat detects downsizing implicit conversions to range types (e.g.,int -> range[0..255]orrange[1..256] -> range[0..255]) that could cause runtime panics. Safe conversions likerange[0..255] -> range[0..65535]and explicit casts are not warned on.
Standard library additions and changes
-
setutils.symmetricDifferencealong with its operator versionsetutils.`-+-`and in-place versionsetutils.togglehave been added to more efficiently calculate the symmetric difference of bitsets. -
strutils.multiReplaceoverload for character set replacements in a single pass. Useful for string sanitation. Follows existing multiReplace semantics. -
std/filesadds:- Exports
CopyFlagenum andFilePermissiontype for fine-grained control of file operations - New file operation procs with
Pathsupport:getFilePermissions,setFilePermissionsfor managing permissionstryRemoveFilefor file deletioncopyFilewith configurable buffer size and symlink handlingcopyFileWithPermissionsto preserve file attributescopyFileToDirfor copying files into directories
- Exports
-
std/dirsadds:- New directory operation procs with
Pathsupport:copyDirwith special file handling optionscopyDirWithPermissionsto recursively preserve attributes
- New directory operation procs with
-
system.setLenUninitnow supports refc, JS and VM backends. -
std/parseoptnow supports multiple parser modes via aCliModeenum. Modes includeNim(default, fully compatible) and two new experimental modes:LaxandGnufor different option parsing behaviors.
std/mathThe^symbol now supports floating-point as exponent in addition to the Natural type.min,max, andsequtils'minIndex,maxIndexandminmaxforopenArrays now accept a comparison function.system.substrimplementation now usescopymem(wrapped Cmemcpy) for copying data, if available at compilation.system.newStringUninitis now considered free of side-effects allowing it to be used with--experimental:strictFuncs.
Language changes
-
An experimental option
--experimental:typeBoundOpshas been added that implements the RFC https://github.com/nim-lang/RFCs/issues/380. This makes the behavior of interfaces likehash,$,==etc. more reliable for nominal types across indirect/restricted imports.# objs.nim import std/hashes type Obj* = object x*, y*: int z*: string # to be ignored for equality proc `==`*(a, b: Obj): bool = a.x == b.x and a.y == b.y proc hash*(a: Obj): Hash = $!(hash(a.x) &! hash(a.y))# main.nim {.experimental: "typeBoundOps".} from objs import Obj # objs.hash, objs.`==` not imported import std/tables var t: Table[Obj, int] t[Obj(x: 3, y: 4, z: "debug")] = 34 echo t[Obj(x: 3, y: 4, z: "ignored")] # 34See the experimental manual for more information.
Compiler changes
- Fixed a bug where
sizeof(T)inside atypedesctemplate called from a generic type'swhenclause would error with "'sizeof' requires '.importc' types to be '.completeStruct'". The issue was thathasValuelessStaticsinsemtypinst.nimdidn't recognizetyTypeDesc(tyGenericParam)as an unresolved generic parameter.
Tool changes
- Added
--rawflag when generating JSON docs to not render markup. - Added
--stdinfileflag to name of the file used when running program from stdin (defaults tostdinfile.nim) - Added
--styleCheck:warningflag to treat style check violations as warnings.
Documentation changes
- Added documentation for the
completeStructpragma in the manual.