use std::{path::PathBuf, process::exit, sync::OnceLock};
use clap::{Parser, Subcommand};
use color_eyre::eyre::{eyre, OptionExt};
use directories::ProjectDirs;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
use himalaya::config::TomlConfig;
use pimalaya_tui::terminal::config::TomlConfig as _;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none, DisplayFromStr, NoneAsEmptyString};
use tracing::level_filters::LevelFilter;
static CONFIG: OnceLock<AppConfig> = OnceLock::new();
#[serde_as]
#[skip_serializing_none]
#[derive(Debug, Default, Parser, Serialize, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
#[arg(long, value_name = "DIR")]
data_dir: Option<PathBuf>,
#[arg(short, long, value_name = "FILE")]
config: Option<PathBuf>,
#[arg(long, value_name = "FILE")]
himalaya_config: Option<PathBuf>,
#[arg(long, value_name = "LEVEL", default_value = "debug", alias = "log")]
#[serde_as(as = "NoneAsEmptyString")]
log_level: Option<LevelFilter>,
#[arg(short, long, value_name = "ACCOUNT", alias = "account")]
#[serde_as(as = "NoneAsEmptyString")]
account_name: Option<String>,
#[arg(short, long, value_name = "FOLDER")]
#[serde_as(as = "NoneAsEmptyString")]
folder: Option<String>,
#[clap(subcommand)]
pub command: Option<Command>,
}
#[derive(Debug, Serialize, Subcommand, Clone, Copy)]
pub enum Command {
#[serde(rename = "print-default-config")]
PrintDefaultConfig,
}
#[serde_as]
#[derive(Debug, Deserialize, Serialize)]
pub struct AppConfig {
pub data_dir: PathBuf,
pub himalaya_config: Option<PathBuf>,
#[serde_as(as = "DisplayFromStr")]
pub log_level: LevelFilter,
pub account_name: Option<String>,
pub folder: Option<String>,
}
impl Default for AppConfig {
fn default() -> Self {
let data_dir = default_data_dir();
let himalaya_config = default_himalaya_config();
Self {
data_dir,
himalaya_config,
log_level: LevelFilter::DEBUG,
account_name: None,
folder: None,
}
}
}
fn default_data_dir() -> PathBuf {
project_dirs()
.map(|dirs| dirs.data_dir().to_path_buf())
.unwrap()
}
fn default_config_file() -> PathBuf {
project_dirs()
.map(|dirs| dirs.config_dir().join("config.toml"))
.unwrap()
}
fn project_dirs() -> color_eyre::Result<ProjectDirs> {
ProjectDirs::from("net", "joshka", "nitidus").ok_or_eyre("user home directory not found")
}
fn default_himalaya_config() -> Option<PathBuf> {
TomlConfig::default_path().ok()
}
pub fn init() -> color_eyre::Result<()> {
let cli = Cli::parse();
let config_file = cli.config.clone().unwrap_or_else(default_config_file);
let config = Figment::new()
.merge(Serialized::defaults(AppConfig::default()))
.merge(Toml::file(config_file))
.merge(Env::prefixed("NITIDUS_"))
.merge(Serialized::defaults(cli.clone()))
.extract::<AppConfig>()?;
match cli.command {
Some(Command::PrintDefaultConfig) => {
println!("{:#?}", config);
exit(0);
}
_ => (),
}
CONFIG
.set(config)
.map_err(|config| eyre!("failed to set config {config:?}"))
}
pub fn get() -> &'static AppConfig {
CONFIG.get().expect("config not initialized")
}