app: improve config loading and merging

This commit is contained in:
Schrottkatze 2024-01-15 10:43:35 +01:00
parent ea2e5d6075
commit 7c9dca0ae2
Signed by: schrottkatze
GPG key ID: DFD0FD205943C14A
4 changed files with 73 additions and 54 deletions

View file

@ -1,6 +1,10 @@
use clap::Parser; use clap::Parser;
use self::{cli::Args, config_file::Configs}; use self::{
cli::Args,
config_file::{find_config_file, Configs},
error::ConfigError,
};
mod cli; mod cli;
mod config_file; mod config_file;
@ -11,14 +15,49 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn read() -> Self { pub fn read() -> Result<Self, ConfigError> {
let args = Args::parse(); let args = Args::parse();
let cfg = Configs::read(args.config_file); let config_path = if let Some(config_path) = args.config_path {
config_path
} else {
find_config_file()?
};
let file_config = Configs::read(config_path)?;
Self { Ok(Self {
// this is negated because to an outward api, the negative is more intuitive, // this is negated because to an outward api, the negative is more intuitive,
// while in the source the other way around is more intuitive // while in the source the other way around is more intuitive
startup_msg: !(args.no_startup_message || cfg.no_startup_message), startup_msg: !(args.no_startup_message || file_config.no_startup_message),
})
}
}
pub mod error {
#[derive(Debug)]
pub enum ConfigError {
NoConfigDir,
NoConfigFileFound,
IoError(std::io::Error),
UnknownExtension(Option<String>),
SerdeJsonError(serde_json::Error),
SerdeRonError(ron::error::SpannedError),
}
impl From<std::io::Error> for ConfigError {
fn from(value: std::io::Error) -> Self {
Self::IoError(value)
}
}
impl From<serde_json::Error> for ConfigError {
fn from(value: serde_json::Error) -> Self {
Self::SerdeJsonError(value)
}
}
impl From<ron::error::SpannedError> for ConfigError {
fn from(value: ron::error::SpannedError) -> Self {
Self::SerdeRonError(value)
} }
} }
} }

View file

@ -6,7 +6,7 @@ use clap::Parser;
pub(crate) struct Args { pub(crate) struct Args {
/// Read this config file. /// Read this config file.
#[arg(short, long)] #[arg(short, long)]
pub config_file: Option<PathBuf>, pub config_path: Option<PathBuf>,
#[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")] #[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")]
pub no_startup_message: bool, pub no_startup_message: bool,
} }

View file

@ -1,12 +1,11 @@
use std::{ use std::{
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::error_reporting::{report_serde_json_err, report_serde_ron_err}; use super::error::ConfigError;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Configs { pub struct Configs {
@ -17,59 +16,37 @@ pub struct Configs {
} }
/// what the fuck serde why do i need this /// what the fuck serde why do i need this
pub(crate) fn default_example_value() -> i32 { fn default_example_value() -> i32 {
43 43
} }
pub(crate) fn default_no_startup_msg() -> bool { fn default_no_startup_msg() -> bool {
false false
} }
impl Configs { pub(super) fn find_config_file() -> Result<PathBuf, ConfigError> {
pub fn read(custom_path: Option<PathBuf>) -> Self { let Some(config_path) = dirs::config_dir() else {
use owo_colors::OwoColorize; return Err(ConfigError::NoConfigDir);
let p = match custom_path { };
Some(p) => p,
None => {
let config_path = dirs::config_dir().expect("config dir should exist");
let ron_path = config_path.with_file_name("config.ron"); let ron_path = config_path.with_file_name("config.ron");
let json_path = config_path.with_file_name("config.json"); let json_path = config_path.with_file_name("config.json");
if Path::new(&ron_path).exists() { if Path::new(&ron_path).exists() {
ron_path Ok(ron_path)
} else if Path::new(&json_path).exists() { } else if Path::new(&json_path).exists() {
json_path Ok(json_path)
} else { } else {
eprintln!("{}: couldn't find config file", "Fatal error".red()); Err(ConfigError::NoConfigFileFound)
process::exit(1)
} }
} }
};
impl Configs {
pub fn read(p: PathBuf) -> Result<Self, ConfigError> {
match p.extension().map(|v| v.to_str().unwrap()) { match p.extension().map(|v| v.to_str().unwrap()) {
Some("ron") => Self::read_json(&fs::read_to_string(p).unwrap()), Some("ron") => Ok(serde_json::from_str(&fs::read_to_string(p)?)?),
Some("json") => Self::read_ron(&fs::read_to_string(p).unwrap()), Some("json") => Ok(ron::from_str(&fs::read_to_string(p)?)?),
None | Some(_) => { e => Err(ConfigError::UnknownExtension(e.map(|v| v.to_owned()))),
eprintln!(
"{}: couldn't determine config file type",
"Fatal error".red()
);
process::exit(1)
}
}
}
pub fn read_json(config_text: &str) -> Configs {
match serde_json::from_str(config_text) {
Ok(c) => c,
Err(e) => report_serde_json_err(config_text, e),
}
}
pub fn read_ron(config_text: &str) -> Configs {
match ron::from_str(config_text) {
Ok(c) => c,
Err(e) => report_serde_ron_err(config_text, e),
} }
} }
} }

View file

@ -2,11 +2,14 @@ use config::Config;
use welcome_msg::print_startup_msg; use welcome_msg::print_startup_msg;
mod config; mod config;
#[allow(unused)]
mod error_reporting; mod error_reporting;
mod welcome_msg; mod welcome_msg;
fn main() { fn main() {
let cfg = Config::read(); // TODO: proper error handling
let cfg = Config::read().unwrap();
if cfg.startup_msg { if cfg.startup_msg {
print_startup_msg(); print_startup_msg();