use clap::Parser; use cli::Args; use welcome_msg::print_startup_msg; use crate::config::Configs; mod cli { use std::path::PathBuf; use clap::Parser; #[derive(Parser)] pub(crate) struct Args { /// Read this config file. #[arg(short, long)] pub config_file: Option, #[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")] pub no_startup_message: bool, } } mod config { use std::{ fs, path::{Path, PathBuf}, process, }; use serde::{Deserialize, Serialize}; use crate::error_reporting::{report_serde_json_err, report_serde_ron_err}; #[derive(Debug, Serialize, Deserialize)] pub struct Configs { #[serde(default = "default_example_value")] pub example_value: i32, #[serde(default = "default_no_startup_msg")] pub no_startup_message: bool, } /// what the fuck serde why do i need this fn default_example_value() -> i32 { 43 } fn default_no_startup_msg() -> bool { false } impl Configs { pub fn read(custom_path: Option) -> 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) } } }; 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), } } } } mod error_reporting { use std::process; use ron::error::Position; pub fn report_serde_json_err(src: &str, err: serde_json::Error) -> ! { report_serde_err(src, err.line(), err.column(), err.to_string()) } pub fn report_serde_ron_err(src: &str, err: ron::error::SpannedError) -> ! { let Position { line, col } = err.position; report_serde_err(src, line, col, err.to_string()) } pub fn report_serde_err(src: &str, line: usize, col: usize, msg: String) -> ! { use ariadne::{Label, Report, Source}; let offset = try_reconstruct_loc(src, line, col); Report::build(ariadne::ReportKind::Error, "test", offset) .with_label( Label::new(("test", offset..offset)).with_message("Something went wrong here!"), ) .with_message(msg) .with_note( "We'd like to give better errors, but serde errors are horrible to work with...", ) .finish() .print(("test", Source::from(src))) .unwrap(); process::exit(1); } fn try_reconstruct_loc(src: &str, line_nr: usize, col_nr: usize) -> usize { let (line_nr, col_nr) = (line_nr - 1, col_nr - 1); src.lines() .enumerate() .fold(0, |acc, (i, line)| match i.cmp(&line_nr) { std::cmp::Ordering::Less => acc + line.len() + 1, std::cmp::Ordering::Equal => acc + col_nr, std::cmp::Ordering::Greater => acc, }) } } mod welcome_msg { use time::{Month, OffsetDateTime}; pub fn print_startup_msg() { // now or fallback to utc let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); if now.month() == Month::June { println!("Hello, thanks for using iOwO and happy pride month!"); println!("Why pride month is important in {}", now.year()); } else { println!("Hello, thanks for using iOwO! :3"); } } } fn main() { let args = Args::parse(); let cfg = Configs::read(args.config_file); if !(args.no_startup_message || cfg.no_startup_message) { print_startup_msg(); } }