commitlint_rs/
config.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::{fs, path::PathBuf};
4
5use crate::rule::Rules;
6
7/// Default Root config file path to search for.
8const DEFAULT_CONFIG_ROOT: &str = ".";
9
10/// Default commitlintrc configuration files
11/// If the user didn't specify a configuration file with -c or --config argument,
12/// we will try to find one of these files in the current directory.
13const DEFAULT_CONFIG_FILE: [&str; 4] = [
14    ".commitlintrc",
15    ".commitlintrc.json",
16    ".commitlintrc.yaml",
17    ".commitlintrc.yml",
18];
19
20/// Config represents the configuration of commitlint.
21#[derive(Clone, Debug, Default, Deserialize, Serialize)]
22#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
23pub struct Config {
24    /// Rules represents the rules of commitlint.
25    pub rules: Rules,
26}
27
28impl fmt::Display for Config {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        let s = serde_yaml::to_string(&self).unwrap();
31        write!(f, "{}", s)
32    }
33}
34
35/// Load configuration from the specified path.
36pub async fn load(path: Option<PathBuf>) -> Result<Config, String> {
37    let config_file = match &path {
38        Some(p) => Some(p.clone()),
39        None => find_config_file(PathBuf::from(DEFAULT_CONFIG_ROOT)),
40    };
41
42    match (config_file, path) {
43        // If the file was specified and found, load it.
44        (Some(p), _) => load_config_file(p).await,
45        // If the file was not specified and not found, return default config.
46        (None, None) => Ok(Config::default()),
47        // If the was explicitly specified but not found, return an error.
48        (None, Some(p)) => Err(format!("Configuration file not found in {}", p.display())),
49    }
50}
51
52/// Find configuration file in the specified path.
53/// Note that the first file found will be returned.
54pub fn find_config_file(path: PathBuf) -> Option<PathBuf> {
55    let mut path = path;
56    for file in DEFAULT_CONFIG_FILE.iter() {
57        path.push(file);
58        if path.exists() {
59            return Some(path);
60        }
61        path.pop();
62    }
63
64    None
65}
66
67/// Load config file from the specified path.
68pub async fn load_config_file(path: PathBuf) -> Result<Config, String> {
69    if !path.exists() {
70        return Err(format!(
71            "Configuration file not found in {}",
72            path.display()
73        ));
74    }
75
76    match path.extension() {
77        Some(ext) => match ext.to_str() {
78            Some("json") => load_json_config_file(path).await,
79            Some("yaml") | Some("yml") => load_yaml_config_file(path).await,
80            _ => load_unknown_config_file(path).await,
81        },
82        None => Err(format!(
83            "Unsupported configuration file format: {}",
84            path.display()
85        )),
86    }
87}
88
89/// Load JSON config file from the specified path.
90async fn load_json_config_file(path: PathBuf) -> Result<Config, String> {
91    let text = fs::read_to_string(path).unwrap();
92
93    match serde_json::from_str::<Config>(&text) {
94        Ok(config) => Ok(config),
95        Err(err) => Err(format!("Failed to parse configuration file: {}", err)),
96    }
97}
98
99/// Load YAML config file from the specified path.
100async fn load_yaml_config_file(path: PathBuf) -> Result<Config, String> {
101    let text = fs::read_to_string(path).unwrap();
102
103    match serde_yaml::from_str::<Config>(&text) {
104        Ok(config) => Ok(config),
105        Err(err) => Err(format!("Failed to parse configuration file: {}", err)),
106    }
107}
108
109/// Try to load configuration file from the specified path.
110/// First try to load it as JSON, then as YAML.
111/// If both fail, return an error.
112async fn load_unknown_config_file(path: PathBuf) -> Result<Config, String> {
113    let text = fs::read_to_string(path.clone()).unwrap();
114
115    if let Ok(config) = serde_json::from_str::<Config>(&text) {
116        return Ok(config);
117    }
118
119    if let Ok(config) = serde_yaml::from_str::<Config>(&text) {
120        return Ok(config);
121    }
122
123    Err(format!(
124        "Failed to parse configuration file: {}",
125        path.display()
126    ))
127}