use anyhow::{Context as _, Result, ensure};
use camino::Utf8PathBuf;
use documented::DocumentedFields;
use etcetera::BaseStrategy as _;
use figment2::{
Figment,
providers::{Env, Format as _, Toml},
};
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use toml_edit::DocumentMut;
#[derive(Clone, SmartDefault, Deserialize, Serialize, DocumentedFields)]
#[serde(default, deny_unknown_fields)]
pub(crate) struct Config {
pub(crate) cwd_only: bool,
#[default = true]
pub(crate) show_preview: bool,
#[default = 110]
pub(crate) preview_min_width: u16,
#[default = 14]
pub(crate) preview_message_lines: usize,
#[default = 5]
pub(crate) rescan_interval: u64,
}
impl Config {
pub(crate) fn path() -> Result<Utf8PathBuf> {
let dirs = etcetera::base_strategy::Xdg::new()?;
let config_dir = Utf8PathBuf::try_from(dirs.config_dir())?;
return Ok(config_dir.join("ccf").join("config.toml"));
}
fn default_toml() -> Result<String> {
let toml = toml_edit::ser::to_string_pretty(&Self::default())?;
let mut doc: DocumentMut = toml.parse()?;
for (i, comment) in Self::FIELD_DOCS.iter().enumerate() {
let prefix = if i == 0 { format!("# {comment}\n") } else { format!("\n# {comment}\n") };
let name = Self::FIELD_NAMES[i];
let mut key = doc.key_mut(name).with_context(|| format!("{name} is missing from the serialized config"))?;
key.leaf_decor_mut().set_prefix(prefix);
}
return Ok(doc.to_string());
}
pub(crate) fn ensure_exists() -> Result<()> {
let path = Self::path()?;
if path.exists() {
return Ok(());
}
if let Some(parent) = path.parent() {
fs_err::create_dir_all(parent)?;
}
fs_err::write(&path, Self::default_toml()?)?;
return Ok(());
}
pub(crate) fn load() -> Result<Self> {
let path = Self::path()?;
let config: Self = Figment::new()
.merge(Toml::file(&path))
.merge(Env::prefixed("CCF_CONFIG_"))
.extract()
.with_context(|| format!("invalid config {path}"))?;
ensure!(config.rescan_interval > 0, "invalid config {path}: rescan_interval must be at least 1");
return Ok(config);
}
}