start working on a cli app #7

Merged
schrottkatze merged 12 commits from :app into main 2024-01-20 19:09:52 +00:00
5 changed files with 140 additions and 151 deletions
Showing only changes of commit 96374b6491 - Show all commits

12
crates/app/src/cli.rs Normal file
View file

@ -0,0 +1,12 @@
use std::path::PathBuf;
use clap::Parser;
#[derive(Parser)]
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,
}

74
crates/app/src/config.rs Normal file
View file

@ -0,0 +1,74 @@
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<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)
multisamplednight marked this conversation as resolved Outdated

For later, not now: Should use proper error handling.

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

put that on the non-existing todo list for when we have a proper error handling and reporting system
}
}
};
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),
}
}
}

View file

@ -0,0 +1,37 @@
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) -> ! {
multisamplednight marked this conversation as resolved Outdated

Maybe report_serde_err could take a custom error, which the errors from ron and serde_json are converted into.

Then that streamlined error could report a bit more specific using serde_error::Error::classify and ron::Error.

Maybe `report_serde_err` could take a custom error, which the errors from `ron` and `serde_json` are converted into. Then that streamlined error could report a bit more specific using `serde_error::Error::classify` and `ron::Error`.

ye, that's a hack bc i was too lazy to implement a proper error type ^^'

ye, that's a hack bc i was too lazy to implement a proper error type ^^'
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,
})
}

View file

@ -4,157 +4,10 @@ use welcome_msg::print_startup_msg;
use crate::config::Configs; use crate::config::Configs;
mod cli { mod cli;
use std::path::PathBuf; mod config;
mod error_reporting;
use clap::Parser; mod welcome_msg;
#[derive(Parser)]
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,
}
}
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<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::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() { fn main() {
let args = Args::parse(); let args = Args::parse();

View file

@ -0,0 +1,13 @@
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());
multisamplednight marked this conversation as resolved Outdated

Can always use now_utc, since no calculations are performed based on the timezone.

Can always use `now_utc`, since no calculations are performed based on the timezone.

the day might very well be timezone dependent, depending on where you live though?

the day might very well be timezone dependent, depending on where you live though?

Nevermind, I misinterpreted now_utc. I thought it'd return the current system time and assume it to be UTC (like SystemTime::now does), but it doesn't:

>> :dep time = { features = ["local-offset"] }
   Compiling libc v0.2.152
   Compiling powerfmt v0.2.0
   Compiling num_threads v0.1.6
   Compiling deranged v0.3.11
   Compiling time v0.3.31
>> use time::OffsetDateTime;
>> OffsetDateTime::now_utc()
2024-01-12 21:03:54.32211146 +00:00:00
>> OffsetDateTime::now_local()
Ok(2024-01-12 22:03:58.663618677 +01:00:00)
Nevermind, I misinterpreted `now_utc`. I thought it'd return the current system time and assume it to be UTC (like `SystemTime::now` does), but it doesn't: ``` >> :dep time = { features = ["local-offset"] } Compiling libc v0.2.152 Compiling powerfmt v0.2.0 Compiling num_threads v0.1.6 Compiling deranged v0.3.11 Compiling time v0.3.31 >> use time::OffsetDateTime; >> OffsetDateTime::now_utc() 2024-01-12 21:03:54.32211146 +00:00:00 >> OffsetDateTime::now_local() Ok(2024-01-12 22:03:58.663618677 +01:00:00) ```
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");
}
}