ctl_core/input/entries.rs
1//! The top-level entries of a YAML or TOML text, each with the byte range of
2//! its value, how the value is written and, for a string, its text.
3//!
4//! A caller that reads a mapping it did not write, such as the front matter of
5//! a Markdown file, learns where each value sits and what it reads as, with no
6//! scanner of its own. The format crates decide the types: serde-saphyr for
7//! YAML, which resolves anchors, aliases and tags, and the `toml` crate for
8//! TOML. The spans come from yamled and from the `toml` tree.
9
10use std::ops::Range;
11
12#[cfg(feature = "input-toml")]
13use super::toml_entries;
14#[cfg(feature = "input-yaml")]
15use super::yaml_entries;
16use super::{Format, Input, InputError};
17
18/// How the value of an [`Entry`] is written.
19///
20/// Non-exhaustive: a later release can name another way to write a value.
21/// TOML writes a string four ways, which map onto the first two quoted forms
22/// and the two multi-line ones, and writes a number, a boolean or a date as
23/// [`Written::Plain`].
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum Written {
27 /// A YAML plain scalar, or a TOML number, boolean or date.
28 Plain,
29 /// A single-quoted YAML scalar, or a TOML literal string `'…'`.
30 SingleQuoted,
31 /// A double-quoted YAML scalar, or a TOML basic string `"…"`.
32 DoubleQuoted,
33 /// A YAML literal block scalar, `|`.
34 Literal,
35 /// A YAML folded block scalar, `>`.
36 Folded,
37 /// A TOML multi-line basic string, `"""…"""`.
38 MultilineBasic,
39 /// A TOML multi-line literal string, `'''…'''`.
40 MultilineLiteral,
41 /// A YAML alias, `*name`.
42 Alias,
43 /// A YAML sequence or mapping, or a TOML array or table.
44 Collection,
45}
46
47/// One entry of the top-level mapping or table of a text.
48///
49/// Non-exhaustive: a later release can add a field.
50#[derive(Clone, Debug, PartialEq, Eq)]
51#[non_exhaustive]
52pub struct Entry {
53 /// The key, as the parser read it: quotes and escapes resolved. A YAML key
54 /// that is a number, a boolean or a null is its text as written.
55 pub key: String,
56 /// The bytes of the value in [`Input::text`], as written. A quoted string
57 /// includes its quotes and a block scalar its header. An anchor or a tag
58 /// before a YAML value is outside it, and an alias is `*name`.
59 pub value: Range<usize>,
60 /// How the value is written.
61 pub written: Written,
62 /// The text of the value, when the parser reads it as a string, and `None`
63 /// for a number, a boolean, a null, a date, a collection or an alias to
64 /// one. A YAML alias to a string, and a scalar under `!!str`, hold the
65 /// string.
66 pub string: Option<String>,
67}
68
69impl Input {
70 /// The entries of the text's top-level mapping or table, in the order
71 /// written.
72 ///
73 /// An entry whose key is a YAML sequence, mapping or alias is left out, and
74 /// the entries around it keep their own ranges. A text whose root is no
75 /// mapping, such as a sequence or a scalar, has no entries.
76 ///
77 /// The listing refuses nothing that YAML allows, so a key written twice,
78 /// a merge key (`<<`) and a `.inf` or `.NaN` are entries like the rest,
79 /// where [`Input::parse`] refuses them. The text must pass the limits in
80 /// force, as [`Input::parse`] holds it to, and a YAML input builds the
81 /// document that [`Input::place`] keeps.
82 ///
83 /// ```
84 /// # #[cfg(feature = "input-yaml")]
85 /// # {
86 /// use ctl_core::input::Input;
87 ///
88 /// let input = Input::new("page.yaml", "title: A page\nyear: 2026\n");
89 /// let entries = input.top_level()?;
90 /// let title = &entries[0];
91 /// assert_eq!(title.key, "title");
92 /// assert_eq!(&input.text()[title.value.clone()], "A page");
93 /// assert_eq!(title.string.as_deref(), Some("A page"));
94 /// assert_eq!(entries[1].string, None);
95 /// # }
96 /// # Ok::<(), ctl_core::input::InputError>(())
97 /// ```
98 ///
99 /// # Errors
100 ///
101 /// An [`InputError`] with the parser's problem and its line, as
102 /// [`Input::parse`] gives, for a text that is no YAML or TOML or goes past
103 /// a limit.
104 pub fn top_level(&self) -> Result<Vec<Entry>, InputError> {
105 match self.format {
106 #[cfg(feature = "input-yaml")]
107 Format::Yaml => yaml_entries::entries(self),
108 #[cfg(feature = "input-toml")]
109 Format::Toml => toml_entries::entries(self),
110 }
111 }
112}