encoding/toml
Reading and writing TOML configuration files.
import encoding.toml
TOML is the configuration format of [section] tables and key = value lines used by Cargo.toml, pyproject.toml and many server configs. This package reads TOML text into a dynamic Value and writes a Value back as TOML. It has five functions: DecodeStrict, Decode and Valid for reading, EncodeStrict and Encode for writing.
Pick TOML for files people edit by hand. For data that programs exchange use encoding/json; for YAML files such as CI or Kubernetes configs use encoding/yaml; for KEY=value secrets files use encoding/envfile. If you only want settings loaded from a file with defaults and environment overrides, config may already do the job.
How TOML maps to a Value
| TOML | Value |
|---|---|
[table], [a.b], inline { k = v }, dotted keys a.b = 1 | object (nested) |
[[name]] | array of objects |
"basic" with escapes, 'literal' | string |
42, -7, 1_000, 0x1F, 0o17, 0b101 | int (64-bit) |
3.5, 1e6, inf, nan | float |
true, false | bool |
dates and times such as 1979-05-27T07:32:00Z | string, unchanged |
What trips people up
- Use
DecodeStrict, notDecode, on files people edit.Decodenever reports an error: on broken input it returns whatever it read before the mistake, so a typo silently drops the rest of the file. - The parser is lenient. It checks syntax, not every rule of the TOML specification. A key or
[table]defined twice is accepted (the tables merge, the later key wins), and an unquoted word such asmode = fastis read as the string"fast"instead of being rejected. - Some syntax is missing. Multi-line strings in triple quotes (
"""or''') make the document invalid, and the 8-digit\Uescape is not decoded. Dates stay strings; parse them with time if you need them. - Writing is not formatting.
Encodewrites keys in no fixed order, drops keys whose value is null, and writes an array of objects as an inline array[{...}, {...}]rather than[[table]]blocks. The result is valid TOML that reads back to the same data, but comments and layout of an original file are lost.
Guide: read a project manifest
Project files such as Cargo.toml or pyproject.toml mix plain tables, inline tables and arrays of tables. DecodeStrict reads the whole file into a Value and tells you through ok whether it was valid; from there the value functions walk it.
import encoding.toml
import encoding.value
import sort
func main:
text := `[package]
name = "shop"
version = "1.4.0"
authors = ["Sara", "Reza"]
[dependencies]
http = "2.1"
json = { version = "1.0", optional = true }
log = "0.9"
[[bin]]
name = "shop"
path = "src/main.salam"
[[bin]]
name = "shop-admin"
path = "src/admin.salam"
`
mut ok := false
doc := toml.DecodeStrict(text, ok)
if not ok:
println "not valid TOML"
ret 1
end
pkg := value.ObjectGet(doc, "package")
println value.AsString(value.ObjectGet(pkg, "name")), value.AsString(value.ObjectGet(pkg, "version"))
println "authors:", value.ArrayLen(value.ObjectGet(pkg, "authors"))
deps := value.ObjectGet(doc, "dependencies")
names := value.ObjectKeys(deps)
sort.QuickSort(names)
each n in names:
d := value.ObjectGet(deps, n)
if value.IsString(d):
println "dep", n, value.AsString(d)
else:
println "dep", n, value.AsString(value.ObjectGet(d, "version")), "(optional)"
end
end
bins := value.ObjectGet(doc, "bin")
repeat value.ArrayLen(bins) in i:
b := value.ArrayGet(bins, i)
println "binary", value.AsString(value.ObjectGet(b, "name")), "from", value.AsString(value.ObjectGet(b, "path"))
end
endA dependency can be a plain version string or an inline table with more detail, so the program asks IsString before reading it. Objects do not keep the order of their keys; sorting ObjectKeys with sort.QuickSort gives a stable listing.
Guide: TOML into a struct
For settings you use all over a program, load them into a struct once. Encode the decoded value as JSON with json.EncodeDefault and let json.UnmarshalLenient fill the struct: keys the file leaves out keep the struct's defaults, and a [server] table fills a nested struct.
import encoding.toml
import encoding.json
import encoding.encerr
struct Server:
pub host: str = "127.0.0.1"
pub port: int = 8000
end
struct Settings:
pub title: str = ""
pub debug: bool = false
pub server: Server = Server {}
pub allowed: Vector<str> = Vector {}
end
func main:
text := `title = "Shop admin"
allowed = ["10.0.0.0/8", "192.168.1.0/24"]
[server]
port = 9090
`
mut ok := false
doc := toml.DecodeStrict(text, ok)
mut s := Settings {}
mut e := encerr.NoError()
if not ok or not json.UnmarshalLenient(json.EncodeDefault(doc), s, e):
println "bad settings"
ret 1
end
println s.title, "debug:", s.debug
println "listen on", s.server.host, s.server.port
println "allowed networks:", s.allowed.len(), s.allowed.get(0)
endGuide: write a TOML file
To write TOML, build a Value object and pass it to Encode or EncodeStrict. A nested object becomes a [table]. The top level must be an object, since a TOML document is always a table; EncodeStrict reports false for anything else.
import encoding.toml
import encoding.value
func replica(host: str, weight: int): value.Value:
mut r := value.NewValueObject()
value.ObjectSet(r, "host", value.ValueString(host))
value.ObjectSet(r, "weight", value.ValueInt(weight))
ret r
end
func main:
mut db := value.NewValueObject()
value.ObjectSet(db, "url", value.ValueString("postgres://localhost/shop"))
value.ObjectSet(db, "pool", value.ValueInt(8))
value.ObjectSet(db, "timeout", value.ValueFloat(30.0))
value.ObjectSet(db, "password", value.ValueNull())
mut replicas := value.NewValueArray()
value.ArrayPush(replicas, replica("10.0.0.2", 2))
value.ArrayPush(replicas, replica("10.0.0.3", 1))
value.ObjectSet(db, "replicas", replicas)
mut root := value.NewValueObject()
value.ObjectSet(root, "database", db)
mut ok := false
text := toml.EncodeStrict(root, ok)
println "encoded:", ok
print text
back := value.ObjectGet(toml.Decode(text), "database")
println "timeout is still a float:", value.IsFloat(value.ObjectGet(back, "timeout"))
println "password written:", value.ObjectHas(back, "password")
println "replicas read back:", value.ArrayLen(value.ObjectGet(back, "replicas"))
endThree things show in the output. The null password is left out, because TOML has no null. The array of replica objects is written inline on one line. The float 30.0 keeps its .0, so it reads back as a float and not as the integer 30. Keys come out in no particular order, so never compare the text with a fixed string; compare the data, for example with value.Equal after decoding the text again.
Guide: catching mistakes
Use DecodeStrict or Valid on any file a person edits. They catch broken syntax: a key without a value, an unclosed string, array or table header.
import encoding.toml
func check(label: str, text: str):
mut ok := false
_ := toml.DecodeStrict(text, ok)
println label, ok
end
func main:
check("good: ", "a = 1\nb = 'two'\n")
check("no value: ", "a = \n")
check("unclosed string: ", "a = \"open\n")
check("unclosed array: ", "a = [1, 2\n")
check("unclosed table: ", "[server\nport = 1\n")
check("multi-line text: ", "a = \"\"\"x\"\"\"\n")
check("duplicate key: ", "a = 1\na = 2\n")
check("unquoted string: ", "mode = fast\n")
println "Valid agrees:", toml.Valid("a = 1"), toml.Valid("a = ")
endThe last two lines are the leniency described above: a duplicate key is accepted and the later value wins, and mode = fast passes as the string "fast", although real TOML requires quotes. If either would hide a real mistake in your files, check the keys you care about after decoding, for example with value.IsString and a list of allowed values.
Functions
DecodeStrict
func DecodeStrict(text: str, ok &: bool): value.Valueok false when the text is not valid.import encoding.toml
import encoding.value
func main:
text := "title = \"salam\"\n\n[server]\nhost = \"localhost\"\nport = 8080\ntags = [\"a\", \"b\"]\n"
println toml.Valid(text), toml.Valid("= no key")
v := toml.Decode(text)
server := value.ObjectGet(v, "server")
println value.AsString(value.ObjectGet(v, "title")), value.AsInt(value.ObjectGet(server, "port"))
println toml.Encode(v)
mut read := false
_ := toml.DecodeStrict("a = \n", read)
println read
mut wrote := false
println toml.EncodeStrict(value.ValueInt(1), wrote), wrote
endDecode
func Decode(text: str): value.ValueValue without reporting errors: on invalid text it returns whatever was read before the error, so prefer DecodeStrict for files people edit.The example for this item is shown with the ones above: encoding-toml-Decode.salam
Valid
func Valid(text: str): boolThe example for this item is shown with the ones above: encoding-toml-Decode.salam
EncodeStrict
func EncodeStrict(v: value.Value, ok &: bool): strok turns false when the top level is not an object or an array holds a null; null keys in a table are skipped.The example for this item is shown with the ones above: encoding-toml-Decode.salam
Encode
func Encode(v: value.Value): strThe example for this item is shown with the ones above: encoding-toml-Decode.salam