razorbill 0.1.0

A toolkit for bespoke static site generators.
Documentation
use std::path::{Path, PathBuf};
use std::{fmt, fs};

use serde::Deserialize;
use thiserror::Error;

use crate::content::{parse_front_matter, FileInfo};

#[derive(Debug)]
pub struct Page {
    pub meta: PageFrontMatter,
    pub file: FileInfo,
    pub path: PagePath,
    pub slug: String,
    pub raw_content: String,
}

#[derive(Debug)]
pub struct PagePath(pub(crate) String);

impl fmt::Display for PagePath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl PagePath {
    pub fn from_file_path(
        root_path: impl AsRef<Path>,
        file_path: impl AsRef<Path>,
    ) -> Result<Self, ()> {
        let file_path = file_path.as_ref().strip_prefix(root_path).unwrap();

        let parent = file_path.parent().unwrap().to_str().unwrap();
        let slug = file_path.file_stem().unwrap().to_str().unwrap();

        if parent.is_empty() {
            Ok(Self(format!("/{slug}")))
        } else {
            Ok(Self(format!("/{parent}/{slug}")))
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct PageFrontMatter {
    pub title: Option<String>,
    pub slug: Option<String>,
    pub template: Option<String>,
}

#[derive(Error, Debug)]
pub enum ParsePageError {
    #[error("failed to read page: {0}")]
    Io(#[from] std::io::Error),

    #[error("invalid front matter in '{filepath}'")]
    InvalidFrontMatter { filepath: PathBuf },
}

impl Page {
    pub fn from_path(
        root_path: impl AsRef<Path>,
        path: impl AsRef<Path>,
    ) -> Result<Self, ParsePageError> {
        let path = path.as_ref();
        let contents = fs::read_to_string(path)?;

        Self::parse(&contents, root_path, path)
    }

    pub fn parse(
        text: &str,
        root_path: impl AsRef<Path>,
        filepath: &Path,
    ) -> Result<Self, ParsePageError> {
        let (front_matter, content) =
            parse_front_matter::<PageFrontMatter>(text).ok_or_else(|| {
                ParsePageError::InvalidFrontMatter {
                    filepath: filepath.to_owned(),
                }
            })?;

        let file = FileInfo {
            path: filepath.to_owned(),
            parent: filepath.parent().unwrap_or(root_path.as_ref()).to_owned(),
        };

        let slug = front_matter
            .slug
            .clone()
            .unwrap_or_else(|| filepath.file_stem().unwrap().to_string_lossy().to_string());

        let path = PagePath::from_file_path(root_path, &file.path).unwrap();

        Ok(Self {
            meta: front_matter,
            file,
            path,
            slug,
            raw_content: content.to_string(),
        })
    }
}