tailhook / knuffel Goto Github PK
View Code? Open in Web Editor NEWRust KDL parser and derive implementation
License: Apache License 2.0
Rust KDL parser and derive implementation
License: Apache License 2.0
Just like #[knuffel(child)] field: bool
maps the presence/absence of field
to true
/false
,
I expect #[knuffel(child)] field: Option<T>
for T
withDecode
(without Decode
) to map the presence/absence of field { ..t }
(field t
) to Some(t)
/None
where t
: T
.
Currently we get this error in some cases:
error[E0277]: the trait bound std::option::Option<T>: Decode<S> is not satisfied
.
field modes of T |
Option<T> |
---|---|
all fields are property |
DOESN’T WORK |
all fields are children |
WORKS |
I’m not sure why this is happening, but this does not work (resulting in "unexpected property"):
#[derive(knuffel::Decode)]
pub struct الطاب {
#[knuffel(property)]
pub الطاب: i64,
}
… but this, with an explicit name
, does:
#[derive(knuffel::Decode)]
pub struct الطاب {
#[knuffel(property(name = "الطاب"))]
pub الطاب: i64,
}
I have a KDL document like:
pad {
left "Left"
right "Right"
up "Up"
down "Down"
}
And I want to decode it into the following structure.
#[derive(knuffel::Decode)]
pub struct Config {
#[knuffel(child)]
pub pad: Pad,
}
#[derive(knuffel::Decode)]
pub struct Pad {
#[knuffel(child, str, unwrap(argument))]
pub left: KeyMap,
#[knuffel(child, str, unwrap(argument))]
pub right: KeyMap,
#[knuffel(child, str, unwrap(argument))]
pub up: KeyMap,
#[knuffel(child, str, unwrap(argument))]
pub down: KeyMap,
}
#[derive(enum_utils::FromStr)]
pub enum KeyMap {
Left,
Right,
Up,
Down
}
This is a very common pattern when working with serde but it seems knuffel doesnt support enums as arguments/properties very well?
I tried the knuffel derive attributes that I included in this example as well as a few others but all of them either failed to compile or failed to parse the kdl.
As we can see below,
knuffel's mantra in mapping KDL to Rust should be simple: a node's name is a struct's name or an enum's variant name:
|
|
|
structs | ||
#[derive(Decode)] struct Node0; |
#[derive(Decode)] struct Node1( #[knuffel(argument)] i32, #[knuffel(argument)] String ); |
#[derive(Decode)] struct Node2( #[knuffel(property(name = "a"))] i32, #[knuffel(property(name = "b"))] String ); |
#[derive(Decode)] struct Node1 { #[knuffel(argument)] a: i32, #[knuffel(argument)] b: String } |
#[derive(Decode)] struct Node2 { #[knuffel(property)] a: i32, #[knuffel(property)] b: String } |
|
enums | ||
#[derive(Decode)] enum Node { Node0, Node1( #[knuffel(argument)] i32, #[knuffel(argument)] String ), Node2( #[knuffel(property(name = "a"))] i32, #[knuffel(property(name = "b"))] String ) } |
||
#[derive(Decode)] enum Node { Node0, Node1 { #[knuffel(argument)] a: i32, #[knuffel(argument)] b: String }, Node2 { #[knuffel(property)] a: i32, #[knuffel(property)] b: String } } |
But when it comes to modelling children, knuffel supposes a different syntactical site to be those children's names:
node {
child 1
}
is modelled not by
#[derive(Decode)]
struct Node {
#[knuffel(child)]
a: Child
}
#[derive(Decode)]
struct Child(#[knuffel(argument)] i32);
but
#[derive(Decode)]
struct Node {
#[knuffel(child)]
child: A
}
#[derive(Decode)]
struct A(#[knuffel(argument)] i32);
And this is the cause of every problem in knuffel ––
Since the child's name is anchored by the field's name, enums with Decode
cannot be used for the single child. #7
This especially comes up when combined with serde's Serialize
derive macro and knuffel::parse::<Vec<Node>>
pattern, where the Node
's aren't camel-cased (node
) but outputted raw (Node
).
Redundancy
children
directive takes name
argument.node_name
attribute.type_name
attribute.Partiality
Tuple structs cannot have children, only struct structs can.
Incompleteness
Structural typing
Instead of nominal typing which must be the culmination in writing KDL and the shared spirit with Rust.
Hi, awesome library but I'm struggling with representing a rather freestyle structure like this:
genres {
rock
metal
pop {
albums-by-year {
y2000 "a" "b" "c"
y2001 "d" "e"
}
}
}
Here, for example I expect there to be a top-level genres
node but I don't restrict which and how many child nodes it has. Then each node again is expected to have smth like albums-by-year
which itself is again pretty freestyle key-value table. How can I do this with knuffel
? I expect to be able to do smth like this:
#[derive(Debug, knuffel::Decode)]
struct Genre {
#[knuffel(node_name)]
node_name: String // name of the genre
#[knuffel(children)]
info: Option<Info>
}
#[derive(Debug, knuffel::Decode)]
struct Genres {
#[knuffel(children)]
genres: Vec<Genre>,
}
#[derive(knuffel::Decode)]
struct TestCase {
#[knuffel(argument)]
r#ref: String,
}
causes the Decode
macro to panic with `"seen_r#ref"` is not a valid identifier
.
Are there any plans to support writing to the KDL format?
Hello! I would like to parse the following document:
image "nginx" {
reference "docker.io/library/nginx:1.23.4"
}
image "nginx" reference="docker.io/library/nginx:1.23.4"
Imagine that image
could have 100 files. Or, it could have 1. Opening an entire block for just one argument seems excessive, but specifying 100 options using properties is similar. It would be nice if there was a way to decode either #knuffel(child, unwrap(argument))
or #knuffel(property)
with a single derive argument. Here is my current, though admittedly fairly digusting, solution:
enum Image {
Explicit(ExplicitImage),
Inline(InlineImage),
}
impl<S: ErrorSpan> Decode<S> for Image {
fn decode_node(
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, knuffel::errors::DecodeError<S>> {
if node.properties.is_empty() {
::knuffel::Decode::decode_node(node, ctx).map(Self::Explicit)
} else {
::knuffel::Decode::decode_node(node, ctx).map(Self::Inline)
}
}
}
#[derive(knuffel::Decode)]
struct ExplicitImage {
#[knuffel(argument)]
name: String,
#[knuffel(child, unwrap(argument))]
reference: String,
}
#[derive(knuffel::Decode)]
struct InlineImage {
#[knuffel(argument)]
name: String,
#[knuffel(property)]
reference: String,
}
At the end of the day, we end up with two identical structs and a manual Decode
implementation. That's a lot of boilerplate!
Maybe I'm doing something wrong, but trying to do this:
use knuffel;
#[derive(knuffel::Decode)]
pub enum MyEnum {
MyFirstVariant,
VariantWithTuple(u8),
}
fn main() {
println!("Hello, world!");
}
I get:
$ cargo check
error[E0277]: the trait bound `u8: Decode<S>` is not satisfied
--> src/main.rs:3:10
|
3 | #[derive(knuffel::Decode)]
| ^^^^^^^^^^^^^^^ the trait `Decode<S>` is not implemented for `u8`
|
= help: the following other types implement trait `Decode<S>`:
Arc<T>
Box<T>
MyEnum
Node<T>
Rc<T>
Spanned<Node<T>, T>
= note: this error originates in the derive macro `knuffel::Decode` (in Nightly builds, run with -Z macro-backtrace for more info)
Any clues?
I am not able to figure out how to parse a node which takes arguments of different types i.e, int
, float
or string
. For example.
/* Only Floats */
margin 0.5 0.5 0.5 0.5
/* Only Int */
margin 1 2 3 4
/* Mixed */
margin 0.5 1 1.5 "$.top - 3"
And I want that in, somewhat, similar structure as below.
#![allow(dead_code)]
#[derive(Debug, Default, knuffel::Decode)]
struct Document {
#[knuffel(child)]
margin: Option<Direction>,
}
#[derive(Debug, Default, knuffel::Decode)]
struct Direction {
top: Val,
right: Val,
bottom: Val,
left: Val,
}
#[derive(Debug, knuffel::Decode)]
enum Val {
Int(#[knuffel(argument)] u32),
Float(#[knuffel(argument)] u32),
Str(#[knuffel(argument)] String),
}
impl Default for Val {
fn default() -> Self {
Self::Int(0)
}
}
fn main() {
let doc: Document = knuffel::parse("keeb.kdl", r#"margin 0.5 1 1.5 "$.top - 3""#).unwrap();
dbg!(&doc);
}
I am getting unexpected argument
after the margin
. This code doesn't seem right to me either, but I also couldn't figure out how to fix this.
Given
#[derive(Debug, knuffel::Decode)]
struct SomeDocument {
#[knuffel(child)]
some_node: SomeEnum,
}
#[derive(Debug, knuffel::Decode)]
enum SomeEnum {
SomeVariant(SomeVariant),
AnotherVariant(AnotherVariant),
}
#[derive(Debug, knuffel::Decode)]
struct SomeVariant {}
#[derive(Debug, knuffel::Decode)]
struct AnotherVariant {}
if I try to parse
some-variant
I get errors: "unexpected node some-variant
" and "child node some-node
is required"
but if I try to parse
some-node
I get "expected one of some-variant
, another-variant
"
I tried many variations of this in hope that I'm just misunderstanding KDL or knuffel, but in the end I checked the Decode implementations with cargo expand
and it appears that the node is expected to have name of the parent struct field and one of the enum variants at the same time.
full reproduction here: https://github.com/silen-z/knuffel-enum-bug-repro/blob/main/src/lib.rs
Given the following:
#[derive(Clone, Debug, knuffel::Decode)]
pub struct ManifestNode {
#[knuffel(child, unwrap(argument))]
pub version: f32,
}
cargo check
error[E0277]: the trait bound `f32: DecodeScalar<S>` is not satisfied
--> backend/src/manifest.rs:9:24
|
9 | #[derive(Clone, Debug, knuffel::Decode)]
| ^^^^^^^^^^^^^^^ the trait `DecodeScalar<S>` is not implemented for `f32`
Why is this?
e.g. I have
#[derive(knuffel::Decode)]
pub struct Config {
#[knuffel(children(name = "replace"))]
pub replace: Vec<ReplaceRegex>,
#[knuffel(children(name = "job"))]
pub jobs: Vec<Job>,
}
It turns out that a lot of replace
rules end up being used in practice, and I'd like to allow the use of r
as a shorthand. With current knuffel, I'd have to do this as a separate field. I would instead like to do something like the following:
#[derive(knuffel::Decode)]
pub struct Config {
#[knuffel(children(name = "replace", name = "r"))]
pub replace: Vec<ReplaceRegex>,
#[knuffel(children(name = "job"))]
pub jobs: Vec<Job>,
}
and both replace
and r
children will go into self.replace
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.