package otoml
Install
Dune Dependency
Authors
Maintainers
Sources
md5=0369a4f615ad2bf86abd84467588f948
sha512=4986ea5890d9d970122850a42951f5a81002108f758199e8c44ad25e779a90ea7425b574523fdc82db50ef2ddd290eeee5cf017f2f5fad7da65e96f277ec6b64
Description
OTOML is a library for parsing, manipulating, and pretty-printing TOML files.
- Fully 1.0.0-compliant.
- No extra dependencies: default implementation uses native numbers and represents dates as strings.
- Provides a functor for building alternative implementations: plug your own bignum and calendar libraries if required.
- Informative parse error reporting.
- Pretty-printer offers flexible indentation options.
Published: 02 Sep 2021
README
OTOML
A TOML parsing and manipulation library for OCaml.
In short:
TOML 1.0-compliant.
Transparent (no abstract types).
Easy access to deeply nested values.
Preserves original syntax variant (e.g. inline vs normal table) when parsing and printing.
Flexible pretty-printing options.
Does not force a calendar or bignum library dependency on you (you can plug your own into the functor).
Goals
The main goal for writing another TOML library is to provide a library for manipulating TOML files, not just reading them.
TOML is designed as a configuration file format. It's not just a serialization format for machines to talk to one another. A lot of time it's written, edited, and read by humans.
That is why TOML supports comments and multiple ways to write the same data.
Ideally, when a program reads a TOML file and writes it back, it should be able to echo it back and respect user's choice of using inline records vs sections (i.e. section = {...}
vs [section]
) and so on.
OTOML preserves that information and makes it available to the user.
It also offers a convenient interface for accessing and modifying values in deeply nested tables.
Example
(* Parse a TOML string. *)
utop # let t = Otoml.Parser.from_string "
[settings]
[settings.basic]
crash_randomly = true
" ;;
val t : Otoml.t =
Otoml.TomlTable
[("settings",
Otoml.TomlTable
[("basic", Otoml.TomlTable [("crash_randomly", Otoml.TomlBoolean true)])])]
(* Look up a deeply nested value with a known type. *)
utop # Otoml.find t Otoml.get_boolean ["settings"; "basic"; "crash_randomly"] ;;
- : bool = true
(* Update a deeply nested value. *)
utop # let t = Otoml.update t ["settings"; "basic"; "crash_randomly"] (Some (Otoml.TomlInteger 0)) ;;
val t : Otoml.t =
Otoml.TomlTable
[("settings",
Otoml.TomlTable
[("basic", Otoml.TomlTable [("crash_randomly", Otoml.TomlInteger 0)])])]
(* Look up a value and convert it to desired type (if possible). *)
utop # Otoml.find t (Otoml.get_boolean ~strict:false) ["settings"; "basic"; "crash_randomly"] ;;
- : bool = false
(* There's a pretty-printer, too! *)
utop # let t = Otoml.Parser.from_string "[foo] \n [foo.bar] \n baz = {quux = false} \n xyzzy = [ ] \n" |>
Otoml.Printer.to_channel ~indent_width:4 ~indent_subtables:true ~collapse_tables:true stdout ;;
[foo.bar]
baz = {quux = false}
xyzzy = []
val t : unit = ()
Bring your own dependencies
The TOML specification requires support for datetime values and arbitrary large numbers. For a language that uses machine types and doesn't have datetime support in the standrad library, it means that implementations have to make a choice whether to be light on dependencies and easy to use or be standard-compliant.
OTOML solves that problem with OCaml functors.
The default implementation is provided for convenience: it represents integer and floating point numbers with OCaml's native int
and float
types, and stores date/time values as strings that you can parse with your favorite calendar library.
However, it's not hardcoded but built with a functor. This is how you could assemble the default implementation yourself.
module DefaultToml = Otoml.Base.Make (Otoml.Base.OCamlInteger) (Otoml.Base.OCamlFloat) (Otoml.Base.StringDate)
Thus you can replace any of the modules or all of them with your own. For example, this is how you can use zarith for a big integer implementation but keep native floats and simple string dates:
(* No signature ascription:
`module BigInteger : Otoml.Base.TomlInteger` would make the type t abstract,
which is inconvenient.
*)
module BigInteger = struct
type t = Z.t
let of_string = Z.of_string
let to_string = Z.to_string
let of_boolean b = if b then Z.one else Z.zero
let to_boolean n = (n <> Z.zero)
end
module MyToml = Otoml.Base.Make (BigInteger) (Otoml.Base.OCamlFloat) (Otoml.Base.StringDate)
Deviations from the TOML 1.0 specification
The default implementation is not completely compliant with the standard. These are the deviations:
The default implementation uses OCaml's native integer type, which is 63-bit on 64-bit architectures and 31-bit on 32-bit ones.
The default implementation does not interpret datetime values at all, only checks them for superficial validity and returns as strings.
For example, "1993-09-947" is considered invalid (as expected), but 1993-02-29 is valid despite the fact that 1993 wasn't a leap year.
Thus, actual precision depends on the library you use to parse those date strings or plug into the functor.