erst_shared/
lib.rs

1#[macro_use]
2extern crate pest_derive;
3
4pub mod err {
5
6    #[derive(Debug, derive_more::From, derive_more::Display)]
7    pub enum Error {
8        EnvVar(std::env::VarError),
9        Parse(String),
10        Msg(Msg),
11        Io(std::io::Error),
12    }
13
14    #[derive(Debug, derive_more::Display)]
15    pub struct Msg(pub String);
16
17    impl Error {
18        pub fn msg<T: std::fmt::Display>(error: T) -> Self {
19            Msg(error.to_string()).into()
20        }
21    }
22
23    impl std::error::Error for Error {
24        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
25            match *self {
26                Error::EnvVar(ref inner) => Some(inner),
27                Error::Io(ref inner) => Some(inner),
28                _ => None,
29            }
30        }
31    }
32
33    pub type Result<T> = std::result::Result<T, Error>;
34}
35
36pub mod parser {
37    #[derive(Parser)]
38    #[grammar = "erst.pest"]
39    pub struct ErstParser;
40}
41
42pub mod exp {
43    pub use bstr::{BString, B};
44    pub use pest::Parser;
45}
46
47pub mod utils {
48
49    use std::path::PathBuf;
50
51    pub fn templates_dir() -> crate::err::Result<PathBuf> {
52        let out = std::env::var("ERST_TEMPLATES_DIR")
53            .map(PathBuf::from)
54            .or_else(|_| {
55                std::env::var("CARGO_MANIFEST_DIR").map(|x| PathBuf::from(x).join("templates"))
56            })
57            .unwrap_or_else(|_| PathBuf::from("templates"));
58        Ok(out)
59    }
60}
61
62#[cfg(feature = "dynamic")]
63pub mod dynamic {
64
65    use std::path::{Path, PathBuf};
66
67    pub fn generate_code_cache() -> crate::err::Result<()> {
68        let pkg_name = std::env::var("CARGO_PKG_NAME")?;
69        let xdg_dirs = xdg::BaseDirectories::with_prefix(format!("erst/{}", &pkg_name))
70            .map_err(crate::err::Error::msg)?;
71
72        for path in template_paths() {
73            let path_name =
74                path.file_name().ok_or_else(|| crate::err::Error::msg("No file name"))?;
75            let cache_file_path = xdg_dirs.place_cache_file(path_name)?;
76            let template_code = get_template_code(&path)?;
77
78            if let Ok(cache_file_contents) = std::fs::read_to_string(&cache_file_path) {
79                if cache_file_contents == template_code {
80                    continue;
81                }
82            }
83
84            std::fs::write(&cache_file_path, &template_code)?;
85        }
86
87        Ok(())
88    }
89
90    pub fn get_code_cache_path(path: impl AsRef<Path>) -> Option<PathBuf> {
91        fn inner(path: impl AsRef<Path>) -> crate::err::Result<Option<PathBuf>> {
92            let pkg_name = std::env::var("CARGO_PKG_NAME")?;
93            let xdg_dirs = xdg::BaseDirectories::with_prefix(format!("erst/{}", &pkg_name))
94                .map_err(crate::err::Error::msg)?;
95            let path_as_ref = path.as_ref();
96            let path_name =
97                path_as_ref.file_name().ok_or_else(|| crate::err::Error::msg("No file name"))?;
98
99            Ok(xdg_dirs.find_cache_file(&path_name))
100        }
101        inner(path).ok().and_then(|x| x)
102    }
103
104    fn collect_paths(path: impl AsRef<Path>) -> Vec<PathBuf> {
105        let mut out = Vec::new();
106        let path_as_ref = path.as_ref();
107        for entry in std::fs::read_dir(path_as_ref)
108            .into_iter()
109            .flat_map(|x| x)
110            .flat_map(|x| x)
111            .map(|x| x.path())
112        {
113            if entry.is_dir() {
114                out.extend(collect_paths(&entry))
115            } else {
116                out.push(entry);
117            }
118        }
119        out
120    }
121
122    fn template_paths() -> Vec<PathBuf> {
123        super::utils::templates_dir().as_ref().map(collect_paths).unwrap_or_else(|_| Vec::new())
124    }
125
126    fn get_template_code(path: impl AsRef<Path>) -> crate::err::Result<String> {
127        use crate::{
128            exp::Parser as _,
129            parser::{ErstParser, Rule},
130        };
131
132        let template = std::fs::read_to_string(&path)?;
133
134        let mut buffer = String::from("{");
135
136        let pairs = ErstParser::parse(Rule::template, &template)
137            .map_err(|e| crate::err::Error::Parse(e.to_string()))?;
138
139        for pair in pairs {
140            match pair.as_rule() {
141                Rule::code => {
142                    buffer.push_str(pair.into_inner().as_str());
143                }
144                Rule::expr => {
145                    buffer.push_str(pair.into_inner().as_str());
146                    buffer.push_str(";");
147                }
148                _ => {}
149            }
150        }
151
152        buffer.push_str("}");
153
154        let block = syn::parse_str::<syn::Block>(&buffer).map_err(crate::err::Error::msg)?;
155
156        let stmts = &block.stmts;
157
158        Ok(quote::quote!(#(#stmts)*).to_string())
159    }
160}