Toml
Reading and writing TOML without losing anything on the way through.
Toml.parseBytes source
|> Result.map Toml.toString
--> Ok (the same bytes back)
That round trip is exact for a document nobody has edited, and it is exact
because of how the AST is shaped rather than because of any effort here: the
whitespace, the comments and the source spelling of every number are all in it.
See Toml.Ast.
Which module you want
Take the example from https://toml.io:
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
data = [ ["delta", "phi"], [3.14] ]
temp_targets = { cpu = 79.5, case = 72.0 }
[servers]
[servers.alpha]
ip = "10.0.0.1"
role = "frontend"
[servers.beta]
ip = "10.0.0.2"
role = "backend"
parseBytes turns it into a Document, and every module below works on that or
on something made from it. Each one has this same file read through in its own
docs:
-
Read it into your own types --
Toml.Decode.Toml.Decode.fromBytes (Toml.Decode.at [ "servers", "alpha", "ip" ] Toml.Decode.string) source --> Ok "10.0.0.1" -
Change one thing and write it back --
Toml.Edit.Toml.parseBytes source |> Result.map (Toml.Edit.set [ "database", "enabled" ] (Toml.Edit.bool False)) |> Result.map Toml.toString -
Build a file like it from nothing --
Toml.Encode. -
Walk what the file says, when the keys are the data and no decoder can be written ahead of time --
Toml.Table. -
Walk what the file is, character by character --
Toml.Ast.
A parsed TOML file. See Toml.Ast.Document for what is
in one.
Reading
Read a TOML file from a string.
Offered for convenience and for callers who already have the text. It cannot
detect an encoding error, by construction -- see parseBytes.
A byte order mark it can still see. Not every way of reading a file as text
removes one, so if the string begins with U+FEFF that is taken to be the
file's mark, dropped, and recorded so that toString puts it
back. If something removed it before the text got here, the document is
recorded as having none, since whatever removed it did not say.
Read a TOML file from its bytes.
This is the one to use. It is the only entry point that can tell a valid
file from an invalid one, because two of the things that make a TOML file
invalid are decided before there is a String to look at:
- The encoding.
Bytes.toStringis a strict UTF-8 decode, so a file with a bad byte sequence in it -- or a UTF-16 file, or a lone surrogate -- is refused here. By the time a caller holds aString, any such error has already been repaired intoU+FFFD, andU+FFFDis a legal TOML character, so it cannot be found again afterwards. - The byte order mark. Exactly one leading mark is allowed and is not part
of the document. Decoding removes it silently, so this function looks at the
first three bytes itself and records what it saw, which is what lets
toStringput it back.
A document with nothing in it.
This exists for one situation, and it is the situation a program that keeps a config file is in every time it saves: the file may not be there yet.
when found is
Nothing ->
-- No file. The first run.
Ok Toml.empty
Just bytes ->
Toml.parseBytes bytes
|> Result.map (Toml.Edit.set [ "theme" ] (Toml.Edit.string "dark"))
With it, creating the file and changing one value in it are the same code --
Toml.Edit.set adds a key that is not there, and every key is
missing from an empty document. Without it a program needs a second path that
writes the file from scratch, which is a second place to keep the shape of the
file and the first place for the two to drift apart.
A file that is there and will not parse is not this. It is tempting to write
Toml.parseBytes bytes |> Result.withDefault Toml.empty and be done with both
cases at once, and it is the one thing not to write: a config file with a typo
in it would parse to Err, become the empty document, and be written back over
the user's file, comments and all, on the next save. empty is for a file that
does not exist. A file that exists and cannot be read is an error to show the
user, and the save has to not happen.
Toml.toString empty is "", and a value set into it lands on line one with
no leading blank.
Writing
Write a document back out.
Write a document back out as bytes, which is the way back from
parseBytes.
The byte order mark comes with it if the source had one. toString puts the
mark back too -- it is a character in the string it returns -- so this is
Bytes.fromString after it and is offered only so that reading and writing a
file are the same shape.
Errors
Where the parser stopped and what it wanted there.
An error as a line of text.
"line 4, column 9: expected an '=' after the key"