// el-config — Layered, typed configuration for el-ui. // // Each layer is a key->value map. `Config::get(key)` walks layers from highest // to lowest priority and returns the first hit. Values are parsed into the // caller-requested type via `parse_`. // ── Environment ────────────────────────────────────────────────────────────── let ENV_DEVELOPMENT: String = "development" let ENV_STAGING: String = "staging" let ENV_PRODUCTION: String = "production" let ENV_TEST: String = "test" fn env_current() -> String { let raw: String = env("EL_ENV") if str_eq(raw, "") { return ENV_DEVELOPMENT } raw } fn env_is_production(name: String) -> Bool { str_eq(name, "production") } // ── Errors ─────────────────────────────────────────────────────────────────── let CFG_ERR_MISSING: String = "config.missing" let CFG_ERR_PARSE: String = "config.parse_error" let CFG_ERR_TYPE: String = "config.type_error" // ── Sources ────────────────────────────────────────────────────────────────── let SRC_ENV: String = "env" let SRC_DOTENV: String = "dotenv" let SRC_MANIFEST: String = "manifest" let SRC_DEFAULTS: String = "defaults" let SRC_MAP: String = "map" type ConfigSource { kind: String priority: Int // higher = wins map_json: String // JSON: key -> string value } fn source_env_vars(prefix: String) -> ConfigSource { // Snapshot env at construction (the runtime exposes env_keys()). let map: String = "{}" let keys: String = env_keys_with_prefix(prefix) let n: Int = json_array_len(keys) let i: Int = 0 while i < n { let raw_key: String = json_array_get(keys, i) let value: String = env(raw_key) let logical_key: String = str_to_lower(str_replace(str_slice(raw_key, str_len(prefix), str_len(raw_key)), "_", ".")) let map = json_set(map, logical_key, "\"" + value + "\"") let i = i + 1 } { "kind": "env", "priority": 100, "map_json": map } } fn source_dotenv(path: String) -> ConfigSource { let raw: String = "" if fs_exists(path) { let raw = fs_read(path) } let map: String = dotenv_parse(raw) { "kind": "dotenv", "priority": 80, "map_json": map } } fn source_manifest(manifest_path: String, current_env: String) -> ConfigSource { let raw: String = fs_read(manifest_path) let base: String = manifest_section(raw, "config") let env_key: String = "env." + current_env let env_overrides: String = manifest_section(raw, env_key) let map: String = json_merge(base, env_overrides) { "kind": "manifest", "priority": 60, "map_json": map } } fn source_defaults(map_json: String) -> ConfigSource { { "kind": "defaults", "priority": 0, "map_json": map_json } } // ── Config ─────────────────────────────────────────────────────────────────── type Config { environment: String // development | staging | production | test sources_json: String // JSON array of ConfigSource } fn config_new(env_name: String) -> Config { { "environment": env_name, "sources_json": "[]" } } fn config_add_source(c: Config, src: ConfigSource) -> Config { let updated: String = json_array_push_sorted(c.sources_json, json_encode(src), "priority", true) { "environment": c.environment, "sources_json": updated } } fn config_set_defaults(c: Config, defaults_map: String) -> Config { config_add_source(c, source_defaults(defaults_map)) } fn config_get_string(c: Config, key: String) -> String { let n: Int = json_array_len(c.sources_json) let i: Int = 0 while i < n { let src_json: String = json_array_get(c.sources_json, i) let map: String = json_get(src_json, "map_json") let v: String = json_get(map, key) if !str_eq(v, "") { return v } let i = i + 1 } "" } fn config_get_int(c: Config, key: String) -> Int { let raw: String = config_get_string(c, key) if str_eq(raw, "") { return 0 } str_to_int(raw) } fn config_get_bool(c: Config, key: String) -> Bool { let raw: String = str_to_lower(config_get_string(c, key)) if str_eq(raw, "true") { return true } if str_eq(raw, "1") { return true } if str_eq(raw, "yes") { return true } false } // Strict variants — non-empty required. fn config_require_string(c: Config, key: String) -> String { let v: String = config_get_string(c, key) if str_eq(v, "") { panic(CFG_ERR_MISSING + ":" + key) } v } // ── load_from_toml — convenience for TOML config files ────────────────────── fn config_load_from_toml(path: String, env_name: String) -> Config { let cfg: Config = config_new(env_name) let raw: String = fs_read(path) let map: String = toml_to_json_flat(raw) // dotted keys let cfg = config_add_source(cfg, source_defaults(map)) cfg } // ── .env parser (minimal) ─────────────────────────────────────────────────── fn dotenv_parse(raw: String) -> String { let map: String = "{}" let lines: String = str_split(raw, "\n") let n: Int = json_array_len(lines) let i: Int = 0 while i < n { let line: String = str_trim(json_array_get(lines, i)) if str_eq(line, "") { let i = i + 1 } if !str_eq(line, "") { if !str_starts_with(line, "#") { let eq: Int = str_index_of(line, "=") if eq > 0 { let k: String = str_trim(str_slice(line, 0, eq)) let v: String = str_trim(str_slice(line, eq + 1, str_len(line))) let v = str_strip_quotes(v) let map = json_set(map, str_to_lower(str_replace(k, "_", ".")), "\"" + v + "\"") } let i = i + 1 } } } map } // ── Entry — smoke test ────────────────────────────────────────────────────── let cfg: Config = config_new(env_current()) let defaults: String = "{\"app.name\":\"MyApp\",\"server.port\":\"8080\"}" let cfg = config_set_defaults(cfg, defaults) println("[el-config] env=" + cfg.environment + " app=" + config_get_string(cfg, "app.name"))