aimcal_cli/
config.rs

1// SPDX-FileCopyrightText: 2025-2026 Zexin Yuan <aim@yzx9.xyz>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{error::Error, path::PathBuf, str::FromStr};
6
7use tokio::fs;
8
9use aimcal_core::{APP_NAME, Config as CoreConfig};
10
11#[tracing::instrument]
12pub async fn parse_config(path: Option<PathBuf>) -> Result<(CoreConfig, Config), Box<dyn Error>> {
13    let path = match path {
14        Some(path) => path,
15        None => {
16            // TODO: zero config should works
17            // TODO: search config in multiple locations
18            let config = get_config_dir()?.join(format!("{APP_NAME}/config.toml"));
19            if !config.exists() {
20                return Err(format!("No config found at: {}", config.display()).into());
21            }
22            config
23        }
24    };
25
26    fs::read_to_string(&path)
27        .await
28        .map_err(|e| format!("Failed to read config file at {}: {}", path.display(), e))?
29        .parse::<ConfigRaw>()
30        .map(|a| (a.core, Config {}))
31}
32
33/// Configuration for the Aim application.
34#[derive(Debug, Clone, Copy, serde::Deserialize)]
35pub struct Config;
36
37#[derive(Debug, serde::Deserialize)]
38struct ConfigRaw {
39    core: CoreConfig,
40}
41
42impl FromStr for ConfigRaw {
43    type Err = Box<dyn Error>;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        Ok(toml::from_str(s)?)
47    }
48}
49
50fn get_config_dir() -> Result<PathBuf, Box<dyn Error>> {
51    #[cfg(unix)]
52    let config_dir = xdg::BaseDirectories::new().get_config_home();
53    #[cfg(windows)]
54    let config_dir = dirs::config_dir();
55    config_dir.ok_or_else(|| "User-specific home directory not found".into())
56}