Custom Parsers

Sherwood parses content through a small trait, so you can teach it formats beyond the built-in Markdown and plain text. A parser turns one file’s raw source into a Parsed payload; it never deals with paths or URLs.

use std::path::Path;
use sherwood::{ContentParser, FrontMatter, Parsed, ParserError, Pod};

struct ShoutParser;

impl ContentParser for ShoutParser {
    fn extensions(&self) -> &[&str] {
        &["shout"]
    }

    fn parse(&self, source: &str, _path: &Path) -> Result<Parsed, ParserError> {
        let mut lines = source.lines();
        let title = lines.next().unwrap_or("").to_string();
        let body = lines.collect::<Vec<_>>().join(" ").to_uppercase();
        Ok(Parsed {
            frontmatter: FrontMatter { title, data: Pod::Null },
            content_html: format!("<p>{body}</p>"),
            excerpt_html: None,
        })
    }
}

Register it on a ParserRegistry and pass that to the build:

use std::sync::Arc;
use sherwood::ParserRegistry;

let mut registry = ParserRegistry::default(); // markdown + text built in
registry.register(Arc::new(ShoutParser));

Now any .shout file becomes a page. Files whose extension no parser claims are skipped, so assets can sit in the content tree untouched.

Reusing frontmatter

If your format uses the same --- / +++ frontmatter convention as Markdown, call the public split_frontmatter helper instead of reinventing it:

let (frontmatter, body) = sherwood::split_frontmatter(source)?;

Formats with their own metadata convention — like the built-in text parser, which takes the title from the first line — simply ignore it.