rssg 1.4.0

A rusty static-site generator leveraging shell commands.
use std::fs::{copy, create_dir_all, read_to_string, OpenOptions};
use std::io::Write;
use std::str::FromStr;

use log::{error, debug};
use regex::Regex;
use yaml_front_matter::YamlFrontMatter;

use crate::filepath::FilePath;
use crate::filter::{substitute, Filter};
use crate::tempfile::tempdir;
use crate::template::apply_template;

#[derive(Debug, Clone)]
pub struct Rule {
    rule: Regex,
    filters: Vec<Filter>,
    templates: Vec<String>,
    output: String,
}

impl Rule {
    pub fn new(rule: &str, output: String) -> Result<Self, regex::Error> {
        Ok(Self {
            rule: Regex::new(rule)?,
            filters: Vec::new(),
            templates: Vec::new(),
            output,
        })
    }

    pub fn filter_all(&mut self, mut filters: Vec<Filter>) {
        self.filters.append(&mut filters);
    }

    pub fn template_all(&mut self, mut templates: Vec<String>) {
        self.templates.append(&mut templates);
    }

    pub fn matches(&self, filepath: &FilePath) -> bool {
        self.rule.is_match(&filepath.full())
    }

    pub fn out(&self, path: &FilePath) -> Result<FilePath, &str> {
        FilePath::from_str(&substitute(&self.output, path))
    }

    pub fn exec(&self, path: FilePath, content: &String, output: &String) -> bool {
        let data = match read_to_string(&path.full()) {
            Ok(d) => d,
            Err(e) => {
                error!("Failed to open file {}: {}", path, e);
                return false;
            }
        };

        let (yaml, data) = match YamlFrontMatter::parse::<serde_yaml::Value>(&data) {
            Ok(yaml) => (yaml.metadata, yaml.content),
            Err(_) => (serde_yaml::Value::Null, data),
        };

        let mut cwpath = tempdir(&format!("{path}-yamlless"), &path);

        if let Err(e) = create_dir_all(cwpath.dir()) {
            error!(
                "Failed to create parent directories: {}",
                e
            );
            return false;
        }

        match OpenOptions::new()
            .create(true)
            .write(true)
            .truncate(true)
            .open(&cwpath.full())
        {
            Ok(mut f) => {
                if let Err(e) = f.write_all(data.as_bytes()) {
                    error!("Failed to write to file {}: {}", cwpath, e);
                    return false;
                }
            }
            Err(e) => {
                error!("Failed to open file {}: {}", cwpath, e);
                return false;
            }
        }

        for filter in &self.filters {
            if !filter.exec(&cwpath) {
                return false;
            }

            cwpath = filter.tempdir(&cwpath).unwrap();
        }

        for template in &self.templates {
            debug!("Applying template file {} to {}", template, cwpath);

            let out = tempdir(template, &cwpath);

            if let Err(e) = create_dir_all(out.dir()) {
                error!(
                    "Failed to create parent directories: {}",
                    e
                );
                return false;
            }

            if let Err(e) = create_dir_all(out.dir()) {
                error!(
                    "Failed to create tempfile directory structure for template: {}",
                    e
                );
                return false;
            }

            if let Err(e) = apply_template(template, &cwpath.full(), out.full(), &yaml) {
                error!("Failed to apply template: {}", e);
                return false;
            }

            cwpath = tempdir(template, &cwpath);
        }

        let out = match FilePath::from_str(&substitute(&self.output, &path)) {
            Ok(f) => f.strip_prefix(content).prefix(output),
            Err(e) => {
                error!("Failed to create final file: {}", e);
                return false;
            }
        };

        if let Err(e) = create_dir_all(out.dir()) {
            error!(
                "Failed to create final file parent directories: {}", e
            );
            return false;
        }

        if let Err(e) = copy(cwpath.full(), out.full()) {
            error!("Failed to finalize file output: {}", e);
            return false;
        }

        true
    }
}