cli_config/
core.rs

1use home::home_dir;
2use std::path::PathBuf;
3
4use crate::fs::File;
5
6/// Get the path for creating a new config file
7/// trying to use `$XDG_CONFIG_HOME/{prefix}/{filename}`
8///
9/// - `prefix` is the name of the folder that will contain the config file
10#[cfg(not(windows))]
11fn get_new_config_path(prefix: &str, filename: &str) -> Option<PathBuf> {
12    xdg::BaseDirectories::with_prefix(prefix)
13        .ok()
14        .and_then(|base| base.place_config_file(filename).ok())
15}
16
17/// Get the path for creating a new config file on windows
18///
19/// - `prefix` is the name of the folder that will contain the config file
20#[cfg(windows)]
21fn get_new_config_path(prefix: &str, filename: &str) -> Option<PathBuf> {
22    dirs::config_dir()
23        .map(|p| p.join(&format!("{}\\{}", prefix, filename)))
24        .find(|p| p.exists())
25}
26
27/// Try to find the location of the first config file in the following paths:
28///
29/// 1. $XDG_CONFIG_HOME/{prefix}/{filename}.json
30/// 2. $XDG_CONFIG_HOME/{prefix}.json
31/// 3. $HOME/.config/{prefix}/{filename}
32/// 4. $HOME/.{prefix}
33#[cfg(not(windows))]
34pub fn locate_config(prefix: &str, filename: &str) -> Option<PathBuf> {
35    xdg::BaseDirectories::with_prefix(prefix)
36        .ok()
37        // Search for case n. 1
38        .and_then(|xdg| xdg.find_config_file(filename))
39        .or_else(|| {
40            xdg::BaseDirectories::new()
41                .ok()
42                // Search for case n. 2
43                .and_then(|fallback| fallback.find_config_file(format!("{prefix}.json")))
44        })
45        .or_else(|| {
46            if let Some(home_path) = home_dir() {
47                // Search for case n. 3 ($HOME/.config/{prefix}/{filename})
48                let fallback_path = format!(".config/{prefix}");
49                let fallback = home_path.join(fallback_path).join(filename);
50
51                if fallback.exists() {
52                    return Some(fallback);
53                }
54
55                // Search for case n. 4 ($HOME/.{prefix})
56                let fallback = home_path.join(format!(".{prefix}.json"));
57
58                if fallback.exists() {
59                    return Some(fallback);
60                }
61            }
62
63            None
64        })
65}
66
67/// Get the location of the config file on windows
68#[cfg(windows)]
69pub fn locate_config(prefix: &str, filename: &str) -> Option<PathBuf> {
70    dirs::config_dir()
71        .map(|p| p.join(&format!("{}\\{}", prefix, filename)))
72        .filter(|p| p.exists())
73}
74
75/// Initialize the configuration file for the specified type.
76///
77/// This function returns the path to the configuration file for the specified type. If the file does not exist, it will be created.
78///
79/// # Arguments
80///
81/// * `config` - The configuration object to initialize the file with.
82/// * `prefix` - The name of the folder that will contain the configuration file.
83/// * `filename` - The name of the configuration file.
84///
85///
86/// # Examples
87///
88/// ```
89/// use cli_config::fs::JSONFile;
90///
91/// #[derive(serde::Serialize, serde::Deserialize, Default)]
92/// struct MyConfig {
93///     pub is_first_run: bool,
94/// }
95///
96/// impl JSONFile for MyConfig {}
97///
98/// let config = MyConfig::default();
99/// let prefix = "my-app";
100/// let filename = "config.json";
101///
102/// // Initialize the configuration file
103/// let config_path = cli_config::init(config, prefix, filename).unwrap();
104///
105/// // Use the configuration file
106/// let loaded_config = MyConfig::load(&config_path).unwrap();
107/// println!("Is my first run? {}", loaded_config.is_first_run);
108/// ```
109pub fn init<T>(config: T, prefix: &str, filename: &str) -> crate::Result<PathBuf>
110where
111    T: serde::Serialize + Default + File,
112{
113    let config_path = locate_config(prefix, filename);
114
115    match config_path {
116        None => match get_new_config_path(prefix, filename) {
117            None => Err(crate::error::Error::Custom("Could not create file")),
118            Some(path) => {
119                config.write(&path)?;
120                Ok(path)
121            }
122        },
123        Some(path) => Ok(path),
124    }
125}