# # # Nim's Runtime Library # (c) Copyright 2022 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module contains system facilities for reading command ## line parameters. ## **See also:** ## * `parseopt module `_ for command-line parser beyond ## `parseCmdLine proc`_ include system/inclrtl when defined(nimPreviewSlimSystem): import std/widestrs when defined(nodejs): from std/private/oscommon import ReadDirEffect const weirdTarget = defined(nimscript) or defined(js) when weirdTarget: discard elif defined(windows): import winlean elif defined(posix): import posix else: {.error: "The cmdline module has not been implemented for the target platform.".} # Needed by windows in order to obtain the command line for targets # other than command line targets when defined(windows) and not weirdTarget: template getCommandLine*(): untyped = getCommandLineW() proc parseCmdLine*(c: string): seq[string] {. noSideEffect, rtl, extern: "nos$1".} = ## Splits a `command line`:idx: into several components. ## ## **Note**: This proc is only occasionally useful, better use the ## `parseopt module `_. ## ## On Windows, it uses the `following parsing rules ## `_: ## ## * Arguments are delimited by white space, which is either a space or a tab. ## * The caret character (^) is not recognized as an escape character or ## delimiter. The character is handled completely by the command-line parser ## in the operating system before being passed to the argv array in the ## program. ## * A string surrounded by double quotation marks ("string") is interpreted ## as a single argument, regardless of white space contained within. A ## quoted string can be embedded in an argument. ## * A double quotation mark preceded by a backslash (\") is interpreted as a ## literal double quotation mark character ("). ## * Backslashes are interpreted literally, unless they immediately precede ## a double quotation mark. ## * If an even number of backslashes is followed by a double quotation mark, ## one backslash is placed in the argv array for every pair of backslashes, ## and the double quotation mark is interpreted as a string delimiter. ## * If an odd number of backslashes is followed by a double quotation mark, ## one backslash is placed in the argv array for every pair of backslashes, ## and the double quotation mark is "escaped" by the remaining backslash, ## causing a literal double quotation mark (") to be placed in argv. ## ## On Posix systems, it uses the following parsing rules: ## Components are separated by whitespace unless the whitespace ## occurs within ``"`` or ``'`` quotes. ## ## See also: ## * `parseopt module `_ ## * `paramCount proc`_ ## * `paramStr proc`_ ## * `commandLineParams proc`_ result = @[] var i = 0 var a = "" while true: setLen(a, 0) # eat all delimiting whitespace while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i) if i >= c.len: break when defined(windows): # parse a single argument according to the above rules: var inQuote = false while i < c.len: case c[i] of '\\': var j = i while j < c.len and c[j] == '\\': inc(j) if j < c.len and c[j] == '"': for k in 1..(j-i) div 2: a.add('\\') if (j-i) mod 2 == 0: i = j else: a.add('"') i = j+1 else: a.add(c[i]) inc(i) of '"': inc(i) if not inQuote: inQuote = true elif i < c.len and c[i] == '"': a.add(c[i]) inc(i) else: inQuote = false break of ' ', '\t': if not inQuote: break a.add(c[i]) inc(i) else: a.add(c[i]) inc(i) else: case c[i] of '\'', '\"': var delim = c[i] inc(i) # skip ' or " while i < c.len and c[i] != delim: add a, c[i] inc(i) if i < c.len: inc(i) else: while i < c.len and c[i] > ' ': add(a, c[i]) inc(i) add(result, a) when defined(nimdoc): # Common forward declaration docstring block for parameter retrieval procs. proc paramCount*(): int {.tags: [ReadIOEffect].} = ## Returns the number of `command line arguments`:idx: given to the ## application. ## ## Unlike `argc`:idx: in C, if your binary was called without parameters this ## will return zero. ## You can query each individual parameter with `paramStr proc`_ ## or retrieve all of them in one go with `commandLineParams proc`_. ## ## **Availability**: When generating a dynamic library (see `--app:lib`) on ## Posix this proc is not defined. ## Test for availability using `declared() `_. ## ## See also: ## * `parseopt module `_ ## * `parseCmdLine proc`_ ## * `paramStr proc`_ ## * `commandLineParams proc`_ ## ## **Examples:** ## ## .. code-block:: nim ## when declared(paramCount): ## # Use paramCount() here ## else: ## # Do something else! proc paramStr*(i: int): string {.tags: [ReadIOEffect].} = ## Returns the `i`-th `command line argument`:idx: given to the application. ## ## `i` should be in the range `1..paramCount()`, the `IndexDefect` ## exception will be raised for invalid values. Instead of iterating ## over `paramCount()`_ with this proc you can ## call the convenience `commandLineParams()`_. ## ## Similarly to `argv`:idx: in C, ## it is possible to call `paramStr(0)` but this will return OS specific ## contents (usually the name of the invoked executable). You should avoid ## this and call `getAppFilename()`_ instead. ## ## **Availability**: When generating a dynamic library (see `--app:lib`) on ## Posix this proc is not defined. ## Test for availability using `declared() `_. ## ## See also: ## * `parseopt module `_ ## * `parseCmdLine proc`_ ## * `paramCount proc`_ ## * `commandLineParams proc`_ ## * `getAppFilename proc`_ ## ## **Examples:** ## ## .. code-block:: nim ## when declared(paramStr): ## # Use paramStr() here ## else: ## # Do something else! elif defined(nimscript): discard elif defined(nodejs): type Argv = object of JsRoot let argv {.importjs: "process.argv".} : Argv proc len(argv: Argv): int {.importjs: "#.length".} proc `[]`(argv: Argv, i: int): cstring {.importjs: "#[#]".} proc paramCount*(): int {.tags: [ReadDirEffect].} = result = argv.len - 2 proc paramStr*(i: int): string {.tags: [ReadIOEffect].} = let i = i + 1 if i < argv.len and i >= 0: result = $argv[i] else: raise newException(IndexDefect, formatErrorIndexBound(i - 1, argv.len - 2)) elif defined(windows): # Since we support GUI applications with Nim, we sometimes generate # a WinMain entry proc. But a WinMain proc has no access to the parsed # command line arguments. The way to get them differs. Thus we parse them # ourselves. This has the additional benefit that the program's behaviour # is always the same -- independent of the used C compiler. var ownArgv {.threadvar.}: seq[string] ownParsedArgv {.threadvar.}: bool proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # Docstring in nimdoc block. if not ownParsedArgv: ownArgv = parseCmdLine($getCommandLine()) ownParsedArgv = true result = ownArgv.len-1 proc paramStr*(i: int): string {.rtl, extern: "nos$1", tags: [ReadIOEffect].} = # Docstring in nimdoc block. if not ownParsedArgv: ownArgv = parseCmdLine($getCommandLine()) ownParsedArgv = true if i < ownArgv.len and i >= 0: result = ownArgv[i] else: raise newException(IndexDefect, formatErrorIndexBound(i, ownArgv.len-1)) elif defined(genode): proc paramStr*(i: int): string = raise newException(OSError, "paramStr is not implemented on Genode") proc paramCount*(): int = raise newException(OSError, "paramCount is not implemented on Genode") elif weirdTarget or (defined(posix) and appType == "lib"): proc paramStr*(i: int): string {.tags: [ReadIOEffect].} = raise newException(OSError, "paramStr is not implemented on current platform") proc paramCount*(): int {.tags: [ReadIOEffect].} = raise newException(OSError, "paramCount is not implemented on current platform") elif not defined(createNimRtl) and not(defined(posix) and appType == "lib"): # On Posix, there is no portable way to get the command line from a DLL. var cmdCount {.importc: "cmdCount".}: cint cmdLine {.importc: "cmdLine".}: cstringArray proc paramStr*(i: int): string {.tags: [ReadIOEffect].} = # Docstring in nimdoc block. if i < cmdCount and i >= 0: result = $cmdLine[i] else: raise newException(IndexDefect, formatErrorIndexBound(i, cmdCount-1)) proc paramCount*(): int {.tags: [ReadIOEffect].} = # Docstring in nimdoc block. result = cmdCount-1 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 ## executable filename, call `getAppFilename()`_. ## ## **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() ## `_. ## ## See also: ## * `parseopt module `_ ## * `parseCmdLine proc`_ ## * `paramCount proc`_ ## * `paramStr proc`_ ## * `getAppFilename proc`_ ## ## **Examples:** ## ## .. code-block:: nim ## when declared(commandLineParams): ## # Use commandLineParams() here ## else: ## # Do something else! result = @[] for i in 1..paramCount(): result.add(paramStr(i)) else: proc commandLineParams*(): seq[string] {.error: "commandLineParams() unsupported by dynamic libraries".} = discard