diff --git a/changelog.md b/changelog.md index 665346ad7009e..1b7ec1a576e24 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,14 @@ errors. - Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`. + +- `std/parseopt`: `initOptParser` and `getopt` no longer fall back to `commandLineParams()` when given empty input (`""` or `@[]`). An explicit empty string or sequence now produces an empty parser. Code that relied on `""` or `@[]` values to implicitly re-parse the OS command line must be updated to call `initOptParser()` with the `cmdline` argument omitted instead. + The `cmdline = ""` default was removed from the string overload of `initOptParser`. The no-argument form now unambiguously resolves to the `seq[string]` overload with `commandLineParams()` as its default. + + +- `std/cmdline.commandLineParams()` for NimScript now consistently returns only the script arguments (excluding the `nim` executable and its flags), fixing inconsistency between `std/cmdline` and `std/parseopt`. + + ## Standard library additions and changes [//]: # "Additions:" diff --git a/compiler/nim.nim b/compiler/nim.nim index ed6774983cbb1..3befb8377c2b7 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -48,7 +48,8 @@ proc nimbleLockExists(config: ConfigRef): bool = return fileExists(pd.string / nimbleLock) proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = - var p = parseopt.initOptParser(cmd) + var p = if cmd.len == 0: parseopt.initOptParser() + else: parseopt.initOptParser(cmd) var argsCount = 0 config.commandLine.setLen 0 diff --git a/drnim/drnim.nim b/drnim/drnim.nim index eb0d89aa28715..964f38ae0a839 100644 --- a/drnim/drnim.nim +++ b/drnim/drnim.nim @@ -1208,7 +1208,8 @@ proc mainCommand(graph: ModuleGraph) = genSuccessX(graph.config) proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = - var p = parseopt.initOptParser(cmd) + var p = if cmd.len == 0: parseopt.initOptParser() + else: parseopt.initOptParser(cmd) var argsCount = 1 config.commandLine.setLen 0 diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index 52c26d5a352fc..c0b56f4d6453d 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -450,39 +450,18 @@ proc initOptParser(cmdline: openArray[string]; ) 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 - var firstNimsFound = false - for i in countup(0, paramCount()): - if firstNimsFound: - result.cmds[ctr] = paramStr(i) - inc ctr, 1 - if paramStr(i).toLowerAscii().endsWith(".nims") and not firstNimsFound: - firstNimsFound = true - result.cmds = newSeq[string](paramCount()-i) - else: - result.cmds = newSeq[string](paramCount()) - for i in countup(1, paramCount()): - result.cmds[i-1] = paramStr(i) - else: - # we cannot provide this for NimRtl creation on Posix, because we can't - # access the command line arguments then! - raiseAssert "empty command line given but" & - " real command line is not accessible" -proc initOptParser*(cmdline: seq[string]; +proc initOptParser*(cmdline: seq[string] = commandLineParams(); shortNoVal: set[char] = {}; longNoVal: seq[string] = @[]; mode: CliMode = NimMode): OptParser = - ## Initializes the command line parser. + ## Initializes the command line parser from a sequence of arguments. ## ## **Parameters:** ## - ## - `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. + ## - `cmdline`: Sequence of arguments to parse. Defaults to + ## `os.commandLineParams()`, which retrieves the actual command line arguments + ## at the call site. Empty value means no arguments and has no side-effects. ## - `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. @@ -499,8 +478,8 @@ proc initOptParser*(cmdline: seq[string]; shortNoVal = {'l'}, longNoVal = @["left"]) initOptParser(cmdline, shortNoVal, longNoVal, toRules(mode)) -proc initOptParser*(cmdline: seq[string], - shortNoVal: set[char] = {}, +proc initOptParser*(cmdline: seq[string] = commandLineParams(); + shortNoVal: set[char] = {}; longNoVal: seq[string] = @[]; allowWhitespaceAfterColon: bool): OptParser {.deprecated: "`allowWhitespaceAfterColon` is deprecated, use parser modes instead".} = @@ -517,19 +496,22 @@ proc initOptParser*(cmdline: seq[string], if allowWhitespaceAfterColon == false: nimrules.excl prSepAllowDelimAfter initOptParser(cmdline, shortNoVal, longNoVal, nimrules) -proc initOptParser*(cmdline = ""; +proc initOptParser*(cmdline: string; 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. + ## The `cmdline` string is split into tokens with `parseCmdLine proc`_ + ## 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. + ## - `cmdline`: Command line string to parse. Empty value means no arguments + ## and has no side-effects. To parse the actual OS command line arguments, + ## use the `initOptParser(seq[string], seq[char], seq[string], NimMode)`, + ## or just omit this parameter, which the compiler will resolve to that + ## overload automatically. ## - `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. @@ -548,7 +530,7 @@ proc initOptParser*(cmdline = ""; shortNoVal = {'l'}, longNoVal = @["left"]) initOptParser(parseCmdLine(cmdline), shortNoVal, longNoVal, toRules(mode)) -proc initOptParser*(cmdline = ""; +proc initOptParser*(cmdline: string; shortNoVal: set[char] = {}; longNoVal: seq[string] = @[]; allowWhitespaceAfterColon: bool): OptParser {.deprecated: @@ -585,10 +567,10 @@ proc handleShortOption(p: var OptParser; cmd: string) = p.inShortState = false 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 + 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: @@ -779,16 +761,18 @@ 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] = @[]; +iterator getopt*(cmdline: seq[string] = commandLineParams(); shortNoVal: set[char] = {}; longNoVal: seq[string] = @[]; mode: CliMode = NimMode): tuple[kind: CmdLineKind, key, val: string] = ## Convenience iterator for iterating over command line arguments. ## - ## This creates a new `OptParser<#OptParser>`_. If no command line - ## arguments are provided, the real command line as provided by the - ## `os` module is retrieved instead. + ## This creates a new `OptParser<#OptParser>`_. + ## + ## `cmdline` is a sequence of arguments to parse. Defaults to + ## `os.commandLineParams()`, which retrieves the actual command line arguments + ## at the call site. Empty value means no arguments and has no side-effects. ## ## `shortNoVal` and `longNoVal` are used to specify which options ## do not take values. See the `documentation about these diff --git a/lib/std/cmdline.nim b/lib/std/cmdline.nim index 140c458f22474..8491512a620d3 100644 --- a/lib/std/cmdline.nim +++ b/lib/std/cmdline.nim @@ -279,9 +279,14 @@ when declared(paramCount) or defined(nimdoc): proc commandLineParams*(): seq[string] = ## Convenience proc which returns the command line parameters. ## - ## This returns **only** the parameters. If you want to get the application + ## Unlike `paramStr proc`_, returns **only** the parameters. + ## If you want to get the application ## executable filename, call `getAppFilename() `_. ## + ## When used from NimScript, arguments preceding and including the `.nims` + ## file are also excluded, returning only the arguments intended for the + ## script. + ## ## **Availability**: On Posix there is no portable way to get the command ## line from a DLL and thus the proc isn't defined in this environment. You ## can test for its availability with `declared() @@ -303,8 +308,23 @@ when declared(paramCount) or defined(nimdoc): ## # Do something else! ## ``` result = @[] - for i in 1..paramCount(): - result.add(paramStr(i)) + when defined(nimscript): + func isNimScriptExt(s: openArray[char]): bool = + let L = s.len + (L >= 5 and s[L-5] == '.' and ( + (s[L-4] in {'n', 'N'}) and + (s[L-3] in {'i', 'I'}) and + (s[L-2] in {'m', 'M'}) and + (s[L-1] in {'s', 'S'}))) + var firstNimsFound = false + for i in 0..paramCount(): # nimscript needs to check index 0 + if firstNimsFound: + result.add(paramStr(i)) + elif isNimScriptExt(paramStr(i)): + firstNimsFound = true + else: + for i in 1..paramCount(): + result.add(paramStr(i)) else: proc commandLineParams*(): seq[string] {.error: "commandLineParams() unsupported by dynamic libraries".} = diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index 6ee1433c07d50..06f43b3a78c44 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -668,7 +668,8 @@ proc mainCommand(graph: ModuleGraph) = close(results) proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = - var p = parseopt.initOptParser(cmd) + var p = if cmd.len == 0: parseopt.initOptParser() + else: parseopt.initOptParser(cmd) var findProject = false while true: parseopt.next(p)