cmdline.nim 11 KB

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