Custom Parsers

Sherwood parses content through a small trait, so you can teach it formats beyond the built-in Markdown. 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 built in
registry.register(Arc::new(ShoutParser));

Now any .shout file becomes a page. Files whose extension no parser claims are copied verbatim to the mirrored output path, so assets (images, downloads) can sit in the content tree next to the pages that use them.

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 ShoutParser above, which takes the title from the first line — simply ignore it.