Toml.Encode
Writing a TOML file from scratch.
Toml.Encode.toString
[ Toml.Encode.field "name" (Toml.Encode.string "widget")
, Toml.Encode.field "server"
(Toml.Encode.table
[ Toml.Encode.field "host" (Toml.Encode.string "example.com")
, Toml.Encode.field "port" (Toml.Encode.int 8080)
]
)
]
--> name = "widget"
-->
--> [server]
--> host = "example.com"
--> port = 8080
This is the other direction from Toml.Decode, and the other
half of Toml.Edit: editing is for a file that exists, and this is
for one that does not.
You choose the shape, not a heuristic
A TOML table can be written as a [header] section or as { inline = "table" },
and an array of tables as [[header]] sections or as an array of inline ones.
Both spellings mean the same thing, so a library has to either guess or ask.
This one asks: table and tableArray produce
headers, inlineTable and array produce braces and
brackets. The only place the choice is taken away is inside an array, where a
header is not available and a table is written inline whatever it was built
with.
Fields come out in the order you give them, because you chose it. Within a
table, though, the scalars are written before the sections, since a key written
after a [header] would land inside that header's table rather than beside it.
The style
One space either side of the equals sign, one blank line before each header and
before each commented field, none at the top of the file or under a header, no
indentation, \\n line endings, and a final newline. There is no way to change
any of that, on the grounds that a file this produced has no author to have an
opinion -- and the moment one does, the file should be read and edited rather
than regenerated, which is what the rest of this package is for.
Comments are the exception that proves it, since a comment is content rather
than style: commented puts one on a field, and where the blank
line goes around it is still not yours to say.
A whole file
The example from https://toml.io, built from nothing. The nesting of the
fields is the nesting of the file, and table at each level is what asks for a
[header] rather than braces:
Toml.Encode.toString
[ Toml.Encode.field "owner"
(Toml.Encode.table
[ Toml.Encode.field "name" (Toml.Encode.string "Tom Preston-Werner")
, Toml.Encode.field "dob" (Toml.Encode.offsetDateTime dob)
]
)
, Toml.Encode.field "database"
(Toml.Encode.table
[ Toml.Encode.field "enabled" (Toml.Encode.bool True)
, Toml.Encode.field "ports"
(Toml.Encode.array
[ Toml.Encode.int 8000, Toml.Encode.int 8001, Toml.Encode.int 8002 ]
)
, Toml.Encode.field "data"
(Toml.Encode.array
[ Toml.Encode.array
[ Toml.Encode.string "delta", Toml.Encode.string "phi" ]
, Toml.Encode.array [ Toml.Encode.float 3.14 ]
]
)
, Toml.Encode.field "temp_targets"
(Toml.Encode.inlineTable
[ Toml.Encode.field "cpu" (Toml.Encode.float 79.5)
, Toml.Encode.field "case" (Toml.Encode.float 72.0)
]
)
]
)
, Toml.Encode.field "servers"
(Toml.Encode.table
[ Toml.Encode.field "alpha"
(Toml.Encode.table
[ Toml.Encode.field "ip" (Toml.Encode.string "10.0.0.1")
, Toml.Encode.field "role" (Toml.Encode.string "frontend")
]
)
, Toml.Encode.field "beta"
(Toml.Encode.table
[ Toml.Encode.field "ip" (Toml.Encode.string "10.0.0.2")
, Toml.Encode.field "role" (Toml.Encode.string "backend")
]
)
]
)
]
where dob is Civil.DateTime.fromString "1979-05-27T07:32:00-08:00", unwrapped
from its Maybe. That writes:
[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"
which is the original file except that the original had a space inside each
bracket -- [ 8000, 8001, 8002 ] -- and this writes none. That difference is the
whole distinction between this module and Toml.Edit. The spaces
belonged to whoever wrote the file, and a file built from nothing has nobody to
have written them.
Two things in that example are worth pointing at:
[servers]comes out on a line of its own with nothing under it, becauseservershas no keys of its own and two sub-tables. That is what the nesting said, and it is a legal and ordinary TOML file.temp_targetsusesinlineTablerather thantable, which is the only reason it comes out in braces. Swap the two and it becomes a[database.temp_targets]section instead. Nothing here guesses.
A value on its way into a file.
A key, the value under it, and the comments that explain it.
Build one with field and, if it needs explaining,
commented. The record is open so that it can be read apart;
constructing one by hand works too and only saves a function call.
A key and the value under it.
Toml.Encode.field "port" (Toml.Encode.int 8080)
Comments
Comments on a field: a block of whole lines above it, and one after the value on the same line.
Each string is the text after the #, the leading space included, because
# note and #note are different files and neither is this module's to choose.
There is no blank-line field here, unlike
Toml.Edit.Comments, for the reason the whole of the
style gives: a file this module wrote has no author to have an
opinion about its spacing, so the spacing is decided here. A commented field
gets a blank line above it.
Explain a field.
Toml.Encode.toString
[ Toml.Encode.field "a" (Toml.Encode.int 1)
, Toml.Encode.field "port" (Toml.Encode.int 8080)
|> Toml.Encode.commented
{ leading = [ " What to listen on." ], trailing = Just " http" }
]
--> a = 1
-->
--> # What to listen on.
--> port = 8080 # http
The blank line above the block is the style rather than a choice,
and it is not written at the top of a file or under a [header], where there
would be nothing above it to separate it from.
A file nobody can read is not much better than no file, and comments are the reason to reach for a TOML library that keeps them rather than one that does not. This is that reason applied to the direction that writes a file instead of reading one.
On a field whose value is a table or a
tableArray, the comments go with the [header] line, since
that is the line the field became. On a tableArray they go with the first
[[header]] only, the block above it and the trailing comment beside it -- they
explain the key, and repeating them over every item would explain nothing.
On a field inside an array or an
inlineTable they are dropped, because there is no line for
them to be on. That is the same boundary
Toml.Edit draws at a brace and for the
same reason: what is inside one is written on one line, and a comment runs to
the end of a line.
Writing it out
The fields as a document, which Toml.toString will write
and which Toml.Edit can go on changing.
The fields as text.
Scalars
A string.
An integer that fits in a Gren Int.
An integer of any size.
A float. inf and nan are written as those words; everything else gets a
decimal point even when it does not need one, since 1 without one is an
integer in TOML.
An exact decimal.
A boolean.
A date and time with an offset.
A date and time with no offset.
A date.
A time of day.
Structures
An array, on one line: [1, 2, 3].
Tables inside it are written inline, since there is no header to give them.
A table written as a [header] section.
Toml.Encode.table [ Toml.Encode.field "port" (Toml.Encode.int 80) ]
--> [server]
--> port = 80
Except inside an array, where there is no header to be had and it comes out as
{ port = 80 }.
A table written on one line: { port = 80 }.
An array of tables, written as a [[header]] for each.
Toml.Encode.tableArray
[ [ Toml.Encode.field "name" (Toml.Encode.string "alpha") ]
, [ Toml.Encode.field "name" (Toml.Encode.string "beta") ]
]
--> [[peer]]
--> name = "alpha"
-->
--> [[peer]]
--> name = "beta"
An empty one has no headers to write, so it is written as peer = [] rather
than not at all. A reader cannot tell the two apart, and a key must not vanish
from a file because its array happened to be empty.