https://github.com/tailhook/knuffel
Rust KDL parser and derive implementation
https://github.com/tailhook/knuffel
Last synced: 10 months ago
JSON representation
Rust KDL parser and derive implementation
- Host: GitHub
- URL: https://github.com/tailhook/knuffel
- Owner: tailhook
- License: apache-2.0
- Created: 2021-11-13T21:18:48.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2024-02-08T20:52:06.000Z (about 2 years ago)
- Last Synced: 2025-03-31T05:06:30.486Z (11 months ago)
- Language: Rust
- Size: 241 KB
- Stars: 88
- Watchers: 3
- Forks: 11
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
A [KDL](https://kdl.dev) file format parser with great error reporting and
convenient derive macros.
# About KDL
To give you some background on the KDL format. Here is a small example:
```kdl
foo 1 key="val" "three" {
bar
(role)baz 1 2
}
```
Here is what are annotations for all the datum as described by the
[specification] and this guide:
```text
foo 1 "three" key="val" { ╮
─┬─ ┬ ───┬─── ────┬──── │
│ │ │ ╰───── property (can be multiple) │
│ │ │ │
│ ╰────┴────────────── arguments │
│ │
└── node name ├─ node "foo", with
│ "bar" and "baz"
bar │ being children
(role)baz 1 2 │
──┬─ │
└────── type name for node named "baz" │
} ╯
```
(note, the order of properties doesn't matter as well as the order of
properties with respect to arguments, so I've moved arguments to have less
intersections for the arrows)
# Usage
Most common usage of this library is using `derive` and [parse] function:
```rust
#[derive(knuffel::Decode)]
enum TopLevelNode {
Route(Route),
Plugin(Plugin),
}
#[derive(knuffel::Decode)]
struct Route {
#[knuffel(argument)]
path: String,
#[knuffel(children(name="route"))]
subroutes: Vec,
}
#[derive(knuffel::Decode)]
struct Plugin {
#[knuffel(argument)]
name: String,
#[knuffel(property)]
url: String,
}
# fn main() -> miette::Result<()> {
let config = knuffel::parse::>("example.kdl", r#"
route "/api" {
route "/api/v1"
}
plugin "http" url="https://example.org/http"
"#)?;
# Ok(())
# }
```
This parses into a vector of nodes as enums `TopLevelNode`, but you also use some node as a root of the document if there is no properties and arguments declared:
```rust,ignore
#[derive(knuffel::Decode)]
struct Document {
#[knuffel(child, unwrap(argument))]
version: Option,
#[knuffel(children(name="route"))]
routes: Vec,
#[knuffel(children(name="plugin"))]
plugins: Vec,
}
let config = parse::("example.kdl", r#"
version "2.0"
route "/api" {
route "/api/v1"
}
plugin "http" url="https://example.org/http"
"#)?;
```
See description of [Decode](derive@Decode) and
[DecodeScalar](derive@DecodeScalar) for the full
reference on allowed attributes and parse modes.
# Errors
This crate publishes nice errors, like this:

To make them working, [miette]'s "fancy" feature must be enabled in the final
application's `Cargo.toml`:
```toml
[dependencies]
miette = { version="4.3.0", features=["fancy"] }
```
And the error returned from parser should be converted to [miette::Report] and
printed with debugging handler. The most manual way to do that is:
```rust
# #[derive(knuffel::Decode, Debug)]
# struct Config {}
# let file_name = "1.kdl";
# let text = "";
let config = match knuffel::parse::(file_name, text) {
Ok(config) => config,
Err(e) => {
println!("{:?}", miette::Report::new(e));
std::process::exit(1);
}
};
```
But usually function that returns `miette::Result` is good enough:
```rust,no_run
# use std::fs;
# #[derive(knuffel::Decode)]
# struct Config {}
use miette::{IntoDiagnostic, Context};
fn parse_config(path: &str) -> miette::Result {
let text = fs::read_to_string(path).into_diagnostic()
.wrap_err_with(|| format!("cannot read {:?}", path))?;
Ok(knuffel::parse(path, &text)?)
}
fn main() -> miette::Result<()> {
let config = parse_config("my.kdl")?;
# Ok(())
}
```
See [miette guide] for other ways of configuring error output.
# The Name
KDL is pronounced as cuddle. "Knuffel" means the same as cuddle in Dutch.
License
=======
Licensed under either of
* Apache License, Version 2.0,
(./LICENSE-APACHE or )
* MIT license (./LICENSE-MIT or )
at your option.
Contribution
------------
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.
[specification]: https://github.com/kdl-org/kdl/blob/main/SPEC.md
[miette]: https://docs.rs/miette/
[miette guide]: https://docs.rs/miette/latest/miette/#-handler-options