pyc_shell/config/
mod.rs

1//! ### config
2//!
3//! `config` is the module which handles pyc configuration parsing
4
5/*
6*
7*   Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
8*
9* 	This file is part of "Pyc"
10*
11*   Pyc is free software: you can redistribute it and/or modify
12*   it under the terms of the GNU General Public License as published by
13*   the Free Software Foundation, either version 3 of the License, or
14*   (at your option) any later version.
15*
16*   Pyc is distributed in the hope that it will be useful,
17*   but WITHOUT ANY WARRANTY; without even the implied warranty of
18*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19*   GNU General Public License for more details.
20*
21*   You should have received a copy of the GNU General Public License
22*   along with Pyc.  If not, see <http://www.gnu.org/licenses/>.
23*
24*/
25
26extern crate yaml_rust;
27
28mod configparser;
29
30use configparser::ConfigParser;
31use std::collections::HashMap;
32use std::fmt;
33use yaml_rust::{Yaml, YamlLoader};
34
35use std::path::PathBuf;
36
37//Types
38#[derive(Clone)]
39pub struct Config {
40    pub language: String,
41    pub shell_config: ShellConfig,
42    pub alias: HashMap<String, String>,
43    pub output_config: OutputConfig,
44    pub prompt_config: PromptConfig,
45}
46
47#[derive(Clone)]
48pub struct ShellConfig {
49    pub exec: String,
50    pub args: Vec<String>
51}
52
53#[derive(Clone)]
54pub struct OutputConfig {
55    pub translate_output: bool,
56}
57
58#[derive(Clone)]
59pub struct PromptConfig {
60    pub prompt_line: String,
61    pub history_size: usize,
62    pub translate: bool,
63    pub break_enabled: bool,
64    pub break_str: String,
65    pub min_duration: usize,
66    pub rc_ok: String,
67    pub rc_err: String,
68    pub git_branch: String,
69    pub git_commit_ref: usize,
70    pub git_commit_prepend: Option<String>,
71    pub git_commit_append: Option<String>
72}
73
74#[derive(Copy, Clone, PartialEq, fmt::Debug)]
75pub enum ConfigErrorCode {
76    NoSuchFileOrDirectory,
77    CouldNotReadFile,
78    YamlSyntaxError,
79}
80
81pub struct ConfigError {
82    pub code: ConfigErrorCode,
83    pub message: String,
84}
85
86impl fmt::Display for ConfigErrorCode {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        let code_str: &str = match self {
89            ConfigErrorCode::NoSuchFileOrDirectory => "NoSuchFileOrDirectory",
90            ConfigErrorCode::CouldNotReadFile => "CouldNotReadFile",
91            ConfigErrorCode::YamlSyntaxError => "YamlSyntaxError",
92        };
93        write!(f, "{}", code_str)
94    }
95}
96
97impl fmt::Display for ConfigError {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        write!(f, "{} ({})", self.message, self.code)
100    }
101}
102
103impl Config {
104    /// ### default
105    ///
106    /// Instantiates a default configuration struct
107    pub fn default() -> Config {
108        let alias_config: HashMap<String, String> = HashMap::new();
109        Config {
110            language: String::from("ru"),
111            shell_config: ShellConfig::default(),
112            alias: alias_config,
113            output_config: OutputConfig::default(),
114            prompt_config: PromptConfig::default(),
115        }
116    }
117
118    /// ### parse_config
119    ///
120    /// `parse_config` parse a YAML configuration file and return a Config struct
121    pub fn parse_config(config_file: PathBuf) -> Result<Config, ConfigError> {
122        //Read configuration file
123        let config_str: String = match std::fs::read_to_string(config_file.clone()) {
124            Ok(config) => config,
125            Err(err) => match err.kind() {
126                std::io::ErrorKind::NotFound => {
127                    return Err(ConfigError {
128                        code: ConfigErrorCode::NoSuchFileOrDirectory,
129                        message: format!("No such file or directory: {}", config_file.display()),
130                    })
131                }
132                _ => {
133                    return Err(ConfigError {
134                        code: ConfigErrorCode::CouldNotReadFile,
135                        message: format!("Could not read file {}", config_file.display())
136                    })
137                }
138            },
139        };
140        Config::parse_config_str(config_str)
141    }
142
143    /// ### parse_config_str
144    ///
145    /// Parse configuration as string
146    fn parse_config_str(config: String) -> Result<Config, ConfigError> {
147        //Parse YAML file
148        let yaml_docs: Vec<Yaml> = match YamlLoader::load_from_str(config.as_str()) {
149            Ok(doc) => doc,
150            Err(_) => {
151                return Err(ConfigError {
152                    code: ConfigErrorCode::YamlSyntaxError,
153                    message: String::from("Configuration is not a valid YAML"),
154                });
155            }
156        };
157        //Check there is at least one document
158        if yaml_docs.len() == 0 {
159            return Err(ConfigError {
160                code: ConfigErrorCode::YamlSyntaxError,
161                message: String::from("File does not contain any YAML document"),
162            });
163        };
164        let yaml_doc: &Yaml = &yaml_docs[0];
165        //Look for keys and get configuration parts
166        //Get language
167        let language: String = match ConfigParser::get_child(&yaml_doc, String::from("language")) {
168            Ok(node) => match Config::parse_language(&node) {
169                Ok(l) => l,
170                Err(err) => return Err(err),
171            },
172            Err(_) => String::from("ru"),
173        };
174        //Get alias
175        let alias_config: HashMap<String, String> = match ConfigParser::get_child(&yaml_doc, String::from("alias")) {
176                Ok(node) => match Config::parse_alias(&node) {
177                    Ok(cfg) => cfg,
178                    Err(err) => return Err(err),
179                },
180                Err(_) => HashMap::new(),
181        };
182        let shell_config: ShellConfig = match ConfigParser::get_child(&yaml_doc, String::from("shell")) {
183            Ok(node) => match ShellConfig::parse_config(&node) {
184                Ok(cfg) => cfg,
185                Err(err) => return Err(err)
186            },
187            Err(_) => ShellConfig::default()
188        };
189        //Get output config
190        let output_config: OutputConfig =
191            match ConfigParser::get_child(&yaml_doc, String::from("output")) {
192                Ok(node) => match OutputConfig::parse_config(&node) {
193                    Ok(config) => config,
194                    Err(err) => return Err(err),
195                },
196                Err(_) => OutputConfig::default(),
197            };
198        //Get prompt config
199        let prompt_config: PromptConfig =
200            match ConfigParser::get_child(&yaml_doc, String::from("prompt")) {
201                Ok(node) => match PromptConfig::parse_config(&node) {
202                    Ok(config) => config,
203                    Err(err) => return Err(err),
204                },
205                Err(_) => PromptConfig::default(),
206            };
207        Ok(Config {
208            language: language,
209            shell_config: shell_config,
210            alias: alias_config,
211            output_config: output_config,
212            prompt_config: prompt_config,
213        })
214    }
215
216    /// ### get_alias
217    ///
218    ///  Get alias from configuration
219    pub fn get_alias(&self, alias: &String) -> Option<String> {
220        match self.alias.get(alias) {
221            Some(cmd) => Some(cmd.clone()),
222            None => None,
223        }
224    }
225
226    /// ### parse_alias
227    ///
228    /// Parse alias in Pyc configuration file
229    fn parse_alias(alias_yaml: &Yaml) -> Result<HashMap<String, String>, ConfigError> {
230        if !alias_yaml.is_array() {
231            return Err(ConfigError {
232                code: ConfigErrorCode::YamlSyntaxError,
233                message: String::from("'alias' key is not an array"),
234            });
235        }
236        let mut alias_table: HashMap<String, String> = HashMap::new();
237        //Iterate over alias
238        for pair in alias_yaml.as_vec().unwrap() {
239            for p in pair.as_hash().unwrap().iter() {
240                let key: String = String::from(p.0.as_str().unwrap());
241                let value: String = String::from(p.1.as_str().unwrap());
242                alias_table.insert(key, value);
243            }
244        }
245        Ok(alias_table)
246    }
247
248    /// ### parse_language
249    ///
250    /// Parse language YAML object
251    fn parse_language(language_yaml: &Yaml) -> Result<String, ConfigError> {
252        match language_yaml.as_str() {
253            Some(s) => Ok(String::from(s)),
254            None => Err(ConfigError {
255                code: ConfigErrorCode::YamlSyntaxError,
256                message: String::from("'language' is not a string"),
257            }),
258        }
259    }
260}
261
262impl ShellConfig {
263    pub fn default() -> ShellConfig {
264        ShellConfig {
265            exec: String::from("bash"),
266            args: vec![]
267        }
268    }
269
270    pub fn parse_config(shell_yaml: &Yaml) -> Result<ShellConfig, ConfigError> {
271        let exec: String = match ConfigParser::get_string(&shell_yaml, String::from("exec")) {
272            Ok(s) => s,
273            Err(err) => return Err(err)
274        };
275
276        let args: Vec<String> = match ConfigParser::get_child(&shell_yaml, String::from("args")) {
277            Ok(args_yaml) => {
278                let mut args: Vec<String> = Vec::new();
279                //Iterate over args
280                for arg in args_yaml.as_vec().unwrap() {
281                    args.push(match arg.as_str() {
282                        Some(s) => String::from(s),
283                        None => return Err(ConfigError {code: ConfigErrorCode::YamlSyntaxError, message: String::from("Shell arg is not a string")})
284                    });
285                }
286                args
287            },
288            Err(_) => Vec::new()
289        };
290        Ok(ShellConfig {
291            exec: exec,
292            args: args
293        })
294    }
295}
296
297impl OutputConfig {
298    pub fn default() -> OutputConfig {
299        OutputConfig {
300            translate_output: true,
301        }
302    }
303
304    pub fn parse_config(output_yaml: &Yaml) -> Result<OutputConfig, ConfigError> {
305        let translate_output: bool =
306            match ConfigParser::get_bool(&output_yaml, String::from("translate")) {
307                Ok(t) => t,
308                Err(err) => return Err(err),
309            };
310        Ok(OutputConfig {
311            translate_output: translate_output,
312        })
313    }
314}
315
316impl PromptConfig {
317    /// ### default
318    ///
319    /// Instantiate a default PromptConfig struct
320    pub fn default() -> PromptConfig {
321        PromptConfig {
322            prompt_line: String::from("${USER}@${HOSTNAME}:${WRKDIR}$"),
323            history_size: 256,
324            translate: false,
325            break_enabled: false,
326            break_str: String::from("❯"),
327            min_duration: 2000,
328            rc_ok: String::from("✔"),
329            rc_err: String::from("✖"),
330            git_branch: String::from("on "),
331            git_commit_ref: 8,
332            git_commit_append: None,
333            git_commit_prepend: None
334        }
335    }
336
337    /// ### parse_config
338    ///
339    /// Parse a PromptConfig from YAML configuration file
340    pub fn parse_config(prompt_config_yaml: &Yaml) -> Result<PromptConfig, ConfigError> {
341        //Prompt line
342        let prompt_line: String =
343            match ConfigParser::get_string(&prompt_config_yaml, String::from("prompt_line")) {
344                Ok(ret) => ret,
345                Err(err) => return Err(err),
346            };
347        //History size
348        let history_size: usize =
349            match ConfigParser::get_usize(&prompt_config_yaml, String::from("history_size")) {
350                Ok(ret) => ret,
351                Err(err) => return Err(err),
352            };
353        //History size
354        let translate: bool =
355            match ConfigParser::get_bool(&prompt_config_yaml, String::from("translate")) {
356                Ok(ret) => ret,
357                Err(err) => return Err(err),
358            };
359        //Break
360        let brk: &Yaml = match ConfigParser::get_child(&prompt_config_yaml, String::from("break")) {
361            Ok(ret) => ret,
362            Err(err) => return Err(err),
363        };
364        //Break enabled
365        let break_enabled: bool = match ConfigParser::get_bool(&brk, String::from("enabled")) {
366            Ok(ret) => ret,
367            Err(err) => return Err(err),
368        };
369        //Break with
370        let break_str: String = match ConfigParser::get_string(&brk, String::from("with")) {
371            Ok(ret) => ret,
372            Err(err) => return Err(err),
373        };
374        //Duration
375        let duration: &Yaml =
376            match ConfigParser::get_child(&prompt_config_yaml, String::from("duration")) {
377                Ok(ret) => ret,
378                Err(err) => return Err(err),
379            };
380        //Minimum duration
381        let min_duration: usize =
382            match ConfigParser::get_usize(&duration, String::from("min_elapsed_time")) {
383                Ok(ret) => ret,
384                Err(err) => return Err(err),
385            };
386        //Rc
387        let rc: &Yaml = match ConfigParser::get_child(&prompt_config_yaml, String::from("rc")) {
388            Ok(ret) => ret,
389            Err(err) => return Err(err),
390        };
391        //Rc_ok
392        let rc_ok: String = match ConfigParser::get_string(&rc, String::from("ok")) {
393            Ok(ret) => ret,
394            Err(err) => return Err(err),
395        };
396        //Rc err
397        let rc_err: String = match ConfigParser::get_string(&rc, String::from("error")) {
398            Ok(ret) => ret,
399            Err(err) => return Err(err),
400        };
401        //Git
402        let git: &Yaml = match ConfigParser::get_child(&prompt_config_yaml, String::from("git")) {
403            Ok(ret) => ret,
404            Err(err) => return Err(err),
405        };
406        //Git branch
407        let git_branch: String = match ConfigParser::get_string(&git, String::from("branch")) {
408            Ok(ret) => ret,
409            Err(err) => return Err(err),
410        };
411        //Git commit ref
412        let git_commit_ref: usize =
413            match ConfigParser::get_usize(&git, String::from("commit_ref_len")) {
414                Ok(ret) => ret,
415                Err(err) => return Err(err),
416            };
417        //Git commit prepend
418        let git_commit_prepend: Option<String> =
419            match ConfigParser::get_string(&git, String::from("commit_prepend")) {
420                Ok(ret) => Some(ret),
421                Err(_) => None,
422            };
423        //Git commit append
424        let git_commit_append: Option<String> =
425            match ConfigParser::get_string(&git, String::from("commit_append")) {
426                Ok(ret) => Some(ret),
427                Err(_) => None,
428            };
429        Ok(PromptConfig {
430            prompt_line: prompt_line,
431            history_size: history_size,
432            translate: translate,
433            break_enabled: break_enabled,
434            break_str: break_str,
435            min_duration: min_duration,
436            rc_ok: rc_ok,
437            rc_err: rc_err,
438            git_branch: git_branch,
439            git_commit_ref: git_commit_ref,
440            git_commit_append: git_commit_append,
441            git_commit_prepend: git_commit_prepend
442        })
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use std::io::Write;
450
451    #[test]
452    fn test_config_default() {
453        let config: Config = Config::default();
454        assert!(config.get_alias(&String::from("чд")).is_none());
455        assert_eq!(config.output_config.translate_output, true);
456        assert_eq!(config.language, String::from("ru"));
457        let prompt_config: PromptConfig = config.prompt_config;
458        assert_eq!(prompt_config.prompt_line, String::from("${USER}@${HOSTNAME}:${WRKDIR}$"));
459        assert_eq!(prompt_config.break_enabled, false);
460        assert_eq!(prompt_config.break_str, String::from("❯"));
461        assert_eq!(prompt_config.git_branch, String::from("on "));
462        assert_eq!(prompt_config.git_commit_ref, 8);
463        assert_eq!(prompt_config.git_commit_prepend, None);
464        assert_eq!(prompt_config.git_commit_append, None);
465        assert_eq!(prompt_config.history_size, 256);
466        assert_eq!(prompt_config.min_duration, 2000);
467        assert_eq!(prompt_config.rc_err, String::from("✖"));
468        assert_eq!(prompt_config.rc_ok, String::from("✔"));
469        assert_eq!(prompt_config.translate, false);
470        assert_eq!(config.shell_config.exec, String::from("bash"));
471        assert_eq!(config.shell_config.args.len(), 0);
472    }
473
474    #[test]
475    fn test_config_file() {
476        //Try to parse a configuration file
477        let config_file: tempfile::NamedTempFile = write_config_file_en();
478        let config_file_path: PathBuf = PathBuf::from(config_file.path().to_str().unwrap());
479        println!("Generated config file: {}", config_file_path.display());
480        let config: Result<Config, ConfigError> =Config::parse_config(config_file_path);
481        assert!(config.is_ok());
482        let config: Config = config.ok().unwrap();
483        // Verify parameters
484        assert!(config.get_alias(&String::from("чд")).is_some());
485        assert_eq!(config.output_config.translate_output, true);
486        assert_eq!(config.language, String::from("ru"));
487        let prompt_config: PromptConfig = config.prompt_config;
488        assert_eq!(prompt_config.prompt_line, String::from("${USER}@${HOSTNAME}:${WRKDIR}$"));
489        assert_eq!(prompt_config.break_enabled, false);
490        assert_eq!(prompt_config.break_str, String::from("❯"));
491        assert_eq!(prompt_config.git_branch, String::from("on "));
492        assert_eq!(prompt_config.git_commit_ref, 8);
493        assert_eq!(prompt_config.git_commit_prepend, None);
494        assert_eq!(prompt_config.git_commit_append, None);
495        assert_eq!(prompt_config.history_size, 256);
496        assert_eq!(prompt_config.min_duration, 2000);
497        assert_eq!(prompt_config.rc_err, String::from("✖"));
498        assert_eq!(prompt_config.rc_ok, String::from("✔"));
499        assert_eq!(prompt_config.translate, false);
500        assert_eq!(config.shell_config.exec, String::from("bash"));
501        assert_eq!(config.shell_config.args.len(), 0);
502        
503    }
504
505    #[test]
506    fn test_config_no_file() {
507        assert_eq!(
508            Config::parse_config(PathBuf::from("config.does.not.exist.yml"))
509                .err()
510                .unwrap()
511                .code,
512            ConfigErrorCode::NoSuchFileOrDirectory
513        );
514    }
515
516    #[cfg(not(target_os = "macos"))]
517    #[test]
518    fn test_config_not_accessible() {
519        assert_eq!(
520            Config::parse_config(PathBuf::from("/dev/ttyS0"))
521                .err()
522                .unwrap()
523                .code,
524            ConfigErrorCode::CouldNotReadFile
525        );
526    }
527
528    #[test]
529    fn test_config_en_alias() {
530        //Try to parse a configuration file
531        let config: String =
532            String::from("alias:\n  - чд: \"cd\"\n  - пвд: \"pwd\"\n  - уич: \"which\"");
533        match Config::parse_config_str(config) {
534            Ok(config) => {
535                //Verify alias parameters
536                assert_eq!(
537                    config.get_alias(&String::from("чд")).unwrap(),
538                    String::from("cd")
539                );
540                assert_eq!(
541                    config.get_alias(&String::from("пвд")).unwrap(),
542                    String::from("pwd")
543                );
544                assert_eq!(
545                    config.get_alias(&String::from("уич")).unwrap(),
546                    String::from("which")
547                );
548                assert!(config
549                    .get_alias(&String::from("thiskeydoesnotexist"))
550                    .is_none());
551            }
552            Err(error) => panic!(
553                "Parse_config should have returned OK, but returned {} ({:?})",
554                error.message, error.code
555            ),
556        };
557    }
558
559    #[test]
560    fn test_config_no_alias() {
561        //Try to parse a configuration file
562        let config: String = String::from("language: ru\n");
563        let config: Config = Config::parse_config_str(config).ok().unwrap();
564        assert!(config.get_alias(&String::from("чд")).is_none());
565    }
566
567    #[test]
568    fn test_config_alias_not_array() {
569        let config: String = String::from("alias: 5\n");
570        assert_eq!(
571            Config::parse_config_str(config).err().unwrap().code,
572            ConfigErrorCode::YamlSyntaxError
573        );
574    }
575
576    #[test]
577    fn test_config_shell_config() {
578        let config: String = String::from("shell:\n  exec: \"sh\"\n  args:\n    - \"-l\"\n    - \"-h\"\n");
579        let config: Config = Config::parse_config_str(config).ok().unwrap();
580        assert_eq!(config.shell_config.exec, String::from("sh"));
581        assert_eq!(config.shell_config.args, vec![String::from("-l"), String::from("-h")]);
582    }
583
584    #[test]
585    fn test_config_shell_config_bad() {
586        let config: String = String::from("shell:\n  args:\n    - \"-l\"\n    - \"-h\"\n");
587        assert!(Config::parse_config_str(config).is_err());
588        let config: String = String::from("shell:\n  args: 5\n");
589        assert!(Config::parse_config_str(config).is_err());
590    }
591
592    #[test]
593    fn test_config_output_config() {
594        let config: String =
595            String::from("alias:\n  - чд: \"cd\"\n  - пвд: \"pwd\"\n  - уич: \"which\"");
596        let config: Config = Config::parse_config_str(config).ok().unwrap();
597        assert!(config.output_config.translate_output);
598        //Try to parse a configuration file
599        let config: String = String::from("output:\n  translate: false\n");
600        let config: Config = Config::parse_config_str(config).ok().unwrap();
601        assert!(!config.output_config.translate_output);
602    }
603
604    #[test]
605    fn test_config_bad_output_config() {
606        let config: String = String::from("output: 5\n");
607        assert_eq!(
608            Config::parse_config_str(config).err().unwrap().code,
609            ConfigErrorCode::YamlSyntaxError
610        );
611        let config: String = String::from("output:\n  translate: foobar\n");
612        assert_eq!(
613            Config::parse_config_str(config).err().unwrap().code,
614            ConfigErrorCode::YamlSyntaxError
615        );
616        let config: String = String::from("output:\n  trsnlate: true\n");
617        assert_eq!(
618            Config::parse_config_str(config).err().unwrap().code,
619            ConfigErrorCode::YamlSyntaxError
620        );
621    }
622
623    #[test]
624    fn test_config_language() {
625        let config: String = String::from("language: bg\n");
626        let config: Config = Config::parse_config_str(config).ok().unwrap();
627        assert_eq!(config.language, String::from("bg"));
628    }
629
630    #[test]
631    fn test_config_language_missing() {
632        let config: String = String::from("output:\n  translate: false\n");
633        let config: Config = Config::parse_config_str(config).ok().unwrap();
634        assert_eq!(config.language, String::from("ru"));
635    }
636
637    #[test]
638    #[should_panic]
639    fn test_config_language_badvalue() {
640        let config: String = String::from("language:\n  name: ru\n");
641        assert!(Config::parse_config_str(config).is_ok());
642    }
643
644    #[test]
645    fn test_config_prompt_default() {
646        let config: String = String::from("language:\n  ru\n");
647        let config: Config = Config::parse_config_str(config).ok().unwrap();
648        let prompt_config: PromptConfig = config.prompt_config;
649        assert_eq!(prompt_config.prompt_line, String::from("${USER}@${HOSTNAME}:${WRKDIR}$"));
650        assert_eq!(prompt_config.break_enabled, false);
651        assert_eq!(prompt_config.break_str, String::from("❯"));
652        assert_eq!(prompt_config.git_branch, String::from("on "));
653        assert_eq!(prompt_config.git_commit_ref, 8);
654        assert_eq!(prompt_config.history_size, 256);
655        assert_eq!(prompt_config.min_duration, 2000);
656        assert_eq!(prompt_config.rc_err, String::from("✖"));
657        assert_eq!(prompt_config.rc_ok, String::from("✔"));
658        assert_eq!(prompt_config.translate, false);
659    }
660
661    #[test]
662    fn test_config_prompt() {
663        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n    commit_prepend: \"(\"\n    commit_append: \")\"\n");
664        let config: Config = Config::parse_config_str(config).ok().unwrap();
665        //Verify config parameters
666        let prompt_config: PromptConfig = config.prompt_config;
667        assert_eq!(prompt_config.prompt_line, String::from("${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}"));
668        assert_eq!(prompt_config.break_enabled, false);
669        assert_eq!(prompt_config.break_str, String::from(">"));
670        assert_eq!(prompt_config.git_branch, String::from("on "));
671        assert_eq!(prompt_config.git_commit_ref, 4);
672        assert_eq!(prompt_config.git_commit_prepend, Some(String::from("(")));
673        assert_eq!(prompt_config.git_commit_append, Some(String::from(")")));
674        assert_eq!(prompt_config.history_size, 1024);
675        assert_eq!(prompt_config.min_duration, 5000);
676        assert_eq!(prompt_config.rc_err, String::from("x_x"));
677        assert_eq!(prompt_config.rc_ok, String::from("^_^"));
678        assert_eq!(prompt_config.translate, true);
679    }
680
681    #[test]
682    fn test_config_prompt_bad() {
683        let config: String = String::from("prompt:\n  prompt_le: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
684        assert!(Config::parse_config_str(config).is_err());
685        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  histosize: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
686        assert!(Config::parse_config_str(config).is_err());
687        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  trslate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
688        assert!(Config::parse_config_str(config).is_err());
689        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  bak:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
690        assert!(Config::parse_config_str(config).is_err());
691        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    eled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
692        assert!(Config::parse_config_str(config).is_err());
693        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    th: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
694        assert!(Config::parse_config_str(config).is_err());
695        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  dution:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
696        assert!(Config::parse_config_str(config).is_err());
697        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsime: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
698        assert!(Config::parse_config_str(config).is_err());
699        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  r:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
700        assert!(Config::parse_config_str(config).is_err());
701        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    o: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
702        assert!(Config::parse_config_str(config).is_err());
703        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    err: \"x_x\"\n  git:\n    branch: \"on \"\n    commit_ref_len: 4\n");
704        assert!(Config::parse_config_str(config).is_err());
705        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  gi:\n    branch: \"on \"\n    commit_ref_len: 4\n");
706        assert!(Config::parse_config_str(config).is_err());
707        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    brch: \"on \"\n    commit_ref_len: 4\n");
708        assert!(Config::parse_config_str(config).is_err());
709        let config: String = String::from("prompt:\n  prompt_line: \"${USER} on ${HOSTNAME} in ${WRKDIR} ${GIT_BRANCH} (${GIT_COMMIT}) ${CMD_TIME}\"\n  history_size: 1024\n  translate: true\n  break:\n    enabled: false\n    with: \">\"\n  duration:\n    min_elapsed_time: 5000\n  rc:\n    ok: \"^_^\"\n    error: \"x_x\"\n  git:\n    branch: \"on \"\n    com_ref_len: 4\n");
710        assert!(Config::parse_config_str(config).is_err());
711    }
712
713    #[test]
714    fn test_config_bad_syntax() {
715        let config: String = String::from("foobar: 5:\n");
716        assert_eq!(
717            Config::parse_config_str(config).err().unwrap().code,
718            ConfigErrorCode::YamlSyntaxError
719        );
720    }
721
722    #[test]
723    fn test_config_empty_yaml() {
724        let config: String = String::from("\n");
725        assert_eq!(
726            Config::parse_config_str(config).err().unwrap().code,
727            ConfigErrorCode::YamlSyntaxError
728        );
729    }
730
731    #[test]
732    fn test_config_error_display() {
733        println!(
734            "{};{};{}",
735            ConfigErrorCode::CouldNotReadFile,
736            ConfigErrorCode::NoSuchFileOrDirectory,
737            ConfigErrorCode::YamlSyntaxError
738        );
739        println!(
740            "{}",
741            ConfigError {
742                code: ConfigErrorCode::NoSuchFileOrDirectory,
743                message: String::from("No such file or directory ~/.config/pyc/pyc.yml")
744            }
745        );
746    }
747
748    /// ### write_config_file_en
749    /// Write configuration file to a temporary directory and return the file path
750    fn write_config_file_en() -> tempfile::NamedTempFile {
751        // Write
752        let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
753        write!(
754            tmpfile,
755            "alias:\n  - чд: \"cd\"\n  - пвд: \"pwd\"\n  - уич: \"which\""
756        )
757        .unwrap();
758        tmpfile
759    }
760}