1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#[macro_use]
extern crate pest_derive;
#[macro_use]
extern crate quote;

pub mod parser {
    #[derive(Parser)]
    #[grammar = "erst.pest"]
    pub struct ErstParser;
}

pub mod exp {
    pub use pest::Parser;
}

pub mod utils {

    use std::path::{Path, PathBuf};

    pub fn templates_dir() -> Result<PathBuf, std::env::VarError> {
        let out = std::env::var("ERST_TEMPLATES_DIR").map(PathBuf::from).or_else(|_| {
            std::env::var("CARGO_MANIFEST_DIR").map(|x| PathBuf::from(x).join("templates"))
        })?;
        Ok(out)
    }

    #[cfg(feature = "dynamic")]
    pub fn generate_code_cache() -> Result<(), Box<std::error::Error>> {
        let pkg_name = std::env::var("CARGO_PKG_NAME")?;
        let xdg_dirs = xdg::BaseDirectories::with_prefix(format!("erst/{}", &pkg_name))?;

        for path in template_paths() {
            let path_name = path.file_name().ok_or_else(|| "No file name")?;
            let cache_file_path = xdg_dirs.place_cache_file(path_name)?;
            let template_code = get_template_code(&path)?;

            if let Ok(cache_file_contents) = std::fs::read_to_string(&cache_file_path) {
                if cache_file_contents == template_code {
                    continue;
                }
            }

            std::fs::write(&cache_file_path, &template_code)?;
        }

        Ok(())
    }

    #[cfg(feature = "dynamic")]
    pub fn rerun_if_templates_changed() -> Result<(), Box<std::error::Error>> {
        let pkg_name = std::env::var("CARGO_PKG_NAME")?;
        let cache_dir =
            xdg::BaseDirectories::with_prefix(format!("erst/{}", &pkg_name))?.get_cache_home();

        for path in collect_paths(&cache_dir) {
            println!("cargo:rerun-if-changed={}", path.display());
        }

        Ok(())
    }

    fn collect_paths(path: impl AsRef<Path>) -> Vec<PathBuf> {
        let mut out = Vec::new();
        let path_as_ref = path.as_ref();
        for entry in std::fs::read_dir(path_as_ref)
            .into_iter()
            .flat_map(|x| x)
            .flat_map(|x| x)
            .map(|x| x.path())
        {
            if entry.is_dir() {
                out.extend(collect_paths(&entry))
            } else {
                out.push(entry);
            }
        }
        out
    }

    fn template_paths() -> Vec<PathBuf> {
        templates_dir().as_ref().map(collect_paths).unwrap_or_else(|_| Vec::new())
    }

    fn get_template_code(path: impl AsRef<Path>) -> Result<String, Box<std::error::Error>> {
        use crate::{
            exp::Parser as _,
            parser::{ErstParser, Rule},
        };

        let template = std::fs::read_to_string(&path)?;

        let mut buffer = String::from("{");

        let pairs = ErstParser::parse(Rule::template, &template)?;

        for pair in pairs {
            match pair.as_rule() {
                Rule::code => {
                    buffer.push_str(pair.into_inner().as_str());
                }
                Rule::expr => {
                    buffer.push_str(pair.into_inner().as_str());
                    buffer.push_str(";");
                }
                _ => {}
            }
        }

        buffer.push_str("}");

        let block = syn::parse_str::<syn::Block>(&buffer)?;

        let stmts = &block.stmts;

        Ok(quote!(#(#stmts)*).to_string())
    }

}