std/toml
import std/toml to use it. Source: src/std/toml.iyi at v0.18.0.
What it reads, which is all of 1.0.0:
* Keys bare (`A-Za-z0-9_-`, digits alone too, always a string),
quoted as a basic or a literal string (the empty `""` included), and
dotted, with blanks around the dots.
* Basic strings with the nine escapes the format has (`\b`, `\t`,
`\n`, `\f`, `\r`, `\"`, `\\`, `\uXXXX`, `\UXXXXXXXX`, the last two
naming a Unicode scalar value), literal strings, and the multi-line
forms of both: the line break right after the opening `"""` or `'''`
is dropped, a line-ending backslash drops the blanks and line breaks
after it, and one or two quotes may stand just before the closing
three. A CRLF inside a multi-line string is read as LF, as the spec
allows and Python's `tomllib` does.
* Integers decimal (`+`/`-`, no leading zero but `0`, `+0` and `-0`),
`0x`, `0o` and `0b` (no sign, leading zeros allowed), an underscore
only between two digits, into an `Int64`; floats with a fraction, an
exponent or both (a digit on each side of the point), `inf` and
`nan` with either sign, into a `Float64`, the nearest double to the
decimal (the prelude's `String#to_f` rounds correctly).
* `true` and `false`.
* Offset date-times, local date-times, local dates and local times,
each a `TOMLDateTime` that keeps its kind. `T`, `t` or a space
between date and time, `Z`/`z` or `+HH:MM`/`-HH:MM` for the offset.
Fractional seconds are kept to the nanosecond; digits past the ninth
are dropped, which the spec allows ("truncated, not rounded").
* Arrays of anything, mixed, across lines, with comments and a
trailing comma; inline tables, on one line, closed to extension.
* `[table]` and `[[array.of.tables]]` headers, with blanks inside the
brackets, and the tables dotted keys open.Why its own date-time type and not std/time's Time: three of the four kinds are not instants (a local date-time has no zone, a local date no time, a local time no date), and an offset date-time keeps the offset it was written with, which a Time turned to UTC would lose and dump would then not write back. And std/time reads the clock and the zone files, which this module, pure computation over a String, has no reason to reach. TOMLDateTime#unix_seconds gives an offset date-time's instant to whoever wants a Time of it.
What it refuses, each a TOMLError naming the line and the column (counted in characters, from 1) where the trouble is:
* Text that is not UTF-8, and a byte order mark (the grammar has none,
and `tomllib` refuses one too).
* A control character other than tab in a comment or a string
(U+0000-U+0008, U+000A-U+001F, U+007F), a CR not before an LF, a
line break in a single-line string, an escape the format does not
have (TOML 1.1's `\e` and `\xHH` among them) or one naming a
surrogate or past U+10FFFF, a string that does not close, six or
more quotes closing a multi-line string.
* A key with no value or a value with no key, a key/value pair
followed by more than a comment on its line, a bare key with a
character outside its set, a multi-line string as a key.
* An integer with a leading zero, a doubled, leading or trailing
underscore, a sign on `0x`/`0o`/`0b`, an uppercase prefix, or a value
past the `Int64` range (the spec requires the refusal; `tomllib`
reads any size, so the gate compares these as this reader's own); a
float with no digit on one side of its point (`.7`, `7.`, `3.e+20`).
* A date that is not one (February 30th, month 13, hour 24, minute or
second 60 - a leap second is refused, as `tomllib`'s datetime does),
a time without its seconds (allowed only from TOML 1.1), an offset
past 23:59, a date-time missing a part.
* An array or an inline table that does not close, a missing comma, a
trailing comma or a line break in an inline table.
* Every redefinition the spec forbids: a key given twice (`a` and
`"a"` are one key), a `[table]` declared twice, a `[table]` header
naming a table that dotted keys made, dotted keys adding to a table
a `[header]` declared or to one dotted keys made under another
header, a header or dotted key going into an inline table or an
array written as a value, `[[x]]` where `x` is a table or a static
array, `[x]` where `x` is an array of tables, a value where a table
is. A header may still go into a table dotted keys made
(`[fruit.apple.texture]` after `apple.color = "red"` under
`[fruit]`), and a super-table may be declared after its sub-table.
These follow the spec's text and its examples, and where the text is
silent, what `tomllib` does: dotted keys may add to a table a header
made only on the way to another (`[a.b.c]` then `[a]` `b.d = 1`),
and once they have, that table may not be declared by a header.
* Nesting past DEPTH_LIMIT levels, counting tables, arrays and inline
tables alike, so `==`, `dump` and `hash` over any value this reader
answers stay within a task's stack.TOML.parse(text) answers Any | TOMLError; TOML.parse?(text) answers nil where parse answers the error, as JSON.parse? and YAML.parse? do. Neither panics on anything a document says. TOML.parse(text).or_panic panics with the sentence, and TOML.from_toml panics as YAML.from_yaml does.
TOML.dump(value) writes a table as TOML that this reader and tomllib read to an equal value: a table's plain keys first, as key = value, then each sub-table under a [header] and each non-empty array whose items are all tables as [[header]] entries; other arrays and tables inside arrays are written inline. Keys are bare where the bare set allows and quoted otherwise; strings are basic strings, with ", \ and every control character escaped (so no string spans lines); floats are the shortest text that reads back as the same double, inf, -inf and nan as TOML spells them. A value that is not a table cannot be a document and is refused, a panic: that is the program's mistake.
derive toml_serializable makes a type read and write itself as a table, a key per field, as std/yaml's derive yaml_serializable does: to_toml, self.from_toml(text), initialize(toml : Any) and the ToTOML trait's to_toml_any. TOML has no null: a nil field is left out when written and a missing key reads as nil where the field admits it; a nil anywhere else (inside an array, as a hash value) cannot be written and is refused by name.
Any holds a value: raw is the union, as_x/as_x? are the pairs, []/[]?/dig/dig? walk it, == is structural (two NaNs are equal, a document that says nan twice says the same thing) and hash agrees with it. As in std/yaml, every test of the union goes through Any#kind, which tests the reference types first.
toml_serializablemacro
derive toml_serializable, in a struct or class whose fields are written with their types: the type reads and writes itself as a TOML table, a key per field, named as the field is.
pub struct Coord
getter x : Float64
getter name : String
derive toml_serializable
end Coord.from_toml("x = 1.5\nname = \"a\"\n").x # => 1.5
coord.to_toml # => "x = 1.5\nname = \"a\"\n"It is std/yaml's derive yaml_serializable over TOML, field for field: a value is read and written as TOML.read_value and TOML.to_any take its type (Bool, Int32, Int64, UInt8, UInt64, Float64, String, TOMLDateTime, Any, an Array or a Hash(String, _) of them, another derived type, each of them or nil). TOML has no null, so a nil field is not written, and a missing key is read as YAML's derive reads one: a field with a default keeps it, a nilable field holds nil, and any other is refused by name. A key the type has no field for is passed over. A type that is several writes derive serializable, yaml_serializable, toml_serializable: what this one generates shares no method with theirs (to_toml, from_toml, to_toml_any and initialize(toml : Any) of this module's Any).
pub macro toml_serializable(declaration)Anystruct
The value a document holds. A document is a table: TOML.parse answers an Any whose raw is a Hash(String, Any).
pub struct Any
alias Type = Bool | Int64 | Float64 | String | TOMLDateTime | Array(Any) | Hash(String, Any)
def !=(other : Any) : Bool
# Structural: same kind, same value; a table equals a table with the
# same pairs in any order. `1` and `1.0` are different values, two NaNs
# the same one, and `0.0` equals `-0.0` as the doubles do. Arrays and
# tables are compared in methods of their own and scalars in a third,
# so the frames a deep value recurses through stay small: with all of
# it here, comparing two arrays nested 100 deep overflowed a task's
# stack in a plain build.
def ==(other : Any) : Bool
def [](key : String) : Any
def [](index : Int32) : Any
def []?(key : String) : Any | ::Nil
def []?(index : Int32) : Any | ::Nil
def as_a : Array(Any)
def as_a? : Array(Any) | ::Nil
def as_bool : Bool
def as_bool? : Bool | ::Nil
def as_datetime : TOMLDateTime
def as_datetime? : TOMLDateTime | ::Nil
# A float, or an integer widened to one.
def as_f : Float64
def as_f? : Float64 | ::Nil
def as_h : Hash(String, Any)
def as_h? : Hash(String, Any) | ::Nil
def as_i : Int64
def as_i? : Int64 | ::Nil
def as_s : String
def as_s? : String | ::Nil
def dig(key : String | Int32) : Any
def dig(key : String | Int32, *rest : String | Int32) : Any
def dig?(key : String | Int32) : Any | ::Nil
def dig?(key : String | Int32, *rest : String | Int32) : Any | ::Nil
# Agrees with `==`: a table's hash does not depend on its order, `0.0`
# and `-0.0` share one and so do all NaNs. Mixed as std/yaml's `Any`
# mixes, for the spread that file measured.
def hash : Int32
def initialize(raw : Type)
def initialize(value : Int32)
# A value as TOML writes it inline: strings quoted and escaped, floats
# as `dump` spells them, tables as inline tables.
def inspect : String
# Which member of the union `raw` is, as one of the KIND_ constants.
# Reference types are tested first, for the reason std/yaml's header
# gives.
def kind : Int32
# The name of the kind, for the sentences below.
def kind_name : String
def raw : Type
# Items of an array, keys of a table, characters of a string.
def size : Int32
# A string as itself, anything else as `inspect` writes it.
def to_s : String
endTOMLstruct
pub struct TOML
# TOML for a table, ending in a line break unless the table is empty.
def self.dump(value : Any) : String
# The document `text` holds, read into `type`: `Bool`, `Int32`, `Int64`,
# `UInt8`, `UInt64`, `Float64`, `String`, `TOMLDateTime`, `Any`, a type
# with `derive toml_serializable`, one of those or nil, or an `Array` or a
# `Hash(String, _)` of those. Text that is not TOML panics with the
# `TOMLError`'s sentence; a value of another kind, or an integer outside
# the type, is refused.
def self.from_toml(text : String, type : T.class) : T forall T
def initialize
# The document `text` holds, a table, or what is wrong with it.
def self.parse(text : String) : Any | TOMLError
# The document, or nil where `parse` answers a `TOMLError`.
def self.parse?(text : String) : Any | ::Nil
def self.read_value(value : Any, type : Bool.class) : Bool
def self.read_value(value : Any, type : Int32.class) : Int32
def self.read_value(value : Any, type : Int64.class) : Int64
def self.read_value(value : Any, type : UInt8.class) : UInt8
def self.read_value(value : Any, type : UInt64.class) : UInt64
# A float, or an integer widened to one: `y = 2` is the Float64 2.0.
def self.read_value(value : Any, type : Float64.class) : Float64
def self.read_value(value : Any, type : String.class) : String
def self.read_value(value : Any, type : TOMLDateTime.class) : TOMLDateTime
def self.read_value(value : Any, type : Any.class) : Any
def self.read_value(value : Any, type : Array(T).class) : Array(T) forall T
def self.read_value(value : Any, type : Hash(String, V).class) : Hash(String, V) forall V
# A type that reads itself, the `initialize(toml)` a `derive
# toml_serializable` wrote; a type that admits nil reads as the other
# type, since a value that is there is never null.
def self.read_value(value : Any, type : T.class) : T forall T
# The `Any` a value is written as. TOML has no null: a nil is refused
# here, named, and a derived type leaves a nil field out before it gets
# this far. Taken out before the rest dispatches, for the reason
# std/json's `write_or_null` gives.
def self.to_any(val : T) : Any forall T
# TOML for any table `to_any` makes of `obj`, as `dump` writes it.
def self.to_toml(obj : T) : String forall T
def self.write_value(val : Bool) : Any
def self.write_value(val : Int32) : Any
def self.write_value(val : Int64) : Any
def self.write_value(val : UInt8) : Any
# A TOML integer is an `Int64`: a `UInt64` past it cannot be written.
def self.write_value(val : UInt64) : Any
def self.write_value(val : Float64) : Any
def self.write_value(val : String) : Any
def self.write_value(val : TOMLDateTime) : Any
def self.write_value(val : Any) : Any
def self.write_value(val : Array(T)) : Any forall T
# TOML keys are strings, so a hash is written when its keys are.
def self.write_value(val : Hash(String, V)) : Any forall V
def self.write_value(val : T) : Any forall T : ToTOML
endTOMLDateTimestruct
A TOML date, time or date-time, with its kind. An offset date-time has every field and offset_minutes (0 for Z); a local date-time every field but the offset; a local date the date fields alone, a local time the time fields alone, the others 0. Two values are equal when their kind and every field are: 07:32:00Z and 08:32:00+01:00 are one instant written two ways, and this keeps which way (unix_seconds says they are the same instant).
pub struct TOMLDateTime
def !=(other : TOMLDateTime) : Bool
def ==(other : TOMLDateTime) : Bool
def day : Int32
def self.days_in_month(year : Int32, month : Int32) : Int32
def hash : Int32
def hour : Int32
# Fields that do not make a value of the kind are the caller's mistake,
# refused with what is wrong; `problem` says it without panicking.
def initialize(kind : Int32, year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32, offset_minutes : Int32)
def inspect : String
def kind : Int32
def kind_name : String
def self.leap_year?(year : Int32) : Bool
def self.local_date(year : Int32, month : Int32, day : Int32) : TOMLDateTime
def local_date? : Bool
def self.local_date_time(year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0) : TOMLDateTime
def local_date_time? : Bool
def self.local_time(hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0) : TOMLDateTime
def local_time? : Bool
def minute : Int32
def month : Int32
def nanosecond : Int32
def offset? : Bool
def self.offset_date_time(year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32 = 0, offset_minutes : Int32 = 0) : TOMLDateTime
def offset_minutes : Int32
# `value` in decimal with zeros in front to `width` digits.
def self.pad(value : Int32, width : Int32) : String
# Nil when the fields make a value of `kind`, else what is wrong with
# them, as the reader and `new` both say it.
def self.problem(kind : Int32, year : Int32, month : Int32, day : Int32, hour : Int32, minute : Int32, second : Int32, nanosecond : Int32, offset_minutes : Int32) : String | ::Nil
def second : Int32
# As TOML writes it (RFC 3339 for an offset date-time): `T` between
# date and time, `Z` for a zero offset, the fraction with its trailing
# zeros dropped and left out when it is zero.
def to_s : String
# Seconds since 1970-01-01T00:00:00Z of an offset date-time, the
# fraction left in `nanosecond`. The other three kinds name no instant.
def unix_seconds : Int64
def year : Int32
endTOMLErrorstruct
What is wrong with a document, and where: line and column count from 1, a column in characters. message is "<reason> at line L, column C".
pub struct TOMLError
def column : Int32
def initialize(reason : String, line : Int32, column : Int32)
def line : Int32
def reason : String
def to_s : String
endToTOMLtrait
What TOML.write_value and TOML.to_any take besides the built-in types: a value whose type implements it, as a derive toml_serializable type does.
pub trait ToTOML
abstract def to_toml_any : Any
endError for TOMLErrorimpl
impl Error for TOMLError
def message : String
end
TOML 1.0.0 (https://toml.io/en/v1.0.0), read and written in iyi alone: a reader that answers a document as an
Anyor aTOMLErrorsaying what is wrong and where, a writer whose text reads back to the value it was given, andderive toml_serializable.