cooklang 0.18.6

Cooklang parser with opt-in extensions
Documentation
#[derive(Debug)]
pub struct FrontMatterSplit<'i> {
    pub yaml_text: &'i str,
    pub yaml_offset: usize,
    pub cooklang_text: &'i str,
    pub cooklang_offset: usize,
}

const YAML_FENCE: &str = "---";

pub fn parse_frontmatter(input: &str) -> Option<FrontMatterSplit<'_>> {
    let mut fences = fences(input, YAML_FENCE);
    let (_, yaml_start) = fences.next()?;
    let (yaml_end, cooklang_start) = fences.next()?;
    let yaml_text = &input[yaml_start..yaml_end];
    let cooklang_text = &input[cooklang_start..];
    Some(FrontMatterSplit {
        yaml_text,
        yaml_offset: yaml_start,
        cooklang_text,
        cooklang_offset: cooklang_start,
    })
}

fn lines_with_offset(s: &str) -> impl Iterator<Item = (&str, usize)> {
    let mut offset = 0;
    s.split_inclusive('\n').map(move |l| {
        let l_offset = offset;
        offset += l.len();
        (l, l_offset)
    })
}

fn fences<'a>(s: &'a str, fence: &'static str) -> impl Iterator<Item = (usize, usize)> + 'a {
    let lines = lines_with_offset(s);
    lines.filter_map(move |(line, offset)| {
        if line.trim_end() == fence {
            let fence_start = offset;
            let fence_end = offset + line.len();
            return Some((fence_start, fence_end));
        }
        None
    })
}