envvars.nim 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2022 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## The `std/envvars` module implements environment variable handling.
  10. import std/oserrors
  11. when defined(nimPreviewSlimSystem):
  12. import std/assertions
  13. type
  14. ReadEnvEffect* = object of ReadIOEffect ## Effect that denotes a read
  15. ## from an environment variable.
  16. WriteEnvEffect* = object of WriteIOEffect ## Effect that denotes a write
  17. ## to an environment variable.
  18. when not defined(nimscript):
  19. when defined(nodejs):
  20. proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
  21. var ret = default.cstring
  22. let key2 = key.cstring
  23. {.emit: "const value = process.env[`key2`];".}
  24. {.emit: "if (value !== undefined) { `ret` = value };".}
  25. result = $ret
  26. proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
  27. var key2 = key.cstring
  28. var ret: bool
  29. {.emit: "`ret` = `key2` in process.env;".}
  30. result = ret
  31. proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
  32. var key2 = key.cstring
  33. var val2 = val.cstring
  34. {.emit: "process.env[`key2`] = `val2`;".}
  35. proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
  36. var key2 = key.cstring
  37. {.emit: "delete process.env[`key2`];".}
  38. iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
  39. var num: int
  40. var keys: RootObj
  41. {.emit: "`keys` = Object.keys(process.env); `num` = `keys`.length;".}
  42. for i in 0..<num:
  43. var key, value: cstring
  44. {.emit: "`key` = `keys`[`i`]; `value` = process.env[`key`];".}
  45. yield ($key, $value)
  46. # commented because it must keep working with js+VM
  47. # elif defined(js):
  48. # {.error: "requires -d:nodejs".}
  49. else:
  50. when defined(windows):
  51. proc c_putenv(envstring: cstring): cint {.importc: "_putenv", header: "<stdlib.h>".}
  52. from std/private/win_setenv import setEnvImpl
  53. import std/winlean
  54. when defined(nimPreviewSlimSystem):
  55. import std/widestrs
  56. type wchar_t {.importc: "wchar_t", header: "<stdlib.h>".} = int16
  57. proc c_wgetenv(varname: ptr wchar_t): ptr wchar_t {.importc: "_wgetenv",
  58. header: "<stdlib.h>".}
  59. proc getEnvImpl(env: cstring): WideCString =
  60. let r: WideCString = env.newWideCString
  61. cast[WideCString](c_wgetenv(cast[ptr wchar_t](r)))
  62. else:
  63. proc c_getenv(env: cstring): cstring {.
  64. importc: "getenv", header: "<stdlib.h>".}
  65. proc c_setenv(envname: cstring, envval: cstring, overwrite: cint): cint {.importc: "setenv", header: "<stdlib.h>".}
  66. proc c_unsetenv(env: cstring): cint {.importc: "unsetenv", header: "<stdlib.h>".}
  67. proc getEnvImpl(env: cstring): cstring = c_getenv(env)
  68. proc getEnv*(key: string, default = ""): string {.tags: [ReadEnvEffect].} =
  69. ## Returns the value of the `environment variable`:idx: named `key`.
  70. ##
  71. ## If the variable does not exist, `""` is returned. To distinguish
  72. ## whether a variable exists or it's value is just `""`, call
  73. ## `existsEnv(key) proc`_.
  74. ##
  75. ## See also:
  76. ## * `existsEnv proc`_
  77. ## * `putEnv proc`_
  78. ## * `delEnv proc`_
  79. ## * `envPairs iterator`_
  80. runnableExamples:
  81. assert getEnv("unknownEnv") == ""
  82. assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist"
  83. let env = getEnvImpl(key)
  84. if env == nil:
  85. result = default
  86. else:
  87. result = $env
  88. proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} =
  89. ## Checks whether the environment variable named `key` exists.
  90. ## Returns true if it exists, false otherwise.
  91. ##
  92. ## See also:
  93. ## * `getEnv proc`_
  94. ## * `putEnv proc`_
  95. ## * `delEnv proc`_
  96. ## * `envPairs iterator`_
  97. runnableExamples:
  98. assert not existsEnv("unknownEnv")
  99. result = getEnvImpl(key) != nil
  100. proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} =
  101. ## Sets the value of the `environment variable`:idx: named `key` to `val`.
  102. ## If an error occurs, `OSError` is raised.
  103. ##
  104. ## See also:
  105. ## * `getEnv proc`_
  106. ## * `existsEnv proc`_
  107. ## * `delEnv proc`_
  108. ## * `envPairs iterator`_
  109. when defined(windows):
  110. if key.len == 0 or '=' in key:
  111. raise newException(OSError, "invalid key, got: " & $(key, val))
  112. if setEnvImpl(key, val, 1'i32) != 0'i32:
  113. raiseOSError(osLastError(), $(key, val))
  114. else:
  115. if c_setenv(key, val, 1'i32) != 0'i32:
  116. raiseOSError(osLastError(), $(key, val))
  117. proc delEnv*(key: string) {.tags: [WriteEnvEffect].} =
  118. ## Deletes the `environment variable`:idx: named `key`.
  119. ## If an error occurs, `OSError` is raised.
  120. ##
  121. ## See also:ven
  122. ## * `getEnv proc`_
  123. ## * `existsEnv proc`_
  124. ## * `putEnv proc`_
  125. ## * `envPairs iterator`_
  126. template bail = raiseOSError(osLastError(), key)
  127. when defined(windows):
  128. #[
  129. # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-160
  130. > You can remove a variable from the environment by specifying an empty string (that is, "") for value_string
  131. note that nil is not legal
  132. ]#
  133. if key.len == 0 or '=' in key:
  134. raise newException(OSError, "invalid key, got: " & key)
  135. let envToDel = key & "="
  136. if c_putenv(cstring envToDel) != 0'i32: bail
  137. else:
  138. if c_unsetenv(key) != 0'i32: bail
  139. when defined(windows):
  140. when defined(cpp):
  141. proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importcpp: "(NI16*)wcschr((const wchar_t *)#, #)",
  142. header: "<string.h>".}
  143. else:
  144. proc strEnd(cstr: WideCString, c = 0'i32): WideCString {.importc: "wcschr",
  145. header: "<string.h>".}
  146. elif defined(macosx) and not defined(ios) and not defined(emscripten):
  147. # From the manual:
  148. # Shared libraries and bundles don't have direct access to environ,
  149. # which is only available to the loader ld(1) when a complete program
  150. # is being linked.
  151. # The environment routines can still be used, but if direct access to
  152. # environ is needed, the _NSGetEnviron() routine, defined in
  153. # <crt_externs.h>, can be used to retrieve the address of environ
  154. # at runtime.
  155. proc NSGetEnviron(): ptr cstringArray {.importc: "_NSGetEnviron",
  156. header: "<crt_externs.h>".}
  157. elif defined(haiku):
  158. var gEnv {.importc: "environ", header: "<stdlib.h>".}: cstringArray
  159. else:
  160. var gEnv {.importc: "environ".}: cstringArray
  161. iterator envPairsImpl(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
  162. when defined(windows):
  163. let env = getEnvironmentStringsW()
  164. var e = env
  165. if e != nil:
  166. while true:
  167. let eend = strEnd(e)
  168. let kv = $e
  169. let p = find(kv, '=')
  170. yield (substr(kv, 0, p-1), substr(kv, p+1))
  171. e = cast[WideCString](cast[ByteAddress](eend)+2)
  172. if int(eend[1]) == 0: break
  173. discard freeEnvironmentStringsW(env)
  174. else:
  175. var i = 0
  176. when defined(macosx) and not defined(ios) and not defined(emscripten):
  177. var gEnv = NSGetEnviron()[]
  178. while gEnv[i] != nil:
  179. let kv = $gEnv[i]
  180. inc(i)
  181. let p = find(kv, '=')
  182. yield (substr(kv, 0, p-1), substr(kv, p+1))
  183. proc envPairsImplSeq(): seq[tuple[key, value: string]] = raiseAssert "implemented in the vmops" # vmops
  184. iterator envPairs*(): tuple[key, value: string] {.tags: [ReadEnvEffect].} =
  185. ## Iterate over all `environments variables`:idx:.
  186. ##
  187. ## In the first component of the tuple is the name of the current variable stored,
  188. ## in the second its value.
  189. ##
  190. ## Works in native backends, nodejs and vm, like the following APIs:
  191. ## * `getEnv proc`_
  192. ## * `existsEnv proc`_
  193. ## * `putEnv proc`_
  194. ## * `delEnv proc`_
  195. when nimvm:
  196. for ai in envPairsImplSeq(): yield ai
  197. else:
  198. when defined(nimscript): discard
  199. else:
  200. for ai in envPairsImpl(): yield ai