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