Skip to main content

core_markdown/mdast/
frontmatter.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::unist::Position;
5use alloc::string::String;
6
7/// Language-agnostic frontmatter with an optional, opaque format hint.
8///
9/// The contents are raw text. An explicit info string identifies the language;
10/// untagged `---` and `+++` delimiters imply YAML and TOML respectively.
11/// The Markdown parser leaves decoding the contents to callers.
12///
13/// Markdown serialization retains the stored info string and body while
14/// normalizing spacing around the info string. Edited content can require a
15/// longer fence, a different marker, or a trailing body line ending to keep
16/// the frontmatter valid.
17#[derive(Clone, Debug, Eq, PartialEq)]
18#[cfg_attr(
19    feature = "serde",
20    derive(serde::Serialize, serde::Deserialize),
21    serde(rename_all = "camelCase")
22)]
23pub struct Frontmatter {
24    /// Raw content between the fence lines, including its trailing line ending.
25    pub value: String,
26    /// The whole info string, with surrounding spaces and tabs removed.
27    /// Escapes and character references remain literal, as in Markly.
28    /// Empty for an implicit language; use `language()` to resolve the default.
29    pub info: String,
30    /// Opening fence marker: a backtick, tilde, dash, or plus.
31    pub fence: char,
32    /// Number of opening fence markers. Serialization may lengthen the fence
33    /// to keep edited content from closing it prematurely.
34    pub fence_length: usize,
35    /// Number of closing fence markers. Closing code fences can be longer
36    /// than their opening fence.
37    pub closing_fence_length: usize,
38    /// Source position, including the fences.
39    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
40    pub position: Option<Position>,
41}
42
43impl Frontmatter {
44    /// Return the explicit language hint, or the delimiter's default language.
45    /// Untagged dash and plus delimiters imply YAML and TOML respectively.
46    /// The parser does not validate or decode the named language.
47    #[must_use]
48    pub fn language(&self) -> Option<&str> {
49        self.info
50            .split_ascii_whitespace()
51            .next()
52            .or(match self.fence {
53                '-' => Some("yaml"),
54                '+' => Some("toml"),
55                _ => None,
56            })
57    }
58}