start working on a cli app #7
4 changed files with 73 additions and 54 deletions
|
@ -1,6 +1,10 @@
|
|||
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 config_file;
|
||||
|
@ -11,14 +15,49 @@ pub struct Config {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn read() -> Self {
|
||||
pub fn read() -> Result<Self, ConfigError> {
|
||||
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,
|
||||
// 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),
|
||||
}
|
||||
multisamplednight marked this conversation as resolved
Outdated
|
||||
|
||||
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 {
|
||||
/// Read this config file.
|
||||
#[arg(short, long)]
|
||||
pub config_file: Option<PathBuf>,
|
||||
pub config_path: Option<PathBuf>,
|
||||
#[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")]
|
||||
schrottkatze marked this conversation as resolved
Outdated
multisamplednight
commented
Might want a Might want a [`clap::builder::BoolishValueParser`](https://docs.rs/clap/latest/clap/builder/struct.BoolishValueParser.html) here.
schrottkatze
commented
How do I use that with claps derive api? How do I use that with claps derive api?
multisamplednight
commented
Through
Through
```diff
- #[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")]
+ #[arg(long, env = "NO_STARTUP_MESSAGE", action = ArgAction::SetTrue, value_parser = BoolishValueParser::new())]
```
|
||||
pub no_startup_message: bool,
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error_reporting::{report_serde_json_err, report_serde_ron_err};
|
||||
use super::error::ConfigError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Configs {
|
||||
|
@ -17,59 +16,37 @@ pub struct Configs {
|
|||
}
|
||||
|
||||
/// what the fuck serde why do i need this
|
||||
pub(crate) fn default_example_value() -> i32 {
|
||||
fn default_example_value() -> i32 {
|
||||
43
|
||||
}
|
||||
|
||||
pub(crate) fn default_no_startup_msg() -> bool {
|
||||
fn default_no_startup_msg() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub(super) fn find_config_file() -> Result<PathBuf, ConfigError> {
|
||||
let Some(config_path) = dirs::config_dir() else {
|
||||
return Err(ConfigError::NoConfigDir);
|
||||
};
|
||||
|
||||
let ron_path = config_path.with_file_name("config.ron");
|
||||
let json_path = config_path.with_file_name("config.json");
|
||||
|
||||
if Path::new(&ron_path).exists() {
|
||||
Ok(ron_path)
|
||||
} else if Path::new(&json_path).exists() {
|
||||
Ok(json_path)
|
||||
} else {
|
||||
Err(ConfigError::NoConfigFileFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl Configs {
|
||||
pub fn read(custom_path: Option<PathBuf>) -> Self {
|
||||
use owo_colors::OwoColorize;
|
||||
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 json_path = config_path.with_file_name("config.json");
|
||||
|
||||
if Path::new(&ron_path).exists() {
|
||||
ron_path
|
||||
} else if Path::new(&json_path).exists() {
|
||||
json_path
|
||||
} else {
|
||||
eprintln!("{}: couldn't find config file", "Fatal error".red());
|
||||
process::exit(1)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn read(p: PathBuf) -> Result<Self, ConfigError> {
|
||||
match p.extension().map(|v| v.to_str().unwrap()) {
|
||||
Some("ron") => Self::read_json(&fs::read_to_string(p).unwrap()),
|
||||
Some("json") => Self::read_ron(&fs::read_to_string(p).unwrap()),
|
||||
None | Some(_) => {
|
||||
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),
|
||||
Some("ron") => Ok(serde_json::from_str(&fs::read_to_string(p)?)?),
|
||||
Some("json") => Ok(ron::from_str(&fs::read_to_string(p)?)?),
|
||||
e => Err(ConfigError::UnknownExtension(e.map(|v| v.to_owned()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,14 @@ use config::Config;
|
|||
use welcome_msg::print_startup_msg;
|
||||
|
||||
mod config;
|
||||
|
||||
#[allow(unused)]
|
||||
mod error_reporting;
|
||||
mod welcome_msg;
|
||||
|
||||
fn main() {
|
||||
let cfg = Config::read();
|
||||
// TODO: proper error handling
|
||||
let cfg = Config::read().unwrap();
|
||||
|
||||
if cfg.startup_msg {
|
||||
multisamplednight marked this conversation as resolved
Outdated
multisamplednight
commented
Might want to streamline those two, in order to avoid accidentally checking only one of them in future. The Might want to streamline those two, in order to avoid accidentally checking only one of them in future.
The `figment` crate might be worth looking at in that case.
schrottkatze
commented
i'd prefer to try at least to some degree reduce dependencies, but i'll work on something like that then i'd prefer to try at least to some degree reduce dependencies, but i'll work on something like that then
multisamplednight
commented
You don't need to use You don't _need_ to use `figment`. Custom merge logic works fine, too.
|
||||
print_startup_msg();
|
||||
|
|
Loading…
Reference in a new issue
For later, not now: Should use proper error handling.
put that on the non-existing todo list for when we have a proper error handling and reporting system