cmdline.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2022 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module contains system facilities for reading command
  10. ## line parameters.
  11. ## **See also:**
  12. ## * `parseopt module <parseopt.html>`_ for command-line parser beyond
  13. ## `parseCmdLine proc`_
  14. include system/inclrtl
  15. when defined(nimPreviewSlimSystem):
  16. import std/widestrs
  17. when defined(nodejs):
  18. from std/private/oscommon import ReadDirEffect
  19. const weirdTarget = defined(nimscript) or defined(js)
  20. when weirdTarget:
  21. discard
  22. elif defined(windows):
  23. import std/winlean
  24. elif defined(posix):
  25. import std/posix
  26. # Needed by windows in order to obtain the command line for targets
  27. # other than command line targets
  28. when defined(windows) and not weirdTarget:
  29. template getCommandLine*(): untyped = getCommandLineW()
  30. proc parseCmdLine*(c: string): seq[string] {.
  31. noSideEffect, rtl, extern: "nos$1".} =
  32. ## Splits a `command line`:idx: into several components.
  33. ##
  34. ## **Note**: This proc is only occasionally useful, better use the
  35. ## `parseopt module <parseopt.html>`_.
  36. ##
  37. ## On Windows, it uses the `following parsing rules
  38. ## <https://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_:
  39. ##
  40. ## * Arguments are delimited by white space, which is either a space or a tab.
  41. ## * The caret character (^) is not recognized as an escape character or
  42. ## delimiter. The character is handled completely by the command-line parser
  43. ## in the operating system before being passed to the argv array in the
  44. ## program.
  45. ## * A string surrounded by double quotation marks ("string") is interpreted
  46. ## as a single argument, regardless of white space contained within. A
  47. ## quoted string can be embedded in an argument.
  48. ## * A double quotation mark preceded by a backslash (\") is interpreted as a
  49. ## literal double quotation mark character (").
  50. ## * Backslashes are interpreted literally, unless they immediately precede
  51. ## a double quotation mark.
  52. ## * If an even number of backslashes is followed by a double quotation mark,
  53. ## one backslash is placed in the argv array for every pair of backslashes,
  54. ## and the double quotation mark is interpreted as a string delimiter.
  55. ## * If an odd number of backslashes is followed by a double quotation mark,
  56. ## one backslash is placed in the argv array for every pair of backslashes,
  57. ## and the double quotation mark is "escaped" by the remaining backslash,
  58. ## causing a literal double quotation mark (") to be placed in argv.
  59. ##
  60. ## On Posix systems, it uses the following parsing rules:
  61. ## Components are separated by whitespace unless the whitespace
  62. ## occurs within ``"`` or ``'`` quotes.
  63. ##
  64. ## See also:
  65. ## * `parseopt module <parseopt.html>`_
  66. ## * `paramCount proc`_
  67. ## * `paramStr proc`_
  68. ## * `commandLineParams proc`_
  69. result = @[]
  70. var i = 0
  71. var a = ""
  72. while true:
  73. setLen(a, 0)
  74. # eat all delimiting whitespace
  75. while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
  76. if i >= c.len: break
  77. when defined(windows):
  78. # parse a single argument according to the above rules:
  79. var inQuote = false
  80. while i < c.len:
  81. case c[i]
  82. of '\\':
  83. var j = i
  84. while j < c.len and c[j] == '\\': inc(j)
  85. if j < c.len and c[j] == '"':
  86. for k in 1..(j-i) div 2: a.add('\\')
  87. if (j-i) mod 2 == 0:
  88. i = j
  89. else:
  90. a.add('"')
  91. i = j+1
  92. else:
  93. a.add(c[i])
  94. inc(i)
  95. of '"':
  96. inc(i)
  97. if not inQuote: inQuote = true
  98. elif i < c.len and c[i] == '"':
  99. a.add(c[i])
  100. inc(i)
  101. else:
  102. inQuote = false
  103. break
  104. of ' ', '\t':
  105. if not inQuote: break
  106. a.add(c[i])
  107. inc(i)
  108. else:
  109. a.add(c[i])
  110. inc(i)
  111. else:
  112. case c[i]
  113. of '\'', '\"':
  114. var delim = c[i]
  115. inc(i) # skip ' or "
  116. while i < c.len and c[i] != delim:
  117. add a, c[i]
  118. inc(i)
  119. if i < c.len: inc(i)
  120. else:
  121. while i < c.len and c[i] > ' ':
  122. add(a, c[i])
  123. inc(i)
  124. add(result, move a)
  125. when defined(nimdoc):
  126. # Common forward declaration docstring block for parameter retrieval procs.
  127. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  128. ## Returns the number of `command line arguments`:idx: given to the
  129. ## application.
  130. ##
  131. ## Unlike `argc`:idx: in C, if your binary was called without parameters this
  132. ## will return zero.
  133. ## You can query each individual parameter with `paramStr proc`_
  134. ## or retrieve all of them in one go with `commandLineParams proc`_.
  135. ##
  136. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  137. ## Posix this proc is not defined.
  138. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  139. ##
  140. ## See also:
  141. ## * `parseopt module <parseopt.html>`_
  142. ## * `parseCmdLine proc`_
  143. ## * `paramStr proc`_
  144. ## * `commandLineParams proc`_
  145. ##
  146. ## **Examples:**
  147. ##
  148. ## ```nim
  149. ## when declared(paramCount):
  150. ## # Use paramCount() here
  151. ## else:
  152. ## # Do something else!
  153. ## ```
  154. proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
  155. ## Returns the `i`-th `command line argument`:idx: given to the application.
  156. ##
  157. ## `i` should be in the range `1..paramCount()`, the `IndexDefect`
  158. ## exception will be raised for invalid values. Instead of iterating
  159. ## over `paramCount()`_ with this proc you can
  160. ## call the convenience `commandLineParams()`_.
  161. ##
  162. ## Similarly to `argv`:idx: in C,
  163. ## it is possible to call `paramStr(0)` but this will return OS specific
  164. ## contents (usually the name of the invoked executable). You should avoid
  165. ## this and call `getAppFilename() <os.html#getAppFilename>`_ instead.
  166. ##
  167. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  168. ## Posix this proc is not defined.
  169. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  170. ##
  171. ## See also:
  172. ## * `parseopt module <parseopt.html>`_
  173. ## * `parseCmdLine proc`_
  174. ## * `paramCount proc`_
  175. ## * `commandLineParams proc`_
  176. ## * `getAppFilename proc <os.html#getAppFilename>`_
  177. ##
  178. ## **Examples:**
  179. ##
  180. ## ```nim
  181. ## when declared(paramStr):
  182. ## # Use paramStr() here
  183. ## else:
  184. ## # Do something else!
  185. ## ```
  186. elif defined(nimscript): discard
  187. elif defined(nodejs):
  188. type Argv = object of JsRoot
  189. let argv {.importjs: "process.argv".} : Argv
  190. proc len(argv: Argv): int {.importjs: "#.length".}
  191. proc `[]`(argv: Argv, i: int): cstring {.importjs: "#[#]".}
  192. proc paramCount*(): int {.tags: [ReadDirEffect].} =
  193. result = argv.len - 2
  194. proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
  195. let i = i + 1
  196. if i < argv.len and i >= 0:
  197. result = $argv[i]
  198. else:
  199. raise newException(IndexDefect, formatErrorIndexBound(i - 1, argv.len - 2))
  200. elif defined(windows):
  201. # Since we support GUI applications with Nim, we sometimes generate
  202. # a WinMain entry proc. But a WinMain proc has no access to the parsed
  203. # command line arguments. The way to get them differs. Thus we parse them
  204. # ourselves. This has the additional benefit that the program's behaviour
  205. # is always the same -- independent of the used C compiler.
  206. var
  207. ownArgv {.threadvar.}: seq[string]
  208. ownParsedArgv {.threadvar.}: bool
  209. proc paramCount*(): int {.rtl, extern: "nos$1", tags: [ReadIOEffect].} =
  210. # Docstring in nimdoc block.
  211. if not ownParsedArgv:
  212. ownArgv = parseCmdLine($getCommandLine())
  213. ownParsedArgv = true
  214. result = ownArgv.len-1
  215. proc paramStr*(i: int): string {.rtl, extern: "nos$1",
  216. tags: [ReadIOEffect].} =
  217. # Docstring in nimdoc block.
  218. if not ownParsedArgv:
  219. ownArgv = parseCmdLine($getCommandLine())
  220. ownParsedArgv = true
  221. if i < ownArgv.len and i >= 0:
  222. result = ownArgv[i]
  223. else:
  224. raise newException(IndexDefect, formatErrorIndexBound(i, ownArgv.len-1))
  225. elif defined(genode):
  226. proc paramStr*(i: int): string =
  227. raise newException(OSError, "paramStr is not implemented on Genode")
  228. proc paramCount*(): int =
  229. raise newException(OSError, "paramCount is not implemented on Genode")
  230. elif weirdTarget or (defined(posix) and appType == "lib"):
  231. proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
  232. raise newException(OSError, "paramStr is not implemented on current platform")
  233. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  234. raise newException(OSError, "paramCount is not implemented on current platform")
  235. elif not defined(createNimRtl) and
  236. not(defined(posix) and appType == "lib"):
  237. # On Posix, there is no portable way to get the command line from a DLL.
  238. var
  239. cmdCount {.importc: "cmdCount".}: cint
  240. cmdLine {.importc: "cmdLine".}: cstringArray
  241. proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
  242. # Docstring in nimdoc block.
  243. if i < cmdCount and i >= 0:
  244. result = $cmdLine[i]
  245. else:
  246. raise newException(IndexDefect, formatErrorIndexBound(i, cmdCount-1))
  247. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  248. # Docstring in nimdoc block.
  249. result = cmdCount-1
  250. when declared(paramCount) or defined(nimdoc):
  251. proc commandLineParams*(): seq[string] =
  252. ## Convenience proc which returns the command line parameters.
  253. ##
  254. ## This returns **only** the parameters. If you want to get the application
  255. ## executable filename, call `getAppFilename() <os.html#getAppFilename>`_.
  256. ##
  257. ## **Availability**: On Posix there is no portable way to get the command
  258. ## line from a DLL and thus the proc isn't defined in this environment. You
  259. ## can test for its availability with `declared()
  260. ## <system.html#declared,untyped>`_.
  261. ##
  262. ## See also:
  263. ## * `parseopt module <parseopt.html>`_
  264. ## * `parseCmdLine proc`_
  265. ## * `paramCount proc`_
  266. ## * `paramStr proc`_
  267. ## * `getAppFilename proc <os.html#getAppFilename>`_
  268. ##
  269. ## **Examples:**
  270. ##
  271. ## ```nim
  272. ## when declared(commandLineParams):
  273. ## # Use commandLineParams() here
  274. ## else:
  275. ## # Do something else!
  276. ## ```
  277. result = @[]
  278. for i in 1..paramCount():
  279. result.add(paramStr(i))
  280. else:
  281. proc commandLineParams*(): seq[string] {.error:
  282. "commandLineParams() unsupported by dynamic libraries".} =
  283. discard