aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app.rs16
-rw-r--r--src/config.rs90
-rw-r--r--src/main.rs58
3 files changed, 150 insertions, 14 deletions
diff --git a/src/app.rs b/src/app.rs
index d5edae0..6a0044b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -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
XMR address: 854DmXNrxULU3ZFJVs4Wc8PFhbq29RhqHhY8W6cdWrtFN3qmooKyyeYPcDzZTNRxphhJ5UzASQfAdEMwSteVqymk28aLhqj