///| YAML frontmatter parsing (split from block_parser.mbt).
///|
/// Try to parse frontmatter (YAML between `---` delimiters) at the very start
/// of the document.
fn BlockParser::try_parse_frontmatter(self : BlockParser) -> Frontmatter? {
guard self.len >= 3 else { return None }
guard matches_at(self.source, 0, "---") else { return None }
let mut i = skip_spaces_tabs(self.source, 3)
guard i >= self.len || self.source.unsafe_get(i) == '\n' else { return None }
if i < self.len {
i = i + 1
}
let yaml = StringBuilder()
let mut found_closing = false
let mut end = i
while i < self.len {
let (line, next) = self.read_line(i)
if line.has_prefix("---") &&
is_blank_text(line.unsafe_substring(start=3, end=line.length())) {
found_closing = true
end = next
break
}
yaml.write_string(line)
yaml.write_char('\n')
i = next
}
guard found_closing else { return None }
let raw = yaml.to_string()
let entries = parse_simple_yaml(raw)
// Without any `key: value` line this is a thematic break followed by a
// setext heading, not frontmatter.
guard !entries.is_empty() else { return None }
Some({ raw, entries, span: Span::new(0, end) })
}
///|
/// Parse simple YAML (key: value pairs only)
fn parse_simple_yaml(yaml : String) -> Array[(String, String)] {
let entries : Array[(String, String)] = []
for line in yaml.split("\n") {
let trimmed = line.trim(chars=" \t\r").to_owned()
if trimmed.is_empty() || trimmed.has_prefix("#") {
continue
}
let mut colon = -1
for i = 0; i < trimmed.length(); i = i + 1 {
if trimmed.unsafe_get(i) == ':' {
colon = i
break
}
}
if colon <= 0 {
continue
}
let key = trimmed
.unsafe_substring(start=0, end=colon)
.trim(chars=" \t")
.to_owned()
let mut value = trimmed
.unsafe_substring(start=colon + 1, end=trimmed.length())
.trim(chars=" \t")
.to_owned()
let vlen = value.length()
if vlen >= 2 {
let first = value.unsafe_get(0)
let last = value.unsafe_get(vlen - 1)
if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
value = value.unsafe_substring(start=1, end=vlen - 1)
}
}
entries.push((key, value))
}
entries
}