1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//! `---`-fenced YAML frontmatter splitting shared by markdown config files
//! (SKILL.md, model prompts). Callers own YAML parsing and body policy.
/// Why a document has no complete frontmatter block.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum FrontmatterError {
/// The first line is not a `---` fence.
MissingOpening,
/// No `---` fence closes the block.
MissingClosing,
}
/// A document split at its frontmatter fences.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Split<'a> {
/// YAML between the fences, including the last YAML line's terminator.
pub(crate) yaml: &'a str,
/// Everything after the closing fence line, unmodified.
pub(crate) body: &'a str,
}
/// Splits `source` when its first line is a `---` fence. Fence lines accept
/// LF or CRLF endings; slices always fall on line boundaries.
pub(crate) fn split(source: &str) -> Result<Split<'_>, FrontmatterError> {
let is_fence = |line: &str| line.trim_end_matches(['\n', '\r']) == "---";
let mut lines = source.split_inclusive('\n');
let opening = lines
.next()
.filter(|line| is_fence(line))
.ok_or(FrontmatterError::MissingOpening)?;
let yaml_start = opening.len();
let mut offset = yaml_start;
for line in lines {
if is_fence(line) {
return Ok(Split {
yaml: &source[yaml_start..offset],
body: &source[offset + line.len()..],
});
}
offset += line.len();
}
Err(FrontmatterError::MissingClosing)
}
#[cfg(test)]
#[path = "frontmatter_tests.rs"]
mod tests;