ttz/src/config.rs
2026-06-01 09:11:03 -07:00

92 lines
2.8 KiB
Rust

use std::{
env,
ffi::{OsStr, OsString},
fs,
path::PathBuf,
};
use chrono_tz::Tz;
use color_eyre::{
Result,
eyre::{Context as _, eyre},
};
use kdl::KdlDocument;
#[derive(Debug)]
pub(crate) struct Config {
pub(crate) locations: Vec<Location>,
}
impl Config {
/// Reads ttz.kdl config from a file.
///
/// Uses the provided path if `Some`; otherwise defaults to `ttz.kdl` within
/// `$XDG_CONFIG_HOME` if set or `~/.config` if not.
pub(crate) fn load_from_file(file_path: Option<&OsStr>) -> Result<Self> {
let default_file_path: Result<OsString> = env::var("XDG_CONFIG_HOME")
.ok()
.map(Into::<PathBuf>::into)
.or(env::home_dir().map(|mut buf| {
buf.push(".config");
buf
}))
.map(|mut buf| {
buf.push("ttz.kdl");
buf.into()
})
.ok_or(eyre!("Unable to determine config home"));
let file_path = if let Some(file_path) = file_path {
file_path
} else {
&default_file_path?
};
if !fs::exists(file_path)? {
return Err(eyre!("Unable to find config file"));
}
let config_raw = fs::read_to_string(file_path)?;
let doc: KdlDocument = config_raw
.parse()
.wrap_err(eyre!("Unable to parse config file"))?;
let mut locations: Vec<Location> = Vec::with_capacity(doc.nodes().len());
for node in doc.nodes() {
if node.name().value() != "location" {
return Err(eyre!(
"Config parse error: only `location` nodes are allowed"
));
}
let name = node
.entry(0)
.and_then(|entry| entry.value().as_string())
.ok_or(eyre!(
"Config parse error: each node must have an argument for its name"
))?
.to_owned();
let tz_raw: &str = node
.entry("tz")
.ok_or(eyre!(
"Config parse error: each node must have a `tz` property"
))?
.value()
.as_string()
.ok_or(eyre!("Config parse error: tz must be a string"))?;
let tz: Tz = tz_raw.parse().wrap_err(eyre!(
"Config parse error: not a chrono-tz time zone: {tz_raw}",
))?;
locations.push(Location { name, tz })
}
if locations.is_empty() {
return Err(eyre!(
"Config parse error: must have at least one `location`"
));
}
Ok(Self { locations })
}
}
#[derive(Clone, Debug)]
pub(crate) struct Location {
pub(crate) name: String,
pub(crate) tz: Tz,
}