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}