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
}