SCUA

How-to

Read config files (TOML and INI)

SCUA reads the common config formats out of the box. toml and ini are pure modules — parsing text needs no capability grant — and both parse to a nested table, which is the whole point: the result pairs directly with first-class paths, so you read config with cfg @ "server/port" instead of a chain of field lookups.

(JSON is also built in — see json in the builtins reference. For YAML, see "What's not here yet" below.)

#TOML

import toml

let text = "
[server]
host = \"0.0.0.0\"
port = 8080
debug = true
"

match toml.parse(text)
  Ok(cfg) -> do
    print(cfg @ "server/host")    -- "0.0.0.0"
    print(cfg @ "server/port")    -- 8080  (a real int)
    print(cfg @ "server/debug")   -- true  (a real bool)
    print(cfg @ "server/region" ?? "us-east")  -- missing key → default
  end
  Error(why) -> print(`bad config: {why}`)
end

toml.parse(s) returns Ok(table) or Error(reason) — a malformed document is a value you handle, not a crash. Sections nest (a dotted [a.b] header nests two levels), and scalar values keep their type (int, bool, string).

Today this is the config subset of TOML: [sections] and string/int/bool keys — which covers the overwhelming majority of real config files. Arrays, floats, inline tables, and datetimes are not yet supported and parse to an Error. Full TOML 1.0 is a planned follow-up.

#INI

import ini

let text = "
[db]
host = db.internal
port = 5432
"

match ini.parse(text)
  Ok(cfg) -> do
    print(cfg @ "db/host")               -- "db.internal"
    print(tonumber(cfg @ "db/port"))     -- 5432
  end
  Error(why) -> print(`bad config: {why}`)
end

INI has no value types, so every value is text — use tonumber where you want a number. [section] headers nest (dotted names like [db.pool] nest further), # and ; start full-line comments, and both key = value and key: value are accepted.

#Reading from a file

The examples above use inline strings so they're self-contained. In a real tool you'd read the file with the fs capability and feed it straight in — the ? operator threads the read failure through:

import fs
import toml

fn load_config()
  let text = fs.read_text("config.toml")?   -- Error short-circuits if the file is missing/!UTF-8
  return toml.parse(text)                    -- Ok(table) | Error(reason)
end

Run it with scua --allow-fs=. tool.scua.

#Why paths

Because the parsers return nested tables, the same @ path access, set, and path"..." values that work on any table work on parsed config — and a missing key reads as nil, so ?? gives you a default in one line. See the runnable examples/config.scua.

#What's not here yet

  • YAML — deliberately deferred. Full YAML is large and ambiguous (anchors, tags, the "Norway problem"); a safe, strictly-bounded subset is planned but its exact boundary needs pinning first. Until then, prefer TOML or JSON for new config.
  • Writing/serializing config (toml.encode/ini.encode) — parse-only for now.
  • Full TOML 1.0 (arrays, floats, inline tables, datetimes) — a planned follow-up.