diff options
| author | Elvis Claros Castro <elvis@claros.ar> | 2026-09-26 19:00:45 -0300 |
|---|---|---|
| committer | Elvis Claros Castro <elvis@claros.ar> | 2026-09-26 19:00:45 -0300 |
| commit | a946ba81f7a6089713337644053b00b825824a98 (patch) | |
| tree | df7c98401b3a879a0283e940afa3fecc9453d9d4 /src/config.rs | |
| download | drae-cli-main.tar.gz drae-cli-main.zip | |
Diffstat (limited to 'src/config.rs')
| -rw-r--r-- | src/config.rs | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..4e81bbe --- /dev/null +++ b/src/config.rs @@ -0,0 +1,134 @@ +//! Optional configuration in `~/.config/drae/config.toml`. +//! +//! Everything has a sensible default: the file is only needed to change +//! something. Command-line flags always win over it. The keys are in Spanish +//! because the users are Spanish speakers. +//! +//! ```toml +//! fuente = "auto" # auto | dle | rae-api +//! timeout = 15 +//! # cache = "~/.cache/drae/entries.db" +//! # diccionario = "/usr/share/hunspell/es_AR.dic" +//! ``` + +use std::path::{Path, PathBuf}; + +use anyhow::{Context, Result}; +use serde::Deserialize; + +use crate::cache::Cache; + +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConfigFile { + /// Where to read from: `auto` (rae-api with fallback), `dle` or `rae-api`. + #[serde(rename = "fuente")] + pub source: Option<String>, + /// Seconds to wait for each request. + pub timeout: Option<u64>, + /// Cache database. + pub cache: Option<String>, + /// Hunspell `.dic` used for suggestions. + #[serde(rename = "diccionario")] + pub dictionary: Option<String>, + /// Fixed output width; defaults to the terminal width. + #[serde(rename = "ancho")] + pub width: Option<usize>, +} + +#[derive(Debug, Clone)] +pub struct Config { + pub source: String, + pub timeout: u64, + pub cache: PathBuf, + pub dictionary: Option<PathBuf>, + pub width: Option<usize>, +} + +impl Default for Config { + fn default() -> Self { + Config { + source: "auto".into(), + timeout: 15, + cache: Cache::default_path(), + dictionary: None, + width: None, + } + } +} + +pub fn default_path() -> PathBuf { + let base = std::env::var_os("XDG_CONFIG_HOME") + .map(PathBuf::from) + .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config"))) + .unwrap_or_else(|| PathBuf::from(".config")); + base.join("drae").join("config.toml") +} + +/// Loads the file if it exists. A missing file is not an error. +pub fn load(explicit: Option<&Path>) -> Result<Config> { + let path = explicit.map(PathBuf::from).unwrap_or_else(default_path); + let mut cfg = Config::default(); + let Ok(raw) = std::fs::read_to_string(&path) else { + return Ok(cfg); + }; + let file: ConfigFile = toml::from_str(&raw) + .with_context(|| format!("no se pudo leer la configuración {}", path.display()))?; + + if let Some(s) = file.source { + cfg.source = s; + } + if let Some(t) = file.timeout { + cfg.timeout = t; + } + if let Some(c) = file.cache { + cfg.cache = expand_tilde(&c); + } + if let Some(d) = file.dictionary { + cfg.dictionary = Some(expand_tilde(&d)); + } + cfg.width = file.width.or(cfg.width); + Ok(cfg) +} + +fn expand_tilde(s: &str) -> PathBuf { + match s.strip_prefix("~/") { + Some(rest) => match std::env::var_os("HOME") { + Some(home) => PathBuf::from(home).join(rest), + None => PathBuf::from(s), + }, + None => PathBuf::from(s), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn missing_file_yields_defaults() { + let cfg = load(Some(Path::new("/does/not/exist/config.toml"))).unwrap(); + assert_eq!(cfg.source, "auto"); + assert_eq!(cfg.timeout, 15); + } + + #[test] + fn parses_a_config_file() { + let dir = std::env::temp_dir().join(format!("drae-cfg-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let p = dir.join("config.toml"); + std::fs::write(&p, "fuente = \"dle\"\ntimeout = 30\nancho = 72\n").unwrap(); + let cfg = load(Some(&p)).unwrap(); + assert_eq!(cfg.source, "dle"); + assert_eq!(cfg.timeout, 30); + assert_eq!(cfg.width, Some(72)); + std::fs::remove_dir_all(&dir).ok(); + } + + #[test] + fn expands_a_leading_tilde() { + std::env::set_var("HOME", "/home/test"); + assert_eq!(expand_tilde("~/x/y.db"), PathBuf::from("/home/test/x/y.db")); + assert_eq!(expand_tilde("/abs/x.db"), PathBuf::from("/abs/x.db")); + } +} |