{"id":16949492,"url":"https://github.com/tailhook/knuffel","last_synced_at":"2025-04-07T06:12:57.529Z","repository":{"id":42427340,"uuid":"427774694","full_name":"tailhook/knuffel","owner":"tailhook","description":"Rust KDL parser and derive implementation","archived":false,"fork":false,"pushed_at":"2024-02-08T20:52:06.000Z","size":247,"stargazers_count":88,"open_issues_count":8,"forks_count":11,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-31T05:06:30.486Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tailhook.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-13T21:18:48.000Z","updated_at":"2025-03-29T19:47:05.000Z","dependencies_parsed_at":"2024-10-27T00:58:09.888Z","dependency_job_id":null,"html_url":"https://github.com/tailhook/knuffel","commit_stats":{"total_commits":158,"total_committers":4,"mean_commits":39.5,"dds":0.01898734177215189,"last_synced_commit":"c44c6b0c0f31ea6d1174d5d2ed41064922ea44ca"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Fknuffel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Fknuffel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Fknuffel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tailhook%2Fknuffel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tailhook","download_url":"https://codeload.github.com/tailhook/knuffel/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247601449,"owners_count":20964864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T21:54:57.412Z","updated_at":"2025-04-07T06:12:57.513Z","avatar_url":"https://github.com/tailhook.png","language":"Rust","readme":"A [KDL](https://kdl.dev) file format parser with great error reporting and\nconvenient derive macros.\n\n# About KDL\n\nTo give you some background on the KDL format. Here is a small example:\n```kdl\nfoo 1 key=\"val\" \"three\" {\n    bar\n    (role)baz 1 2\n}\n```\n\nHere is what are annotations for all the datum as described by the\n[specification] and this guide:\n```text\nfoo 1 \"three\" key=\"val\" {                           ╮\n─┬─ ┬ ───┬─── ────┬────                             │\n │  │    │        ╰───── property (can be multiple) │\n │  │    │                                          │\n │  ╰────┴────────────── arguments                  │\n │                                                  │\n └── node name                                      ├─ node \"foo\", with\n                                                    │  \"bar\" and \"baz\"\n    bar                                             │  being children\n    (role)baz 1 2                                   │\n     ──┬─                                           │\n       └────── type name for node named \"baz\"       │\n}                                                   ╯\n```\n(note, the order of properties doesn't matter as well as the order of\nproperties with respect to arguments, so I've moved arguments to have less\nintersections for the arrows)\n\n# Usage\n\nMost common usage of this library is using `derive` and [parse] function:\n```rust\n#[derive(knuffel::Decode)]\nenum TopLevelNode {\n    Route(Route),\n    Plugin(Plugin),\n}\n\n#[derive(knuffel::Decode)]\nstruct Route {\n    #[knuffel(argument)]\n    path: String,\n    #[knuffel(children(name=\"route\"))]\n    subroutes: Vec\u003cRoute\u003e,\n}\n\n#[derive(knuffel::Decode)]\nstruct Plugin {\n    #[knuffel(argument)]\n    name: String,\n    #[knuffel(property)]\n    url: String,\n}\n\n# fn main() -\u003e miette::Result\u003c()\u003e {\nlet config = knuffel::parse::\u003cVec\u003cTopLevelNode\u003e\u003e(\"example.kdl\", r#\"\n    route \"/api\" {\n        route \"/api/v1\"\n    }\n    plugin \"http\" url=\"https://example.org/http\"\n\"#)?;\n# Ok(())\n# }\n```\n\nThis 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:\n```rust,ignore\n#[derive(knuffel::Decode)]\nstruct Document {\n    #[knuffel(child, unwrap(argument))]\n    version: Option\u003cString\u003e,\n    #[knuffel(children(name=\"route\"))]\n    routes: Vec\u003cRoute\u003e,\n    #[knuffel(children(name=\"plugin\"))]\n    plugins: Vec\u003cPlugin\u003e,\n}\n\nlet config = parse::\u003cDocument\u003e(\"example.kdl\", r#\"\n    version \"2.0\"\n    route \"/api\" {\n        route \"/api/v1\"\n    }\n    plugin \"http\" url=\"https://example.org/http\"\n\"#)?;\n```\n\nSee description of [Decode](derive@Decode) and\n[DecodeScalar](derive@DecodeScalar) for the full\nreference on allowed attributes and parse modes.\n\n# Errors\n\nThis crate publishes nice errors, like this:\n\n\u003cimg width=\"50%\" src=\"https://raw.githubusercontent.com/tailhook/knuffel/main/images/error.png\" alt=\"\nScreenshot of error. Here is how narratable printer would print the error:\nError: single char expected after `Alt+`\n    Diagnostic severity: error\n\\\nBegin snippet for test.kdl starting at line 17, column 1\n\\\nsnippet line 17:     }\nsnippet line 18:     key \u0026quot;Alt+\u0026quot; mode=\u0026quot;normal\u0026quot; {\n    label starting at line 18, column 10: invalid value\nsnippet line 19:         move-focus \u0026quot;left\u0026quot;\n\"\u003e\n\nTo make them working, [miette]'s \"fancy\" feature must be enabled in the final\napplication's `Cargo.toml`:\n```toml\n[dependencies]\nmiette = { version=\"4.3.0\", features=[\"fancy\"] }\n```\nAnd the error returned from parser should be converted to [miette::Report] and\nprinted with debugging handler. The most manual way to do that is:\n```rust\n# #[derive(knuffel::Decode, Debug)]\n# struct Config {}\n# let file_name = \"1.kdl\";\n# let text = \"\";\nlet config = match knuffel::parse::\u003cConfig\u003e(file_name, text) {\n    Ok(config) =\u003e config,\n    Err(e) =\u003e {\n         println!(\"{:?}\", miette::Report::new(e));\n         std::process::exit(1);\n    }\n};\n```\nBut usually function that returns `miette::Result` is good enough:\n```rust,no_run\n# use std::fs;\n# #[derive(knuffel::Decode)]\n# struct Config {}\nuse miette::{IntoDiagnostic, Context};\n\nfn parse_config(path: \u0026str) -\u003e miette::Result\u003cConfig\u003e {\n    let text = fs::read_to_string(path).into_diagnostic()\n        .wrap_err_with(|| format!(\"cannot read {:?}\", path))?;\n    Ok(knuffel::parse(path, \u0026text)?)\n}\nfn main() -\u003e miette::Result\u003c()\u003e {\n    let config = parse_config(\"my.kdl\")?;\n    # Ok(())\n}\n```\n\nSee [miette guide] for other ways of configuring error output.\n\n# The Name\n\nKDL is pronounced as cuddle. \"Knuffel\" means the same as cuddle in Dutch.\n\n\nLicense\n=======\n\nLicensed under either of\n\n* Apache License, Version 2.0,\n  (./LICENSE-APACHE or \u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e)\n* MIT license (./LICENSE-MIT or \u003chttp://opensource.org/licenses/MIT\u003e)\n  at your option.\n\nContribution\n------------\n\nUnless you explicitly state otherwise, any contribution intentionally\nsubmitted for inclusion in the work by you, as defined in the Apache-2.0\nlicense, shall be dual licensed as above, without any additional terms or\nconditions.\n\n\n[specification]: https://github.com/kdl-org/kdl/blob/main/SPEC.md\n[miette]: https://docs.rs/miette/\n[miette guide]: https://docs.rs/miette/latest/miette/#-handler-options\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailhook%2Fknuffel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftailhook%2Fknuffel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftailhook%2Fknuffel/lists"}