aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock64
-rw-r--r--Cargo.toml3
-rw-r--r--src/app.rs16
-rw-r--r--src/config.rs90
-rw-r--r--src/main.rs58
5 files changed, 217 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dbf7f8f..f9e2d9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 01c0792..c5e7235 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
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