mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-03 11:42:28 +00:00
Add bring-your-own-buffer versions of os.lookup_env and os.get_env
And make `core:terminal` use it so that `core:log` can be imported with `-default-to-nil-allocator`, in which the actual allocator is set up in `main()`. Windows was tricky because of the utf-8 <> utf-16 conversion, so we use some temporary stack buffers for that purpose, limiting the non-allocating version there to 512 utf-16 characters each for the key and environment value. In general the value is (obviously) limited to the size of the supplied buffer, and a `.Buffer_Full` error is returned if that buffer is insufficient. If the key is not found, the procedure returns `.Env_Var_Not_Found`. TODO: - Factor out buffer-backed utf8 + utf16 conversion to `core:sys/util` to more easily apply this pattern. - Add similar `lookup_env` and `get_env` procedures to `core:os/os2`. Fixes #5336
This commit is contained in:
@@ -8,7 +8,7 @@ import "base:runtime"
|
||||
// Otherwise the returned value will be empty and the boolean will be false
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
if key == "" {
|
||||
return
|
||||
}
|
||||
@@ -29,17 +29,77 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
return
|
||||
}
|
||||
|
||||
// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer.
|
||||
// Note that it is limited to environment names and values of 512 utf-16 values each
|
||||
// due to the necessary utf-8 <> utf-16 conversion.
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
key_buf: [513]u16
|
||||
n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, raw_data(key), i32(len(key)), nil, 0)
|
||||
if n1 == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
n1 = win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, raw_data(key), i32(len(key)), raw_data(key_buf[:]), n1)
|
||||
if n1 == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
n2 := win32.GetEnvironmentVariableW(raw_data(key_buf[:]), nil, 0)
|
||||
if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
|
||||
val_buf: [513]u16
|
||||
n2 = win32.GetEnvironmentVariableW(raw_data(key_buf[:]), raw_data(val_buf[:]), u32(len(val_buf[:])))
|
||||
if n2 == 0 && get_last_error() == ERROR_ENVVAR_NOT_FOUND {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else if int(n2) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
n3 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(val_buf[:]), -1, nil, 0, nil, nil)
|
||||
if n3 == 0 {
|
||||
return
|
||||
} else if int(n3) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
n4 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(val_buf[:]), -1, raw_data(buf), n3, nil, nil)
|
||||
if n4 == 0 {
|
||||
return
|
||||
} else if int(n4) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
}
|
||||
|
||||
for i in 0..<n3 {
|
||||
if buf[i] == 0 {
|
||||
n3 = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(buf[:n3]), nil
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
// get_env retrieves the value of the environment variable named by the key
|
||||
// It returns the value, which will be empty if the variable is not present
|
||||
// To distinguish between an empty value and an unset value, use lookup_env
|
||||
// NOTE: the value will be allocated with the supplied allocator
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
|
||||
// set_env sets the value of the environment variable named by the key
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
k := win32.utf8_to_wstring(key)
|
||||
|
||||
@@ -35,6 +35,9 @@ General_Error :: enum u32 {
|
||||
|
||||
File_Is_Pipe,
|
||||
Not_Dir,
|
||||
|
||||
// Environment variable not found.
|
||||
Env_Var_Not_Found,
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string {
|
||||
case .Pattern_Has_Separator: return "pattern has separator"
|
||||
case .File_Is_Pipe: return "file is pipe"
|
||||
case .Not_Dir: return "file is not directory"
|
||||
case .Env_Var_Not_Found: return "environment variable not found"
|
||||
}
|
||||
case io.Error:
|
||||
switch e {
|
||||
|
||||
@@ -1055,9 +1055,10 @@ flush :: proc(fd: Handle) -> Error {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
|
||||
@@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -839,11 +839,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -463,9 +463,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -474,11 +475,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
|
||||
@(private, require_results)
|
||||
_processor_core_count :: proc() -> int {
|
||||
info: haiku.system_info
|
||||
|
||||
@@ -250,11 +250,26 @@ current_thread_id :: proc "contextless" () -> int {
|
||||
return 0
|
||||
}
|
||||
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> string {
|
||||
value, _ := lookup_env(key, allocator)
|
||||
return value
|
||||
}
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
@@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
@@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
set_env :: proc(key, value: string) -> Error {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
key_cstring := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
|
||||
@@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) {
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
path_str := strings.clone_to_cstring(key, context.temp_allocator)
|
||||
// NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults.
|
||||
cstr := _unix_getenv(path_str)
|
||||
if cstr == nil {
|
||||
return "", false
|
||||
@@ -886,11 +886,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
if len(key) + 1 > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, key)
|
||||
}
|
||||
|
||||
if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" {
|
||||
return "", .Env_Var_Not_Found
|
||||
} else {
|
||||
if len(value) > len(buf) {
|
||||
return "", .Buffer_Full
|
||||
} else {
|
||||
copy(buf, value)
|
||||
return string(buf[:len(value)]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
|
||||
@(require_results)
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -240,11 +240,26 @@ exit :: proc "contextless" (code: int) -> ! {
|
||||
wasi.proc_exit(wasi.exitcode_t(code))
|
||||
}
|
||||
|
||||
lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
@(require_results)
|
||||
lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
get_env :: proc(key: string, allocator := context.allocator) -> string {
|
||||
value, _ := lookup_env(key, allocator)
|
||||
return value
|
||||
}
|
||||
@(require_results)
|
||||
lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) {
|
||||
return "", .Env_Var_Not_Found
|
||||
}
|
||||
lookup_env :: proc{lookup_env_alloc, lookup_env_buffer}
|
||||
|
||||
@(require_results)
|
||||
get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) {
|
||||
value, _ = lookup_env(key, allocator)
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
get_env_buf :: proc(buf: []u8, key: string) -> (value: string) {
|
||||
value, _ = lookup_env(buf, key)
|
||||
return
|
||||
}
|
||||
get_env :: proc{get_env_alloc, get_env_buf}
|
||||
@@ -11,17 +11,17 @@ import "core:strings"
|
||||
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
|
||||
|
||||
get_no_color :: proc() -> bool {
|
||||
if no_color, ok := os.lookup_env("NO_COLOR"); ok {
|
||||
defer delete(no_color)
|
||||
buf: [128]u8
|
||||
if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil {
|
||||
return no_color != ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
get_environment_color :: proc() -> Color_Depth {
|
||||
buf: [128]u8
|
||||
// `COLORTERM` is non-standard but widespread and unambiguous.
|
||||
if colorterm, ok := os.lookup_env("COLORTERM"); ok {
|
||||
defer delete(colorterm)
|
||||
if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil {
|
||||
// These are the only values that are typically advertised that have
|
||||
// anything to do with color depth.
|
||||
if colorterm == "truecolor" || colorterm == "24bit" {
|
||||
@@ -29,8 +29,7 @@ get_environment_color :: proc() -> Color_Depth {
|
||||
}
|
||||
}
|
||||
|
||||
if term, ok := os.lookup_env("TERM"); ok {
|
||||
defer delete(term)
|
||||
if term, err := os.lookup_env(buf[:], "TERM"); err == nil {
|
||||
if strings.contains(term, "-truecolor") {
|
||||
return .True_Color
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user