app: improve config loading and merging
This commit is contained in:
parent
ea2e5d6075
commit
7c9dca0ae2
4 changed files with 73 additions and 54 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue