diff options
Diffstat (limited to 'src/config.rs')
-rw-r--r-- | src/config.rs | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6995e24 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,130 @@ +//! Configuration file handling. + +use crate::error::JournalError; +use crate::opt::Opt; + +use directories_next::ProjectDirs; +use serde::Deserialize; +use std::default::Default; +use std::path::{Path, PathBuf}; + +const APP: &str = "jt"; + +// The configuration file we read. +// +// Some of the fields are optional in the file. We will use default +// values for those, or get them command line options. +#[derive(Debug, Default, Deserialize)] +#[serde(deny_unknown_fields)] +struct InputConfiguration { + dirname: Option<PathBuf>, + editor: Option<String>, + entries: Option<PathBuf>, +} + +impl InputConfiguration { + fn read(filename: &Path) -> Result<Self, JournalError> { + let text = std::fs::read(filename) + .map_err(|err| JournalError::ReadConfig(filename.to_path_buf(), err))?; + let config = serde_yaml::from_slice(&text) + .map_err(|err| JournalError::ConfigSyntax(filename.to_path_buf(), err))?; + Ok(config) + } +} + +/// The run-time configuration. +/// +/// This is the configuration as read from the configuration file, if +/// any, and with all command line options applied. Nothing here is +/// optional. +#[derive(Debug, Deserialize)] +pub struct Configuration { + /// The directory where the journal is stored. + pub dirname: PathBuf, + + /// The editor to open for editing journal entry drafts. + pub editor: String, + + /// The directory where new entries are put. + /// + /// This is the full path name, not relative to `dirname`. + pub entries: PathBuf, +} + +impl Configuration { + /// Read configuration file. + /// + /// The configuration is read from the file specified by the user + /// on the command line, or from a default location following the + /// XDG base directory specification. Note that only one of those + /// is read. + /// + /// It's OK for the default configuration file to be missing, but + /// if one is specified by the user explicitly, that one MUST + /// exist. + pub fn read(opt: &Opt) -> Result<Self, JournalError> { + let proj_dirs = + ProjectDirs::from("", "", APP).expect("could not figure out home directory"); + let filename = match &opt.global.config { + Some(path) => { + if !path.exists() { + return Err(JournalError::ConfigMissing(path.to_path_buf())); + } + path.to_path_buf() + } + None => proj_dirs.config_dir().to_path_buf().join("config.yaml"), + }; + let input = if filename.exists() { + InputConfiguration::read(&filename)? + } else { + InputConfiguration::default() + }; + + let dirname = if let Some(path) = &opt.global.dirname { + path.to_path_buf() + } else if let Some(path) = &input.dirname { + expand_tilde(path) + } else { + proj_dirs.data_dir().to_path_buf() + }; + + Ok(Self { + dirname: dirname.clone(), + editor: if let Some(name) = &opt.global.editor { + name.to_string() + } else if let Some(name) = &input.editor { + name.to_string() + } else { + "/usr/bin/editor".to_string() + }, + entries: if let Some(entries) = &opt.global.entries { + dirname.join(entries) + } else if let Some(entries) = &input.entries { + dirname.join(entries) + } else { + dirname.join("entries") + }, + }) + } + + /// Write configuration to stdout. + pub fn dump(&self) { + println!("{:#?}", self); + } +} + +fn expand_tilde(path: &Path) -> PathBuf { + if path.starts_with("~/") { + if let Some(home) = std::env::var_os("HOME") { + let mut expanded = PathBuf::from(home); + for comp in path.components().skip(1) { + expanded.push(comp); + } + expanded + } else { + path.to_path_buf() + } + } else { + path.to_path_buf() + } +} |