cmdline.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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 winlean
  24. elif defined(posix):
  25. import posix
  26. else:
  27. {.error: "The cmdline module has not been implemented for the target platform.".}
  28. # Needed by windows in order to obtain the command line for targets
  29. # other than command line targets
  30. when defined(windows) and not weirdTarget:
  31. template getCommandLine*(): untyped = getCommandLineW()
  32. proc parseCmdLine*(c: string): seq[string] {.
  33. noSideEffect, rtl, extern: "nos$1".} =
  34. ## Splits a `command line`:idx: into several components.
  35. ##
  36. ## **Note**: This proc is only occasionally useful, better use the
  37. ## `parseopt module <parseopt.html>`_.
  38. ##
  39. ## On Windows, it uses the `following parsing rules
  40. ## <http://msdn.microsoft.com/en-us/library/17w5ykft.aspx>`_:
  41. ##
  42. ## * Arguments are delimited by white space, which is either a space or a tab.
  43. ## * The caret character (^) is not recognized as an escape character or
  44. ## delimiter. The character is handled completely by the command-line parser
  45. ## in the operating system before being passed to the argv array in the
  46. ## program.
  47. ## * A string surrounded by double quotation marks ("string") is interpreted
  48. ## as a single argument, regardless of white space contained within. A
  49. ## quoted string can be embedded in an argument.
  50. ## * A double quotation mark preceded by a backslash (\") is interpreted as a
  51. ## literal double quotation mark character (").
  52. ## * Backslashes are interpreted literally, unless they immediately precede
  53. ## a double quotation mark.
  54. ## * If an even number of backslashes is followed by a double quotation mark,
  55. ## one backslash is placed in the argv array for every pair of backslashes,
  56. ## and the double quotation mark is interpreted as a string delimiter.
  57. ## * If an odd number of backslashes is followed by a double quotation mark,
  58. ## one backslash is placed in the argv array for every pair of backslashes,
  59. ## and the double quotation mark is "escaped" by the remaining backslash,
  60. ## causing a literal double quotation mark (") to be placed in argv.
  61. ##
  62. ## On Posix systems, it uses the following parsing rules:
  63. ## Components are separated by whitespace unless the whitespace
  64. ## occurs within ``"`` or ``'`` quotes.
  65. ##
  66. ## See also:
  67. ## * `parseopt module <parseopt.html>`_
  68. ## * `paramCount proc`_
  69. ## * `paramStr proc`_
  70. ## * `commandLineParams proc`_
  71. result = @[]
  72. var i = 0
  73. var a = ""
  74. while true:
  75. setLen(a, 0)
  76. # eat all delimiting whitespace
  77. while i < c.len and c[i] in {' ', '\t', '\l', '\r'}: inc(i)
  78. if i >= c.len: break
  79. when defined(windows):
  80. # parse a single argument according to the above rules:
  81. var inQuote = false
  82. while i < c.len:
  83. case c[i]
  84. of '\\':
  85. var j = i
  86. while j < c.len and c[j] == '\\': inc(j)
  87. if j < c.len and c[j] == '"':
  88. for k in 1..(j-i) div 2: a.add('\\')
  89. if (j-i) mod 2 == 0:
  90. i = j
  91. else:
  92. a.add('"')
  93. i = j+1
  94. else:
  95. a.add(c[i])
  96. inc(i)
  97. of '"':
  98. inc(i)
  99. if not inQuote: inQuote = true
  100. elif i < c.len and c[i] == '"':
  101. a.add(c[i])
  102. inc(i)
  103. else:
  104. inQuote = false
  105. break
  106. of ' ', '\t':
  107. if not inQuote: break
  108. a.add(c[i])
  109. inc(i)
  110. else:
  111. a.add(c[i])
  112. inc(i)
  113. else:
  114. case c[i]
  115. of '\'', '\"':
  116. var delim = c[i]
  117. inc(i) # skip ' or "
  118. while i < c.len and c[i] != delim:
  119. add a, c[i]
  120. inc(i)
  121. if i < c.len: inc(i)
  122. else:
  123. while i < c.len and c[i] > ' ':
  124. add(a, c[i])
  125. inc(i)
  126. add(result, a)
  127. when defined(nimdoc):
  128. # Common forward declaration docstring block for parameter retrieval procs.
  129. proc paramCount*(): int {.tags: [ReadIOEffect].} =
  130. ## Returns the number of `command line arguments`:idx: given to the
  131. ## application.
  132. ##
  133. ## Unlike `argc`:idx: in C, if your binary was called without parameters this
  134. ## will return zero.
  135. ## You can query each individual parameter with `paramStr proc`_
  136. ## or retrieve all of them in one go with `commandLineParams proc`_.
  137. ##
  138. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  139. ## Posix this proc is not defined.
  140. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  141. ##
  142. ## See also:
  143. ## * `parseopt module <parseopt.html>`_
  144. ## * `parseCmdLine proc`_
  145. ## * `paramStr proc`_
  146. ## * `commandLineParams proc`_
  147. ##
  148. ## **Examples:**
  149. ##
  150. ## .. code-block:: nim
  151. ## when declared(paramCount):
  152. ## # Use paramCount() here
  153. ## else:
  154. ## # Do something else!
  155. proc paramStr*(i: int): string {.tags: [ReadIOEffect].} =
  156. ## Returns the `i`-th `command line argument`:idx: given to the application.
  157. ##
  158. ## `i` should be in the range `1..paramCount()`, the `IndexDefect`
  159. ## exception will be raised for invalid values. Instead of iterating
  160. ## over `paramCount()`_ with this proc you can
  161. ## call the convenience `commandLineParams()`_.
  162. ##
  163. ## Similarly to `argv`:idx: in C,
  164. ## it is possible to call `paramStr(0)` but this will return OS specific
  165. ## contents (usually the name of the invoked executable). You should avoid
  166. ## this and call `getAppFilename()`_ instead.
  167. ##
  168. ## **Availability**: When generating a dynamic library (see `--app:lib`) on
  169. ## Posix this proc is not defined.
  170. ## Test for availability using `declared() <system.html#declared,untyped>`_.
  171. ##
  172. ## See also:
  173. ## * `parseopt module <parseopt.html>`_
  174. ## * `parseCmdLine proc`_
  175. ## * `paramCount proc`_
  176. ## * `commandLineParams proc`_
  177. ## * `getAppFilename proc`_
  178. ##
  179. ## **Examples:**
  180. ##
  181. ## .. code-block:: nim
  182. ## when declared(paramStr):
  183. ## # Use paramStr() here
  184. ## else:
  185. ## # Do something else!
  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()`_.
  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`_
  268. ##
  269. ## **Examples:**
  270. ##
  271. ## .. code-block:: nim
  272. ## when declared(commandLineParams):
  273. ## # Use commandLineParams() here
  274. ## else:
  275. ## # Do something else!
  276. result = @[]
  277. for i in 1..paramCount():
  278. result.add(paramStr(i))
  279. else:
  280. proc commandLineParams*(): seq[string] {.error:
  281. "commandLineParams() unsupported by dynamic libraries".} =
  282. discard