use serde::Deserialize;
use std::path::PathBuf;
use tracing::warn;
#[derive(Debug, Default, Deserialize)]
pub struct CliConfig {
#[serde(default)]
pub update: UpdateConfig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum UpdateMode {
Off,
Notify,
Install,
}
#[derive(Debug, Deserialize)]
pub struct UpdateConfig {
#[serde(default = "default_mode")]
pub mode: UpdateMode,
#[serde(default)]
pub check_interval: Option<String>,
}
impl Default for UpdateConfig {
fn default() -> Self {
UpdateConfig {
mode: default_mode(),
check_interval: None,
}
}
}
fn default_mode() -> UpdateMode {
UpdateMode::Notify
}
pub fn path() -> Option<PathBuf> {
dirs::config_dir().map(|d| d.join("kanade").join("config.toml"))
}
const TEMPLATE: &str = r#"# kanade CLI configuration
[update]
# What to do about new releases on ordinary invocations:
# off — never check
# notify — check in the background, print a banner when newer (default)
# install — silently update in the background (next run uses it)
# `KANADE_NO_AUTOUPDATE=1` disables everything regardless of this file.
mode = "notify"
# Interval between background checks (humantime). Default: 24h.
# check_interval = "24h"
"#;
pub fn load() -> CliConfig {
let Some(p) = path() else {
return CliConfig::default();
};
match std::fs::read_to_string(&p) {
Ok(s) => toml::from_str(&s).unwrap_or_else(|e| {
warn!(error = %e, path = %p.display(), "malformed CLI config — using defaults");
CliConfig::default()
}),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
if let Some(dir) = p.parent() {
let _ = std::fs::create_dir_all(dir);
}
let _ = std::fs::write(&p, TEMPLATE);
CliConfig::default()
}
Err(_) => CliConfig::default(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn template_parses_to_defaults() {
let cfg: CliConfig = toml::from_str(TEMPLATE).expect("template parses");
assert_eq!(cfg.update.mode, UpdateMode::Notify);
assert!(cfg.update.check_interval.is_none());
}
}