tomlet

Package Version Hex Docs

A round-tripping TOML parser and writer for Gleam.

Tomlet parses TOML 1.0.0 into an opaque document that retains comments, key order, and surrounding trivia. Unedited documents round-trip to their original text; edited values are written back while preserving nearby comments and document structure. Document stays opaque so Tomlet can evolve its internal syntax tree without breaking the public API. Inspired by Rust’s toml_edit and Python’s tomlkit.

gleam add tomlet
import tomlet

pub fn main() {
  let source =
    "
# the user's favorite snack
snack = \"tomato\"  # raw, with salt
"

  case tomlet.parse(source) {
    Ok(doc) ->
      case tomlet.set_string(doc, ["snack"], "tomato sandwich") {
        Ok(updated) -> tomlet.to_string(updated)
        // -> "
        // # the user's favorite snack
        // snack = \"tomato sandwich\"  # raw, with salt
        // "
        Error(error) -> handle_edit_error(error)
      }
    Error(error) -> handle_parse_error(error)
  }
}

The handle_* functions above are placeholders for your own error handling; each case surfaces the ParseError, GetError, or EditError that tomlet returns instead of crashing.

Parsing and typed access

Use tomlet.parse for String input, or tomlet.parse_bytes when raw bytes need TOML-compliant UTF-8 and BOM validation before parsing.

case tomlet.parse_bytes(<<"answer = 42\n":utf8>>) {
  Ok(doc) ->
    case tomlet.get_int(doc, ["answer"]) {
      Ok(answer) -> answer
      Error(error) -> handle_get_error(error)
    }
  Error(error) -> handle_parse_error(error)
}

Typed accessors include get_string, get_int, get_bool, get_float, get_date, get_time, and get_datetime. Use get when you need to inspect any TOML value through the public tomlet.Value type, including dates, times, arrays, inline tables, standard tables (StandardTableValue), and arrays of tables.

case tomlet.parse("released = 2026-05-25\n") {
  Ok(doc) ->
    case tomlet.get(doc, ["released"]) {
      Ok(tomlet.DateValue(date)) -> tomlet.date_to_string(date)
      // -> "2026-05-25"
      _ -> handle_unexpected_value()
    }
  Error(error) -> handle_parse_error(error)
}

Inline table values are addressed with the same key path syntax:

case tomlet.parse("pkg = { name = \"tomato\" }\n") {
  Ok(doc) ->
    case tomlet.get_string(doc, ["pkg", "name"]) {
      Ok(name) -> name
      Error(error) -> handle_get_error(error)
    }
  Error(error) -> handle_parse_error(error)
}

Typed accessor mismatches return WrongType(path, expected), where expected is a stable ExpectedType variant such as ExpectedString, ExpectedInt, or ExpectedDateTime.

Parse errors use stable variants for machine handling. InvalidSyntax and DuplicateKey carry byte offsets; use tomlet.line_column(input, offset) when displaying diagnostics to users.

TOML version

tomlet.parse and tomlet.parse_bytes accept TOML 1.1 by default. The 1.1 features tomlet supports beyond 1.0 are:

To parse strictly against TOML 1.0 and reject this 1.1-only syntax, pass the version directly:

let assert Error(_) = tomlet.parse_with("t = 07:32\n", tomlet.Toml10)

parse_with and parse_bytes_with take a TomlVersion (Toml10 or Toml11).

Checked edits

Edit operations return Result(Document, EditError) so applications can tell successful edits from invalid paths, key conflicts, missing keys, and unsafe comment text. Comment insertion rejects TOML-forbidden comment control characters. New keys are emitted as bare TOML keys when possible and quoted when needed. Current edit helpers include set_string, set_int, set_bool, set_float, set_date, set_time, set_datetime, set_array, set_inline_table, append_array_of_tables, remove, and insert_comment_before. Construct typed date, time, and date-time values with date_from_string, time_from_string, and datetime_from_string.

The set_* helpers can replace existing values inside inline tables, such as ["pkg", "name"] in pkg = { name = "tomato" }. Missing nested keys inside an existing inline table are reported as InlineTableInsertUnsupported; create those keys by rewriting the table shape explicitly rather than relying on implicit insertion.

Structural read values that cannot be represented in a write context are rejected explicitly. For example, passing StandardTableValue or ArrayOfTablesValue to set_array, set_inline_table, or append_array_of_tables returns Error(InvalidValue) instead of silently flattening the table shape.

Examples

Read typed values:

let input =
  "title = \"Tomlet\"\n"
  <> "version = 1\n"
  <> "enabled = true\n"
  <> "ratio = 3.14\n"

case tomlet.parse(input) {
  Ok(doc) ->
    case
      tomlet.get_string(doc, ["title"]),
      tomlet.get_int(doc, ["version"]),
      tomlet.get_bool(doc, ["enabled"]),
      tomlet.get_float(doc, ["ratio"])
    {
      Ok(title), Ok(version), Ok(enabled), Ok(ratio) ->
        #(title, version, enabled, ratio)
      _, _, _, _ -> handle_get_error()
    }
  Error(error) -> handle_parse_error(error)
}

Edit a document and write it back:

let input =
  "# package metadata\n"
  <> "name = \"tomlet\"\n"
  <> "version = 0\n"
  <> "draft = true\n"

case tomlet.parse(input) {
  Ok(doc) ->
    case tomlet.set_int(doc, ["version"], 1) {
      Ok(doc) ->
        case tomlet.remove(doc, ["draft"]) {
          Ok(doc) ->
            case
              tomlet.insert_comment_before(
                doc,
                ["version"],
                "first stable release",
              )
            {
              Ok(doc) -> tomlet.to_string(doc)
              // -> "
              // # package metadata
              // name = \"tomlet\"
              // # first stable release
              // version = 1
              // "
              Error(error) -> handle_edit_error(error)
            }
          Error(error) -> handle_edit_error(error)
        }
      Error(error) -> handle_edit_error(error)
    }
  Error(error) -> handle_parse_error(error)
}

Start from an empty document:

let doc = tomlet.new()

case tomlet.set_string(doc, ["package", "name"], "tomlet") {
  Ok(doc) ->
    case tomlet.set_int(doc, ["package", "version"], 1) {
      Ok(doc) -> tomlet.to_string(doc)
      // -> "
      // [package]
      // name = \"tomlet\"
      // version = 1
      // "
      Error(error) -> handle_edit_error(error)
    }
  Error(error) -> handle_edit_error(error)
}

Report parse errors with line and column information:

let input = "name = \n"

case tomlet.parse(input) {
  Ok(_) -> Nil
  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.
  }
  Error(tomlet.DuplicateKey(_, 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.
  }
  Error(tomlet.InvalidEncoding) -> Nil
}

Public API

The semver-stable public API is the top-level tomlet module. Other tomlet/* modules are internal implementation details and may change without notice.

API stability policy

Contributing

See DEV.md for contributor setup, test commands, changelog guidelines, and release-readiness checks.

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Tomlet by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Search Document