5 releases (2 stable)
1.0.1 | Mar 2, 2025 |
---|---|
0.3.1 | Jan 20, 2024 |
0.2.0 | Jan 15, 2024 |
0.1.0 | Jan 14, 2024 |
#264 in Encoding
18,650 downloads per month
64KB
2K
SLoC
BOML
A dead-simple, efficient, dependency-free TOML parser for Rust.
Special thanks to cubic and Speykious for their help while developing BOML!
Usage
Parsing
BOML requires no imports - just call boml::parse
with the TOML source code, then use the returned Toml
value similarly to a hashmap:
fn parse_cargo_toml() {
let source = include_str!("../Cargo.toml");
let toml = boml::parse(source).unwrap();
// Get the package table from the `Cargo.toml` file
let package = toml.get_table("package").unwrap();
}
Data Types
In the above snippet, we used get_table
to read a value, because we knew the value was a table. BOML provides equivalent methods for every TOML type (get_string
, get_integer
, etc). However, BOML also provides a get
method, which allows you to resolve the type yourself. For example:
use boml::prelude::TomlValue;
let source = include_str!("../Cargo.toml");
let toml = boml::parse(source).unwrap();
// Specific types via `.get_<type>`
let package = toml.get_table("package").unwrap();
let name = package.get_string("name").unwrap();
assert_eq!(name, "boml");
// Dynamic types via `.get`
let package_untyped = toml.get("package").unwrap();
// You can then convert the value to a specific type with helper methods or
// pattern matching:
let package = toml.get("package").unwrap().as_table().unwrap();
let name = package.get("name").unwrap().as_string().unwrap();
assert_eq!(name, "boml");
let Some(TomlValue::Table(package)) = toml.get("package") else {
panic!("I love boilerplate, why would anyone use helper methods");
};
match package.get("name").unwrap() {
TomlValue::String(name) => assert_eq!(name.as_str(), "boml"),
TomlValue::Integer(int) => println!("{int} is a pretty weird name, bro"),
_ => panic!("Expected string or int for package name")
}
You can also determine a value's type without touching its data, via the .ty()
method and TomlValueType
enum:
use boml::prelude::TomlValueType;
let source = include_str!("../Cargo.toml");
let toml = boml::parse(source).unwrap();
let package = toml.get("package").unwrap();
assert_eq!(package.ty(), TomlValueType::Table);
Error Handling
There are 2 sources of errors in BOML: A parsing error, or an error from one of
the get_<type>
methods. These use the TomlError
and TomlGetError
types,
respectively.
TomlError
, the parsing error type, stores the span of text where the parsing
error occurred, and a TomlErrorKind
which describes the type of error at that
span. Printing the error will show the error kind and the region of TOML that had the error.
TomlGetError
is an error from one of the get_<type>
methods in tables. It
occurs when there's no value for the provided key (InvalidKey
) or when the
types aren't the same (TypeMismatch
- could happen if, for example, you try
to get a String
value with get_table
). A TypeMismatch
error stores the
actual TOML value and its type, so you can still attempt to use it if possible.
Date and Time Types
TOML supports 4 data types related to dates and times. BOML will parse these types, but performs no validation on those date and time types, because time is hard. BOML does not even check if an hour is between 0 and 23 or if a minute is between 0 and 60.
The only guarantee BOML makes about date/time values is that they are formatted according to RFC 3339. This does not mean the date/time values are actually valid. It just means that the year was a four-digit number, the month was a two-digit number, etc.
You should pass date/time values parsed with BOML to another crate - such as chrono or jiff - before actually using them.
If you enable the crate feature chrono
, BOML will provide From
and Into
implementations to convert TOML date/time types into Chrono date/time types.
TOML Compliance
BOML passes all valid tests cases of the official TOML test suite for TOML 1.0.
BOML does parse some invalid test cases without erroring, meaning it may parse something that's technically invalid TOML as valid TOML.
To run BOML against the TOML test suite yourself, see tests/toml_test.rs.
TOML 1.1 is not currently supported, but support for it will be added if it's released.
Efficiency
BOML aims to be very fast. On a Framework 16, BOML parses the entire TOML test suite - ~1.8k lines of TOML - in ~.003 seconds. You can run this benchmark yourself with cargo +nightly t toml_test_speed --release -- -Zunstable-options --report-time --ignored
.
Here's some more in-depth details on BOML's efficiency:
- BOML only copies data from the original TOML source string in two places:
- Strings with escape sequences. Parts of the string that aren't escaped have to be copied to a new string, so the escape can be added.
- Floats. Floats are really hard to parse correctly (and efficiently), so BOML uses the standard library's float parser. Unfortunately TOML allows underscores in floats, while the standard library does not, so BOML first has to copy the float to a new buffer and remove any underscores. This process does not allocate.
- BOML only allocates memory in three places:
- Creating hashmaps for TOML tables
- Creating vecs for TOML arrays
- Copying strings with escape sequences to process the escape sequences
To-Do
- Support for serializing TOML
no_std
support? Currently only allocated types from the standard library are used, so it should be possible- Improve error messages to be more like rustc or https://github.com/brendanzab/codespan
So what does "BOML" stand for
Yes.
Dependencies
~0–280KB