Skip to main content

crusty_hooks/
config.rs

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}