1use std::collections::HashMap;
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::{fs, io};
5
6use flexstr::SharedStr;
7
8use crate::var::Vars;
9use crate::{CodeFragments, Error, TokenVars};
10
11const BUF_SIZE: usize = u16::MAX as usize;
12
13const DEFAULT_FILENAME: &str = "flexgen.toml";
14
15#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
19#[serde(untagged)]
20pub enum FragmentItem {
21 Fragment(SharedStr),
24 FragmentListRef(SharedStr),
26}
27
28#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq)]
31struct FragmentLists(HashMap<SharedStr, Vec<FragmentItem>>);
32
33impl FragmentLists {
34 pub fn build(&self) -> Self {
35 let mut lists = HashMap::with_capacity(self.0.len());
36
37 for (key, fragments) in &self.0 {
38 let mut new_fragments = Vec::with_capacity(fragments.len());
39
40 for fragment in fragments {
41 match fragment {
42 FragmentItem::Fragment(s) | FragmentItem::FragmentListRef(s) => {
43 if self.0.contains_key(s) {
45 new_fragments.push(FragmentItem::FragmentListRef(s.clone()));
46 } else {
47 new_fragments.push(FragmentItem::Fragment(s.clone()));
48 }
49 }
50 }
51 }
52
53 lists.insert(key.clone(), new_fragments);
54 }
55
56 Self(lists)
57 }
58
59 pub fn validate_code_fragments(&self, code: &CodeFragments) -> Result<(), Error> {
60 let mut missing = Vec::new();
61
62 for fragments in self.0.values() {
64 let v: Vec<_> = fragments
65 .iter()
66 .filter_map(|fragment| match fragment {
67 FragmentItem::Fragment(name) if !code.contains_key(name) => Some(name.clone()),
68 _ => None,
69 })
70 .collect();
71
72 missing.extend(v);
74 }
75
76 if missing.is_empty() {
77 Ok(())
78 } else {
79 Err(Error::MissingFragments(missing))
80 }
81 }
82
83 pub fn validate_file(&self, name: &SharedStr, f: &File) -> Result<(), Error> {
84 if !self.0.contains_key(&f.fragment_list) {
86 return Err(Error::MissingFragmentList(
87 f.fragment_list.clone(),
88 name.clone(),
89 ));
90 }
91
92 let mut missing = Vec::new();
93
94 'top: for exception in &f.fragment_list_exceptions {
95 if self.0.contains_key(exception) {
97 continue;
98 }
99
100 for fragment_list in self.0.values() {
102 if fragment_list.iter().any(|fragment| match fragment {
104 FragmentItem::Fragment(name) => name == exception,
105 _ => false,
106 }) {
107 continue 'top;
108 }
109 }
110
111 missing.push(exception.clone());
113 }
114
115 if missing.is_empty() {
116 Ok(())
117 } else {
118 Err(Error::MissingFragmentListExceptions(missing, name.clone()))
119 }
120 }
121
122 #[inline]
123 pub fn fragment_list(&self, name: &SharedStr) -> Result<&Vec<FragmentItem>, Error> {
124 self.0
125 .get(name)
126 .ok_or_else(|| Error::FragmentListNotFound(name.clone()))
127 }
128}
129
130#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq)]
133struct General {
134 #[serde(default)]
135 base_path: PathBuf,
136 #[serde(default)]
137 rust_fmt: RustFmt,
138 #[serde(default)]
139 vars: Vars,
140}
141
142impl General {
143 #[inline]
144 fn build_rust_fmt(&self) -> Option<rust_format::RustFmt> {
145 self.rust_fmt.build_rust_fmt()
146 }
147}
148
149#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq)]
150struct RustFmt {
151 #[serde(default)]
152 omit_final_format: bool,
153 #[serde(default)]
154 path: Option<PathBuf>,
155 #[serde(default)]
156 options: HashMap<SharedStr, SharedStr>,
157}
158
159impl RustFmt {
160 fn build_rust_fmt(&self) -> Option<rust_format::RustFmt> {
161 if !self.omit_final_format {
162 let mut config = if !self.options.is_empty() {
163 let map = self.options.iter().map(|(k, v)| (&**k, &**v)).collect();
164 rust_format::Config::from_hash_map(map)
165 } else {
166 rust_format::Config::new()
167 };
168 if let Some(path) = &self.path {
169 config = config.rust_fmt_path(path.clone())
170 }
171
172 Some(rust_format::RustFmt::from_config(config))
173 } else {
174 None
175 }
176 }
177}
178
179#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq)]
180struct File {
181 path: PathBuf,
182 fragment_list: SharedStr,
183 #[serde(default)]
184 fragment_list_exceptions: Vec<SharedStr>,
185 vars: Vars,
186}
187
188#[derive(Clone, Debug, Default, serde::Deserialize, PartialEq)]
190pub struct Config {
191 #[serde(default)]
192 general: General,
193 fragment_lists: FragmentLists,
194 files: HashMap<SharedStr, File>,
195}
196
197impl Config {
198 pub fn from_toml_reader(r: impl io::Read) -> Result<Config, Error> {
200 let mut reader = io::BufReader::new(r);
201 let mut buffer = String::with_capacity(BUF_SIZE);
202 reader.read_to_string(&mut buffer)?;
203
204 Ok(toml::from_str(&buffer)?)
205 }
206
207 pub fn from_default_toml_file() -> Result<Config, Error> {
209 let f = fs::File::open(DEFAULT_FILENAME)?;
210 Self::from_toml_reader(f)
211 }
212
213 pub fn from_toml_file(cfg_name: impl AsRef<Path>) -> Result<Config, Error> {
215 let f = fs::File::open(cfg_name)?;
216 Self::from_toml_reader(f)
217 }
218
219 pub(crate) fn build_and_validate(&mut self, code: &CodeFragments) -> Result<(), Error> {
220 self.fragment_lists = self.fragment_lists.build();
222
223 self.fragment_lists.validate_code_fragments(code)?;
224 for (name, file) in &self.files {
225 self.fragment_lists.validate_file(name, file)?;
226 }
227
228 Ok(())
229 }
230
231 #[inline]
233 pub fn file_names(&self) -> Vec<&SharedStr> {
234 self.files.keys().collect()
235 }
236
237 #[inline]
239 fn file(&self, name: &SharedStr) -> Result<&File, Error> {
240 self.files
241 .get(name)
242 .ok_or_else(|| Error::FileNotFound(name.clone()))
243 }
244
245 pub fn file_path(&self, name: &SharedStr) -> Result<PathBuf, Error> {
247 let file = self.file(name)?;
248 let base_path = self.general.base_path.as_os_str();
249
250 let mut path = PathBuf::with_capacity(base_path.len() + file.path.as_os_str().len());
251 path.push(base_path);
252 path.push(&file.path);
253 Ok(path)
254 }
255
256 #[inline]
257 fn convert_vars(vars: &Vars) -> Result<TokenVars, Error> {
258 vars.iter()
259 .map(|(key, value)| match value.to_token_item() {
260 Ok(value) => Ok((key.clone(), value)),
261 Err(err) => Err(err),
262 })
263 .collect()
264 }
265
266 #[inline]
267 fn general_vars(&self) -> Result<TokenVars, Error> {
268 Self::convert_vars(&self.general.vars)
269 }
270
271 #[inline]
272 fn file_vars(&self, name: &SharedStr) -> Result<TokenVars, Error> {
273 Self::convert_vars(&self.file(name)?.vars)
274 }
275
276 #[inline]
278 pub fn vars(&self, name: &SharedStr) -> Result<TokenVars, Error> {
279 let mut vars = self.general_vars()?;
280 vars.extend(self.file_vars(name)?);
281 Ok(vars)
282 }
283
284 #[inline]
286 pub fn fragment_list(&self, name: &SharedStr) -> Result<&Vec<FragmentItem>, Error> {
287 self.fragment_lists.fragment_list(name)
288 }
289
290 #[inline]
292 pub fn file_fragment_list(&self, name: &SharedStr) -> Result<&Vec<FragmentItem>, Error> {
293 let name = &self.file(name)?.fragment_list;
294 self.fragment_list(name)
295 }
296
297 #[inline]
299 pub fn file_fragment_exceptions(&self, name: &SharedStr) -> Result<&Vec<SharedStr>, Error> {
300 Ok(&self.file(name)?.fragment_list_exceptions)
301 }
302
303 #[inline]
305 pub fn build_rust_fmt(&self) -> Option<rust_format::RustFmt> {
306 self.general.build_rust_fmt()
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use std::collections::HashMap;
313 use std::path::PathBuf;
314 use std::str::FromStr;
315
316 use flexstr::{shared_str, SharedStr};
317 use pretty_assertions::assert_eq;
318
319 use crate::config::{Config, File, FragmentItem, FragmentLists, General, RustFmt};
320 use crate::var::{CodeValue, VarItem, VarValue};
321
322 const CONFIG: &str = r#"
323 [general]
324 base_path = "src/"
325
326 [general.rust_fmt]
327 path = "rustfmt"
328
329 [general.vars]
330 product = "FlexStr"
331 generate = true
332 count = 5
333 suffix = "$ident$Str"
334 list = [ "FlexStr", true, 5, "$ident$Str" ]
335
336 [fragment_lists]
337 impl = [ "impl_struct", "impl_core_ref" ]
338 impl_struct = [ "empty", "from_ref" ]
339
340 [files.str]
341 path = "strings/generated/std_str.rs"
342 fragment_list = "impl"
343 fragment_list_exceptions = [ "impl_core_ref" ]
344
345 [files.str.vars]
346 str_type = "str"
347 "#;
348
349 fn general() -> General {
350 let mut vars = HashMap::new();
351
352 let product = VarValue::String(shared_str!("FlexStr"));
353 vars.insert(shared_str!("product"), VarItem::Single(product.clone()));
354
355 let generate = VarValue::Bool(true);
356 vars.insert(shared_str!("generate"), VarItem::Single(generate.clone()));
357
358 let count = VarValue::Number(5);
359 vars.insert(shared_str!("count"), VarItem::Single(count.clone()));
360
361 let suffix = VarValue::CodeValue(CodeValue::from_str("$ident$Str").unwrap());
362 vars.insert(shared_str!("suffix"), VarItem::Single(suffix.clone()));
363
364 vars.insert(
365 shared_str!("list"),
366 VarItem::List(vec![product, generate, count, suffix]),
367 );
368
369 let rust_fmt = RustFmt {
370 omit_final_format: false,
371 path: Some("rustfmt".into()),
372 options: Default::default(),
373 };
374
375 General {
376 base_path: PathBuf::from("src/"),
377 rust_fmt,
378 vars,
379 }
380 }
381
382 fn fragment_lists() -> FragmentLists {
383 use FragmentItem::*;
384
385 let mut lists = HashMap::new();
386 lists.insert(
387 shared_str!("impl"),
388 vec![
389 Fragment(shared_str!("impl_struct")),
390 Fragment(shared_str!("impl_core_ref")),
391 ],
392 );
393 lists.insert(
394 shared_str!("impl_struct"),
395 vec![
396 Fragment(shared_str!("empty")),
397 Fragment(shared_str!("from_ref")),
398 ],
399 );
400 FragmentLists(lists)
401 }
402
403 fn files() -> HashMap<SharedStr, File> {
404 let mut str_vars = HashMap::new();
405 str_vars.insert(
406 shared_str!("str_type"),
407 VarItem::Single(VarValue::String(shared_str!("str"))),
408 );
409
410 let files_str = File {
411 path: PathBuf::from("strings/generated/std_str.rs"),
412 fragment_list: shared_str!("impl"),
413 fragment_list_exceptions: vec![shared_str!("impl_core_ref")],
414 vars: str_vars,
415 };
416
417 let mut files = HashMap::new();
418 files.insert(shared_str!("str"), files_str);
419 files
420 }
421
422 #[test]
423 fn from_reader() {
424 let actual = Config::from_toml_reader(CONFIG.as_bytes()).unwrap();
425 let expected = Config {
426 general: general(),
427 fragment_lists: fragment_lists(),
428 files: files(),
429 };
430
431 assert_eq!(expected, actual);
432 }
433}