diff options
| -rw-r--r-- | Cargo.lock | 64 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/app.rs | 16 | ||||
| -rw-r--r-- | src/config.rs | 90 | ||||
| -rw-r--r-- | src/main.rs | 58 |
5 files changed, 217 insertions, 14 deletions
@@ -68,6 +68,12 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + +[[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -216,6 +222,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -246,9 +258,12 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" name = "marika-finger-blaster" version = "0.0.2" dependencies = [ + "anyhow", "clap", "crossterm", "rand", + "serde", + "serde_json", "tokio", ] @@ -415,6 +430,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -720,3 +778,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" @@ -4,7 +4,10 @@ version = "0.0.2" edition = "2024" [dependencies] +anyhow = "1.0.101" clap = { version = "4.5.27", features = ["derive"] } crossterm = "0.28.1" rand = "0.8.5" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" tokio = { version = "1.43.1", features = ["sync", "macros", "rt", "rt-multi-thread", "time"] } @@ -1,10 +1,10 @@ use std::{ collections::HashSet, - error::Error, io::{Stdout, Write, stdout}, time::Duration, }; +use anyhow::Result; use crossterm::{ ExecutableCommand, QueueableCommand, cursor::{MoveDown, MoveTo, MoveToColumn, RestorePosition, SavePosition, SetCursorStyle}, @@ -21,6 +21,7 @@ use tokio::{ }; use crate::{ + config::Quote, error::{TerminalTooSmallError, TyperError, WordTooLongError}, input::{Event, handle_input}, state::State, @@ -49,8 +50,9 @@ pub struct App<'a> { } impl App<'_> { - pub fn new(quote: &str) -> App<'_> { + pub fn new(quote: &Quote) -> App<'_> { let (event_tx, event_rx): (Sender<Event>, Receiver<Event>) = channel(10); + let quote = quote.text.as_str(); App { stdout: stdout(), quote: quote.split_whitespace().filter(|s| !s.is_empty()).collect(), @@ -68,7 +70,7 @@ impl App<'_> { } } - async fn run(&mut self) -> Result<(f64, f64, String), Box<dyn Error>> { + async fn run(&mut self) -> Result<(f64, f64, String)> { self.stdout .execute(EnterAlternateScreen)? .execute(SetCursorStyle::SteadyBar)?; @@ -99,7 +101,7 @@ impl App<'_> { return Ok((wpm, accuracy, history)); } - pub async fn start(&mut self) -> Result<(), Box<dyn Error>> { + pub async fn start(&mut self) -> Result<()> { let (wpm, accuracy, history) = self.run().await?; if self.completed { println!( @@ -144,7 +146,7 @@ impl App<'_> { return a.join(" "); } - async fn process(&mut self) -> Result<(), Box<dyn Error>> { + async fn process(&mut self) -> Result<()> { let event = self.event_rx.recv().await.unwrap(); match event { Event::Terminate => self.running = false, @@ -160,7 +162,7 @@ impl App<'_> { return Ok(()); } - async fn handle_keypress(&mut self, k: char) -> Result<(), Box<dyn Error>> { + async fn handle_keypress(&mut self, k: char) -> Result<()> { self.state.buffer.push(k); let buffer_length = self.state.buffer.chars().count(); let current_word = self.quote[self.state.current]; @@ -195,7 +197,7 @@ impl App<'_> { } } - async fn render(&mut self) -> Result<(), Box<dyn Error>> { + async fn render(&mut self) -> Result<()> { if !self.should_render { return Ok(()); } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..90ac579 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,90 @@ +use std::{env, fs, path::PathBuf}; + +use anyhow::{Result, anyhow}; +use rand::{Rng, rngs::ThreadRng}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Quoter { + #[serde(skip)] + randomizer: ThreadRng, + groups: ( + (usize, usize), + (usize, usize), + (usize, usize), + (usize, usize), + ), + quotes: Vec<Quote>, +} + +impl Quoter { + pub fn get_random(&mut self) -> Result<Quote> { + self.get_range((0, self.quotes.len())) + } + + fn get_range(&mut self, range: (usize, usize)) -> Result<Quote> { + if self.quotes.is_empty() { + return Err(anyhow!("There are no quotes in your quote files")); + } + let (l, r) = range; + if l > r || l > self.quotes.len() || r > self.quotes.len() { + return Err(anyhow!("Your quotes file is corrupted")); + } + Ok(self + .quotes + .get(self.randomizer.gen_range(l..=r)) + .cloned() + .unwrap()) + } + + pub fn get_short(&mut self) -> Result<Quote> { + self.get_range(self.groups.0) + } + + pub fn get_medium(&mut self) -> Result<Quote> { + self.get_range(self.groups.1) + } + + pub fn get_long(&mut self) -> Result<Quote> { + self.get_range(self.groups.2) + } + + pub fn get_huge(&mut self) -> Result<Quote> { + self.get_range(self.groups.3) + } +} + +#[derive(Deserialize, Clone)] +pub struct Quote { + pub text: String, + pub source: Option<String>, +} + +pub fn get_config_folder() -> Result<PathBuf> { + let mut path = match env::var("HOME") { + Ok(a) => PathBuf::from(a), + Err(_) => panic!("Can't access config folder"), + }; + + path.push(".config"); + path.push("marika-finger-blaster"); + if !path.exists() { + if path.is_file() { + fs::remove_file(&path)?; + } + + fs::create_dir_all(&path)?; + } + Ok(path) +} + +pub fn get_quoter() -> Result<Quoter> { + let mut config_folder = get_config_folder()?; + config_folder.push("quotes.json"); + if !config_folder.exists() { + return Err(anyhow!("There's no quotes.json file")); + } + let r = fs::File::open(&config_folder)?; + let quoter = serde_json::from_reader(r)?; + Ok(quoter) +} diff --git a/src/main.rs b/src/main.rs index efb530f..cce7334 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,41 @@ #![allow(clippy::needless_return)] mod app; +pub mod config; pub mod error; pub mod input; pub mod state; use std::{ - error::Error, fs::read_to_string, io::{IsTerminal, Read, stdin}, path::Path, }; +use anyhow::Result; use app::App; use clap::Parser; use rand::{Rng, thread_rng}; +use crate::config::{Quote, get_quoter}; + #[derive(Parser)] struct Args { /// Turns all text into lowercase (NOOB mode) #[arg(short, long)] lower: bool, + #[arg(short, long)] + short: bool, + #[arg(short, long)] + medium: bool, + #[arg(short, long)] + long: bool, + #[arg(short, long)] + huge: bool, quote: Option<String>, } -fn generate_quotes(path: &Path) -> Result<Vec<String>, Box<dyn Error>> { +fn generate_quotes(path: &Path) -> Result<Vec<String>> { let mut ris = Vec::new(); if path.is_file() { ris.push(read_to_string(path)?); @@ -43,24 +54,57 @@ fn generate_quotes(path: &Path) -> Result<Vec<String>, Box<dyn Error>> { } #[tokio::main] -async fn main() -> Result<(), Box<dyn Error>> { +async fn main() -> Result<()> { let args = Args::parse(); let mut quote = if !stdin().is_terminal() { let mut b = Vec::new(); stdin().read_to_end(&mut b).unwrap(); - String::from_utf8(b).unwrap() + Quote { + text: String::from_utf8(b)?, + source: None, + } } else if let Some(q) = &args.quote { let path = Path::new(q); let mut quotes = generate_quotes(path).unwrap(); let mut rng = thread_rng(); let chosen = rng.gen_range(0..quotes.len()); - quotes.remove(chosen) + Quote { + text: quotes.remove(chosen), + source: None, + } } else { - todo!() + let mut specifier = 0; + if args.short { + specifier += 1; + } + if args.medium { + specifier += 1; + } + if args.long { + specifier += 1; + } + if args.huge { + specifier += 1; + } + if specifier > 1 { + panic!("You can't use more than one quote length specifier"); + } + let mut quoter = get_quoter()?; + if args.short { + quoter.get_short()? + } else if args.medium { + quoter.get_medium()? + } else if args.long { + quoter.get_long()? + } else if args.huge { + quoter.get_huge()? + } else { + quoter.get_random()? + } }; if args.lower { - quote = quote.to_lowercase(); + quote.text = quote.text.to_lowercase(); } // TODO Add more options to choose quotes |
