1use glob::Pattern;
2use std::{
3 fmt,
4 fs::File,
5 path::{Path, PathBuf},
6};
7use which::which;
8
9#[derive(Debug)]
10pub struct ValidationError {
11 pub hook_name: String,
12 pub field: String,
13 pub problem: String,
14}
15
16impl fmt::Display for ValidationError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 write!(
19 f,
20 "hook `{}`: field `{}` — {}",
21 self.hook_name, self.field, self.problem
22 )
23 }
24}
25
26use crate::git_hook::GitHook;
27
28pub fn read_file(config_file: PathBuf) -> Result<Vec<GitHook>, Box<dyn std::error::Error>> {
29 let f: File;
30 if Path::new(&config_file).exists() {
31 f = std::fs::File::open(config_file)?;
32 } else if Path::new("./config.yml").exists() {
33 f = std::fs::File::open("./config.yml")?;
34 } else {
35 return Err(format!(
36 "Cannot locate a config.yml file, please make one here: {:?}",
37 config_file
38 )
39 .into());
40 }
41
42 let hooks: Vec<GitHook> = serde_yaml::from_reader(f)?;
43 Ok(hooks)
44}
45
46pub fn test_config(config_file: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
47 let hooks = read_file(config_file)?;
48
49 let mut errors = Vec::new();
50
51 for hook in hooks {
52 if hook.name.trim().is_empty() {
54 errors.push(ValidationError {
55 hook_name: hook.name.clone(),
56 field: "name".into(),
57 problem: "must not be empty".into(),
58 });
59 }
60
61 let cmd = hook.command.cmd.trim();
63 if cmd.is_empty() {
64 errors.push(ValidationError {
65 hook_name: hook.name.clone(),
66 field: "command.cmd".into(),
67 problem: "must not be empty".into(),
68 });
69 } else {
70 if let Some(dir) = &hook.command.directory {
72 let candidate = dir.join(cmd);
74
75 let found_globally = which(cmd).is_ok();
77
78 if !candidate.exists() && !found_globally {
79 errors.push(ValidationError {
80 hook_name: hook.name.clone(),
81 field: "command.cmd".into(),
82 problem: format!(
83 "could not find executable `{}` in directory {:?} or on PATH",
84 cmd, dir
85 ),
86 });
87 }
88 } else {
89 if which(cmd).is_err() {
91 errors.push(ValidationError {
92 hook_name: hook.name.clone(),
93 field: "command.cmd".into(),
94 problem: format!("could not locate `{}` on PATH", cmd),
95 });
96 }
97 }
98 }
99
100 if let Some(dir) = &hook.command.directory {
102 if dir.as_os_str().is_empty() {
103 errors.push(ValidationError {
104 hook_name: hook.name.clone(),
105 field: "command.directory".into(),
106 problem: "path is empty".into(),
107 });
108 }
109 }
110
111 if hook.glob_pattern.is_empty() {
113 errors.push(ValidationError {
114 hook_name: hook.name.clone(),
115 field: "glob_pattern".into(),
116 problem: "must contain at least one pattern".into(),
117 });
118 } else {
119 for pat in &hook.glob_pattern {
120 if let Err(e) = Pattern::new(pat) {
121 errors.push(ValidationError {
122 hook_name: hook.name.clone(),
123 field: "glob_pattern".into(),
124 problem: format!("invalid glob `{}`: {}", pat, e),
125 });
126 }
127 }
128 }
129 }
130
131 if errors.is_empty() {
132 Ok(())
133 } else {
134 for e in errors {
135 eprintln!("Config error: {}", e);
136 }
137 Err("Config error".into())
138 }
139}
140
141pub fn display_hooks(config_file: PathBuf) {
142 let hooks_result = read_file(config_file);
143 match hooks_result {
144 Ok(hooks) => {
145 for hook in hooks.iter() {
146 println!("{}", hook)
147 }
148 }
149 Err(e) => println!("Some error occured in finding the config file: [{}]", e),
150 }
151}