1use std::collections::HashMap;
2
3const CONFIG_FILE_TEMPLATE: &str = "[hooks]
4pre-commit = [
5 [\"cargo test\"]
6]
7
8[logging]
9verbose = true
10";
11
12const DEFAULT_CONFIG_FILE_NAME: &str = ".crusty-hooks.toml";
13const CONFIG_FILE_NAMES: [&str; 2] = [DEFAULT_CONFIG_FILE_NAME, "crusty-hooks.toml"];
14pub const NO_CONFIG_FILE_FOUND: &str = "No config file found";
15
16pub(crate) const FATAL_ERROR_DURING_CONFIG_LOOKUP: &str =
17 "Fatal error encountered while looking for existing config";
18
19fn find_config_file<F>(root_directory_path: &str, file_exists: F) -> Result<String, String>
20where
21 F: Fn(&str) -> Result<bool, ()>,
22{
23 for &config_file_name in CONFIG_FILE_NAMES.iter() {
24 let path = format!("{root_directory_path}/{config_file_name}");
25 match file_exists(&path) {
26 Err(_) => {
27 return Err(String::from(FATAL_ERROR_DURING_CONFIG_LOOKUP));
28 }
29 Ok(found) => {
30 if found {
31 return Ok(path);
32 }
33 }
34 };
35 }
36
37 Ok(String::from(NO_CONFIG_FILE_FOUND))
38}
39
40pub(super) fn create_default_config_file<F, G>(
41 write_file: F,
42 file_exists: G,
43 root_directory_path: &str,
44) -> Result<(), String>
45where
46 F: Fn(&str, &str, bool) -> Result<(), String>,
47 G: Fn(&str) -> Result<bool, ()>,
48{
49 create_config_file(
50 &write_file,
51 &file_exists,
52 root_directory_path,
53 DEFAULT_CONFIG_FILE_NAME,
54 )
55}
56
57pub(super) fn create_config_file<F, G>(
58 write_file: F,
59 file_exists: G,
60 root_directory_path: &str,
61 desired_config_file_name: &str,
62) -> Result<(), String>
63where
64 F: Fn(&str, &str, bool) -> Result<(), String>,
65 G: Fn(&str) -> Result<bool, ()>,
66{
67 match find_config_file(root_directory_path, &file_exists) {
68 Err(_) => {
69 return Err(String::from(FATAL_ERROR_DURING_CONFIG_LOOKUP));
70 }
71 Ok(path) => {
72 if path != NO_CONFIG_FILE_FOUND {
73 return Ok(());
74 }
75 }
76 };
77
78 let config_file = if CONFIG_FILE_NAMES
79 .iter()
80 .any(|n| n == &desired_config_file_name)
81 {
82 desired_config_file_name
83 } else {
84 DEFAULT_CONFIG_FILE_NAME
85 };
86
87 if write_file(
88 &format!("{}/{}", root_directory_path, config_file),
89 CONFIG_FILE_TEMPLATE,
90 false,
91 )
92 .is_err()
93 {
94 return Err(String::from("Failed to create config file"));
95 };
96 Ok(())
97}
98
99#[derive(Debug, serde::Serialize, serde::Deserialize)]
100pub struct ConfigFile {
101 pub hooks: HashMap<String, Vec<Vec<String>>>,
102}
103
104impl ConfigFile {
105 pub fn try_from_str(x: &str) -> Result<Self, toml::de::Error> {
106 toml::from_str::<Self>(x)
107 }
108}
109
110pub(super) fn get_config_file_contents<F, G>(
111 read_file: F,
112 file_exists: G,
113 root_directory_path: &str,
114) -> Result<String, String>
115where
116 F: Fn(&str) -> Result<String, ()>,
117 G: Fn(&str) -> Result<bool, ()>,
118{
119 let path = match find_config_file(root_directory_path, &file_exists) {
120 Ok(path) => {
121 if path == NO_CONFIG_FILE_FOUND {
122 return Err(String::from(NO_CONFIG_FILE_FOUND));
123 } else {
124 path
125 }
126 }
127 Err(_) => return Err(String::from(NO_CONFIG_FILE_FOUND)),
128 };
129
130 match read_file(&path) {
131 Ok(contents) => Ok(contents),
132 Err(_) => Err(String::from("Failure reading file")),
133 }
134}