Gleam-inspired typed configuration language (POC)
0

Configure Feed

Select the types of activity you want to include in your feed.

glint / src / glint.gleam
5.1 kB 181 lines
1//// Glint — a Gleam-inspired configuration language (POC). 2//// 3//// CLI: 4//// gleam run -- check <file.glint> 5//// gleam run -- dump <file.glint> 6//// gleam run -- dump <file.glint> --json 7//// 8//// Host app (no ceremony): 9//// let c = glint.file("config.glint") 10//// glint.int(c, "port") 11//// glint.string(c, "server.host") 12 13import argv 14import gleam/io 15import gleam/list 16import gleam/result 17import gleam/string 18import glint/check 19import glint/lsp as glint_lsp 20import glint/pipeline 21import glint/value.{type Value} 22import simplifile 23 24// ── Host API ──────────────────────────────────────────────────────── 25// Load once; read fields by path. Wrong field = bug (panic), not a 26// recoverable Result chain — Glint already typechecked the file. 27 28/// Load a `.glint` file and return its root `config` value. 29/// Panics with a readable message if the file is missing or invalid. 30pub fn file(path: String) -> Value { 31 case read(path) { 32 Ok(v) -> v 33 Error(e) -> panic as e 34 } 35} 36 37/// Like `file`, but as a `Result` when you want to handle errors. 38pub fn read(path: String) -> Result(Value, String) { 39 use checked <- result.try(load_file(path)) 40 Ok(checked.config) 41} 42 43/// Nested field access via dots: `"port"`, `"server.host"`. 44pub fn at(cfg: Value, path: String) -> Value { 45 dig(cfg, path_parts(path)) 46} 47 48pub fn string(cfg: Value, path: String) -> String { 49 expect(path, value.as_string(at(cfg, path))) 50} 51 52pub fn int(cfg: Value, path: String) -> Int { 53 expect(path, value.as_int(at(cfg, path))) 54} 55 56pub fn bool(cfg: Value, path: String) -> Bool { 57 expect(path, value.as_bool(at(cfg, path))) 58} 59 60pub fn float(cfg: Value, path: String) -> Float { 61 expect(path, value.as_float(at(cfg, path))) 62} 63 64/// Unit variant tag at path (`mode` → `"Dev"`). 65pub fn unit(cfg: Value, path: String) -> String { 66 expect(path, value.as_unit(at(cfg, path))) 67} 68 69pub fn list(cfg: Value, path: String) -> List(Value) { 70 expect(path, value.as_list(at(cfg, path))) 71} 72 73// ── CLI ───────────────────────────────────────────────────────────── 74 75pub fn main() -> Nil { 76 case argv.load().arguments { 77 ["check", path] -> run_check(path) 78 ["dump", path] -> run_dump(path, False) 79 ["dump", path, "--json"] -> run_dump(path, True) 80 ["lsp"] -> glint_lsp.main() 81 ["help"] | ["--help"] | ["-h"] -> print_usage() 82 [] -> print_usage() 83 args -> { 84 io.println("unknown arguments: " <> string.join(args, " ")) 85 print_usage() 86 } 87 } 88} 89 90fn print_usage() -> Nil { 91 io.println( 92 "Glint POC — typed config language 93 94Usage: 95 gleam run -- check <file.glint> 96 gleam run -- dump <file.glint> 97 gleam run -- dump <file.glint> --json 98 gleam run -- lsp 99", 100 ) 101} 102 103fn run_check(path: String) -> Nil { 104 case load_file(path) { 105 Error(msg) -> io.println_error(msg) 106 Ok(checked) -> 107 io.println( 108 "ok config: " <> check.type_to_string(checked.config_type), 109 ) 110 } 111} 112 113fn run_dump(path: String, as_json: Bool) -> Nil { 114 case load_file(path) { 115 Error(msg) -> io.println_error(msg) 116 Ok(checked) -> 117 case as_json { 118 True -> io.println(value.to_json(checked.config)) 119 False -> io.println(value.to_glint(checked.config)) 120 } 121 } 122} 123 124// ── Lower-level (tests, tooling) ──────────────────────────────────── 125 126/// Load and typecheck a source string. 127pub fn load(source: String) -> Result(check.Checked, String) { 128 case pipeline.load(source) { 129 Error(e) -> Error(pipeline.error_to_string(e)) 130 Ok(checked) -> Ok(checked) 131 } 132} 133 134/// Read + load a file (keeps type info). Prefer `file` / `read` in apps. 135pub fn load_file(path: String) -> Result(check.Checked, String) { 136 case simplifile.read(path) { 137 Error(e) -> 138 Error( 139 "could not read " 140 <> path 141 <> ": " 142 <> simplifile.describe_error(e), 143 ) 144 Ok(source) -> load(source) 145 } 146} 147 148pub fn dump(source: String) -> Result(String, String) { 149 use checked <- result.try(load(source)) 150 Ok(value.to_glint(checked.config)) 151} 152 153pub fn dump_json(source: String) -> Result(String, String) { 154 use checked <- result.try(load(source)) 155 Ok(value.to_json(checked.config)) 156} 157 158// ── internals ─────────────────────────────────────────────────────── 159 160fn path_parts(path: String) -> List(String) { 161 string.split(path, on: ".") 162 |> list.filter(fn(p) { p != "" }) 163} 164 165fn dig(value: Value, parts: List(String)) -> Value { 166 case parts { 167 [] -> value 168 [key, ..rest] -> 169 case value.get(value, key) { 170 Ok(next) -> dig(next, rest) 171 Error(e) -> panic as e 172 } 173 } 174} 175 176fn expect(path: String, r: Result(a, String)) -> a { 177 case r { 178 Ok(v) -> v 179 Error(e) -> panic as { path <> ": " <> e } 180 } 181}