use std::path::PathBuf; use clap::{Parser, Subcommand}; use diaryrs::macros::UnwrapOrFatalAble; use serde::Deserialize; /// Overengineered and overly opinionated Rust-based diary keeper and processor. /// Happy journaling! #[derive(Parser)] #[command(version, about)] pub struct Args { #[command(subcommand)] pub cmd: Commands, } #[derive(Subcommand, Clone)] pub enum Commands { /// Print basic configuration information Info, /// Write diary entry file for today Today, /// Take inventory of the diary entries you have Inventory, } #[derive(Debug, Deserialize)] pub struct Config { // Global options /// The base path for diary entries pub base_path: PathBuf, /// Path where the config file was found. Not deserialised from the file #[serde(skip_deserializing)] pub config_path: PathBuf, } impl Config { const CONFIG_PATHS: [&'static str; 5] = [ "diaryrs.toml", "diary.toml", "~/.config/diaryrs.toml", "~/diaryrs.toml", "~/.diaryrs.toml", ]; pub fn read() -> Result> { let (config_str, config_path) = try_files( Vec::from(Self::CONFIG_PATHS) .into_iter() .map(|p| PathBuf::from(p)) .collect(), ) .unwrap_or_fatal("No config file found"); let config: Config = toml::from_str(&config_str)?; Ok(Config { config_path, ..config }) } } /// Try a list of file paths and return the contents of the first file /// that exists. If all files have been tried none exist, None is returned fn try_files(paths: Vec) -> Option<(String, PathBuf)> { for path in paths { match std::fs::read_to_string(&path) { Ok(contents) => return Some((contents, path)), Err(_) => continue, } } None }