//// Glint — a Gleam-inspired configuration language (POC). //// //// CLI: //// gleam run -- check //// gleam run -- dump //// gleam run -- dump --json //// //// Host app (no ceremony): //// let c = glint.file("config.glint") //// glint.int(c, "port") //// glint.string(c, "server.host") import argv import gleam/io import gleam/list import gleam/result import gleam/string import glint/check import glint/lsp as glint_lsp import glint/pipeline import glint/value.{type Value} import simplifile // ── Host API ──────────────────────────────────────────────────────── // Load once; read fields by path. Wrong field = bug (panic), not a // recoverable Result chain — Glint already typechecked the file. /// Load a `.glint` file and return its root `config` value. /// Panics with a readable message if the file is missing or invalid. pub fn file(path: String) -> Value { case read(path) { Ok(v) -> v Error(e) -> panic as e } } /// Like `file`, but as a `Result` when you want to handle errors. pub fn read(path: String) -> Result(Value, String) { use checked <- result.try(load_file(path)) Ok(checked.config) } /// Nested field access via dots: `"port"`, `"server.host"`. pub fn at(cfg: Value, path: String) -> Value { dig(cfg, path_parts(path)) } pub fn string(cfg: Value, path: String) -> String { expect(path, value.as_string(at(cfg, path))) } pub fn int(cfg: Value, path: String) -> Int { expect(path, value.as_int(at(cfg, path))) } pub fn bool(cfg: Value, path: String) -> Bool { expect(path, value.as_bool(at(cfg, path))) } pub fn float(cfg: Value, path: String) -> Float { expect(path, value.as_float(at(cfg, path))) } /// Unit variant tag at path (`mode` → `"Dev"`). pub fn unit(cfg: Value, path: String) -> String { expect(path, value.as_unit(at(cfg, path))) } pub fn list(cfg: Value, path: String) -> List(Value) { expect(path, value.as_list(at(cfg, path))) } // ── CLI ───────────────────────────────────────────────────────────── pub fn main() -> Nil { case argv.load().arguments { ["check", path] -> run_check(path) ["dump", path] -> run_dump(path, False) ["dump", path, "--json"] -> run_dump(path, True) ["lsp"] -> glint_lsp.main() ["help"] | ["--help"] | ["-h"] -> print_usage() [] -> print_usage() args -> { io.println("unknown arguments: " <> string.join(args, " ")) print_usage() } } } fn print_usage() -> Nil { io.println( "Glint POC — typed config language Usage: gleam run -- check gleam run -- dump gleam run -- dump --json gleam run -- lsp ", ) } fn run_check(path: String) -> Nil { case load_file(path) { Error(msg) -> io.println_error(msg) Ok(checked) -> io.println( "ok config: " <> check.type_to_string(checked.config_type), ) } } fn run_dump(path: String, as_json: Bool) -> Nil { case load_file(path) { Error(msg) -> io.println_error(msg) Ok(checked) -> case as_json { True -> io.println(value.to_json(checked.config)) False -> io.println(value.to_glint(checked.config)) } } } // ── Lower-level (tests, tooling) ──────────────────────────────────── /// Load and typecheck a source string. pub fn load(source: String) -> Result(check.Checked, String) { case pipeline.load(source) { Error(e) -> Error(pipeline.error_to_string(e)) Ok(checked) -> Ok(checked) } } /// Read + load a file (keeps type info). Prefer `file` / `read` in apps. pub fn load_file(path: String) -> Result(check.Checked, String) { case simplifile.read(path) { Error(e) -> Error( "could not read " <> path <> ": " <> simplifile.describe_error(e), ) Ok(source) -> load(source) } } pub fn dump(source: String) -> Result(String, String) { use checked <- result.try(load(source)) Ok(value.to_glint(checked.config)) } pub fn dump_json(source: String) -> Result(String, String) { use checked <- result.try(load(source)) Ok(value.to_json(checked.config)) } // ── internals ─────────────────────────────────────────────────────── fn path_parts(path: String) -> List(String) { string.split(path, on: ".") |> list.filter(fn(p) { p != "" }) } fn dig(value: Value, parts: List(String)) -> Value { case parts { [] -> value [key, ..rest] -> case value.get(value, key) { Ok(next) -> dig(next, rest) Error(e) -> panic as e } } } fn expect(path: String, r: Result(a, String)) -> a { case r { Ok(v) -> v Error(e) -> panic as { path <> ": " <> e } } }