1use crate::{commands_parser, Command};
2use std::{
3 collections::HashMap,
4 path::{Path, PathBuf},
5};
6
7use anyhow::{ensure, Result};
8use clap::crate_name;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Serialize, Deserialize)]
15pub struct Config {
16 editor: Option<String>,
18
19 shell: Option<String>,
21
22 #[serde(default)]
24 templates: HashMap<String, TemplateConfig>,
25}
26
27impl Config {
28 pub fn editor(&self) -> Option<&String> {
29 self.editor.as_ref()
30 }
31
32 pub fn shell(&self) -> Option<&String> {
33 self.shell.as_ref()
34 }
35
36 pub fn find_template(&self, template: &str) -> Option<Template> {
37 self.templates.get(template).map(Into::into)
38 }
39
40 pub fn templates(&self) -> HashMap<String, Template> {
42 self.templates
43 .iter()
44 .map(|(k, v)| (k.into(), v.into()))
45 .collect()
46 }
47
48 pub fn load() -> Result<Self> {
50 let name = crate_name!();
51 Ok(confy::load(name, name)?)
52 }
53
54 pub fn load_from(path: impl AsRef<Path>) -> Result<Self> {
56 ensure!(path.as_ref().is_file(), "config path is not a file");
57 Ok(confy::load_path(path)?)
58 }
59}
60
61impl Default for Config {
62 fn default() -> Self {
63 let mut templates = HashMap::new();
64 templates.insert(
65 String::from("example"),
66 TemplateConfig::Complete(Template {
67 projects_dir: PathBuf::from("/path/to/example/"),
68 editor: Some(String::from("code")),
69 shell: Some(String::from("bash")),
70 commands: vec![
71 String::from("echo hello"),
72 String::from("echo $PWD"),
73 String::from("echo $QK_PROJECT_NAME"),
74 ],
75 name: String::from("example"),
76 }),
77 );
78 Self {
79 editor: Some(String::from("vi")),
80 shell: Some(String::from("sh")),
81 templates,
82 }
83 }
84}
85
86#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
98#[serde(untagged)]
99pub enum TemplateConfig {
100 OnlyProjectsDir(String),
102 Complete(Template),
103}
104
105#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
106pub struct Template {
107 #[serde(skip)]
108 name: String,
109
110 projects_dir: PathBuf,
112
113 editor: Option<String>,
115
116 shell: Option<String>,
118
119 #[serde(default)]
120 commands: Vec<String>,
122}
123
124impl Template {
125 pub fn name(&self) -> &str {
126 &self.name
127 }
128
129 pub fn projects_dir(&self) -> &Path {
130 &self.projects_dir
131 }
132
133 pub fn editor(&self) -> Option<&String> {
134 self.editor.as_ref()
135 }
136
137 pub fn shell(&self) -> Option<&String> {
138 self.shell.as_ref()
139 }
140
141 pub fn commands(&self) -> Result<Vec<Command>> {
143 let mut commands = vec![];
144 for cmd in self.commands.iter() {
145 let units = commands_parser::parse(cmd)?;
146 commands.push(units);
147 }
148 Ok(commands)
149 }
150}
151
152impl From<&TemplateConfig> for Template {
153 fn from(template: &TemplateConfig) -> Template {
154 match template {
155 TemplateConfig::OnlyProjectsDir(projects_dir) => Template {
156 projects_dir: projects_dir.into(),
157 editor: None,
158 shell: None,
159 commands: Vec::new(),
160 name: String::from(""),
161 },
162 TemplateConfig::Complete(template) => template.clone(),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use crate::Unit;
171
172 #[test]
173 fn test_find_template_returns_none_when_empty() {
174 let config = Config {
175 editor: None,
176 shell: None,
177 templates: HashMap::new(),
178 };
179
180 assert_eq!(config.find_template("a"), None);
181 }
182
183 #[test]
184 fn test_find_template_returns_none_when_not_present() {
185 let mut templates = HashMap::new();
186 templates.insert(
187 String::from("a"),
188 TemplateConfig::OnlyProjectsDir(String::from("a")),
189 );
190 let config = Config {
191 editor: None,
192 shell: None,
193 templates,
194 };
195
196 assert_eq!(config.find_template("b"), None);
197 }
198
199 #[test]
200 fn test_find_template_returns_the_template() {
201 let mut templates = HashMap::new();
202
203 templates.insert(
204 String::from("a"),
205 TemplateConfig::OnlyProjectsDir(String::from("a")),
206 );
207
208 templates.insert(
209 String::from("b"),
210 TemplateConfig::OnlyProjectsDir(String::from("b")),
211 );
212
213 templates.insert(
214 String::from("c"),
215 TemplateConfig::OnlyProjectsDir(String::from("c")),
216 );
217
218 let config = Config {
219 editor: None,
220 shell: None,
221 templates,
222 };
223
224 assert_eq!(
225 config.find_template("b"),
226 Some(Template {
227 projects_dir: PathBuf::from("b"),
228 editor: None,
229 shell: None,
230 commands: vec![],
231 name: String::from("")
232 })
233 );
234 }
235
236 #[test]
237 fn test_templates() {
238 let mut templates = HashMap::new();
239
240 templates.insert(
241 String::from("a"),
242 TemplateConfig::OnlyProjectsDir(String::from("a")),
243 );
244
245 templates.insert(
246 String::from("b"),
247 TemplateConfig::Complete(Template {
248 projects_dir: PathBuf::from("b"),
249 editor: Some(String::from("vi")),
250 shell: Some(String::from("zsh")),
251 commands: vec![String::from("echo hello")],
252 name: String::from("b"),
253 }),
254 );
255
256 templates.insert(
257 String::from("c"),
258 TemplateConfig::OnlyProjectsDir(String::from("c")),
259 );
260
261 let config = Config {
262 editor: None,
263 shell: None,
264 templates,
265 };
266
267 let mut expected_templates = HashMap::new();
268
269 expected_templates.insert(
270 String::from("a"),
271 Template {
272 projects_dir: PathBuf::from("a"),
273 editor: None,
274 shell: None,
275 commands: vec![],
276 name: String::from(""),
277 },
278 );
279
280 expected_templates.insert(
281 String::from("b"),
282 Template {
283 projects_dir: PathBuf::from("b"),
284 editor: Some(String::from("vi")),
285 shell: Some(String::from("zsh")),
286 commands: vec![String::from("echo hello")],
287 name: String::from("b"),
288 },
289 );
290
291 expected_templates.insert(
292 String::from("c"),
293 Template {
294 projects_dir: PathBuf::from("c"),
295 editor: None,
296 shell: None,
297 commands: vec![],
298 name: String::from(""),
299 },
300 );
301
302 assert_eq!(config.templates(), expected_templates);
303 }
304
305 #[test]
306 fn test_from_template_config() {
307 let template_config1 = TemplateConfig::OnlyProjectsDir(String::from("a"));
308 let template_config2 = TemplateConfig::Complete(Template {
309 projects_dir: PathBuf::from("b"),
310 editor: Some(String::from("vi")),
311 shell: Some(String::from("fish")),
312 commands: vec![String::from("echo hello")],
313 name: String::from("b"),
314 });
315
316 let template1: Template = (&template_config1).into();
317 let template2: Template = (&template_config2).into();
318
319 assert_eq!(
320 template1,
321 Template {
322 projects_dir: PathBuf::from("a"),
323 editor: None,
324 shell: None,
325 commands: vec![],
326 name: String::from("")
327 }
328 );
329
330 assert_eq!(
331 template2,
332 Template {
333 projects_dir: PathBuf::from("b"),
334 editor: Some(String::from("vi")),
335 shell: Some(String::from("fish")),
336 commands: vec![String::from("echo hello")],
337 name: String::from("b")
338 }
339 );
340 }
341
342 #[test]
343 fn test_commands_method_with_simple_commands() {
344 let template = Template {
345 name: "a".to_string(),
346 projects_dir: "a".into(),
347 editor: None,
348 shell: None,
349 commands: vec![String::from("echo hello world"), String::from("echo hey!")],
350 };
351
352 assert_eq!(
353 template.commands().unwrap(),
354 vec![
355 vec![Unit::Text("echo hello world".to_string())],
356 vec![Unit::Text("echo hey!".to_string())]
357 ]
358 );
359 }
360}