tomlet

A round-tripping TOML parser and writer.

Tomlet parses TOML into an opaque Document, preserves comments and formatting during round-trips, and provides checked helpers for common reads and edits.

Types

A TOML local date value.

Opaque so structured accessors can be added in a later release without breaking existing code. Use date_to_string to read the original lexical form (e.g. "1979-05-27").

pub opaque type Date

A TOML date-time value.

Opaque so structured accessors can be added in a later release without breaking existing code. Use datetime_to_string to read the original lexical form (e.g. "1979-05-27T07:32:00Z").

pub opaque type DateTime

A parsed TOML document.

Documents are opaque so Tomlet can preserve round-trip invariants while the internal syntax tree changes.

pub opaque type Document

Errors that can occur while editing a document.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type EditError {
  EmptyKeyPath
  InvalidKeySegment(segment: String)
  InvalidCommentText
  MissingEditKey(key: List(String))
  KeyConflict(key: List(String))
  InlineTableInsertUnsupported(key: List(String))
  InvalidValue
}

Constructors

  • EmptyKeyPath

    Edit paths must contain at least one key segment.

  • InvalidKeySegment(segment: String)

    A key segment cannot be emitted as TOML.

  • InvalidCommentText

    Comments must be a single line.

  • MissingEditKey(key: List(String))

    The edit requires an existing key, but no value exists at that key path.

  • KeyConflict(key: List(String))

    Inserting the key would conflict with an existing scalar, table, or array of tables.

  • InlineTableInsertUnsupported(key: List(String))

    The edit would have to insert a new key inside an existing inline table.

    Inline tables are written on a single line and cannot be extended in place. Rewrite the table shape explicitly (for example, with set_* on a standard table) instead of relying on implicit insertion.

  • InvalidValue

    The supplied value cannot be represented in the requested edit context.

TOML value kinds used in typed read errors.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type ExpectedType {
  ExpectedString
  ExpectedInt
  ExpectedBool
  ExpectedFloat
  ExpectedDate
  ExpectedTime
  ExpectedDateTime
}

Constructors

  • ExpectedString
  • ExpectedInt
  • ExpectedBool
  • ExpectedFloat
  • ExpectedDate
  • ExpectedTime
  • ExpectedDateTime

Errors that can occur while constructing typed values from raw text.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type FormatError {
  InvalidDate(text: String)
  InvalidTime(text: String)
  InvalidDateTime(text: String)
}

Constructors

  • InvalidDate(text: String)

    The text is not a valid TOML local date literal (YYYY-MM-DD).

  • InvalidTime(text: String)

    The text is not a valid TOML local time literal (HH:MM:SS[.fraction]).

  • InvalidDateTime(text: String)

    The text is not a valid TOML date-time literal.

Errors that can occur while reading typed values from a document.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type GetError {
  KeyNotFound(key: List(String))
  WrongType(key: List(String), expected: ExpectedType)
}

Constructors

  • KeyNotFound(key: List(String))

    No value exists at the requested key path.

  • WrongType(key: List(String), expected: ExpectedType)

    A value exists at the requested key path, but it has a different TOML type.

Errors that can occur while parsing TOML input.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type ParseError {
  InvalidEncoding
  InvalidSyntax(kind: SyntaxErrorKind, offset: Int)
  DuplicateKey(key: List(String), offset: Int)
}

Constructors

  • InvalidEncoding

    Raw bytes could not be decoded as valid TOML text.

  • InvalidSyntax(kind: SyntaxErrorKind, offset: Int)

    TOML syntax was invalid at a byte offset.

  • DuplicateKey(key: List(String), offset: Int)

    A key was defined more than once.

A one-based source position.

Positions are opaque so Tomlet can add more source-location details later without changing the public constructor shape. Use position_line and position_column to inspect one.

pub opaque type Position

A TOML special floating-point value.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

let assert Ok(doc) = tomlet.parse("limit = inf\n")
let assert Ok(tomlet.SpecialFloatValue(tomlet.PositiveInfinity)) =
  tomlet.get(doc, ["limit"])
pub type SpecialFloat {
  PositiveInfinity
  NegativeInfinity
  NotANumber
}

Constructors

  • PositiveInfinity
  • NegativeInfinity
  • NotANumber

Stable categories for TOML syntax errors.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

pub type SyntaxErrorKind {
  ExpectedValue
  ExpectedKey
  ExpectedTableHeader
  InvalidToml
}

Constructors

  • ExpectedValue

    A TOML value was expected.

  • ExpectedKey

    A TOML key was expected.

  • ExpectedTableHeader

    A table header, such as [table] or [[array.table]], was expected.

  • InvalidToml

    TOML syntax was invalid, but the parser does not expose a narrower stable category.

    This catches syntax errors that do not have a narrower stable category.

A TOML local time value.

Opaque so structured accessors can be added in a later release without breaking existing code. Use time_to_string to read the original lexical form (e.g. "07:32:00").

pub opaque type Time

A TOML value without internal formatting trivia.

Variants are part of the stable public API. Adding, removing, or renaming a variant is treated as a breaking change.

The table-shaped variants (InlineTableValue, StandardTableValue, ArrayOfTablesValue) expose their entries as an ordered association list of #(key_path, value) pairs and will remain shaped that way. The key path is the dotted path relative to the table, e.g. ["pkg", "name"] for an entry written as pkg.name = ... inside an inline table.

pub type Value {
  StringValue(String)
  IntValue(Int)
  FloatValue(Float)
  SpecialFloatValue(SpecialFloat)
  BoolValue(Bool)
  DateValue(Date)
  TimeValue(Time)
  DateTimeValue(DateTime)
  ArrayValue(List(Value))
  InlineTableValue(List(#(List(String), Value)))
  StandardTableValue(List(#(List(String), Value)))
  ArrayOfTablesValue(List(List(#(List(String), Value))))
}

Constructors

  • StringValue(String)
  • IntValue(Int)
  • FloatValue(Float)
  • SpecialFloatValue(SpecialFloat)
  • BoolValue(Bool)
  • DateValue(Date)
  • TimeValue(Time)
  • DateTimeValue(DateTime)
  • ArrayValue(List(Value))
  • InlineTableValue(List(#(List(String), Value)))
  • StandardTableValue(List(#(List(String), Value)))
  • ArrayOfTablesValue(List(List(#(List(String), Value))))

Values

pub fn append_array_of_tables(
  doc: Document,
  key: List(String),
  entries: List(#(List(String), Value)),
) -> Result(Document, EditError)

Append a new table to an array of tables at a key path.

A [[key]] header is appended to the document followed by the supplied entries. Works whether or not an array of tables already exists at the key path; if no array of tables exists yet, a new one is created.

let assert Ok(doc) =
  tomlet.append_array_of_tables(tomlet.new(), ["packages"], [
    #(["name"], tomlet.StringValue("tomato")),
  ])
tomlet.to_string(doc)
// -> "
// [[packages]]
// name = \"tomato\"
// "
pub fn date_from_string(
  text: String,
) -> Result(Date, FormatError)

Construct a Date from its TOML lexical form (e.g. "1979-05-27").

let assert Ok(date) = tomlet.date_from_string("1979-05-27")
let assert Ok(doc) = tomlet.set_date(tomlet.new(), ["released"], date)
tomlet.to_string(doc)
// -> "released = 1979-05-27\n"
pub fn date_to_string(date: Date) -> String

Return the original lexical form of a TOML date value.

pub fn datetime_from_string(
  text: String,
) -> Result(DateTime, FormatError)

Construct a DateTime from its TOML lexical form (e.g. "1979-05-27T07:32:00Z").

let assert Ok(datetime) =
  tomlet.datetime_from_string("1979-05-27T07:32:00Z")
let assert Ok(doc) =
  tomlet.set_datetime(tomlet.new(), ["published"], datetime)
tomlet.to_string(doc)
// -> "published = 1979-05-27T07:32:00Z\n"
pub fn datetime_to_string(datetime: DateTime) -> String

Return the original lexical form of a TOML date-time value.

pub fn get(
  doc: Document,
  key: List(String),
) -> Result(Value, GetError)

Read a TOML value at a key path.

Use get instead of the typed get_* helpers when you need to inspect arrays, inline tables, standard tables, arrays of tables, or special floats.

let assert Ok(doc) =
  tomlet.parse("package = { name = \"tomato\", downloads = 42 }\n")
let assert Ok(value) = tomlet.get(doc, ["package"])
// -> tomlet.InlineTableValue([
//   #(["name"], tomlet.StringValue("tomato")),
//   #(["downloads"], tomlet.IntValue(42)),
// ])
pub fn get_bool(
  doc: Document,
  key: List(String),
) -> Result(Bool, GetError)

Read a TOML boolean value at a key path.

pub fn get_date(
  doc: Document,
  key: List(String),
) -> Result(Date, GetError)

Read a TOML local date value at a key path.

pub fn get_datetime(
  doc: Document,
  key: List(String),
) -> Result(DateTime, GetError)

Read a TOML date-time value at a key path.

pub fn get_float(
  doc: Document,
  key: List(String),
) -> Result(Float, GetError)

Read a TOML float value at a key path.

Special floats (inf, -inf, nan) are not returned here; reading one yields WrongType. Use get and match on SpecialFloatValue for those.

pub fn get_int(
  doc: Document,
  key: List(String),
) -> Result(Int, GetError)

Read a TOML integer value at a key path.

pub fn get_string(
  doc: Document,
  key: List(String),
) -> Result(String, GetError)

Read a TOML string value at a key path.

pub fn get_time(
  doc: Document,
  key: List(String),
) -> Result(Time, GetError)

Read a TOML local time value at a key path.

pub fn insert_comment_before(
  doc: Document,
  key: List(String),
  text: String,
) -> Result(Document, EditError)

Insert a standalone comment before an existing key.

The comment text may include a leading #, but must not contain TOML comment control characters. Returns MissingEditKey when the target key does not exist, InvalidCommentText when the comment is unsafe to emit, and EmptyKeyPath when the key path is empty.

let assert Ok(doc) = tomlet.parse("released = 1979-05-27\n")
let assert Ok(doc) =
  tomlet.insert_comment_before(doc, ["released"], "release date")
tomlet.to_string(doc)
// -> "
// # release date
// released = 1979-05-27
// "
pub fn line_column(input: String, offset: Int) -> Position

Convert a byte offset into a one-based line and column.

Offsets beyond the end of the input return the position just after the last character. CRLF is treated as a single line break.

let input = "name = \n"
case tomlet.parse(input) {
  Error(tomlet.InvalidSyntax(_, offset)) -> {
    let position = tomlet.line_column(input, offset)
    let line = tomlet.position_line(position)
    let column = tomlet.position_column(position)
    // Show line and column in your application's diagnostic.
  }
  _ -> Nil
}
pub fn new() -> Document

Create an empty TOML document.

Equivalent to parse("") for downstream callers; the only observable difference is that parse("") initially round-trips to "" even after the document is modified, while the document returned by new always emits its current content.

pub fn parse(input: String) -> Result(Document, ParseError)

Parse TOML 1.0 text into a document.

Successful parses return an opaque Document that preserves comments, formatting trivia, key order, and the original line ending style for round-tripping. Invalid text returns ParseError, including byte offsets for syntax and duplicate-key diagnostics.

pub fn parse_bytes(
  input: BitArray,
) -> Result(Document, ParseError)

Parse TOML bytes into a document.

This validates UTF-8 input and accepts a UTF-8 byte order mark only at the start of the input.

let assert Ok(doc) = tomlet.parse_bytes(<<"answer = 42\n":utf8>>)
let assert Ok(answer) = tomlet.get_int(doc, ["answer"])

tomlet.parse_bytes(<<110, 97, 109, 101, 32, 61, 32, 255, 10>>)
// -> Error(tomlet.InvalidEncoding)
pub fn position_column(position: Position) -> Int

Return the one-based column number for a source position.

pub fn position_line(position: Position) -> Int

Return the one-based line number for a source position.

pub fn remove(
  doc: Document,
  key: List(String),
) -> Result(Document, EditError)

Remove an existing value from a document.

Returns MissingEditKey when the key path does not exist, and EmptyKeyPath when the key path is empty.

pub fn set_array(
  doc: Document,
  key: List(String),
  items: List(Value),
) -> Result(Document, EditError)

Set a TOML array value at a key path.

Items are emitted in order using a default flow-style representation ([a, b, c]). Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

StandardTableValue and ArrayOfTablesValue items are rejected with InvalidValue; use set_inline_table or append_array_of_tables for table-shaped values.

let assert Ok(doc) =
  tomlet.set_array(tomlet.new(), ["ports"], [
    tomlet.IntValue(8000),
    tomlet.IntValue(8001),
  ])
tomlet.to_string(doc)
// -> "ports = [8000, 8001]\n"
pub fn set_bool(
  doc: Document,
  key: List(String),
  value: Bool,
) -> Result(Document, EditError)

Set a TOML boolean value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn set_date(
  doc: Document,
  key: List(String),
  value: Date,
) -> Result(Document, EditError)

Set a TOML local date value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn set_datetime(
  doc: Document,
  key: List(String),
  value: DateTime,
) -> Result(Document, EditError)

Set a TOML date-time value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn set_float(
  doc: Document,
  key: List(String),
  value: Float,
) -> Result(Document, EditError)

Set a TOML float value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn set_inline_table(
  doc: Document,
  key: List(String),
  entries: List(#(List(String), Value)),
) -> Result(Document, EditError)

Set a TOML inline table value at a key path.

Entries are emitted in order using a default flow-style representation ({ a = 1, b = 2 }). Each entry’s key path is rendered as a dotted key when it contains more than one segment. Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

Entry values that are StandardTableValue or ArrayOfTablesValue are rejected with InvalidValue; nest an InlineTableValue instead.

let assert Ok(doc) =
  tomlet.set_inline_table(tomlet.new(), ["pkg"], [
    #(["name"], tomlet.StringValue("tomato")),
    #(["meta", "downloads"], tomlet.IntValue(42)),
  ])
tomlet.to_string(doc)
// -> "pkg = { name = \"tomato\", meta.downloads = 42 }\n"
pub fn set_int(
  doc: Document,
  key: List(String),
  value: Int,
) -> Result(Document, EditError)

Set a TOML integer value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn set_string(
  doc: Document,
  key: List(String),
  value: String,
) -> Result(Document, EditError)

Set a TOML string value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

let assert Ok(doc) =
  tomlet.set_string(tomlet.new(), ["package", "name"], "tomlet")
tomlet.to_string(doc)
// -> "
// [package]
// name = \"tomlet\"
// "
pub fn set_time(
  doc: Document,
  key: List(String),
  value: Time,
) -> Result(Document, EditError)

Set a TOML local time value at a key path.

Existing values are replaced in place. Missing keys are inserted, creating a table header when needed.

pub fn time_from_string(
  text: String,
) -> Result(Time, FormatError)

Construct a Time from its TOML lexical form (e.g. "07:32:00").

let assert Ok(time) = tomlet.time_from_string("07:32:00")
let assert Ok(doc) = tomlet.set_time(tomlet.new(), ["alarm"], time)
tomlet.to_string(doc)
// -> "alarm = 07:32:00\n"
pub fn time_to_string(time: Time) -> String

Return the original lexical form of a TOML time value.

pub fn to_string(doc: Document) -> String

Emit a document as TOML text.

Unedited parsed documents round-trip to their original source text.

Search Document