diff --git a/Cargo.toml b/Cargo.toml index 8b15df1..af9256e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ indicatif = "0.16.2" linkify = "0.8.0" ahash = "0.7.6" rand_xoshiro = "0.6.0" +memmap = "0.7.0" [profile.release] lto = "fat" diff --git a/src/constants.rs b/src/constants.rs index ffef2fd..a8b193f 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,26 +1,26 @@ -pub const FACES_SIZE: usize = 15; -pub const FACES: [&str; FACES_SIZE] = [ - "OwO", "UwU", ">w<", "^w^", "ÚwÚ", "^-^", ":3", "x3", "xDD", ";;w;;", ">_<", ">_>", "^.^", - ":33", "uWu", +pub const FACES_SIZE: usize = 14; +pub const FACES: [&[u8]; FACES_SIZE] = [ + b"OwO", b"UwU", b">w<", b"^w^", b"^-^", b":3", b"x3", b"xDD", b";;w;;", b">_<", b">_>", b"^.^", + b":33", b"uWu", ]; pub const ACTIONS_SIZE: usize = 17; -pub const ACTIONS: [&str; ACTIONS_SIZE] = [ - "*notices bulge*", - "*cries*", - "*hugs tightly*", - "*screams*", - "*looks away*", - "*blushes*", - "*sweats*", - "*cuddles you*", - "*moans*", - "*giggles shyly*", - "*looks at you*", - "*twerks*", - "*sighs*", - "*leans over*", - "*pokes you*", - "*teleports behind you*", - "*shuffles closer*", +pub const ACTIONS: [&[u8]; ACTIONS_SIZE] = [ + b"*notices bulge*", + b"*cries*", + b"*hugs tightly*", + b"*screams*", + b"*looks away*", + b"*blushes*", + b"*sweats*", + b"*cuddles you*", + b"*moans*", + b"*giggles shyly*", + b"*looks at you*", + b"*twerks*", + b"*sighs*", + b"*leans over*", + b"*pokes you*", + b"*teleports behind you*", + b"*shuffles closer*", ]; diff --git a/src/io.rs b/src/io.rs index e1ca5cb..c9833fa 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,46 +1,10 @@ -use std::fs::File; -use std::io::{BufRead, BufReader, BufWriter, Error, Write}; -use std::path::Path; - -pub struct UwUInFile { - file_bytes: u64, - reader: BufReader, - pub buffer: String, -} +use std::io::{BufWriter, Error, Write}; #[repr(transparent)] pub struct UwUOutFile { writer: BufWriter, } -impl UwUInFile { - #[inline] - pub fn new(path: &Path) -> Result { - let file = File::open(path)?; - - Ok(UwUInFile { - file_bytes: file.metadata()?.len(), - reader: BufReader::new(file), - buffer: String::new(), - }) - } - - #[inline] - pub fn read_until_newline(&mut self) -> Result { - self.reader.read_line(&mut self.buffer) - } - - #[inline] - pub fn clear_buffer(&mut self) { - self.buffer.clear(); - } - - #[inline] - pub fn get_file_bytes(&self) -> u64 { - self.file_bytes - } -} - impl UwUOutFile { #[inline] pub fn new(writer: T) -> UwUOutFile { @@ -49,23 +13,8 @@ impl UwUOutFile { } } - #[inline] - pub fn write_newline(&mut self) -> Result<(), Error> { - self.writer.write_all(b"\n") - } - - #[inline] - pub fn write_string(&mut self, write_str: &str) -> Result<(), Error> { - self.writer.write_all(write_str.as_bytes()) - } - #[inline] pub fn write_bytes(&mut self, write_bytes: &[u8]) -> Result<(), Error> { self.writer.write_all(write_bytes) } - - #[inline] - pub fn write_fmt(&mut self, fmt: std::fmt::Arguments) -> Result<(), Error> { - self.writer.write_fmt(fmt) - } } diff --git a/src/lib.rs b/src/lib.rs index 47ed5c1..74950d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use constants::ACTIONS; use constants::ACTIONS_SIZE; use constants::FACES; use constants::FACES_SIZE; -use io::{UwUInFile, UwUOutFile}; +use io::UwUOutFile; use progress_bar::UwUProgressBar; use seeder::UwUSeeder; @@ -19,34 +19,43 @@ mod progress_bar; mod seeder; #[derive(Debug)] -struct Modifiers { - supplied_at_runtime: bool, +pub struct UwUify<'a> { + text: &'a str, + input: &'a str, + output: &'a str, words: f64, faces: f64, actions: f64, stutters: f64, -} - -#[derive(Debug)] -pub struct UwUify<'a> { - text: &'a str, - input: &'a Path, - output: &'a str, - modifiers: Modifiers, random: bool, linkify: LinkFinder, } +impl<'a> Default for UwUify<'a> { + fn default() -> Self { + Self { + text: "", + input: "", + output: "", + words: 1.0, + faces: 0.05, + actions: 0.125, + stutters: 0.225, + random: false, + linkify: LinkFinder::new(), + } + } +} + impl<'a> UwUify<'a> { pub fn new( text: Option<&'a str>, - infile: Option<&'a Path>, + infile: Option<&'a str>, outfile: Option<&'a str>, - supplied_at_runtime: bool, - words: f64, - faces: f64, - actions: f64, - stutters: f64, + words: Option<&'a str>, + faces: Option<&'a str>, + actions: Option<&'a str>, + stutters: Option<&'a str>, random: bool, ) -> UwUify<'a> { // I dislike this @@ -54,21 +63,28 @@ impl<'a> UwUify<'a> { let mut linkify = LinkFinder::new(); linkify.kinds(&[LinkKind::Email, LinkKind::Url]); linkify.url_must_have_scheme(false); - - UwUify { + let mut uwuify = UwUify { text: text.unwrap_or_default(), - input: infile.unwrap_or(Path::new("")), + input: infile.unwrap_or_default(), output: outfile.unwrap_or_default(), - modifiers: Modifiers { - supplied_at_runtime, - words, - faces, - actions, - stutters, - }, random, linkify, + ..Default::default() + }; + + if let Some(words) = words { + uwuify.words = words.parse::().unwrap(); + } + if let Some(faces) = faces { + uwuify.faces = faces.parse::().unwrap(); } + if let Some(actions) = actions { + uwuify.actions = actions.parse::().unwrap(); + } + if let Some(stutters) = stutters { + uwuify.stutters = stutters.parse::().unwrap(); + } + uwuify } pub fn uwuify(&self) -> Result<(), Error> { @@ -84,11 +100,9 @@ impl<'a> UwUify<'a> { } let mut uwu_out_file = UwUOutFile::new(File::create(&self.output)?); + let uwu_progress_bar = UwUProgressBar::new(); self.uwuify_sentence(&self.text, &mut uwu_out_file)?; - let mut uwu_progress_bar = UwUProgressBar::new(self.text.len() as u64); - - uwu_progress_bar.update_progess(self.text.len()); uwu_progress_bar.finish("UwU'ifying Complete!"); Ok(()) } else { @@ -100,7 +114,7 @@ impl<'a> UwUify<'a> { let mut out = UwUOutFile::new(std::io::sink()); self.uwuify_sentence(&self.text, &mut out)?; #[cfg(not(test))] - out.write_newline()?; + out.write_bytes(b"\n")?; Ok(()) } } else { @@ -112,23 +126,17 @@ impl<'a> UwUify<'a> { )); } - let mut uwu_in_file = UwUInFile::new(&self.input)?; - let mut uwu_out_file = UwUOutFile::new(File::create(&self.output)?); - let mut uwu_progress_bar = UwUProgressBar::new(uwu_in_file.get_file_bytes()); - - loop { - let bytes_read_in = uwu_in_file.read_until_newline()?; - if bytes_read_in != 0 { - self.uwuify_sentence(&uwu_in_file.buffer, &mut uwu_out_file)?; - uwu_out_file.write_newline()?; - - uwu_progress_bar.update_progess(bytes_read_in); - uwu_in_file.clear_buffer(); - } else { - uwu_progress_bar.finish("UwU'ifying Complete!"); - return Ok(()); - } - } + let uwu_progress_bar = UwUProgressBar::new(); + self.uwuify_sentence( + unsafe { + std::str::from_utf8_unchecked( + memmap::Mmap::map(&File::open(&self.input)?)?.as_ref(), + ) + }, + &mut UwUOutFile::new(File::create(&self.output)?), + )?; + uwu_progress_bar.finish("UwU'ifying Complete!"); + Ok(()) } } @@ -137,97 +145,66 @@ impl<'a> UwUify<'a> { text: &str, out: &mut UwUOutFile, ) -> Result<(), std::io::Error> { - text.split_whitespace().try_for_each(|word| { - self.uwuify_word(word, out)?; - out.write_string(" ") - }) - } - - fn uwuify_word(&self, word: &str, out: &mut UwUOutFile) -> Result<(), Error> { - let mut seeder = UwUSeeder::new(word, self.random); - let random_value = seeder.random(); - - if !self.modifiers.supplied_at_runtime { - if random_value <= self.modifiers.faces { - out.write_string(FACES[seeder.random_int(0..FACES_SIZE)])?; - out.write_bytes(b" ")?; - } else if random_value <= self.modifiers.actions { - out.write_string(ACTIONS[seeder.random_int(0..ACTIONS_SIZE)])?; - out.write_bytes(b" ")?; - } else if random_value <= self.modifiers.stutters { - (0..seeder.random_int(1..2)).into_iter().try_for_each(|_| { - out.write_bytes(&word.as_bytes()[0..1])?; - out.write_bytes(b"-") - })?; - } - } else { - if random_value <= self.modifiers.stutters { - (0..seeder.random_int(1..2)).into_iter().try_for_each(|_| { - out.write_bytes(&word.as_bytes()[0..1])?; - out.write_bytes(b"-") - })?; - } - if random_value <= self.modifiers.faces { - out.write_string(FACES[seeder.random_int(0..FACES_SIZE)])?; - out.write_bytes(b" ")?; - } - if random_value <= self.modifiers.actions { - out.write_string(ACTIONS[seeder.random_int(0..ACTIONS_SIZE)])?; - out.write_bytes(b" ")?; - } - } - - if self.linkify.links(word).count() > 0 { - return out.write_string(word); - } - - let mut seeder = UwUSeeder::new(word, self.random); - if seeder.random() > self.modifiers.words { - return out.write_string(word); - } - - let word_bytes = word.as_bytes(); - let uwu_text_count = word.len(); - - (0..uwu_text_count).try_for_each(|index| { - let previous_char = - *word_bytes.get(index - 1).unwrap_or_else(|| &word_bytes[0]) as char; - let current_char = word_bytes[index] as char; + text.as_bytes() + .split(|w| matches!(*w, b'\t' | b'\x0C' | b'\r' | b' ')) + .try_for_each(|word| { + let mut seeder = UwUSeeder::new(word, self.random); + let random_value = seeder.random(); + + if random_value <= self.faces { + out.write_bytes(FACES[seeder.random_int(0..FACES_SIZE)])?; + out.write_bytes(b" ")?; + } else if random_value <= self.actions { + out.write_bytes(ACTIONS[seeder.random_int(0..ACTIONS_SIZE)])?; + out.write_bytes(b" ")?; + } else if random_value <= self.stutters { + (0..seeder.random_int(1..2)).into_iter().try_for_each(|_| { + out.write_bytes(&word[0..1])?; + out.write_bytes(b"-") + })?; + } - match current_char { - 'L' | 'R' => out.write_bytes(b"W"), - 'l' | 'r' => out.write_bytes(b"w"), - 'E' | 'e' => match previous_char { - 'N' | 'n' => out.write_fmt(format_args!("y{}", current_char)), - _ => out.write_fmt(format_args!("{}", current_char)), - }, - 'A' | 'I' | 'O' | 'U' | 'a' | 'i' | 'o' | 'u' => match previous_char { - 'N' | 'n' => out.write_fmt(format_args!("y{}", current_char)), - _ => out.write_fmt(format_args!("{}", current_char)), - }, - _ => out.write_fmt(format_args!("{}", current_char)), - } - }) + if self + .linkify + .links(unsafe { std::str::from_utf8_unchecked(word) }) + .count() + > 0 + || random_value > self.words + { + out.write_bytes(word)?; + } else { + (0..word.len()).try_for_each(|index| match word[index] { + b'L' | b'R' => out.write_bytes(b"W"), + b'l' | b'r' => out.write_bytes(b"w"), + b'E' | b'e' | b'A' | b'I' | b'O' | b'U' | b'a' | b'i' | b'o' | b'u' => { + match word.get(index - 1).unwrap_or(&word[0]) { + b'N' | b'n' => out.write_bytes(&[b'y', word[index]]), + _ => out.write_bytes(&[word[index]]), + } + } + _ => out.write_bytes(&[word[index]]), + })?; + } + out.write_bytes(b" ") + }) } } #[cfg(test)] mod tests { extern crate test; + use linkify::{LinkFinder, LinkKind}; #[bench] fn uwu_bench(b: &mut test::Bencher) { - let uwuify = super::UwUify::new( - Some(include_str!("test.txt")), - None, - None, - false, - 1.0, - 0.05, - 0.125, - 0.225, - false, - ); + let mut linkify = LinkFinder::new(); + linkify.kinds(&[LinkKind::Email, LinkKind::Url]); + linkify.url_must_have_scheme(false); + let uwuify = super::UwUify { + text: include_str!("test.txt"), + linkify, + ..Default::default() + }; b.iter(|| uwuify.uwuify()); } } diff --git a/src/main.rs b/src/main.rs index cae42f9..87415ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,6 @@ use clap::{Arg, ArgGroup, ErrorKind}; use uwuify::UwUify; -macro_rules! modifiers_supplied_at_runtime { - ($faces_occurrences:expr,$actions_occurrences:expr,$stutters_occurrences:expr) => { - $faces_occurrences > 0 || $actions_occurrences > 0 || $stutters_occurrences > 0 - }; -} - macro_rules! app { () => { clap::App::new(env!("CARGO_PKG_NAME")) @@ -103,17 +97,12 @@ fn main() { match UwUify::new( matches.value_of("text"), - matches.value_of("infile").map(|f| std::path::Path::new(f)), + matches.value_of("infile"), matches.value_of("outfile"), - modifiers_supplied_at_runtime!( - matches.occurrences_of("faces"), - matches.occurrences_of("actions"), - matches.occurrences_of("stutters") - ), - matches.value_of_t_or_exit("words"), - matches.value_of_t_or_exit("faces"), - matches.value_of_t_or_exit("actions"), - matches.value_of_t_or_exit("stutters"), + matches.value_of("words"), + matches.value_of("faces"), + matches.value_of("actions"), + matches.value_of("stutters"), matches.is_present("random"), ) .uwuify() diff --git a/src/progress_bar.rs b/src/progress_bar.rs index 93e94ae..841d102 100644 --- a/src/progress_bar.rs +++ b/src/progress_bar.rs @@ -5,20 +5,16 @@ pub struct UwUProgressBar(ProgressBar); impl UwUProgressBar { #[inline] - pub fn new(file_total_bytes: u64) -> UwUProgressBar { - let progress_bar = ProgressBar::new(file_total_bytes); - progress_bar.set_style(ProgressStyle::default_bar() - .template("{spinner:.green} [{elapsed_precise}] [{bar:60.cyan/blue}] {bytes}/{total_bytes} ({eta}) {msg}") - .progress_chars("#>-")); + pub fn new() -> UwUProgressBar { + let progress_bar = ProgressBar::new_spinner(); + progress_bar.set_style( + ProgressStyle::default_spinner().template("{spinner:.green} [{elapsed_precise}] {msg}"), + ); + progress_bar.enable_steady_tick(100); UwUProgressBar(progress_bar) } - #[inline] - pub fn update_progess(&mut self, bytes_read_in: usize) { - self.0.inc(bytes_read_in as u64); - } - #[inline] pub fn finish(&self, message: &'static str) { self.0.finish_with_message(message); diff --git a/src/seeder.rs b/src/seeder.rs index 9572bb8..c5e04ee 100644 --- a/src/seeder.rs +++ b/src/seeder.rs @@ -13,10 +13,10 @@ pub struct UwUSeeder { impl UwUSeeder { #[inline] - pub fn new(word: &str, random: bool) -> UwUSeeder { + pub fn new(word: &[u8], random: bool) -> UwUSeeder { let entropy = if !random { let mut hasher = ahash::AHasher::default(); - hasher.write(word.as_bytes()); + hasher.write(word); hasher.finish() } else { rand::rngs::OsRng.next_u64() @@ -30,7 +30,7 @@ impl UwUSeeder { #[inline] pub fn random(&mut self) -> f64 { - f64::from_ne_bytes(self.floating.next_u64().to_ne_bytes()) + self.floating.gen_range(0.0..1.0) } #[inline]