1use std::{collections::BTreeMap, env, fmt::Debug, fs::File, path::Path};
13use tera::{Map, Value};
14
15pub const DEFAULT_NUMERIC_CODE_TYPE: &str = "u16";
24
25pub const DEFAULT_DATA_DIR: &str = "data";
26
27pub const DEFAULT_TEMPLATE_DIR: &str = "templates";
28
29pub type DataRow = Map<String, Value>;
30
31pub type DataMap = BTreeMap<String, Map<String, Value>>;
32
33pub trait Data {
34 fn new(type_name: &'static str) -> Self
35 where
36 Self: Sized;
37 fn new_with_inner(type_name: &'static str, inner_type_name: &'static str) -> Self
38 where
39 Self: Sized;
40 fn type_name(&self) -> &'static str;
41 fn inner_type_name(&self) -> Option<&'static str>;
42 fn has_inner_type(&self) -> bool {
43 self.inner_type_name().is_some()
44 }
45 fn all_ids(&self) -> Vec<&String> {
46 self.rows().keys().collect()
47 }
48 fn all_ids_sorted(&self) -> Value {
49 let mut all_ids = self.all_ids();
50 all_ids.sort();
51 all_ids.dedup();
52 Value::Array(
53 all_ids
54 .into_iter()
55 .map(|s| Value::String(s.into()))
56 .collect(),
57 )
58 }
59 fn rows(&self) -> &DataMap;
60 fn rows_mut(&mut self) -> &mut DataMap;
61 fn into_rows(self) -> DataMap;
62 fn contains(&self, id: &str) -> bool {
63 self.rows().contains_key(id)
64 }
65 fn get(&self, id: &str) -> Option<&DataRow> {
66 self.rows().get(id)
67 }
68 fn get_mut(&mut self, id: &str) -> Option<&mut DataRow> {
69 self.rows_mut().get_mut(id)
70 }
71 fn insert_row(&mut self, id: &str, row: DataRow) {
72 self.rows_mut().insert(id.to_string(), row);
73 }
74 fn insert_row_value(&mut self, id: &str, key: &str, value: Value) {
75 let row = self.get_mut(id).unwrap();
76 row.insert(key.to_string(), value);
77 }
78}
79
80#[derive(Debug, Default)]
81pub struct SimpleData {
82 type_name: &'static str,
83 inner_type_name: Option<&'static str>,
84 rows: DataMap,
85}
86
87pub fn process<T, I, P, F, R>(
92 init: I,
93 process_data: P,
94 finalize: F,
95 render: R,
96) -> Result<(), Box<dyn std::error::Error>>
97where
98 I: FnOnce() -> Result<T, Box<dyn std::error::Error>>,
99 P: FnOnce(T) -> Result<T, Box<dyn std::error::Error>>,
100 F: FnOnce(T) -> Result<tera::Context, Box<dyn std::error::Error>>,
101 R: FnOnce(tera::Context) -> Result<tera::Context, Box<dyn std::error::Error>>,
102{
103 init()
104 .and_then(process_data)
105 .and_then(finalize)
106 .and_then(render)?;
107 Ok(())
108}
109
110pub fn default_init<T>() -> Result<T, Box<dyn std::error::Error>>
111where
112 T: Default,
113{
114 Ok(Default::default())
115}
116
117pub fn default_finalize<T>(data: T) -> Result<tera::Context, Box<dyn std::error::Error>>
118where
119 T: Into<tera::Context>,
120{
121 Ok(data.into())
122}
123
124pub fn default_finalize_for<T>(data: T) -> Result<tera::Context, Box<dyn std::error::Error>>
125where
126 T: Data,
127{
128 let mut ctx = tera::Context::new();
129
130 ctx.insert("type_name", &Value::String(data.type_name().into()));
131
132 if let Some(inner_type_name) = data.inner_type_name() {
133 ctx.insert("inner_type_name", &Value::String(inner_type_name.into()));
134 }
135
136 ctx.insert("all_ids", &data.all_ids_sorted());
137
138 ctx.insert(
139 "codes",
140 &Value::Object(
141 data.into_rows()
142 .into_iter()
143 .map(|(key, value)| (key, Value::Object(value)))
144 .collect(),
145 ),
146 );
147
148 Ok(ctx)
149}
150
151#[inline]
152pub fn input_file_name(name: &str) -> String {
153 let file_name = format!("{}/{}", DEFAULT_DATA_DIR, name);
154 rerun_if_changed(&file_name);
155 file_name
156}
157
158#[inline]
159pub fn rerun_if_changed(file_name: &str) {
160 println!("cargo:rerun-if-changed={}", file_name);
161}
162
163#[inline]
164pub fn rerun_if_template_changed(file_name: &str) {
165 println!(
166 "cargo:rerun-if-changed={}/{}",
167 DEFAULT_TEMPLATE_DIR, file_name
168 );
169}
170
171pub fn make_default_renderer<S1, S2>(
172 template_name: S1,
173 generated_file_name: S2,
174) -> impl Fn(tera::Context) -> Result<tera::Context, Box<dyn std::error::Error>>
175where
176 S1: Into<String>,
177 S2: Into<String>,
178{
179 let template_name = template_name.into();
180 let generated_file_name = generated_file_name.into();
181 move |ctx: tera::Context| -> Result<tera::Context, Box<dyn std::error::Error>> {
182 let output_dir: String = env::var("OUT_DIR").unwrap();
183 let file_name = Path::new(&output_dir).join(&generated_file_name);
184
185 rerun_if_template_changed(&template_name);
186
187 let tera = tera::Tera::new(&format!("{}/*._rs", DEFAULT_TEMPLATE_DIR))?;
188
189 let file = File::create(file_name)?;
190 tera.render_to(&template_name, &ctx, file)?;
191
192 Ok(ctx)
193 }
194}
195
196impl Data for SimpleData {
205 fn new(type_name: &'static str) -> Self
206 where
207 Self: Sized,
208 {
209 Self {
210 type_name,
211 inner_type_name: None,
212 rows: Default::default(),
213 }
214 }
215 fn new_with_inner(type_name: &'static str, inner_type_name: &'static str) -> Self
216 where
217 Self: Sized,
218 {
219 Self {
220 type_name,
221 inner_type_name: Some(inner_type_name),
222 rows: Default::default(),
223 }
224 }
225
226 fn type_name(&self) -> &'static str {
227 self.type_name
228 }
229
230 fn inner_type_name(&self) -> Option<&'static str> {
231 self.inner_type_name
232 }
233
234 fn rows(&self) -> &BTreeMap<String, Map<String, Value>> {
235 &self.rows
236 }
237
238 fn rows_mut(&mut self) -> &mut BTreeMap<String, Map<String, Value>> {
239 &mut self.rows
240 }
241
242 fn into_rows(self) -> BTreeMap<String, Map<String, Value>> {
243 self.rows
244 }
245}
246
247impl SimpleData {
248 pub fn retain<F>(&mut self, f: F)
249 where
250 F: FnMut(&String, &mut Map<String, Value>) -> bool,
251 {
252 self.rows.retain(f);
253 }
254}
255
256#[cfg(feature = "csv_tools")]
265#[macro_use]
266pub mod csv;