diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 16 | ||||
| -rw-r--r-- | src/config.rs | 90 | ||||
| -rw-r--r-- | src/main.rs | 58 |
3 files changed, 150 insertions, 14 deletions
@@ -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 |
