Files
desi_explorer/gui/lib/local/dotenv/dotenv.odin
T
sam_oneal ac2d30510c
CI / Detect changed paths (pull_request) Failing after 34m20s
CI / Infra unit tests, vet, and preview (pull_request) Has been skipped
CI / API unit tests and lint (pull_request) Failing after 34m36s
CI / Odin unit tests and build (pull_request) Failing after 34m49s
added support for dotenv files and config
2026-09-07 13:27:51 -06:00

95 lines
2.8 KiB
Odin

package dotenv
import "core:os"
import "core:strconv"
import "core:strings"
// parse parses dotenv-format source (KEY=VALUE lines) into a map allocated
// with allocator. Blank lines, lines starting with '#', and lines without a
// '=' are skipped. Keys and values are trimmed; values may be wrapped in
// double quotes. Precedence with the real process environment is handled by
// the get_* accessors (real env vars win over the file).
parse :: proc(src: string, allocator := context.allocator) -> map[string]string {
result := make(map[string]string, allocator)
it := src
for line in strings.split_lines_iterator(&it) {
tr := strings.trim_space(line)
if len(tr) == 0 || strings.has_prefix(tr, "#") {
continue
}
eq := strings.index_byte(tr, '=')
if eq < 0 {
continue
}
key := strings.trim_space(tr[:eq])
if key == "" {
continue
}
value := strings.trim_space(tr[eq + 1:])
if len(value) >= 2 && value[0] == '"' && value[len(value) - 1] == '"' {
value = value[1:len(value) - 1]
}
// clone so the map outlives the source buffer (e.g. a freed file read)
result[strings.clone(key, allocator)] = strings.clone(value, allocator)
}
return result
}
// parse_file reads a dotenv file from disk and parses it. It returns
// (nil, false) when the file cannot be read (e.g. it does not exist).
parse_file :: proc(filename: string, allocator := context.allocator) -> (map[string]string, bool) {
data, err := os.read_entire_file(filename, allocator)
if err != nil {
return nil, false
}
defer delete(data)
return parse(string(data), allocator), true
}
// destroy frees the cloned keys/values and the map itself. Use it to release
// a map returned by parse/parse_file (plain delete does not free the strings).
destroy :: proc(env: map[string]string) {
for key, value in env {
delete(key)
delete(value)
}
delete(env)
}
// get_string returns the value for key from the real process environment if
// set, otherwise from the parsed dotenv map (or "" if neither has it).
get_string :: proc(env: map[string]string, key: string) -> string {
if value, found := os.lookup_env(key, context.temp_allocator); found {
return value
}
return env[key]
}
// get_bool resolves key and parses it as a boolean (true/false, 1/0,
// yes/no, on/off). Missing or unparsable values yield default.
get_bool :: proc(env: map[string]string, key: string, default := false) -> bool {
if v := get_string(env, key); v != "" {
if parsed, ok := strconv.parse_bool(v); ok {
return parsed
}
}
return default
}
// get_int resolves key and parses it as an integer. Missing or unparsable
// values yield default.
get_int :: proc(env: map[string]string, key: string, default := 0) -> int {
if v := get_string(env, key); v != "" {
if parsed, ok := strconv.parse_int(v); ok {
return parsed
}
}
return default
}