markdownlint-rs 0.2.2

A fast, flexible, configuration-based command-line interface for linting Markdown/CommonMark files
Documentation
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrontMatterType {
    Yaml,
    Toml,
}

const YAML_FRONT_MATTER: &str = "---";
const TOML_FRONT_MATTER: &str = "+++";

#[derive(Debug, Clone)]
pub struct FrontMatter {
    pub matter_type: FrontMatterType,
    pub content: String,
    pub end_line: usize,
}

pub fn detect_front_matter(content: &str) -> Option<FrontMatter> {
    let lines: Vec<&str> = content.lines().collect();
    if lines.is_empty() {
        return None;
    }

    detect_filetype_front_matter(&lines, FrontMatterType::Yaml)
        .or_else(|| detect_filetype_front_matter(&lines, FrontMatterType::Toml))
}

fn detect_filetype_front_matter(
    lines: &[&str],
    matter_type: FrontMatterType,
) -> Option<FrontMatter> {
    if lines.is_empty() {
        return None;
    }

    let front_matter = match matter_type {
        FrontMatterType::Yaml => YAML_FRONT_MATTER,
        FrontMatterType::Toml => TOML_FRONT_MATTER,
    };
    if lines[0] != front_matter {
        return None;
    }

    for (i, line) in lines.iter().enumerate().skip(1) {
        if *line != front_matter {
            continue;
        }
        let content = lines[1..i].join("\n");
        return Some(FrontMatter {
            matter_type,
            content,
            end_line: i + 1,
        });
    }

    None
}

#[allow(dead_code)]
pub fn strip_front_matter(content: &str) -> String {
    if let Some(front_matter) = detect_front_matter(content) {
        let lines: Vec<&str> = content.lines().collect();
        lines[front_matter.end_line..].join("\n")
    } else {
        content.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_detect_yaml_front_matter() {
        let content = "---\ntitle: Test\nauthor: John\n---\n# Heading";
        let fm = detect_front_matter(content).unwrap();

        assert_eq!(fm.matter_type, FrontMatterType::Yaml);
        assert_eq!(fm.content, "title: Test\nauthor: John");
        assert_eq!(fm.end_line, 4);
    }

    #[test]
    fn test_detect_toml_front_matter() {
        let content = "+++\ntitle = \"Test\"\nauthor = \"John\"\n+++\n# Heading";
        let fm = detect_front_matter(content).unwrap();

        assert_eq!(fm.matter_type, FrontMatterType::Toml);
        assert_eq!(fm.content, "title = \"Test\"\nauthor = \"John\"");
        assert_eq!(fm.end_line, 4);
    }

    #[test]
    fn test_no_front_matter() {
        let content = "# Heading\nSome content";
        assert!(detect_front_matter(content).is_none());
    }

    #[test]
    fn test_incomplete_front_matter() {
        let content = "---\ntitle: Test\n# Heading";
        assert!(detect_front_matter(content).is_none());
    }

    #[test]
    fn test_strip_front_matter() {
        let content = "---\ntitle: Test\n---\n# Heading\nContent";
        let stripped = strip_front_matter(content);

        assert_eq!(stripped, "# Heading\nContent");
    }

    #[test]
    fn test_strip_no_front_matter() {
        let content = "# Heading\nContent";
        let stripped = strip_front_matter(content);

        assert_eq!(stripped, content);
    }
}