Toml.Edit
Changing a TOML file and leaving the rest of it alone.
Toml.parseBytes source
|> Result.map (Toml.Edit.set [ "server", "port" ] (Toml.Edit.int 9090))
|> Result.map Toml.toString
Every byte the edit did not touch comes back as it was: the blank lines, the
alignment, the comments, and the 0x1F you wrote instead of 31. That is what
this module is for, and it is why the AST underneath it stores whitespace as
text -- see Toml.Ast.
And an edit that changes nothing touches nothing. Setting a value that is
already what the file says leaves the line exactly as it is written -- see
set, where the reason it has to work that way is spelled out. It is
what makes "write my whole configuration back" a safe thing for a program to
do on every save.
The functions, in short
Most of them come in pairs: the same thing for a key and for a table, or for a whole value and for one element of an array.
| Function | What it is for |
|---|---|
get |
The value at a path, if a key there holds one |
member |
Whether a key is in the file at all |
paths |
Every key, in the order the file writes them |
set |
Put a value at a path, adding the key if it is missing |
respell |
Rewrite a value the file already means, to change its spelling |
introduce |
set, and comments too when this call invented the key |
remove |
Take a key out, with the comments that belong to it |
removeTable |
Take a [header] out, with everything under it |
appendTo |
One more element on the end of an array |
insertAt |
One more element at a position |
setAt |
Replace one element |
respellAt |
Rewrite one element, to change its spelling |
removeAt |
Take one element out, with the note against it |
moveAt |
Move one element, its note with it |
rename |
Rename a key, keeping its value and its comments |
renameTable |
Rename a table, and the headers nested under it |
comments |
The comments that belong to a key |
setComments |
Write those back |
tableComments |
The comments that belong to a [header] line |
setTableComments |
Write those back |
The rest of the module is the values to put in -- string,
int and the others at the bottom -- which build the text for a value
as well as the value itself.
Which comments belong to a key
Deleting a key should delete the comment that explains it and leave alone the one that explains the file. Nothing in TOML says which is which, so this is a convention, and here it is:
- Leading. Own-line comments directly above a key, with no blank line between, belong to it. They go when it goes.
- Trailing. A comment on the same line, after the value, belongs to it.
- Floating. Anything after a blank line belongs to nobody and survives.
- Header. Comments before the first key belong to the document.
The blank line is the escape hatch, and it is worth knowing about. A comment block that introduces a whole section rather than the one key under it should have a blank line beneath it, or deleting that key will take the section heading with it.
A [header] line owns comments the same way a key does: the block directly
above it and the comment beside it are the header's. removeTable
takes them with the table, tableComments reads them and
setTableComments writes them.
What a path means here
A path is the key as the table tree sees it: [ "server", "port" ] finds
port under [server], and finds it equally whether the file wrote it as a
header and a key, as server.port = 9090, or as a header and a dotted key.
It does not reach inside an inline table or an array. a = {b = 1} has the path
[ "a" ] and nothing under it; to change b, replace the whole inline table.
That boundary is where the round trip stops being obvious -- an inline table has
no lines to insert into -- so it is drawn here rather than guessed at.
A path into an array of tables names its last item. That is what TOML
itself means by the path: a [peer.tls] header or a peer.x = 1 key after
the second [[peer]] belongs to the second one. So [ "peer", "x" ] reads,
changes or removes the x of the last [[peer]], and a key that is not there
yet is added to the last one too. The earlier items are reached by walking the
AST, which is what it is there for.
What this module does not check
Only syntax. Whether the file still means something afterwards is decided by
Toml.Table.fromDocument, as it is for a file
someone typed, and an edit can produce one it rejects. The ways that happen are
the ways a path can point at something a key cannot be added beside:
set [ "a", "b" ]whenais an inline table. The path does not reach in, so a[a]header is added forb, andais now defined twice.set [ "a", "b", "c" ]whena.b = 1wrotebas a dotted key. A[a.b]header is added, and a header may not land on a table a dotted key built.set [ "a" ]when[a]is a header. The key and the table collide.
None of those are refused here, because the rule that refuses them is a rule
about what the document means and lives in the layer that decides that. After
editing, read the result back through
Toml.Decode.fromDocument or
Toml.Table.fromDocument before writing it out, if
the paths were not yours to begin with.
A real example
The file from https://toml.io. Everything below starts from it, and only the lines that change are shown -- every other byte of the file comes back as it was, which is the point:
[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"
Change one value. The key, the equals sign and everything around them are untouched:
Toml.Edit.set [ "servers", "alpha", "ip" ] (Toml.Edit.string "10.0.0.9")
--> [servers.alpha]
--> ip = "10.0.0.9"
--> role = "frontend"
Change an array. A value written fresh is spelled by this module rather than by the file, so the author's spaces inside the brackets are not preserved -- there is nothing left of the old array to preserve them from:
Toml.Edit.set [ "database", "ports" ]
(Toml.Edit.array [ Toml.Edit.int 9000, Toml.Edit.int 9001 ])
--> ports = [9000, 9001]
Add a table. Neither [servers.gamma] nor the keys under it exist, so the
header comes with the first key and the second lands beside it:
Toml.Edit.set [ "servers", "gamma", "ip" ] (Toml.Edit.string "10.0.0.3")
>> Toml.Edit.set [ "servers", "gamma", "role" ] (Toml.Edit.string "cache")
--> [servers.beta]
--> ip = "10.0.0.2"
--> role = "backend"
-->
--> [servers.gamma]
--> ip = "10.0.0.3"
--> role = "cache"
Write comments. blankBefore is how a block gets an empty line above it,
which is the only way to ask for one -- a document is a list of expressions and
has no lines to insert between:
Toml.Edit.setComments [ "database", "enabled" ]
{ blankBefore = False
, leading = [ " Turn this off to go quiet." ]
, trailing = Just " on by default"
}
--> [database]
--> # Turn this off to go quiet.
--> enabled = true # on by default
Explain a section. A header owns the block above it the way a key does, and
setTableComments writes it:
Toml.Edit.setTableComments [ "servers", "alpha" ]
{ blankBefore = True, leading = [ " The primary." ], trailing = Nothing }
--> # The primary.
--> [servers.alpha]
--> ip = "10.0.0.1"
Write a value and explain it, but only the first time.
introduce is set plus the comments a key gets when
this is the call that invented it, and nothing at all to a key already in the
file:
Toml.Edit.introduce [ "database", "timeout" ]
{ blankBefore = True, leading = [ " Seconds." ], trailing = Nothing }
(Toml.Edit.int 30)
--> ports = [ 8000, 8001, 8002 ]
--> ...
-->
--> # Seconds.
--> timeout = 30
Rename. A key by rename and a table by
renameTable, which takes the headers nested under it too:
Toml.Edit.rename [ "servers", "alpha", "role" ] "job"
>> Toml.Edit.renameTable [ "database" ] "db"
--> [db]
--> enabled = true
--> ...
--> [servers.alpha]
--> ip = "10.0.0.1"
--> job = "frontend"
Remove a table, and its blank line goes with it:
Toml.Edit.removeTable [ "servers", "beta" ]
--> [servers.alpha]
--> ip = "10.0.0.1"
--> role = "frontend"
--> <end of file>
Put together, an edit is a pipeline from Toml.parseBytes to Toml.toString:
Toml.parseBytes source
|> Result.map
(Toml.Edit.set [ "database", "ports" ] (Toml.Edit.array [ Toml.Edit.int 9000 ])
>> Toml.Edit.set [ "servers", "gamma", "ip" ] (Toml.Edit.string "10.0.0.3")
>> Toml.Edit.renameTable [ "database" ] "db"
)
|> Result.map Toml.toString
To read the edited file back without writing it out and parsing it again, hand
the document to Toml.Decode.fromDocument.
The file that is not there yet
A program that keeps a config file has to handle its first run, and the tidy
way to do that is not to handle it: Toml.empty is a document
with nothing in it, every key is missing from it, and set adds a key
that is missing. So
(when found is
Nothing ->
Ok Toml.empty
Just bytes ->
Toml.parseBytes bytes
)
|> Result.map (Toml.Edit.introduce [ "theme" ] note (Toml.Edit.string "dark"))
creates the file and updates it with the same code, and there is no second
description of the file's shape to drift out of step with the first. The
when is not decoration: Toml.empty stands in for a file that is not there,
and a file that is there and fails to parse has to stay an Err, or the next
save writes an empty document over it -- see Toml.empty.
Toml.Encode is the other way to build a file from nothing, and
it is the better one when the shape of the file comes from the shape of the
data -- nested tables, arrays of tables, a section per item. This module is the
better one when the file has a fixed set of keys and the same code has to cope
with the file already existing.
The value at a path, if a key there holds one.
Whether a key is in the document at all.
get answers this too, but answering it with get means holding a
Toml.Ast.Value in order to throw it away, and the question a program writing
a config file asks is usually this one: is this key already here, or am I the
one putting it there? That is what decides whether it needs an explanation
written above it -- see introduce.
Put a value at a path.
If a key is already there, only its value changes: the key keeps its spelling, the equals sign keeps the whitespace around it, and the comment on the line stays where it was. Nothing else in the file moves.
If there is no such key, one is added. It goes at the end of the table it belongs
to, after the last key already in it, indented to match. If the table does not
exist either, a [header] for it is appended to the end of the file with the key
beneath and a blank line above, and a file that ended with a newline still does.
A top-level key added to a file that has no top-level keys yet goes before the
first [header], with a blank line between, since anything written after a
header belongs to that header's table. It goes above the comment block directly
over that header too, because that block is the header's by the convention
above; a comment with a blank line under it stays where it is.
A value that is already what you are setting it to is left exactly as it is written. This is not thrift, it is the difference between an editor and an encoder, and it is the one thing about this module worth reading twice.
Setting a value replaces the whitespace inside it, because the whitespace was part of the old value and the new one has none of its own. So a list somebody arranged by hand:
ports = [
8000, # http
8001 # https
]
written back with set [ "ports" ] (array [ int 8000, int 8001 ]) would come
out as ports = [8000, 8001]. That is right for a list that changed -- there
is no old formatting to keep for a new value -- and it is destructive for one
that did not.
Which is the case that actually happens. A program that writes its whole
configuration out on every save touches every key, so the key the user
hand-arranged gets flattened by a save about some other key. Comparing first
is what stops that, and doing it here rather than leaving it to every caller is
the only way it gets done: the caller would have to hold the old document, take
it apart, and know that 1.50 and 1.5 are the same number and 0x1F and
31 are the same integer.
The comparison is by value and not by text. A string is its characters after
escapes, an integer or float is its number whatever base or spelling it was
written in, a date-time is the instant and the offset, an array is its elements
in order, and an inline table is its entries in order. So set over 0x1F with
int 31 changes nothing and the file keeps saying 0x1F. To change how a
value is written rather than what it means, use respell.
An edit that gives the file a meaning it cannot have -- a key under an inline table, say -- is not refused here. See what this module does not check.
Write a value even when the file already means it, so that how the value is written changes.
Toml.Edit.respell [ "server", "port" ] (Toml.Edit.int 31)
--> port = 31 # was: port = 0x1F
set leaves a value alone when it already means what it is being set
to. That rule is what makes it safe to write a whole configuration back on
every save, but it also means set can never change how a value is spelled.
respell is set without that rule.
The key keeps everything except its value: its comments, the whitespace around
the equals sign, and its place in the file. Before this function existed the
only way to change a spelling was remove followed by set, which
loses the key's comments along with the line.
Use it on the one key whose spelling you mean to change, not in the code that
saves everything. The usual case is a program that used to write a value with
string and now writes it with
multilineString: set sees no change and leaves older
files spelled the old way, and respell updates them. Calling it on every key
on every save would undo the user's own choices, turning their 'C:\Users'
back into a basic string each time.
On a key that is not in the document this is the same as set.
Set a value, and explain the key if this is the call that put it there.
Toml.Edit.introduce [ "theme" ]
{ blankBefore = True
, leading = [ " Which colour scheme to open in." ]
, trailing = Nothing
}
(Toml.Edit.string "dark")
set and setComments together, except that the
comments are written only when the key was not already in the document.
That condition is the whole reason this exists, and it is a rule about the person on the other end of the file rather than about TOML. A program that keeps a config file wants every key it invents to arrive with a sentence saying what the key is for, because a config file whose fields are undocumented is one nobody opens. It does not want to write that sentence again on every save: the user who deleted it meant to delete it, and the user who rewrote it in their own language has not made a mistake for the next save to correct.
On a document with the key already in it this is exactly set, comments and
all left alone. On Toml.empty it writes the key and the
explanation together, which is how a file that has never existed comes out
documented.
Take a key out, and the comments that belong to it with it.
The line goes, its leading comment block goes, and its trailing comment goes with the line it was on. A floating comment -- one with a blank line above it -- stays, which is the whole point of the blank line.
The blank line above the block goes too, but only when the block leaves a blank
line or the end of the file below it. This is what makes remove the inverse of
introduce: blankBefore is a field of the key's own
Comments, so a program that writes a key when a setting is on and
removes it when the setting goes off would otherwise leave one empty line behind
per cycle. A block with a blank on one side only keeps it, because that blank is
what separates what is above the key from what is below it, and both are staying.
Nothing else changes. Removing the last key of a table leaves the [header]
behind, because an empty table is still a table and saying otherwise would be
this module deciding something the file did not.
Take a whole table out: its [header], everything under it, and the comments
that belong to the header.
Sub-tables go too, since [a.b] is under [a]. An array of tables named by the
path goes entirely, every item of it.
One element of an array
Add one element to the end of the array a key holds. The elements already in it are left exactly as they are written.
Toml.Edit.appendTo [ "zones" ] (Toml.Edit.string "Europe/Oslo")
--> zones = [
--> "Asia/Seoul", # them
--> "America/Chicago", # me
--> "Europe/Oslo",
--> ]
set can write a whole array, and that is fine for a list where every
element is new. But a new array has no formatting, so set throws away the
line breaks, indentation and comments of a list somebody arranged by hand.
appendTo, setAt and removeAt change one element
and leave the rest of the text alone.
The new element is laid out like the one before it: on its own line with the same indent if the array is written that way, or after a space if the array is on one line. It gets a trailing comma if the last element had one, and no comma if it did not.
If the key is not there yet, it is written as a one-element array, so "add this to my list" works on a file that does not have the list yet. If the key holds something other than an array, the document is returned unchanged.
An array inside an inline table counts as not there, because a path stops at
the brace. appendTo [ "a", "b" ] over a = { b = [1] } does not reach b;
it writes an [a] header with a new b under it, and
Toml.Table.fromDocument then rejects the file for
defining a twice. set has the same limit, and the fix is the same: replace
the whole inline table with inlineTable.
Add one element to the array a key holds, at a position. The elements already in it are left exactly as they are written.
Toml.Edit.insertAt [ "zones" ] 1 (Toml.Edit.string "Europe/Oslo")
--> zones = [
--> "Asia/Seoul", # them
--> "Europe/Oslo",
--> "America/Chicago", # me
--> ]
The new element takes the line of the one it displaces: the same indent, and a comma, since something follows it now. The element it pushed down gets the same separator with nothing written on it, because the note that was on that line was written against the value above and stays where it is.
The new element has no note of its own, which is the only honest default: a
note is written against a value, and this value is new. Nothing here writes one
either -- a note against an element is the file's, and the one function that
moves one is moveAt, which takes an element's own along with it.
An index equal to the length of the array appends, which is the one case where
this and appendTo do the same thing -- including writing a
one-element array for a key that is not there yet. Any other index that is not
in the array leaves the document unchanged, as does a key that holds something
other than an array.
Replace one element of the array a key holds, by position. Nothing else changes: not the elements around it, not the whitespace, not the comments.
Toml.Edit.setAt [ "zones" ] 1 (Toml.Edit.string "America/Denver")
--> zones = [
--> "Asia/Seoul", # them
--> "America/Denver", # me
--> ]
The document comes back unchanged if the index is out of range, the key is missing, or the key is not an array.
Like set, this leaves an element alone when it already means the new
value. Rewriting it would replace the whitespace inside it, and a program that
writes its whole configuration back on every save would then flatten an array
it was not changing. To rewrite the element anyway, use
respellAt.
Remove one element from the array a key holds, by position, along with the comment that belongs to it.
Toml.Edit.removeAt [ "zones" ] 0
--> zones = [
--> "America/Chicago", # me
--> ]
Which comment belongs to which element is a convention, as it is for keys: the comment after an element's comma, up to the end of that line, belongs to that element. It is the note a person writes against the value, so it goes when the value goes. Everything after that line ending is the next element's indent and stays put.
Removing the last element takes a little more care, because the trailing comma
and the position of the ] are both written against it. The element before it
inherits both.
The document comes back unchanged if the index is out of range, the key is missing, or the key is not an array. A negative index is out of range; it does not count from the end.
Replace one element of an array even when it already means the new value, so that how the element is written changes.
Toml.Edit.respellAt [ "zones" ] 1
(Maybe.withDefault (Toml.Edit.string "America/Chicago")
(Toml.Edit.literalString "America/Chicago")
)
--> zones = [
--> "Asia/Seoul", # them
--> 'America/Chicago', # me
--> ]
This is to setAt what respell is to set.
setAt skips an element that already means the new value, which is the right
default but makes it unable to change a spelling. The element keeps its line,
its indent and the comment written against it; only the value is rewritten.
Unlike respell, this never creates anything. An index out of range, a missing
key, or a key that is not an array all leave the document unchanged.
Move one element of the array a key holds from one position to another, and take the note written against it along.
Toml.Edit.moveAt [ "zones" ] 1 0
--> zones = [
--> "America/Chicago", # me
--> "Asia/Seoul", # them
--> ]
to is the index the element ends up at, so a Move Up button is
moveAt path index (index - 1) and a Move Down is moveAt path index (index + 1).
This is a function of its own rather than a removeAt followed by
an insertAt because that pair cannot carry the note: removeAt
takes it away with the element, which is right, and insertAt writes an element
that has none. Walking the new order down the list with setAt
instead keeps every note, but keeps it against the position, so a move leaves
# me written against somebody else's city, silently, which is worse than
losing it. A note belongs to the value it was written for, and here it goes
where that value goes.
Only the value and its note move. The indent and the trailing comma are the destination's, because those describe a place in the list rather than the thing in it, and the last position's are the ones that keep the closing bracket where the file put it.
The document comes back unchanged if either index is out of range, if the key is
missing, or if the key is not an array. Moving an element to where it already is
changes nothing at all. There is one more: an element with a note cannot move
onto a line that another element also ends, since there is nowhere on it to
write the note. Below, # one has nowhere to go on the line 2 and 3 share,
so moveAt [ "a" ] 0 1 leaves the file alone.
a = [
1, # one
2, 3,
]
Renaming
Give a key a new name and change nothing else.
Toml.Edit.rename [ "server", "port" ] "listen"
--> [server]
--> listen = 8443 # was: port = 8443
The value stays as it was written, the whitespace around the equals sign stays, and the comments stay with the line. The new name is spelled bare where the grammar allows it and quoted where it does not.
Only the last part of the path is the name: this renames the key port, not the
table server. For a table see renameTable.
Whether the new name is already taken is not asked here. It is not a question
about syntax, so the answer comes from
Toml.Table.fromDocument, like every other question
of its kind -- see the two layers in Toml.Table.
Give a table a new name: its own [header], every header nested under it,
and every dotted key that spells the name out itself.
Toml.Edit.renameTable [ "server" ] "listener"
--> [listener]
--> [listener.tls]
Only the last part of the path is the name, as with rename, so a
table's ancestors never move and nothing has to be lifted into a different
section for the result to mean what it says.
A dotted key changes only where the renamed part is a table it passes through:
renameTable [ "a" ] "x" turns a.b = 1 into x.b = 1 and leaves b alone,
because b names the value rather than a table. By the same rule this does
nothing to a path that names a key: [ "server", "port" ] is not a table, and
rename is the one that renames it.
Comments
The comments that belong to one key.
{ blankBefore = True
, leading = [ " How long to wait." ]
, trailing = Just " seconds"
}
--> <a blank line>
--> # How long to wait.
--> timeout = 30 # seconds
leading and trailing are the text after the #, the leading space
included, because # note and #note are different files and neither is this
module's to choose.
blankBefore is whether an empty line sits above the block. It is here rather
than left to whoever is writing the file because there is no other way to ask
for one -- a document has no lines to insert, only expressions -- and because a
comment block hard against the line above it reads as a continuation of that
line. It is always False at the very top of a document, where a file does not
begin with a blank line, and for the first key directly under its [header],
which belongs against the header rather than held off it.
Set the comments that belong to a key, by the same convention
comments reads them by.
Toml.Edit.setComments [ "timeout" ]
{ blankBefore = True
, leading = [ " How long to wait." ]
, trailing = Just " seconds"
}
--> <a blank line>
--> # How long to wait.
--> timeout = 30 # seconds
comments and this are inverses, so what one reads the other writes back
unchanged -- including the blank line, which is why Comments has
a field for it.
leading = [] takes the block above the key away and trailing = Nothing takes
the comment off the line. Anything separated from the key by a blank line is not
the key's and is left alone, which is the same blank line that protects it from
remove. A key that is not there is left alone too.
blankBefore = True puts a blank line above the block if there is not one
already, and False takes one away if there is. Asking for one does nothing at
the top of the document, where there is no line above to be blank, and does
nothing directly under a [header], where the key is the first of its table and
a blank would hold it off the header it belongs to -- the same rule
Toml.Encode writes by. Taking one away is the
one thing here that reaches past the key's own lines, so it is worth knowing
what it does: the blank line is what separates a floating comment from an owned
one, and removing it makes the block above join this key's -- which is to say
it will go when this key goes.
The comments that belong to a table's [header] line: the block directly
above it and the comment beside it, by the same convention as a key's, and the
same ones removeTable takes with the table.
Toml.Edit.tableComments [ "servers", "alpha" ]
--> { blankBefore = True, leading = [ " The primary." ], trailing = Nothing }
For an array of tables the path means the first [[header]], which is the
other way round from every other path in this module. A path into an array of
tables names its last item when it names a value, because that is the item
TOML means by it. A comment over a header explains the key -- the whole array --
and the first header is where Toml.Encode writes it.
The other items are reached through the AST.
A table with no header line of its own -- one that only [a.b] implied, or one
a dotted key built -- has no line for a comment to be on, and reads as
{ blankBefore = False, leading = [], trailing = Nothing }. So does a table that
is not there.
Set the comments that belong to a table's [header] line, by the same
convention tableComments reads them by.
Toml.Edit.setTableComments [ "servers", "alpha" ]
{ blankBefore = True, leading = [ " The primary." ], trailing = Nothing }
--> <a blank line>
--> # The primary.
--> [servers.alpha]
The two are inverses, as comments and
setComments are, and for an array of tables the path means
the first [[header]] for the reason given there.
This is how a program explains a section it created. introduce
explains a key, and the header it brought with that key has nothing over it
until this is called; calling it only when member said the first
key was not there yet keeps the explanation to the run that invented the
section, which is the same rule introduce keeps for a key.
One difference from setComments: blankBefore = True directly under another
header is honoured. The first key of a table belongs against its header, but a
nested header directly under its parent is two sections meeting, and the blank
between them is the one Toml.Encode keeps. At the top of the
file it is still a no-op.
A table with no header line, or none at all, is left alone.
Looking around
Every key in the document, in the order the file writes them.
Table headers are not keys and are not here; the path of a key written under
[server] begins with server all the same.
The type a value has
A value on its way into a document, built by one of the functions at the bottom of this module.
The same type as Toml.Ast.Value, named here so that an edit
needs one import rather than two. The constructors are in Toml.Ast, for
reading a value apart; nothing here needs them, because a value is built by
string, int and the rest.
Values to put in
Each of these builds the value and the text for it, since a value that was not
read from a file has no text of its own yet. They are
Toml.Literal's, offered here so that an edit needs one import
rather than two, and shared with Toml.Encode so that the two
cannot disagree about how to spell a float.
A string, written in the basic form: "..." on one line, with \n for a
newline.
A string, written in the multi-line form: """...""", with the newlines in
it written as real line breaks rather than as \n.
This is the form to use for text a person will read: a note, a query, a block of help text. Every string can be written this way, so this never fails.
A value the file already means is not rewritten, even if it is spelled
differently. See set. A key that already holds this text as a basic
string keeps the basic string; the form is chosen when the key is first
written. Use respell to change it.
A string, written in the literal form: '...', with no escapes at all.
Returns Nothing if the string contains an apostrophe, a newline, or a control
character other than tab, because a literal string has no way to write those.
It does not fall back to another form: the form a key is written in should not
depend on what the value happens to contain.
A string, written in the multi-line literal form: '''...''', with no
escapes and with the newlines written as real line breaks.
Returns Nothing if the string cannot be written that way: three apostrophes
in a row, an apostrophe at the very end, a lone carriage return, or a control
character other than tab and newline.
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.
An array, on one line: [1, 2, 3].
Toml.Edit.set [ "ports" ]
(Toml.Edit.array [ Toml.Edit.int 80, Toml.Edit.int 443 ])
An array already in the file keeps every character of itself for as long as nothing is set over it, newlines and comments inside the brackets included. This is the spelling for one that is being written fresh.
An inline table, on one line: { a = 1, b = 2 }.
Toml.Edit.set [ "limits" ]
(Toml.Edit.inlineTable
[ { key = "soft", value = Toml.Edit.int 1024 }
, { key = "hard", value = Toml.Edit.int 4096 }
]
)
This is also how to change something inside an inline table, since a path does not reach in -- see what a path means. Build the table you want and set the whole of it.
There is no builder here for a [header] table, because a header is a line
rather than a value and set puts values on lines. A key under a header that
does not exist yet already brings one with it.
The comments that belong to a key, by the convention above.
This and
setCommentsare inverses: what one reads, the other writes back unchanged. A key that is not in the document has no comments and reads as{ blankBefore = False, leading = [], trailing = Nothing }.