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.
54 pub key: String,
55 /// The bytes of the value in [`Input::text`], as written. A quoted string
56 /// includes its quotes and a block scalar its header. An anchor or a tag
57 /// before a YAML value is outside it, and an alias is `*name`.
58 pub value: Range<usize>,
59 /// How the value is written.
60 pub written: Written,
61 /// The text of the value, when the parser reads it as a string, and `None`
62 /// for a number, a boolean, a null, a date, a collection or an alias to
63 /// one. A YAML alias to a string, and a scalar under `!!str`, hold the
64 /// string.
65 pub string: Option<String>,
66}
67
68impl Input {
69 /// The entries of the text's top-level mapping or table, in the order
70 /// written.
71 ///
72 /// An entry whose key is not a string, such as a YAML key that is a
73 /// number, a sequence or an alias, is left out, and the entries around it
74 /// keep their own ranges. A text whose root is no mapping, such
75 /// as a sequence or a scalar, has no entries. The text must pass the limits
76 /// in force, as [`Input::parse`] holds it to, and a YAML input builds the
77 /// document that [`Input::place`] keeps.
78 ///
79 /// ```
80 /// # #[cfg(feature = "input-yaml")]
81 /// # {
82 /// use ctl_core::input::Input;
83 ///
84 /// let input = Input::new("page.yaml", "title: A page\nyear: 2026\n");
85 /// let entries = input.top_level()?;
86 /// let title = &entries[0];
87 /// assert_eq!(title.key, "title");
88 /// assert_eq!(&input.text()[title.value.clone()], "A page");
89 /// assert_eq!(title.string.as_deref(), Some("A page"));
90 /// assert_eq!(entries[1].string, None);
91 /// # }
92 /// # Ok::<(), ctl_core::input::InputError>(())
93 /// ```
94 ///
95 /// # Errors
96 ///
97 /// An [`InputError`] with the parser's problem and its line, as
98 /// [`Input::parse`] gives, including a duplicate key, a YAML merge key and
99 /// a text past a limit.
100 pub fn top_level(&self) -> Result<Vec<Entry>, InputError> {
101 match self.format {
102 #[cfg(feature = "input-yaml")]
103 Format::Yaml => yaml_entries::entries(self),
104 #[cfg(feature = "input-toml")]
105 Format::Toml => toml_entries::entries(self),
106 }
107 }
108}