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 }