diff --git a/changelog.md b/changelog.md index 8b427bfc5..ac1ec1163 100644 --- a/changelog.md +++ b/changelog.md @@ -31,6 +31,8 @@ - Removed deprecated `iup` module from stdlib, it has already moved to [nimble](https://github.com/nim-lang/iup). +- Removed deprecated `iup` module from stdlib, it has already moved to [nimble](https://github.com/nim-lang/iup). +- nodejs now supports osenv: `getEnv`, `putEnv`, `envPairs`, `delEnv`, `existsEnv` - `doAssertRaises` now correctly handles foreign exceptions. ## Language changes diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index 01abf12df..55ace92f2 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -3,228 +3,264 @@ when not declared(os) and not declared(ospaths): {.error: "This is an include file for os.nim!".} -when defined(windows): - from parseutils import skipIgnoreCase +when defined(nodejs): + proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = + var ret: cstring + let key2 = key.cstring + {.emit: "`ret` = process.env[`key2`];".} + result = $ret -proc c_getenv(env: cstring): cstring {. - importc: "getenv", header: "".} -proc c_putenv(env: cstring): cint {. - importc: "putenv", header: "".} -proc c_unsetenv(env: cstring): cint {. - importc: "unsetenv", header: "".} + proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = + var key2 = key.cstring + var ret: bool + {.emit: "`ret` = `key2` in process.env;".} + result = ret -# Environment handling cannot be put into RTL, because the ``envPairs`` -# iterator depends on ``environment``. + proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = + var key2 = key.cstring + var val2 = val.cstring + {.emit: "process.env[`key2`] = `val2`;".} -var - envComputed {.threadvar.}: bool - environment {.threadvar.}: seq[string] + proc delEnv*(key: string) {.tags: [WriteEnvEffect].} = + var key2 = key.cstring + {.emit: "delete process.env[`key2`];".} -when defined(nimV2): - proc unpairedEnvAllocs*(): int = - result = environment.len - if result > 0: inc result + iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} = + var num: int + var keys: RootObj + {.emit: "`keys` = Object.keys(process.env); `num` = `keys`.length;".} + for i in 0..".} - else: - proc strEnd(cstr: WideCString, c = 0'i32): WideCString {. - importc: "wcschr", header: "".} - else: - proc strEnd(cstr: cstring, c = 0'i32): cstring {. - importc: "strchr", header: "".} - - proc getEnvVarsC() = - if not envComputed: - environment = @[] - when useWinUnicode: - var - env = getEnvironmentStringsW() - e = env - if e == nil: return # an error occurred - while true: - var eend = strEnd(e) - add(environment, $e) - e = cast[WideCString](cast[ByteAddress](eend)+2) - if eend[1].int == 0: break - discard freeEnvironmentStringsW(env) - else: - var - env = getEnvironmentStringsA() - e = env - if e == nil: return # an error occurred - while true: - var eend = strEnd(e) - add(environment, $e) - e = cast[cstring](cast[ByteAddress](eend)+1) - if eend[1] == '\0': break - discard freeEnvironmentStringsA(env) - envComputed = true +# commented because it must keep working with js+VM +# elif defined(js): +# {.error: "requires -d:nodejs".} else: - const - useNSGetEnviron = (defined(macosx) and not defined(ios) and not defined(emscripten)) or defined(nimscript) + when defined(windows): + from parseutils import skipIgnoreCase - when useNSGetEnviron: - # From the manual: - # Shared libraries and bundles don't have direct access to environ, - # which is only available to the loader ld(1) when a complete program - # is being linked. - # The environment routines can still be used, but if direct access to - # environ is needed, the _NSGetEnviron() routine, defined in - # , can be used to retrieve the address of environ - # at runtime. - proc NSGetEnviron(): ptr cstringArray {. - importc: "_NSGetEnviron", header: "".} - elif defined(haiku): - var gEnv {.importc: "environ", header: "".}: cstringArray - else: - var gEnv {.importc: "environ".}: cstringArray + proc c_getenv(env: cstring): cstring {. + importc: "getenv", header: "".} + proc c_putenv(env: cstring): cint {. + importc: "putenv", header: "".} + proc c_unsetenv(env: cstring): cint {. + importc: "unsetenv", header: "".} - proc getEnvVarsC() = - # retrieves the variables of char** env of C's main proc - if not envComputed: - environment = @[] - when useNSGetEnviron: - var gEnv = NSGetEnviron()[] - var i = 0 - while gEnv[i] != nil: - add environment, $gEnv[i] - inc(i) - envComputed = true + # Environment handling cannot be put into RTL, because the ``envPairs`` + # iterator depends on ``environment``. -proc findEnvVar(key: string): int = - getEnvVarsC() - var temp = key & '=' - for i in 0..high(environment): - when defined(windows): - if skipIgnoreCase(environment[i], temp) == len(temp): return i - else: - if startsWith(environment[i], temp): return i - return -1 + var + envComputed {.threadvar.}: bool + environment {.threadvar.}: seq[string] -proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = - ## Returns the value of the `environment variable`:idx: named `key`. - ## - ## If the variable does not exist, `""` is returned. To distinguish - ## whether a variable exists or it's value is just `""`, call - ## `existsEnv(key) proc <#existsEnv,string>`_. - ## - ## See also: - ## * `existsEnv proc <#existsEnv,string>`_ - ## * `putEnv proc <#putEnv,string,string>`_ - ## * `delEnv proc <#delEnv,string>`_ - ## * `envPairs iterator <#envPairs.i>`_ - runnableExamples: - assert getEnv("unknownEnv") == "" - assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist" + when defined(nimV2): + proc unpairedEnvAllocs*(): int = + result = environment.len + if result > 0: inc result - when nimvm: - discard "built into the compiler" - else: - var i = findEnvVar(key) - if i >= 0: - return TaintedString(substr(environment[i], find(environment[i], '=')+1)) - else: - var env = c_getenv(key) - if env == nil: return TaintedString(default) - result = TaintedString($env) - -proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = - ## Checks whether the environment variable named `key` exists. - ## Returns true if it exists, false otherwise. - ## - ## See also: - ## * `getEnv proc <#getEnv,string,string>`_ - ## * `putEnv proc <#putEnv,string,string>`_ - ## * `delEnv proc <#delEnv,string>`_ - ## * `envPairs iterator <#envPairs.i>`_ - runnableExamples: - assert not existsEnv("unknownEnv") - - when nimvm: - discard "built into the compiler" - else: - if c_getenv(key) != nil: return true - else: return findEnvVar(key) >= 0 - -proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = - ## Sets the value of the `environment variable`:idx: named `key` to `val`. - ## If an error occurs, `OSError` is raised. - ## - ## See also: - ## * `getEnv proc <#getEnv,string,string>`_ - ## * `existsEnv proc <#existsEnv,string>`_ - ## * `delEnv proc <#delEnv,string>`_ - ## * `envPairs iterator <#envPairs.i>`_ - - # Note: by storing the string in the environment sequence, - # we guarantee that we don't free the memory before the program - # ends (this is needed for POSIX compliance). It is also needed so that - # the process itself may access its modified environment variables! - when nimvm: - discard "built into the compiler" - else: - var indx = findEnvVar(key) - if indx >= 0: - environment[indx] = key & '=' & val - else: - add environment, (key & '=' & val) - indx = high(environment) - when defined(windows) and not defined(nimscript): - when useWinUnicode: - var k = newWideCString(key) - var v = newWideCString(val) - if setEnvironmentVariableW(k, v) == 0'i32: raiseOSError(osLastError()) + when defined(windows) and not defined(nimscript): + # because we support Windows GUI applications, things get really + # messy here... + when useWinUnicode: + when defined(cpp): + proc strEnd(cstr: WideCString, c = 0'i32): WideCString {. + importcpp: "(NI16*)wcschr((const wchar_t *)#, #)", header: "".} else: - if setEnvironmentVariableA(key, val) == 0'i32: raiseOSError(osLastError()) + proc strEnd(cstr: WideCString, c = 0'i32): WideCString {. + importc: "wcschr", header: "".} else: - if c_putenv(environment[indx]) != 0'i32: - raiseOSError(osLastError()) + proc strEnd(cstr: cstring, c = 0'i32): cstring {. + importc: "strchr", header: "".} + + proc getEnvVarsC() = + if not envComputed: + environment = @[] + when useWinUnicode: + var + env = getEnvironmentStringsW() + e = env + if e == nil: return # an error occurred + while true: + var eend = strEnd(e) + add(environment, $e) + e = cast[WideCString](cast[ByteAddress](eend)+2) + if eend[1].int == 0: break + discard freeEnvironmentStringsW(env) + else: + var + env = getEnvironmentStringsA() + e = env + if e == nil: return # an error occurred + while true: + var eend = strEnd(e) + add(environment, $e) + e = cast[cstring](cast[ByteAddress](eend)+1) + if eend[1] == '\0': break + discard freeEnvironmentStringsA(env) + envComputed = true -proc delEnv*(key: string) {.tags: [WriteEnvEffect].} = - ## Deletes the `environment variable`:idx: named `key`. - ## If an error occurs, `OSError` is raised. - ## - ## See also:ven - ## * `getEnv proc <#getEnv,string,string>`_ - ## * `existsEnv proc <#existsEnv,string>`_ - ## * `putEnv proc <#putEnv,string,string>`_ - ## * `envPairs iterator <#envPairs.i>`_ - when nimvm: - discard "built into the compiler" else: - var indx = findEnvVar(key) - if indx < 0: return # Do nothing if the env var is not already set - when defined(windows) and not defined(nimscript): - when useWinUnicode: - var k = newWideCString(key) - if setEnvironmentVariableW(k, nil) == 0'i32: raiseOSError(osLastError()) - else: - if setEnvironmentVariableA(key, nil) == 0'i32: raiseOSError(osLastError()) - else: - if c_unsetenv(key) != 0'i32: - raiseOSError(osLastError()) - environment.delete(indx) + const + useNSGetEnviron = (defined(macosx) and not defined(ios) and not defined(emscripten)) or defined(nimscript) -iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} = - ## Iterate over all `environments variables`:idx:. - ## - ## In the first component of the tuple is the name of the current variable stored, - ## in the second its value. - ## - ## See also: - ## * `getEnv proc <#getEnv,string,string>`_ - ## * `existsEnv proc <#existsEnv,string>`_ - ## * `putEnv proc <#putEnv,string,string>`_ - ## * `delEnv proc <#delEnv,string>`_ - getEnvVarsC() - for i in 0..high(environment): - var p = find(environment[i], '=') - yield (TaintedString(substr(environment[i], 0, p-1)), - TaintedString(substr(environment[i], p+1))) + when useNSGetEnviron: + # From the manual: + # Shared libraries and bundles don't have direct access to environ, + # which is only available to the loader ld(1) when a complete program + # is being linked. + # The environment routines can still be used, but if direct access to + # environ is needed, the _NSGetEnviron() routine, defined in + # , can be used to retrieve the address of environ + # at runtime. + proc NSGetEnviron(): ptr cstringArray {. + importc: "_NSGetEnviron", header: "".} + elif defined(haiku): + var gEnv {.importc: "environ", header: "".}: cstringArray + else: + var gEnv {.importc: "environ".}: cstringArray + + proc getEnvVarsC() = + # retrieves the variables of char** env of C's main proc + if not envComputed: + environment = @[] + when useNSGetEnviron: + var gEnv = NSGetEnviron()[] + var i = 0 + while gEnv[i] != nil: + add environment, $gEnv[i] + inc(i) + envComputed = true + + proc findEnvVar(key: string): int = + getEnvVarsC() + var temp = key & '=' + for i in 0..high(environment): + when defined(windows): + if skipIgnoreCase(environment[i], temp) == len(temp): return i + else: + if startsWith(environment[i], temp): return i + return -1 + + proc getEnv*(key: string, default = ""): TaintedString {.tags: [ReadEnvEffect].} = + ## Returns the value of the `environment variable`:idx: named `key`. + ## + ## If the variable does not exist, `""` is returned. To distinguish + ## whether a variable exists or it's value is just `""`, call + ## `existsEnv(key) proc <#existsEnv,string>`_. + ## + ## See also: + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `delEnv proc <#delEnv,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + runnableExamples: + assert getEnv("unknownEnv") == "" + assert getEnv("unknownEnv", "doesn't exist") == "doesn't exist" + + when nimvm: + discard "built into the compiler" + else: + var i = findEnvVar(key) + if i >= 0: + return TaintedString(substr(environment[i], find(environment[i], '=')+1)) + else: + var env = c_getenv(key) + if env == nil: return TaintedString(default) + result = TaintedString($env) + + proc existsEnv*(key: string): bool {.tags: [ReadEnvEffect].} = + ## Checks whether the environment variable named `key` exists. + ## Returns true if it exists, false otherwise. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `delEnv proc <#delEnv,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + runnableExamples: + assert not existsEnv("unknownEnv") + + when nimvm: + discard "built into the compiler" + else: + if c_getenv(key) != nil: return true + else: return findEnvVar(key) >= 0 + + proc putEnv*(key, val: string) {.tags: [WriteEnvEffect].} = + ## Sets the value of the `environment variable`:idx: named `key` to `val`. + ## If an error occurs, `OSError` is raised. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `delEnv proc <#delEnv,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + + # Note: by storing the string in the environment sequence, + # we guarantee that we don't free the memory before the program + # ends (this is needed for POSIX compliance). It is also needed so that + # the process itself may access its modified environment variables! + when nimvm: + discard "built into the compiler" + else: + var indx = findEnvVar(key) + if indx >= 0: + environment[indx] = key & '=' & val + else: + add environment, (key & '=' & val) + indx = high(environment) + when defined(windows) and not defined(nimscript): + when useWinUnicode: + var k = newWideCString(key) + var v = newWideCString(val) + if setEnvironmentVariableW(k, v) == 0'i32: raiseOSError(osLastError()) + else: + if setEnvironmentVariableA(key, val) == 0'i32: raiseOSError(osLastError()) + else: + if c_putenv(environment[indx]) != 0'i32: + raiseOSError(osLastError()) + + proc delEnv*(key: string) {.tags: [WriteEnvEffect].} = + ## Deletes the `environment variable`:idx: named `key`. + ## If an error occurs, `OSError` is raised. + ## + ## See also:ven + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `envPairs iterator <#envPairs.i>`_ + when nimvm: + discard "built into the compiler" + else: + var indx = findEnvVar(key) + if indx < 0: return # Do nothing if the env var is not already set + when defined(windows) and not defined(nimscript): + when useWinUnicode: + var k = newWideCString(key) + if setEnvironmentVariableW(k, nil) == 0'i32: raiseOSError(osLastError()) + else: + if setEnvironmentVariableA(key, nil) == 0'i32: raiseOSError(osLastError()) + else: + if c_unsetenv(key) != 0'i32: + raiseOSError(osLastError()) + environment.delete(indx) + + iterator envPairs*(): tuple[key, value: TaintedString] {.tags: [ReadEnvEffect].} = + ## Iterate over all `environments variables`:idx:. + ## + ## In the first component of the tuple is the name of the current variable stored, + ## in the second its value. + ## + ## See also: + ## * `getEnv proc <#getEnv,string,string>`_ + ## * `existsEnv proc <#existsEnv,string>`_ + ## * `putEnv proc <#putEnv,string,string>`_ + ## * `delEnv proc <#delEnv,string>`_ + getEnvVarsC() + for i in 0..high(environment): + var p = find(environment[i], '=') + yield (TaintedString(substr(environment[i], 0, p-1)), + TaintedString(substr(environment[i], p+1))) diff --git a/tests/js/tos.nim b/tests/js/tos.nim index 30c19c1ae..07eb3aaa3 100644 --- a/tests/js/tos.nim +++ b/tests/js/tos.nim @@ -19,3 +19,29 @@ block: if not isWindows: doAssert cwd.isAbsolute doAssert relativePath(getCurrentDir() / "foo", "bar") == "../foo" + +import std/sequtils + +template main = + putEnv("foo", "bar") + doAssert getEnv("foo") == "bar" + doAssert existsEnv("foo") + + putEnv("foo", "") + doAssert existsEnv("foo") + putEnv("foo", "bar2") + doAssert getEnv("foo") == "bar2" + + when nimvm: + discard + else: + # need support in vmops: envPairs, delEnv + let s = toSeq(envPairs()) + doAssert ("foo", "bar2") in s + doAssert ("foo", "bar") notin s + + delEnv("foo") + doAssert not existsEnv("foo") + +static: main() +main()