more basic features, config gets read and startup messages

This commit is contained in:
Schrottkatze 2024-01-12 09:31:48 +01:00
parent 33aa131b95
commit 4788278d86
Signed by: schrottkatze
GPG key ID: DFD0FD205943C14A
3 changed files with 357 additions and 46 deletions

View file

@ -6,8 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { workspace = true, features = [ "derive" ] }
clap = { workspace = true, features = [ "derive", "env" ] }
serde = { workspace = true, features = [ "derive" ] }
ron = { workspace = true }
serde_json = { workspace = true }
ariadne = "0.4"
time = { version = "0.3", features = [ "local-offset" ] }
dirs = "5"
owo-colors = "4"

View file

@ -1,48 +1,124 @@
use crate::{config::Configs, error_reporting::report_serde_json_err};
use clap::Parser;
use cli::Args;
use welcome_msg::print_startup_msg;
use crate::config::Configs;
mod cli {
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
struct Args {
#[command(subcommand)]
command: Command,
pub(crate) struct Args {
/// Read this config file.
#[arg(short, long)]
pub config_file: Option<PathBuf>,
#[arg(long, env = "NO_STARTUP_MESSAGE", default_value = "false")]
pub no_startup_message: bool,
}
#[derive(Clone, Subcommand)]
enum Command {}
}
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<'a> {
example_value: i32,
example_string: &'a str,
pub struct Configs {
#[serde(default = "default_example_value")]
pub example_value: i32,
#[serde(default = "default_no_startup_msg")]
pub no_startup_message: bool,
}
impl Configs<'_> {
pub fn read_json(config_text: &str) -> serde_json::Result<Configs<'_>> {
serde_json::from_str(config_text)
/// 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<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)
}
}
};
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::{ops::Range, process};
use std::process;
use ariadne::{Report, Source};
use ron::error::Position;
pub fn report_serde_json_err(src: &str, err: serde_json::Error) -> ! {
use ariadne::{Color, ColorGenerator, Fmt, Label, Report, ReportKind, Source};
let offset = try_reconstruct_loc(src, err.line(), err.column());
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(err.to_string())
.with_message(msg)
.with_note(
"We'd like to give better errors, but serde errors are horrible to work with...",
)
@ -54,25 +130,37 @@ mod error_reporting {
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)| {
if i < line_nr {
acc + line.len()
} else if i == line_nr {
acc + col_nr
} else {
acc
}
})
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() {
const TEST_JSON_CONFIG: &str = "{ \"example_value\": 42, \"example_string\": \"meow\" }";
const TEST_JSON_CONFIG_BROKEN: &str = "{
\"example_value\": \"42\",
\"example_string\": \"meow\"
}";
let args = Args::parse();
let cfg = Configs::read(args.config_file);
dbg!(Configs::read_json(TEST_JSON_CONFIG_BROKEN)
.map_err(|e| report_serde_json_err(TEST_JSON_CONFIG_BROKEN, e)));
if !(args.no_startup_message || cfg.no_startup_message) {
print_startup_msg();
}
}