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}