add dev resources; plumb GUI_ENV_FILE/API_ENV_FILE/API_DESI_DATA into run targets
This commit is contained in:
@@ -80,13 +80,26 @@ destroy :: proc(env: map[string]string) {
|
||||
delete(env)
|
||||
}
|
||||
|
||||
// env_key returns the env key that should bind to a struct field. It prefers
|
||||
// an explicit `env:"NAME"` tag; when the tag is absent or empty it falls
|
||||
// back to the field's name. Matching against the parsed map is
|
||||
// case-insensitive, so API_URL maps onto api_url (or an `env:"API_URL"` tag).
|
||||
@(private)
|
||||
env_key_for_field :: proc(field: reflect.Struct_Field) -> string {
|
||||
if tag_key, ok := reflect.struct_tag_lookup(field.tag, "env");
|
||||
ok && tag_key != "" {
|
||||
return tag_key
|
||||
}
|
||||
return field.name
|
||||
}
|
||||
|
||||
// decode populates dest's fields from env, matching each field by name
|
||||
// (case-insensitively, so API_URL maps onto api_url). Values are converted
|
||||
// to the field's type: string is cloned as-is into allocator, integers are
|
||||
// parsed with strconv.parse_int (decimal/hex/negative), booleans with
|
||||
// strconv.parse_bool, and floats with strconv.parse_f64. Keys missing from
|
||||
// env leave the field at its zero value. It returns false if a present value
|
||||
// cannot be converted to the field's type.
|
||||
// (or by an `env:"NAME"` struct tag). Values are converted to the field's
|
||||
// type: string is cloned as-is into allocator, integers are parsed with
|
||||
// strconv.parse_int (decimal/hex/negative), booleans with strconv.parse_bool,
|
||||
// and floats with strconv.parse_f64. Keys missing from env leave the field
|
||||
// at its zero value. It returns false if a present value cannot be converted
|
||||
// to the field's type.
|
||||
decode :: proc(
|
||||
env: map[string]string,
|
||||
dest: ^$T,
|
||||
@@ -98,11 +111,12 @@ decode :: proc(
|
||||
return false
|
||||
}
|
||||
|
||||
name: string
|
||||
value: string
|
||||
field_ptr := rawptr(dest)
|
||||
st: reflect.Struct_Field
|
||||
for _, i in fields.names[:fields.field_count] {
|
||||
name = fields.names[i]
|
||||
st = reflect.struct_field_at(T, i)
|
||||
name := env_key_for_field(st)
|
||||
value = ""
|
||||
found := false
|
||||
for key, v in env {
|
||||
@@ -157,7 +171,7 @@ decode :: proc(
|
||||
return false
|
||||
}
|
||||
case:
|
||||
// unsupported field type (slices, pointers, ...) is left untouched
|
||||
// unsupported field type (slices, pointers, ...) is left untouched
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,16 @@ Test_Config :: struct {
|
||||
ratio: f64,
|
||||
}
|
||||
|
||||
Tagged_Config :: struct {
|
||||
api_url: string `env:"API_URL"`,
|
||||
port: int `env:"PORT"`,
|
||||
debug: bool `env:"DEBUG"`,
|
||||
}
|
||||
|
||||
Tagged_Empty :: struct {
|
||||
api_url: string `env:""`,
|
||||
}
|
||||
|
||||
// load_env writes src to a unique temp file and parses it via parse_file.
|
||||
// The returned map owns its strings; callers must destroy it.
|
||||
load_env :: proc(t: ^testing.T, src: string) -> map[string]string {
|
||||
@@ -199,3 +209,37 @@ test_decode_hex_and_negative_ints :: proc(t: ^testing.T) {
|
||||
testing.expect(t, dotenv.decode(env, &cfg))
|
||||
testing.expect(t, cfg.port == 31, "hex int should decode")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_decode_honors_env_tags :: proc(t: ^testing.T) {
|
||||
env := load_env(t, "API_URL=http://127.0.0.1:8080\nPORT=9090\nDEBUG=true\n")
|
||||
defer dotenv.destroy(env)
|
||||
|
||||
cfg := Tagged_Config{}
|
||||
testing.expect(t, dotenv.decode(env, &cfg))
|
||||
defer delete(cfg.api_url)
|
||||
|
||||
testing.expect(
|
||||
t,
|
||||
cfg.api_url == "http://127.0.0.1:8080",
|
||||
"env tag should bind API_URL",
|
||||
)
|
||||
testing.expect(t, cfg.port == 9090, "env tag should bind PORT")
|
||||
testing.expect(t, cfg.debug == true, "env tag should bind DEBUG")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_decode_empty_env_tag_falls_back_to_field_name :: proc(t: ^testing.T) {
|
||||
env := load_env(t, "api_url=http://fallback\n")
|
||||
defer dotenv.destroy(env)
|
||||
|
||||
cfg := Tagged_Empty{}
|
||||
testing.expect(t, dotenv.decode(env, &cfg))
|
||||
defer delete(cfg.api_url)
|
||||
|
||||
testing.expect(
|
||||
t,
|
||||
cfg.api_url == "http://fallback",
|
||||
"empty env tag should use field name",
|
||||
)
|
||||
}
|
||||
|
||||
+12
-2
@@ -4,11 +4,21 @@ import "core:os"
|
||||
import dotenv "lib:dotenv/src"
|
||||
|
||||
Config :: struct {
|
||||
api_url: string,
|
||||
api_url: string `env:"API_URL"`,
|
||||
}
|
||||
|
||||
get_config :: proc(env_file: ^string = nil) -> (^Config, ^Error) {
|
||||
env, _ := dotenv.parse_file(".env", context.temp_allocator)
|
||||
path := ".env"
|
||||
if env_file != nil && env_file^ != "" {
|
||||
path = env_file^
|
||||
} else if from_env, ok := os.lookup_env(
|
||||
"GUI_ENV_FILE",
|
||||
context.temp_allocator,
|
||||
); ok {
|
||||
path = from_env
|
||||
}
|
||||
|
||||
env, _ := dotenv.parse_file(path, context.temp_allocator)
|
||||
defer dotenv.destroy(env)
|
||||
|
||||
c := new(Config)
|
||||
|
||||
Reference in New Issue
Block a user