Skip to main content

leenfetch_core/config/
mod.rs

1pub mod defaults;
2pub mod settings;
3
4use self::{
5    defaults::DEFAULT_CONFIG,
6    settings::{Config, Flags, LayoutItem},
7};
8use dirs::config_dir;
9use json5;
10use once_cell::sync::Lazy;
11use std::io::Write;
12use std::path::PathBuf;
13use std::{
14    collections::HashMap,
15    fs::{self, File},
16};
17
18static DEFAULT_CONFIG_CACHE: Lazy<Config> = Lazy::new(|| {
19    json5::from_str(DEFAULT_CONFIG).unwrap_or_else(|e| panic!("Built-in default config is invalid JSON: {e}"))
20});
21
22/// Loads the unified configuration from `config.jsonc`.
23fn load_config() -> Result<Config, String> {
24    let path = config_file("config.jsonc");
25    let data = fs::read_to_string(&path)
26        .map_err(|e| format!("Failed to read config.jsonc ({}): {}", path.display(), e))?;
27    load_config_from_str(&data)
28}
29
30fn load_config_from_str(data: &str) -> Result<Config, String> {
31    json5::from_str(data).map_err(|e| format!("Invalid JSONC in config.jsonc: {}", e))
32}
33
34/// Loads configuration from a custom path when provided.
35pub fn load_config_at(path: Option<&str>) -> Result<Config, String> {
36    match path {
37        Some(custom_path) => {
38            let data = fs::read_to_string(custom_path)
39                .map_err(|err| format!("Failed to read config at {}: {}", custom_path, err))?;
40            load_config_from_str(&data)
41        }
42        None => load_config(),
43    }
44}
45
46/// Returns the built-in default configuration.
47pub fn default_config() -> Config {
48    DEFAULT_CONFIG_CACHE.clone()
49}
50
51/// Returns the built-in default layout section.
52pub fn default_layout() -> Vec<LayoutItem> {
53    default_config().layout
54}
55
56/// Loads the modules section from `config.jsonc`.
57///
58/// # Returns
59///
60/// A `Vec<LayoutItem>` containing the loaded module configuration.
61pub fn load_print_layout() -> Vec<LayoutItem> {
62    let layout = load_config().unwrap_or_else(|e| {
63        eprintln!("leenfetch: config error: {e}; using defaults");
64        default_config()
65    }).layout;
66    if layout.is_empty() {
67        default_layout()
68    } else {
69        layout
70    }
71}
72
73/// Loads the configuration flags from `config.jsonc`.
74///
75/// # Returns
76///
77/// A `Flags` struct containing the loaded configuration.
78pub fn load_flags() -> Flags {
79    load_config().unwrap_or_else(|e| {
80        eprintln!("leenfetch: config error: {e}; using defaults");
81        default_config()
82    }).flags
83}
84
85/// Generates the default unified configuration file.
86///
87/// Writes `config.jsonc` with the default contents. Returns a map with the filename
88/// and whether the operation succeeded, matching the previous multi-file API.
89pub fn generate_config_files() -> HashMap<String, bool> {
90    let mut results = HashMap::new();
91
92    let result = save_to_config_file("config.jsonc", DEFAULT_CONFIG).is_ok();
93    results.insert("config.jsonc".to_string(), result);
94
95    results
96}
97
98/// Saves the provided content to a configuration file with the specified file name.
99///
100/// The function ensures that the directory for the file exists, creating it if necessary.
101/// It then writes the content to the file, overwriting any existing content.
102///
103/// # Arguments
104///
105/// * `file_name` - A string slice that holds the name of the file to be created or overwritten.
106/// * `content` - A string slice containing the content to write to the file.
107///
108/// # Returns
109///
110/// A `Result` which is:
111///
112/// * `Ok(())` if the operation is successful.
113/// * `Err` if an error occurs during directory creation or file writing.
114fn save_to_config_file(file_name: &str, content: &str) -> std::io::Result<()> {
115    let path = config_file(file_name);
116
117    if let Some(parent) = path.parent() {
118        fs::create_dir_all(parent)?;
119    }
120
121    let mut file = File::create(&path)?;
122    file.write_all(content.as_bytes())?;
123
124    Ok(())
125}
126
127/// Deletes the generated configuration file.
128///
129/// This function is used in the `--clean-config` flag, and it removes the default config file.
130/// It returns a HashMap where the key is the config file name and the value indicates
131/// whether the file was deleted.
132pub fn delete_config_files() -> HashMap<String, bool> {
133    let mut results = HashMap::new();
134
135    let file = "config.jsonc";
136    let result = delete_config_file(file).is_ok();
137    results.insert(file.to_string(), result);
138
139    results
140}
141
142/// Deletes the given configuration file, returning an error if the operation fails.
143///
144/// This function is used by `delete_config_files` to remove the generated configuration.
145/// It does not report an error if the file does not exist.
146fn delete_config_file(file_name: &str) -> std::io::Result<()> {
147    let path = config_file(file_name);
148
149    if path.exists() {
150        std::fs::remove_file(path)?;
151    }
152
153    Ok(())
154}
155
156/// Returns a `PathBuf` for the configuration file with the given `name`.
157///
158/// If `XDG_CONFIG_HOME` is set, the function will return a path in that directory.
159/// If `XDG_CONFIG_HOME` is not set, the function will return a path in the current directory.
160///
161/// The returned path will have the "leenfetch" directory as its parent, and the given `name` as its file name.
162fn config_file(name: &str) -> PathBuf {
163    config_dir()
164        .unwrap_or_else(|| PathBuf::from("."))
165        .join("leenfetch")
166        .join(name)
167}
168
169/// Ensures that the configuration file exists.
170///
171/// This function creates the `leenfetch` directory and the config file if they do not exist.
172/// It will not overwrite an existing config file.
173///
174/// Returns a `HashMap` where the key is the config file name and the value indicates
175/// whether the file was created.
176pub fn ensure_config_files_exist() -> HashMap<String, bool> {
177    let mut results = HashMap::new();
178
179    let filename = "config.jsonc";
180    let created = ensure_config_file_exists(filename, DEFAULT_CONFIG).unwrap_or(false);
181    results.insert(filename.to_string(), created);
182
183    results
184}
185
186/// Ensures that the configuration file with the given `file_name` exists.
187///
188/// If the file does not exist, the function will create it with the given `default_content`.
189/// If the file already exists, the function will return `false` without modifying it.
190///
191/// # Returns
192///
193/// A `Result` which is `Ok(true)` if the file was created, or `Ok(false)` if the file already existed.
194/// If an error occurs while attempting to create the file, the function will return `Err`.
195fn ensure_config_file_exists(file_name: &str, default_content: &str) -> std::io::Result<bool> {
196    let path = config_file(file_name);
197
198    if path.exists() {
199        return Ok(false); // Already exists
200    }
201
202    save_to_config_file(file_name, default_content)?;
203    Ok(true) // Created
204}