diff --git a/Makefile.toml b/Makefile.toml index 1b83b43..a849d91 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -9,3 +9,9 @@ workspace=false install_crate="cargo-release" command = "cargo" args = ["release", "--workspace", "${@}"] + +[tasks.readme] +workspace=false +install_crate="cargo-readme" +command = "cargo" +args = ["readme", "-o", "README.md"] diff --git a/README.md b/README.md index 6efa1c8..9e03466 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ +# `kdl` + `kdl` is a "document-oriented" parser and API. That means that, unlike -serde-based implementations, it's meant to preserve formatting when editing, -as well as inserting values with custom formatting. This is useful when -working with human-maintained KDL files. +serde-based implementations, it's meant to preserve formatting when +editing, as well as inserting values with custom formatting. This is +useful when working with human-maintained KDL files. You can think of this crate as [`toml_edit`](https://crates.io/crates/toml_edit), but for KDL. @@ -11,18 +13,58 @@ You can think of this crate as ```rust use kdl::KdlDocument; -let doc: KdlDocument = r#" +let doc_str = r#" hello 1 2 3 + world prop="value" { child 1 child 2 } -"#.parse().expect("failed to parse KDL"); +"#; -assert_eq!(doc.get_args("hello"), vec![&1.into(), &2.into(), &3.into()]); -assert_eq!(doc.get("world").map(|node| &node["prop"]), Some(&"value".into())); +let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL"); + +assert_eq!( + doc.get_args("hello"), + vec![&1.into(), &2.into(), &3.into()] +); + +assert_eq!( + doc.get("world").map(|node| &node["prop"]), + Some(&"value".into()) +); + +// Documents fully roundtrip: +assert_eq!(doc.to_string(), doc_str); ``` -## License +### Controlling Formatting -The code in this repository is covered by [the Apache-2.0 License](LICENSE.md). +By default, everything is created with default formatting. You can parse +items manually to provide custom representations, comments, etc: + +```rust +let node_str = r#" + // indented comment + "formatted" 1 /* comment */ \ + 2; +"#; + +let mut doc = kdl::KdlDocument::new(); +doc.nodes_mut().push(node_str.parse().unwrap()); + +assert_eq!(&doc.to_string(), node_str); +``` + +[`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all +be parsed and managed this way. + +### License + +The code in this repository is covered by [the Apache-2.0 +License](LICENSE.md). + +[`KdlDocument`]: https://docs.rs/kdl/3.0.1-alpha.0/kdl/struct.KdlDocument.html +[`KdlNode`]: https://docs.rs/kdl/3.0.1-alpha.0/kdl/struct.KdlNode.html +[`KdlEntry`]: https://docs.rs/kdl/3.0.1-alpha.0/kdl/struct.KdlEntry.html +[`KdlIdentifier`]: https://docs.rs/kdl/3.0.1-alpha.0/kdl/struct.KdlIdentifier.html diff --git a/README.tpl b/README.tpl new file mode 100644 index 0000000..f71c41b --- /dev/null +++ b/README.tpl @@ -0,0 +1,8 @@ +# `{{crate}}` + +{{readme}} + +[`KdlDocument`]: https://docs.rs/kdl/{{version}}/kdl/struct.KdlDocument.html +[`KdlNode`]: https://docs.rs/kdl/{{version}}/kdl/struct.KdlNode.html +[`KdlEntry`]: https://docs.rs/kdl/{{version}}/kdl/struct.KdlEntry.html +[`KdlIdentifier`]: https://docs.rs/kdl/{{version}}/kdl/struct.KdlIdentifier.html diff --git a/src/document.rs b/src/document.rs index c913a2c..73236ef 100644 --- a/src/document.rs +++ b/src/document.rs @@ -8,6 +8,14 @@ use crate::{parser, KdlError, KdlNode, KdlValue}; /// This type is also used to manage a [`KdlNode`]'s [`Children /// Block`](https://github.com/kdl-org/kdl/blob/main/SPEC.md#children-block), /// when present. +/// +/// # Examples +/// +/// The easiest way to create a `KdlDocument` is to parse it: +/// ```rust +/// # use kdl::KdlDocument; +/// let kdl: KdlDocument = "foo 1 2 3\nbar 4 5 6".parse().expect("parse failed"); +/// ``` #[derive(Debug, Default, Clone, PartialEq)] pub struct KdlDocument { pub(crate) leading: Option, @@ -88,6 +96,9 @@ impl KdlDocument { .collect() } + /// Gets a mutable reference to the first argument (value) of the first + /// child node with a matching name. This is a shorthand utility for cases + /// where a document is being used as a key/value store. pub fn get_arg_mut(&mut self, name: &str) -> Option<&mut KdlValue> { self.get_mut(name) .and_then(|node| node.get_mut(0)) diff --git a/src/entry.rs b/src/entry.rs index 316fa16..ca5c2ca 100644 --- a/src/entry.rs +++ b/src/entry.rs @@ -29,6 +29,7 @@ impl KdlEntry { } } + /// Gets a reference to this entry's name, if it's a property entry. pub fn name(&self) -> Option<&KdlIdentifier> { self.name.as_ref() } diff --git a/src/error.rs b/src/error.rs index 4839d44..b21afec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,12 +15,14 @@ use { #[error("{kind}")] pub struct KdlError { #[source_code] + /// Source string for the KDL document that failed to parse. pub input: String, /// Offset in chars of the error. #[label = "here"] pub offset: usize, + /// Specific error kind for this parser error. pub kind: KdlErrorKind, } @@ -29,18 +31,24 @@ pub struct KdlError { pub enum KdlErrorKind { #[error(transparent)] #[diagnostic(code(kdl::parse_int))] + /// An error occurred while parsing an integer. ParseIntError(ParseIntError), #[error(transparent)] #[diagnostic(code(kdl::parse_float))] + /// An error occurred while parsing a floating point number. ParseFloatError(ParseFloatError), #[error("Expected {0}.")] #[diagnostic(code(kdl::parse_component))] + /// Generic parsing error. The given context string denotes the component + /// that failed to parse. Context(&'static str), #[error("An unspecified error occurred.")] #[diagnostic(code(kdl::other))] + /// Generic unspecified error. If this is returned, the call site should + /// be annotated with context, if possible. Other, } diff --git a/src/lib.rs b/src/lib.rs index 946d534..1d42907 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,32 +1,72 @@ +//! `kdl` is a "document-oriented" parser and API. That means that, unlike +//! serde-based implementations, it's meant to preserve formatting when +//! editing, as well as inserting values with custom formatting. This is +//! useful when working with human-maintained KDL files. +//! +//! You can think of this crate as +//! [`toml_edit`](https://crates.io/crates/toml_edit), but for KDL. +//! +//! ## Example +//! +//! ```rust +//! use kdl::KdlDocument; +//! +//! let doc_str = r#" +//! hello 1 2 3 +//! +//! world prop="value" { +//! child 1 +//! child 2 +//! } +//! "#; +//! +//! let doc: KdlDocument = doc_str.parse().expect("failed to parse KDL"); +//! +//! assert_eq!( +//! doc.get_args("hello"), +//! vec![&1.into(), &2.into(), &3.into()] +//! ); +//! +//! assert_eq!( +//! doc.get("world").map(|node| &node["prop"]), +//! Some(&"value".into()) +//! ); +//! +//! // Documents fully roundtrip: +//! assert_eq!(doc.to_string(), doc_str); +//! ``` +//! +//! ## Controlling Formatting +//! +//! By default, everything is created with default formatting. You can parse +//! items manually to provide custom representations, comments, etc: +//! +//! ```rust +//! let node_str = r#" +//! // indented comment +//! "formatted" 1 /* comment */ \ +//! 2; +//! "#; +//! +//! let mut doc = kdl::KdlDocument::new(); +//! doc.nodes_mut().push(node_str.parse().unwrap()); +//! +//! assert_eq!(&doc.to_string(), node_str); +//! ``` +//! +//! [`KdlDocument`], [`KdlNode`], [`KdlEntry`], and [`KdlIdentifier`] can all +//! be parsed and managed this way. +//! +//! ## License +//! +//! The code in this repository is covered by [the Apache-2.0 +//! License](LICENSE.md). + +#![deny(missing_debug_implementations, nonstandard_style)] +#![warn(missing_docs, unreachable_pub, rust_2018_idioms, unreachable_pub)] +#![cfg_attr(test, deny(warnings))] #![doc(html_logo_url = "https://kdl.dev/logo.svg")] -/// `kdl` is a "document-oriented" parser and API. That means that, unlike -/// serde-based implementations, it's meant to preserve formatting when editing, -/// as well as inserting values with custom formatting. This is useful when -/// working with human-maintained KDL files. -/// -/// You can think of this crate as -/// [`toml_edit`](https://crates.io/crates/toml_edit), but for KDL. -/// -/// ### Example -/// -/// ```rust -/// use kdl::KdlDocument; -/// -/// let doc: KdlDocument = r#" -/// hello 1 2 3 -/// world prop="value" { -/// child 1 -/// child 2 -/// } -/// "#.parse().expect("failed to parse KDL"); -/// -/// assert_eq!(doc.get_args("hello"), vec![&1.into(), &2.into(), &3.into()]); -/// assert_eq!(doc.get("world").map(|node| &node["prop"]), Some(&"value".into())); -/// ``` -/// -/// ## License -/// -/// The code in this repository is covered by [the Apache-2.0 License](LICENSE.md). + pub use document::*; pub use entry::*; pub use error::*; diff --git a/src/node.rs b/src/node.rs index 8a0d4cb..70adcd8 100644 --- a/src/node.rs +++ b/src/node.rs @@ -50,16 +50,17 @@ impl KdlNode { self.name = name.into(); } - /// Gets the node's type, if any. + /// Gets the node's type identifier, if any. pub fn ty(&self) -> Option<&KdlIdentifier> { self.ty.as_ref() } - /// Gets a mutable reference to the node's type. + /// Gets a mutable reference to the node's type identifier. pub fn ty_mut(&mut self) -> &mut Option { &mut self.ty } + /// Sets the node's type identifier. pub fn set_ty(&mut self, ty: impl Into) { self.ty = Some(ty.into()); } @@ -334,7 +335,9 @@ impl KdlNode { /// Represents a [`KdlNode`]'s entry key. #[derive(Debug, Clone, PartialEq)] pub enum NodeKey { + /// Key for a node property entry. Key(KdlIdentifier), + /// Index for a node argument entry (positional value). Index(usize), } diff --git a/src/nom_compat.rs b/src/nom_compat.rs index 784a9d7..6135010 100644 --- a/src/nom_compat.rs +++ b/src/nom_compat.rs @@ -1,7 +1,7 @@ use nom::error::{ErrorKind, ParseError}; use nom::{Err, IResult, Parser}; -pub fn many0(mut f: F) -> impl FnMut(I) -> IResult, E> +pub(crate) fn many0(mut f: F) -> impl FnMut(I) -> IResult, E> where I: Clone + PartialEq, F: Parser, @@ -26,7 +26,7 @@ where } } -pub fn many1(mut f: F) -> impl FnMut(I) -> IResult, E> +pub(crate) fn many1(mut f: F) -> impl FnMut(I) -> IResult, E> where I: Clone + PartialEq, F: Parser, @@ -58,7 +58,7 @@ where } } -pub fn many_till( +pub(crate) fn many_till( mut f: F, mut g: G, ) -> impl FnMut(I) -> IResult, P), E>