rssg 1.6.0

A rusty static-site generator leveraging shell commands.
use std::error::Error;
use std::fmt::Display;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::str::FromStr;

use serde_yaml::Value;

use crate::filepath::FilePath;

const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");

#[derive(Debug)]
pub enum TemplateErr {
    InvalidPath(String),

    TemplateNotFound(String),
    FileNotFound(String),

    FailedToReadTemplate(std::io::Error),
    FailedToReadFile(std::io::Error),

    FailedToWrite(std::io::Error),
}

impl Display for TemplateErr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidPath(file) => write!(f, "Invalid file path {file}"),

            Self::TemplateNotFound(file) => write!(f, "Failed to find template file {file}"),
            Self::FileNotFound(file) => write!(f, "Failed to find input file {file}"),

            Self::FailedToReadTemplate(e) => write!(f, "Failed to read template file: {e}"),
            Self::FailedToReadFile(e) => write!(f, "Failed to read input file: {e}"),

            Self::FailedToWrite(e) => write!(f, "Failed to write output file: {e}"),
        }
    }
}

impl Error for TemplateErr {}

pub fn apply_template<T, F, O>(
    template: T,
    file: F,
    out: O,
    yaml: &Value,
) -> Result<(), TemplateErr>
where
    T: AsRef<Path>,
    F: AsRef<Path>,
    O: AsRef<Path>,
{
    let t = FilePath::from_str(&template.as_ref().display().to_string())
        .map_err(|_| TemplateErr::InvalidPath(template.as_ref().display().to_string()))?
        .prefix("templates")
        .to_string();

    let template = Path::new(&t);

    if !template.exists() {
        return Err(TemplateErr::TemplateNotFound(
            template.display().to_string(),
        ));
    }

    if !file.as_ref().exists() {
        return Err(TemplateErr::FileNotFound(
            file.as_ref().display().to_string(),
        ));
    }

    let mut template_data = match File::open(template) {
        Ok(mut f) => {
            let mut buf = String::new();

            if let Err(e) = f.read_to_string(&mut buf) {
                return Err(TemplateErr::FailedToReadTemplate(e));
            }

            buf
        }

        Err(e) => return Err(TemplateErr::FailedToReadTemplate(e)),
    };

    let data = match File::open(&file) {
        Ok(mut f) => {
            let mut buf = String::new();

            if let Err(e) = f.read_to_string(&mut buf) {
                return Err(TemplateErr::FailedToReadFile(e));
            }

            buf
        }

        Err(e) => return Err(TemplateErr::FailedToReadFile(e)),
    };

    template_data = template_data
        .replace("{{data}}", data.as_str())
        .replace("{{version}}", VERSION.unwrap_or("unknown"));

    let mut last = 0;
    while let Some(mut start) = template_data[(last as usize)..].find("{{data.") {
        start += last as usize;
        let temp = &template_data[start..];
        let end = match temp.find("}}") {
            Some(i) => i + start,
            None => continue,
        };

        let ident = &template_data[start + 7..end];
        let length = if let Some(val) = yaml.get(ident) {
            let to_rep = format!("{{{{data.{ident}}}}}");
            let rep_with = match val {
                Value::String(s) => s.clone(),
                Value::Bool(v) => format!("{v}"),
                Value::Number(v) => format!("{v}"),
                Value::Null => "null".to_string(),
                _ => format!("{val:?}"),
            };

            template_data = template_data.replace(&to_rep, &rep_with);

            rep_with.len() as i64 - to_rep.len() as i64
        } else {
            0
        };

        last = end as i64 + length;
    }

    template_data = template_data.replace("{\\{", "{{");

    match OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(&out)
    {
        Ok(mut f) => {
            if let Err(e) = f.write_all(template_data.as_bytes()) {
                return Err(TemplateErr::FailedToWrite(e));
            }

            Ok(())
        }

        Err(e) => Err(TemplateErr::FailedToWrite(e)),
    }
}