1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
#![doc(html_logo_url = "https://kdl.dev/logo.svg")]
//! KDL is a document language with xml-like semantics that looks like you're invoking a bunch of
//! CLI commands! It's meant to be used both as a serialization format and a configuration language,
//! much like JSON, YAML, or XML.
//!
//! There's a [living specification], as well as [various implementations]. You can also check out the
//! [language FAQ] to answer all your burning questions!
//!
//! This crate is the official/reference implementation of the KDL document language.
//!
//! [living specification]: https://github.com/kdl-org/kdl/blob/main/SPEC.md
//! [various implementations]: https://kdl.dev/#implementations
//! [language FAQ]: https://kdl.dev/#faq
//!
//! ## License
//!
//! The code in this crate is covered by the [Parity License], a strong copyleft license. That
//! means that you can only use this project if you're working on an open source-licensed product
//! (MIT/Apache projects are ok!)
//!
//! [Parity License]: https://github.com/kdl-org/kdl-rs/blob/main/LICENSE.md
//!
//! ## Example KDL File
//!
//! ```text
//! author "Alex Monad" email="alex@example.com" active=true
//!
//! contents {
//! section "First section" {
//! paragraph "This is the first paragraph"
//! paragraph "This is the second paragraph"
//! }
//! }
//!
//! // unicode! comments!
//! π 3.14159
//! ```
//!
//! ## Basic Library Example
//!
//! ```
//! use kdl::{KdlNode, KdlValue};
//! use std::collections::HashMap;
//!
//! assert_eq!(
//! kdl::parse_document("node 1 key=true").unwrap(),
//! vec![
//! KdlNode {
//! name: String::from("node"),
//! values: vec![KdlValue::Int(1)],
//! properties: {
//! let mut temp = HashMap::new();
//! temp.insert(String::from("key"), KdlValue::Boolean(true));
//! temp
//! },
//! children: vec![],
//! }
//! ]
//! )
//! ```
use nom::combinator::all_consuming;
use nom::Finish;
pub use crate::error::{KdlError, KdlErrorKind, TryFromKdlNodeValueError};
pub use crate::node::{KdlNode, KdlValue};
mod error;
mod node;
mod nom_compat;
mod parser;
/// Parse a KDL document from a string into a list of [`KdlNode`]s.
///
/// ```
/// use kdl::{KdlNode, KdlValue};
/// use std::collections::HashMap;
///
/// assert_eq!(
/// kdl::parse_document("node 1 key=true").unwrap(),
/// vec![
/// KdlNode {
/// name: String::from("node"),
/// values: vec![KdlValue::Int(1)],
/// properties: {
/// let mut temp = HashMap::new();
/// temp.insert(String::from("key"), KdlValue::Boolean(true));
/// temp
/// },
/// children: vec![],
/// }
/// ]
/// )
/// ```
pub fn parse_document<I>(input: I) -> Result<Vec<KdlNode>, KdlError>
where
I: AsRef<str>,
{
let input = input.as_ref();
all_consuming(parser::nodes)(input)
.finish()
.map(|(_, arg)| arg)
.map_err(|e| {
let prefix = &input[..(input.len() - e.input.len())];
let (line, column) = calculate_line_column(prefix);
KdlError {
input: input.into(),
offset: prefix.chars().count(),
line,
column,
kind: if let Some(kind) = e.kind {
kind
} else if let Some(ctx) = e.context {
KdlErrorKind::Context(ctx)
} else {
KdlErrorKind::Other
},
}
})
}
/// Calculates the line and column of the end of a `&str`.
///
/// If the line ends on a newline, the (line, column) pair is placed on the previous line instead.
fn calculate_line_column(input: &str) -> (usize, usize) {
let (input, skipped_lines) = parser::count_leading_lines(input);
let input = parser::strip_trailing_newline(input);
(skipped_lines + 1, input.len() + 1) // +1 as we're 1-based
}