Skip to main content

ephemeris_documents/yaml/
source.rs

1//! An editable YAML tree that prints back the source it was read from.
2
3mod leaf;
4mod mapping;
5mod opaque;
6mod read;
7mod text;
8
9use std::fmt;
10
11use yaml_rt::YamlDoc;
12
13use self::leaf::Leaf;
14pub(crate) use self::mapping::{Entry, Mapping};
15pub(crate) use self::opaque::Opaque;
16use self::read::Reader;
17use self::text::Text;
18use crate::backend::Editable;
19use crate::error::{DocumentError, YamlError};
20use crate::header::Applied;
21use crate::path::{KeyPath, Segment};
22
23/// A parsed YAML document.
24///
25/// Block mappings are split into entries. A flow mapping with entries is kept
26/// as raw text until an edit enters it, and then it prints in block style.
27/// Everything else is kept as raw text. Printing the tree gives the source
28/// back, with edits applied.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub(crate) struct Tree {
31    header: Text,
32    root: Entry,
33    footer: Text,
34}
35
36impl Tree {
37    /// Read a YAML document with a single document in it.
38    ///
39    /// # Errors
40    /// Returns an error when the text is not valid YAML or holds more than
41    /// one document.
42    pub(crate) fn parse(text: &str) -> Result<Self, DocumentError> {
43        let document = YamlDoc::parse(text).map_err(DocumentError::yaml_parse)?;
44        match document.document_count() {
45            0 | 1 => Reader::new(text, &document).tree(),
46            count => Err(YamlError::MultipleDocuments { count }.into()),
47        }
48    }
49
50    pub(super) fn new(header: Text, root: Value, footer: Text) -> Self {
51        Self {
52            header,
53            root: Entry::root(root),
54            footer,
55        }
56    }
57
58    /// Whether the node at `path` is a mapping the tree cannot edit.
59    pub(crate) fn is_opaque_at(&self, path: &KeyPath) -> bool {
60        let mut entry = &self.root;
61        for segment in path.segments() {
62            let child = match segment {
63                Segment::Key(key) => entry.value().mapping().and_then(|mapping| mapping.get(key)),
64                Segment::Index(_) => None,
65            };
66            let Some(child) = child else {
67                return false;
68            };
69            entry = child;
70        }
71        entry.value().is_unsupported()
72    }
73
74    /// Print an empty root mapping as `{}`. Call after editing.
75    pub(crate) fn collapse_empty_root(&mut self) {
76        if let Some(mapping) = self.root.value_mut().mapping_mut()
77            && mapping.keys().next().is_none()
78        {
79            mapping.collapse();
80        }
81    }
82
83    /// The root mapping, when the document has one the tree can edit.
84    pub(super) fn into_mapping(self) -> Option<Mapping> {
85        match self.root.into_value() {
86            Value::Mapping(mapping) => Some(mapping),
87            Value::Leaf(_) | Value::Opaque(_) => None,
88        }
89    }
90}
91
92impl Editable for Tree {
93    type Node = Entry;
94
95    fn header_text(&self) -> &str {
96        self.header.as_str()
97    }
98
99    fn leading(&self) -> (&str, &str) {
100        ("", self.header.as_str())
101    }
102
103    fn set_leading(&mut self, applied: Applied<'_>) {
104        self.header = Text::from(applied.into_text());
105    }
106
107    fn root(&self) -> &Entry {
108        &self.root
109    }
110
111    fn root_mut(&mut self) -> &mut Entry {
112        &mut self.root
113    }
114}
115
116impl fmt::Display for Tree {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(f, "{}{}{}", self.header, self.root, self.footer)
119    }
120}
121
122/// A value in the tree.
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub(crate) enum Value {
125    /// A block mapping.
126    Mapping(Mapping),
127    /// The raw text of any other value, from the end of the owning key
128    /// through the end of its last line.
129    Leaf(Leaf),
130    /// The raw text of a flow mapping with entries, or of a mapping the tree
131    /// cannot edit, such as one with explicit keys or mixed indentation. It
132    /// spans the same text as a leaf.
133    Opaque(Opaque),
134}
135
136impl Value {
137    /// The mapping, when this is one. A flow mapping gives its block
138    /// rewrite.
139    pub(crate) fn mapping(&self) -> Option<&Mapping> {
140        match self {
141            Self::Mapping(mapping) => Some(mapping),
142            Self::Opaque(opaque) => opaque.mapping(),
143            Self::Leaf(_) => None,
144        }
145    }
146
147    /// The mapping, mutably, when this is one.
148    pub(crate) fn mapping_mut(&mut self) -> Option<&mut Mapping> {
149        match self {
150            Self::Mapping(mapping) => Some(mapping),
151            Self::Opaque(opaque) => opaque.mapping_mut(),
152            Self::Leaf(_) => None,
153        }
154    }
155
156    /// Whether this is a mapping the tree cannot edit.
157    pub(crate) const fn is_unsupported(&self) -> bool {
158        matches!(self, Self::Opaque(opaque) if opaque.is_unsupported())
159    }
160
161    /// The comment on the key line, with the whitespace before it.
162    fn comment(&self) -> Option<&str> {
163        match self {
164            Self::Mapping(mapping) => mapping.comment(),
165            Self::Leaf(leaf) => leaf.comment(),
166            Self::Opaque(opaque) => opaque.comment(),
167        }
168    }
169
170    /// Put `comment` on the key line unless it has one.
171    fn keep_comment(&mut self, comment: &str) {
172        match self {
173            Self::Mapping(mapping) => mapping.keep_comment(comment),
174            Self::Leaf(leaf) => leaf.keep_comment(comment),
175            Self::Opaque(_) => {}
176        }
177    }
178
179    fn terminate(&mut self) {
180        match self {
181            Self::Mapping(mapping) => mapping.terminate(),
182            Self::Leaf(leaf) => leaf.terminate(),
183            Self::Opaque(opaque) => opaque.terminate(),
184        }
185    }
186}
187
188impl fmt::Display for Value {
189    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190        match self {
191            Self::Mapping(mapping) => mapping.fmt(f),
192            Self::Leaf(leaf) => leaf.fmt(f),
193            Self::Opaque(opaque) => opaque.fmt(f),
194        }
195    }
196}
197
198#[cfg(test)]
199mod tests;