minimal_yaml/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2mod bytes;
3mod errors;
4mod parse;
5mod tests;
6
7pub use crate::errors::YamlParseError;
8
9pub(crate) type Result<T> = std::result::Result<T, YamlParseError>;
10
11use parse::Parser;
12
13use std::{fmt, fmt::Display};
14#[cfg_attr(test, derive(serde::Deserialize, serde::Serialize))]
15#[derive(Clone, Debug, PartialEq, Eq)]
16/// A Yaml Element
17pub enum Yaml<'a> {
18    /// A literal value, losslessly interpreted as a string
19    Scalar(&'a str),
20
21    /// A sequence of values in flow style
22    /// `[x, y, z]`
23    /// or in block style
24    /// ```yaml
25    ///     - x
26    ///     - y
27    ///     - z
28    /// ```
29    Sequence(Vec<Yaml<'a>>),
30
31    /// A mapping from key to value in flow style
32    /// `{x: X, y: Y, z: Z}`
33    /// or in block style
34    /// ```yaml
35    ///     x: X
36    ///     y: Y
37    ///     z: Z
38    /// ```
39    Mapping(Vec<Entry<'a>>),
40}
41#[derive(Debug, Clone, Copy, PartialEq)]
42enum PrintStyle {
43    Block,
44    #[allow(unused)]
45    Flow,
46}
47
48fn print_indent(indent: usize, f: &mut fmt::Formatter) -> fmt::Result {
49    write!(f, "{:indent$}", "", indent = indent)
50}
51
52fn print_yaml(
53    node: &Yaml<'_>,
54    indent: usize,
55    f: &mut fmt::Formatter,
56    style: PrintStyle,
57) -> fmt::Result {
58    const INDENT_AMT: usize = 2;
59    match node {
60        Yaml::Scalar(slice) => write!(f, "{}", slice),
61        Yaml::Sequence(seq) => {
62            match style {
63                PrintStyle::Block => {
64                    for el in seq.iter() {
65                        print_indent(indent, f)?;
66                        write!(f, "-")?;
67                        match el {
68                            Yaml::Scalar(slice) => writeln!(f, " {scal}", scal = slice)?,
69                            Yaml::Sequence(..) | Yaml::Mapping(..) => {
70                                #[allow(clippy::write_with_newline)]
71                                write!(f, "\n")?;
72                                print_yaml(el, indent + INDENT_AMT, f, style)?;
73                            }
74                        }
75                    }
76                }
77                PrintStyle::Flow => {
78                    write!(f, "[ ")?;
79                    let last_idx = seq.len() - 1;
80                    for (idx, elem) in seq.iter().enumerate() {
81                        if idx == last_idx {
82                            write!(f, "{}", elem)?;
83                        } else {
84                            write!(f, "{}, ", elem)?;
85                        }
86                    }
87                    write!(f, " ]")?;
88                }
89            }
90            Ok(())
91        }
92        Yaml::Mapping(map) => {
93            match style {
94                PrintStyle::Block => {
95                    for entry in map.iter() {
96                        match &entry.key {
97                            Yaml::Scalar(..) => {
98                                print_indent(indent, f)?;
99                                print_yaml(&entry.key, indent, f, PrintStyle::Block)?;
100                                write!(f, " ")?;
101                            }
102                            Yaml::Sequence(..) | Yaml::Mapping(..) => {
103                                print_yaml(&entry.key, indent + INDENT_AMT, f, PrintStyle::Block)?;
104                                print_indent(indent, f)?;
105                            }
106                        }
107                        write!(f, ":")?;
108                        match &entry.value {
109                            Yaml::Scalar(..) => {
110                                write!(f, " ")?;
111                                print_yaml(&entry.value, indent, f, PrintStyle::Block)?;
112                                #[allow(clippy::write_with_newline)]
113                                write!(f, "\n")?;
114                            }
115                            Yaml::Sequence(..) | Yaml::Mapping(..) => {
116                                #[allow(clippy::write_with_newline)]
117                                write!(f, "\n")?;
118                                print_yaml(&entry.value, indent + INDENT_AMT, f, PrintStyle::Block)?
119                            }
120                        }
121                    }
122                }
123                PrintStyle::Flow => {
124                    write!(f, "{{")?;
125                    let last_idx = map.len() - 1;
126                    for (idx, entry) in map.iter().enumerate() {
127                        if idx == last_idx {
128                            write!(f, "{}", entry)?;
129                        } else {
130                            write!(f, "{}, ", entry)?;
131                        }
132                    }
133                    write!(f, "}}")?;
134                }
135            }
136            Ok(())
137        }
138    }
139}
140
141impl Display for Yaml<'_> {
142    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143        print_yaml(&self, 0, f, PrintStyle::Block)
144    }
145}
146#[cfg_attr(test, derive(serde::Deserialize, serde::Serialize))]
147#[derive(Clone, Debug, PartialEq, Eq)]
148/// A Yaml map entry
149pub struct Entry<'a> {
150    /// The key associated with the entry
151    #[cfg_attr(test, serde(borrow))]
152    pub key: Yaml<'a>,
153    /// The value which the key maps to
154    #[cfg_attr(test, serde(borrow))]
155    pub value: Yaml<'a>,
156}
157
158impl<'a> Entry<'a> {
159    #[allow(clippy::must_use_candidate)]
160    pub fn new(key: Yaml<'a>, value: Yaml<'a>) -> Self {
161        Self { key, value }
162    }
163}
164
165impl<'a> Display for Entry<'a> {
166    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167        // match self.key
168        write!(f, "{} : {}", self.key, self.value)
169    }
170}
171
172/// Parse Yaml input. Returns the top level Yaml element on success
173/// # Errors
174/// Returns `Err` if the input is invalid Yaml, with a message indicating
175/// where the error occurred and possibly more information on the cause
176pub fn parse(input: &str) -> Result<Yaml<'_>> {
177    let mut parser = Parser::new(input)?;
178    parser.parse()
179}