aimcal_cli/
config.rs

1// SPDX-FileCopyrightText: 2025 Zexin Yuan <aim@yzx9.xyz>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{error::Error, path::PathBuf, str::FromStr};
6
7use aimcal_core::{APP_NAME, Config as CoreConfig};
8use colored::Colorize;
9
10#[tracing::instrument]
11pub async fn parse_config(path: Option<PathBuf>) -> Result<(CoreConfig, Config), Box<dyn Error>> {
12    let path = match path {
13        Some(path) => path,
14        None => {
15            // TODO: zero config should works
16            // TODO: search config in multiple locations
17            let config = get_config_dir()?.join(format!("{APP_NAME}/config.toml"));
18            if !config.exists() {
19                return Err(format!("No config found at: {}", config.display()).into());
20            }
21            config
22        }
23    };
24
25    let content = std::fs::read_to_string(&path)
26        .map_err(|e| format!("Failed to read config file at {}: {}", path.display(), e))?;
27
28    match content.parse::<ConfigRaw>() {
29        Ok(raw) => Ok((raw.core, Config {})),
30        Err(err) => {
31            // If parsing fails, try legacy format
32            match content.parse::<ConfigLegacy>() {
33                Ok(legacy) => {
34                    println!(
35                        "{} Please update your config file by moving all entry to `[core]` sub-table",
36                        "Warning:".yellow(),
37                    );
38                    Ok((legacy.0, Config {}))
39                }
40                Err(_) => Err(err),
41            }
42        }
43    }
44}
45
46/// Configuration for the Aim application.
47#[derive(Debug, Clone, Copy, serde::Deserialize)]
48pub struct Config;
49
50#[derive(Debug, serde::Deserialize)]
51struct ConfigLegacy(CoreConfig);
52
53impl FromStr for ConfigLegacy {
54    type Err = Box<dyn Error>;
55
56    fn from_str(s: &str) -> Result<Self, Self::Err> {
57        Ok(toml::from_str(s)?)
58    }
59}
60
61#[derive(Debug, serde::Deserialize)]
62struct ConfigRaw {
63    core: CoreConfig,
64}
65
66impl FromStr for ConfigRaw {
67    type Err = Box<dyn Error>;
68
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        Ok(toml::from_str(s)?)
71    }
72}
73
74fn get_config_dir() -> Result<PathBuf, Box<dyn Error>> {
75    #[cfg(unix)]
76    let config_dir = xdg::BaseDirectories::new().get_config_home();
77    #[cfg(windows)]
78    let config_dir = dirs::config_dir();
79    config_dir.ok_or("User-specific home directory not found".into())
80}