toml
Parse and serialize TOML, read and write TOML files, and work with nested tables by dotted path. The API mirrors json exactly.
introduce toml| Function | Signature | Returns |
|---|---|---|
parse | parse(s) | map |
stringify | stringify(val) | str |
read | read(path) | map |
write | write(path, val) | None |
get_in | get_in(tbl, path) | value or None |
set_in | set_in(tbl, path, val) | the root map |
del_in | del_in(tbl, path) | the root map |
has_key_in | has_key_in(tbl, path) | bool |
set_where | set_where(arr, key, val, fields) | array |
merge | merge(a, b) | map |
deep_merge | deep_merge(a, b) | map |
Basic map operations are global builtins: has(m, key), keys(m),
values(m), insert(m, key, val), remove(m, key).
#Type mapping
| TOML | Oxigen |
|---|---|
| string | String |
| integer | Integer |
| float | Float |
| boolean | True / False |
| datetime / date / time | String |
| array | Array |
| table | Map |
| array of tables | Array of Map |
TOML has no null, so nothing maps to None. Dates come back as strings —
pass one through datetime.parse if you need a
timestamp.
#Parsing and serializing
#parse(s)
Parse a TOML document. Tables become maps.
introduce toml
t := toml.parse("title = \"MyApp\"\n[server]\nhost = \"localhost\"\nport = 8080\n")
println(t)
// {server: {host: localhost, port: 8080}, title: MyApp}Note that top-level keys and [section] tables end up in the same map; the
order you see is the parser's, not the file's.
Invalid TOML is an Error.
#stringify(val)
Serialize a map to a proper TOML document — [section] headers,
[[array_of_tables]] blocks, and blank lines between sections.
config := {"title": "MyApp", "server": {"host": "localhost", "port": 8080}}
println(toml.stringify(config))title = "MyApp"
[server]
host = "localhost"
port = 8080Scalars are emitted before tables, because TOML requires any bare key to come
before the first [section] header.
#read(path)
Read and parse a TOML file.
settings := toml.read("Cargo.toml")
println(settings["package"]["name"]) // oxigenA key that is not in the file reads back as None, and indexing None is an
error — so reach for get_in when a section may be absent (a workspace
Cargo.toml has no [package], for instance).
#write(path, val)
Serialize a map and write it to a file, replacing what was there.
toml.write("output.toml", {"title": "My App", "version": 1})
println(toml.read("output.toml")) // {title: My App, version: 1}#Nested access by dotted path
#get_in(tbl, path)
The value at a dotted path, or None when any segment is missing.
println(toml.get_in(t, "server.port")) // 8080
println(toml.get_in(t, "server.nope")) // NoneA path addresses tables only — it does not descend into arrays. For an
[[array of tables]], see set_where.
#set_in(tbl, path, val)
Set a value, creating intermediate tables as needed. Returns the root map.
config = toml.set_in(config, "server.ssl", True)
println(toml.get_in(config, "server.ssl")) // TrueMaps are mutable and shared.
set_inmodifies the map you passed and returns that same map. So dodel_in,merge,deep_merge, and theinsert/removebuiltins. Copy first (toml.parse(toml.stringify(config))) if you need the original intact.
#del_in(tbl, path)
Remove a key at a dotted path. Returns the root map.
config = toml.del_in(config, "server.ssl")
println(toml.has_key_in(config, "server.ssl")) // False#has_key_in(tbl, path)
println(toml.has_key_in(config, "server.host")) // True#Arrays of tables
An [[array of tables]] parses to an array of maps, which a dotted path cannot
descend into. set_where edits one by field value; the
array module has the matching
find_where / index_where / filter_where / remove_where.
#set_where(arr, key, val, fields)
Update the first table whose key equals val by deep-merging fields into
it — or append a new table when there is none.
introduce toml
cfg := toml.read("products.toml")
cfg.products = toml.set_where(cfg.products, "name", "hammer", {"sku": 738594937})
cfg.products = toml.set_where(cfg.products, "name", "screw", {"sku": 3})
toml.write("products.toml", cfg)[[products]]
name = "hammer"
sku = 738594937
[[products]]
name = "nail"
sku = 2
[[products]]
name = "screw"
sku = 3Fields not named survive the update, tables lacking the key are skipped rather than erroring, and only the first match is updated. It returns the array, edited in place — assigning the result back is the clearer form.
#Merging
#merge(a, b)
Shallow merge; keys in b win.
println(toml.merge({"a": 1}, {"a": 2, "b": 3})) // {a: 2, b: 3}#deep_merge(a, b)
Recursive merge; nested tables are merged rather than replaced.
a := {"server": {"host": "localhost", "port": 8080}}
b := {"server": {"port": 9090}}
println(toml.deep_merge(a, b))
// {server: {host: localhost, port: 9090}}#Worked example
introduce toml
introduce strings
// Read a project manifest, bump the patch version, write it back.
fun bump(path <str>) {
cfg := toml.read(path)
parts := strings.split(toml.get_in(cfg, "package.version"), ".")
next := "{parts[0]}.{parts[1]}.{int(parts[2]) + 1}"
toml.write(path, toml.set_in(cfg, "package.version", next))
next
}
println(bump("m.toml")) // 0.1.4Layered configuration, defaults last-write-wins:
introduce toml
introduce io
defaults := {"server": {"host": "127.0.0.1", "port": 8000}, "debug": False}
user := option {
io.file_exists("app.toml") -> { toml.read("app.toml") }
{ {} }
}
config := toml.deep_merge(defaults, user)
println(toml.get_in(config, "server.port"))See also: json — same API, different format; datetime for turning TOML date strings into timestamps.