clevert/
config.rs

1use crate::{Error, ErrorKind};
2use serde::Deserialize;
3use std::collections::HashMap;
4use std::env;
5use std::fs;
6
7#[derive(Deserialize, Clone)]
8pub struct Config {
9    pub parent: Option<String>,
10    pub threads_count: Option<usize>,
11    pub ignore_panic: Option<bool>,
12    pub repeat_count: Option<usize>,
13    pub pipe: Option<String>,
14    pub program: Option<String>,
15    pub current_dir: Option<String>,
16    pub args_template: Option<String>,
17    pub input_list: Option<Vec<String>>,
18    pub input_dir: Option<String>,
19    pub input_absolute: Option<bool>,
20    pub input_recursive: Option<bool>,
21    pub output_dir: Option<String>,
22    pub output_absolute: Option<bool>,
23    pub output_recursive: Option<bool>,
24    pub output_overwrite: Option<String>,
25    pub output_extension: Option<String>,
26    pub output_prefix: Option<String>,
27    pub output_suffix: Option<String>,
28    pub output_suffix_serial: Option<bool>,
29}
30
31impl Default for Config {
32    fn default() -> Self {
33        Self {
34            parent: None,
35            threads_count: Some(num_cpus::get()),
36            ignore_panic: Some(false),
37            repeat_count: Some(1),
38            pipe: None, // None | <inherit> | path,
39            program: None,
40            current_dir: None, // only apply on commands, has no effect to self
41            args_template: Some(String::new()),
42            input_list: None,
43            input_dir: None,
44            input_absolute: Some(false),
45            input_recursive: Some(false),
46            output_dir: None,
47            output_absolute: Some(false),
48            output_recursive: Some(false),
49            output_overwrite: Some("allow".to_string()), // allow | forbid | force
50            output_extension: None,
51            output_prefix: None,
52            output_suffix: None,
53            output_suffix_serial: Some(false),
54        }
55    }
56}
57
58impl Config {
59    pub fn merge(&mut self, parent: &Self) {
60        macro_rules! m {
61            ( $( $key:ident ),* ) => {
62                $(
63                    if self.$key.is_none() && parent.$key.is_some() {
64                        self.$key = parent.$key.clone();
65                    }
66                )*
67            };
68        }
69        // javascript: console.log(`{{ struct fields }}`.replace(/pub\s|:.+?>/g,''))
70        m!(
71            parent,
72            threads_count,
73            ignore_panic,
74            repeat_count,
75            pipe,
76            program,
77            current_dir,
78            args_template,
79            input_list,
80            input_dir,
81            input_absolute,
82            input_recursive,
83            output_dir,
84            output_absolute,
85            output_recursive,
86            output_overwrite,
87            output_extension,
88            output_prefix,
89            output_suffix,
90            output_suffix_serial
91        );
92    }
93}
94
95#[derive(Deserialize)]
96pub struct Profile {
97    presets: HashMap<String, Config>,
98    pub current: Option<String>,
99    pub export: Option<Vec<String>>,
100    pub log_level: Option<i32>,
101    pub interactive: Option<bool>,
102}
103
104impl Profile {
105    fn fit(mut self) -> Self {
106        self.export = self.export.or(Some(Vec::new()));
107        self.log_level = self.log_level.or(Some(2));
108        self.interactive = self.interactive.or(Some(true));
109        self
110    }
111
112    fn get(&self, name: &str) -> Result<Config, Error> {
113        let mut current = self.presets.get(name).unwrap().clone();
114
115        // inherit parent's parent
116        let mut depth = 0;
117        while let Some(parent_name) = current.parent.take() {
118            depth += 1;
119            if depth > 64 {
120                return Err(Error {
121                    kind: ErrorKind::Config,
122                    message: "preset inherit depth > 64".to_string(),
123                    ..Default::default()
124                });
125            }
126            if let Some(parent) = self.presets.get(&parent_name) {
127                current.merge(parent);
128                current.parent = parent.parent.clone();
129            } else {
130                return Err(Error {
131                    kind: ErrorKind::Config,
132                    message: format!("parent preset `{parent_name}` not found"),
133                    ..Default::default()
134                });
135            }
136        }
137
138        // inherit `global` and `default`
139        if let Some(parent) = self.presets.get("global") {
140            current.merge(parent);
141        }
142        current.merge(&Default::default());
143
144        Ok(current)
145    }
146
147    pub fn get_current(&self) -> Result<Config, Error> {
148        self.get(self.current.as_ref().unwrap())
149    }
150
151    pub fn keys(&self) -> Vec<&String> {
152        let list = self.export.as_ref().unwrap();
153        self.presets.keys().filter(|k| list.contains(k)).collect()
154    }
155
156    pub fn from_toml(toml_str: &str) -> Result<Self, Error> {
157        Ok(Self::fit(toml::from_str(toml_str).map_err(|e| Error {
158            kind: ErrorKind::Config,
159            inner: Box::new(e),
160            message: "error while config file deserialize".to_string(),
161        })?))
162    }
163
164    pub fn from_default_file() -> Result<Self, Error> {
165        let path = env::current_exe().unwrap();
166        if let Ok(text) = fs::read_to_string(&path.with_extension("toml")) {
167            return Self::from_toml(&text);
168        }
169        Err(Error {
170            kind: ErrorKind::Config,
171            message: "the config file was not found".to_string(),
172            ..Default::default()
173        })
174    }
175}