diff --git a/Cargo.toml b/Cargo.toml index 0b316b2..6e228fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ repository = "https://github.com/sgoudham/uwuifyy" keywords = ["cli", "uwu", "owo", "uwuify", "anime"] categories = ["command-line-utilities"] exclude = [ - "examples/the-complete-works-of-william-shakespeare.txt", - "examples/tiktok_app_reviews.csv", - "examples/tokyo-2020-olympics-tweets.csv", - "examples/uwu/**", - ".github/**", - "scripts/**" + "examples/the-complete-works-of-william-shakespeare.txt", + "examples/tiktok_app_reviews.csv", + "examples/tokyo-2020-olympics-tweets.csv", + "examples/uwu/**", + ".github/**", + "scripts/**", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -31,11 +31,19 @@ linkify = "0.8.0" rand_xoshiro = "0.6.0" ahash = "0.7.6" memmap = "0.7.0" +kaomoji-ru = "1.0.0" [profile.release] lto = "fat" codegen-units = 1 panic = "abort" +[profile.dev] +overflow-checks = false + +[profile.bench] +lto = "fat" +codegen-units = 1 + [features] -bench = [] \ No newline at end of file +bench = [] diff --git a/infile.txt b/infile.txt new file mode 100644 index 0000000..e80fa1c --- /dev/null +++ b/infile.txt @@ -0,0 +1 @@ +According to all known laws of aviation, there is no way a bee should be able to fly. diff --git a/outfile.txt b/outfile.txt new file mode 100644 index 0000000..0cb44dd --- /dev/null +++ b/outfile.txt @@ -0,0 +1 @@ +Accowding to aww uWu knyown waws of aviation, thewe i-is nyo way *moans* a bee shouwd be abwe to fwy. diff --git a/src/constants.rs b/src/constants.rs index 4a9cc51..4102fa9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,19 +1,204 @@ -pub const FACES_SIZE: usize = 14; +pub const FACES_SIZE: usize = 106; + 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 ", + b"OwO", + b"UwU", + b">w<", + b"^w^", + b"^-^", + b":3", + b"x3", + b"xDD", + b";;w;;", + b">_<", + b">_>", + b"^.^", + b":33", + b"uWu", + // (* ^ ω ^) + kaomoji_ru::positive_emotions::JOY[0], + // (´ ∀ ` *) + kaomoji_ru::positive_emotions::JOY[1], + // (o^▽^o) + kaomoji_ru::positive_emotions::JOY[4], + // (⌒▽⌒)☆ + kaomoji_ru::positive_emotions::JOY[5], + // <( ̄︶ ̄)> + kaomoji_ru::positive_emotions::JOY[6], + // ヽ(・∀・)ノ + kaomoji_ru::positive_emotions::JOY[8], + // (´。• ω •。`) + kaomoji_ru::positive_emotions::JOY[9], + // ( ̄ω ̄) + kaomoji_ru::positive_emotions::JOY[10], + // (o・ω・o) + kaomoji_ru::positive_emotions::JOY[12], + // ヽ(*・ω・)ノ + kaomoji_ru::positive_emotions::JOY[14], + // (^人^) + kaomoji_ru::positive_emotions::JOY[16], + // (*´▽`*) + kaomoji_ru::positive_emotions::JOY[18], + // ( ´ ω ` ) + kaomoji_ru::positive_emotions::JOY[20], + // (≧◡≦) + kaomoji_ru::positive_emotions::JOY[22], + // (o´∀`o) + kaomoji_ru::positive_emotions::JOY[23], + // (´• ω •`) + kaomoji_ru::positive_emotions::JOY[24], + // (^▽^) + kaomoji_ru::positive_emotions::JOY[25], + // (⌒ω⌒) + kaomoji_ru::positive_emotions::JOY[26], + // ╰(▔∀▔)╯ + kaomoji_ru::positive_emotions::JOY[28], + // (*^‿^*) + kaomoji_ru::positive_emotions::JOY[30], + // (✯◡✯) + kaomoji_ru::positive_emotions::JOY[32], + // (*≧ω≦*) + kaomoji_ru::positive_emotions::JOY[34], + // (☆▽☆) + kaomoji_ru::positive_emotions::JOY[35], + // \(≧▽≦)/ + kaomoji_ru::positive_emotions::JOY[37], + // ヽ(o^▽^o)ノ + kaomoji_ru::positive_emotions::JOY[38], + // (*°▽°*) + kaomoji_ru::positive_emotions::JOY[40], + // (✧ω✧) + kaomoji_ru::positive_emotions::JOY[42], + // ヽ(*⌒▽⌒*)ノ + kaomoji_ru::positive_emotions::JOY[43], + // ヽ(>∀<☆)ノ + kaomoji_ru::positive_emotions::JOY[48], + // o(≧▽≦)o + kaomoji_ru::positive_emotions::JOY[49], + // (☆ω☆) + kaomoji_ru::positive_emotions::JOY[50], + // (っ˘ω˘ς ) + kaomoji_ru::positive_emotions::JOY[51], + // \(★ω★)/ + kaomoji_ru::positive_emotions::JOY[57], + // (╯✧▽✧)╯ + kaomoji_ru::positive_emotions::JOY[60], + // o(>ω<)o + kaomoji_ru::positive_emotions::JOY[61], + // (´・ᴗ・ ` ) + kaomoji_ru::positive_emotions::JOY[72], + // (¬‿¬ ) + kaomoji_ru::positive_emotions::JOY[77], + // („• ᴗ •„) + kaomoji_ru::positive_emotions::JOY[84], + // (´ ω `♡) + kaomoji_ru::positive_emotions::LOVE[12], + // (♡°▽°♡) + kaomoji_ru::positive_emotions::LOVE[17], + // ♡(。- ω -) + kaomoji_ru::positive_emotions::LOVE[18], + // (´。• ω •。`) ♡ + kaomoji_ru::positive_emotions::LOVE[22], + // (❤ω❤) + kaomoji_ru::positive_emotions::LOVE[39], + // (´,,•ω•,,)♡ + kaomoji_ru::positive_emotions::LOVE[45], + // (*ノωノ) + kaomoji_ru::positive_emotions::EMBARRESMENT[5], + // (⁄ ⁄•⁄ω⁄•⁄ ⁄) + kaomoji_ru::positive_emotions::EMBARRESMENT[17], + // (# ̄ω ̄) + kaomoji_ru::negative_emotions::DISSATISFACTION[7], + // (>m<) + kaomoji_ru::negative_emotions::DISSATISFACTION[9], + // (」°ロ°)」 + kaomoji_ru::negative_emotions::DISSATISFACTION[10], + // (ᗒᗣᗕ)՞ + kaomoji_ru::negative_emotions::DISSATISFACTION[24], + // (#`Д´) + kaomoji_ru::negative_emotions::ANGER[0], + // (・`ω´・) + kaomoji_ru::negative_emotions::ANGER[4], + // (°ㅂ°╬) + kaomoji_ru::negative_emotions::ANGER[17], + // (╬ Ò﹏Ó) + kaomoji_ru::negative_emotions::ANGER[25], + // (´-ω-`) + kaomoji_ru::negative_emotions::SADNESS[2], + // (-ω-、) + kaomoji_ru::negative_emotions::SADNESS[6], + // ( ; ω ; ) + kaomoji_ru::negative_emotions::SADNESS[9], + // ( ╥ω╥ ) + kaomoji_ru::negative_emotions::SADNESS[16], + // (ノωヽ) + kaomoji_ru::negative_emotions::FEAR[0], + // (・_・ヾ + kaomoji_ru::neutral_emotions::CONFUSSION[5], + // ╮( ̄ω ̄;)╭ + kaomoji_ru::neutral_emotions::CONFUSSION[10], + // (*・ω・)ノ + kaomoji_ru::various_actions::GREETING[0], + // (✧∀✧)/ + kaomoji_ru::various_actions::GREETING[25], + // (つ≧▽≦)つ + kaomoji_ru::various_actions::HUGGING[1], + // (つ✧ω✧)つ + kaomoji_ru::various_actions::HUGGING[2], + // ⊂(´• ω •`⊂) + kaomoji_ru::various_actions::HUGGING[8], + // ⊂(・ω・*⊂) + kaomoji_ru::various_actions::HUGGING[9], + // (^ω~) + kaomoji_ru::various_actions::WINKING[3], + // |・ω・) + kaomoji_ru::various_actions::HIDING[0], + // ☆ミ(o*・ω・)ノ + kaomoji_ru::various_actions::RUNNING[0], + // C= C= C= C= C=┌(;・ω・)┘ + kaomoji_ru::various_actions::RUNNING[1], + // ε===(っ≧ω≦)っ + kaomoji_ru::various_actions::RUNNING[6], + // (-ω-) zzZ + kaomoji_ru::various_actions::SLEEPING[3], + // (=^・ω・^=) + kaomoji_ru::animals::CAT[0], + // (=^・ェ・^=) + kaomoji_ru::animals::CAT[1], + // (=①ω①=) + kaomoji_ru::animals::CAT[2], + // ( =ω=)..nyaa + kaomoji_ru::animals::CAT[3], + // (= ; ェ ; =) + kaomoji_ru::animals::CAT[4], + // (=`ω´=) + kaomoji_ru::animals::CAT[5], + // (=^‥^=) + kaomoji_ru::animals::CAT[6], + // ( =ノωヽ=) + kaomoji_ru::animals::CAT[9], + // (=^ ◡ ^=) + kaomoji_ru::animals::CAT[11], + // (=^-ω-^=) + kaomoji_ru::animals::CAT[12], + // ヾ(=`ω´=)ノ” + kaomoji_ru::animals::CAT[13], + // (^• ω •^) + kaomoji_ru::animals::CAT[14], + // (/ =ω=)/ + kaomoji_ru::animals::CAT[15], + // ฅ(•ㅅ•❀)ฅ + kaomoji_ru::animals::CAT[16], + // ଲ(ⓛ ω ⓛ)ଲ + kaomoji_ru::animals::CAT[18], + // (^=◕ᴥ◕=^) + kaomoji_ru::animals::CAT[19], + // ( =ω= ) + kaomoji_ru::animals::CAT[20], + // (^◔ᴥ◔^) + kaomoji_ru::animals::CAT[25], + // ( ・ω・)☞ + kaomoji_ru::special::POINTING, ]; pub const ACTIONS_SIZE: usize = 17; @@ -35,4 +220,4 @@ pub const ACTIONS: [&[u8]; ACTIONS_SIZE] = [ b"*pokes you* ", b"*teleports behind you* ", b"*shuffles closer* ", -]; \ No newline at end of file +]; diff --git a/src/lib.rs b/src/lib.rs index 60a9338..6fe2fa1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ +#![cfg_attr(all(feature = "bench", test), feature(test))] + use std::fs::File; -use std::io::{stdout, BufWriter, Error, ErrorKind, Write}; +use std::io::{BufWriter, Error, ErrorKind, Write}; use std::path::Path; use std::str::from_utf8_unchecked; -use indicatif::{ProgressBar, ProgressStyle}; +use ahash::RandomState; use linkify::{LinkFinder, LinkKind}; use memmap::Mmap; @@ -11,25 +13,43 @@ use constants::ACTIONS; use constants::ACTIONS_SIZE; use constants::FACES; use constants::FACES_SIZE; -use seeder::UwUSeeder; mod constants; -mod seeder; macro_rules! progress_bar { - ($bytes:expr) => {{ - let progress_bar = ProgressBar::new($bytes); - progress_bar.set_style( - ProgressStyle::default_spinner() - .template("{spinner:.magenta} [{elapsed_precise:.bold}] {msg:.green.bold}"), - ); - progress_bar.set_message("UwU'ifying In Progress..."); + () => {{ + let progress_bar = indicatif::ProgressBar::new_spinner() + .with_style( + indicatif::ProgressStyle::default_spinner() + .template("{spinner:.magenta} [{elapsed_precise:.bold}] {msg:.green.bold}"), + ) + .with_message("UwU'ifying In Progress..."); progress_bar.enable_steady_tick(30); progress_bar }}; } +macro_rules! new_seeder { + ($word:expr, $random:expr) => { + ::seed_from_u64( + <[u8] as ahash::CallHasher>::get_hash($word, $random), + ) + }; +} + +macro_rules! random_float { + ($seeder:expr) => { + rand::Rng::gen_range($seeder, 0.0..1.0) + }; +} + +macro_rules! random_int { + ($seeder:expr, $range:expr) => { + rand::Rng::gen_range($seeder, $range) + }; +} + #[derive(Debug)] pub struct UwUify<'a> { text: &'a str, @@ -39,7 +59,7 @@ pub struct UwUify<'a> { faces: f64, actions: f64, stutters: f64, - random: bool, + random: RandomState, is_runtime: bool, linkify: LinkFinder, } @@ -54,7 +74,7 @@ impl<'a> Default for UwUify<'a> { faces: 0.05, actions: 0.125, stutters: 0.225, - random: false, + random: RandomState::with_seeds(69, 420, 28, 95), is_runtime: false, linkify: LinkFinder::new(), } @@ -81,12 +101,15 @@ impl<'a> UwUify<'a> { text: text.unwrap_or_default(), input: infile.unwrap_or_default(), output: outfile.unwrap_or_default(), - random, is_runtime, linkify, ..Default::default() }; + if random { + uwuify.random = RandomState::new(); + } + if let Some(words) = words { uwuify.words = words.parse::().unwrap(); } @@ -103,7 +126,7 @@ impl<'a> UwUify<'a> { uwuify } - pub fn uwuify(&mut self) -> Result<(), Error> { + pub fn uwuify(&self) -> Result<(), Error> { // Handle Text if !self.text.is_empty() { // Handle Text Output @@ -115,11 +138,14 @@ impl<'a> UwUify<'a> { )); } - let uwu_progress_bar = progress_bar!(self.text.len() as u64); + let uwu_progress_bar = progress_bar!(); self.uwuify_sentence(self.text, &mut BufWriter::new(File::create(&self.output)?))?; uwu_progress_bar.finish_with_message("UwU'ifying Complete!"); } else { - self.uwuify_sentence(self.text, &mut BufWriter::new(stdout().lock()))?; + #[cfg(not(test))] + self.uwuify_sentence(self.text, &mut BufWriter::new(std::io::stdout().lock()))?; + #[cfg(test)] + self.uwuify_sentence(self.text, &mut std::io::sink())?; } } else { // Handle File I/O @@ -130,10 +156,9 @@ impl<'a> UwUify<'a> { )); } - let infile = File::open(&self.input)?; - let uwu_progress_bar = progress_bar!(infile.metadata()?.len()); + let uwu_progress_bar = progress_bar!(); self.uwuify_sentence( - unsafe { from_utf8_unchecked(Mmap::map(&infile)?.as_ref()) }, + unsafe { from_utf8_unchecked(Mmap::map(&File::open(&self.input)?)?.as_ref()) }, &mut BufWriter::new(File::create(&self.output)?), )?; uwu_progress_bar.finish_with_message("UwU'ifying Complete!"); @@ -142,19 +167,20 @@ impl<'a> UwUify<'a> { Ok(()) } - pub fn uwuify_sentence(&mut self, text: &str, out: &mut T) -> Result<(), Error> { + pub fn uwuify_sentence(&self, text: &str, out: &mut T) -> Result<(), Error> { text.lines().try_for_each(|line| { line.split_whitespace() .map(|word_str| word_str.as_bytes()) .try_for_each(|word| { - let mut seeder = UwUSeeder::new(word, self.random); - let random_value = seeder.random_float(); + let mut seeder = new_seeder!(word, &self.random); + let random_value = random_float!(&mut seeder); if !self.is_runtime { if random_value <= self.faces { - out.write_all(FACES[seeder.random_int(0..FACES_SIZE)])?; + out.write_all(FACES[random_int!(&mut seeder, 0..FACES_SIZE)])?; + out.write_all(b" ")?; } else if random_value <= self.actions { - out.write_all(ACTIONS[seeder.random_int(0..ACTIONS_SIZE)])?; + out.write_all(ACTIONS[random_int!(&mut seeder, 0..ACTIONS_SIZE)])?; } else if random_value <= self.stutters { match word[0] { b'L' | b'R' => out.write_all(b"W"), @@ -165,10 +191,10 @@ impl<'a> UwUify<'a> { } } else { if random_value <= self.faces { - out.write_all(FACES[seeder.random_int(0..FACES_SIZE)])?; + out.write_all(FACES[random_int!(&mut seeder, 0..FACES_SIZE)])?; } if random_value <= self.actions { - out.write_all(ACTIONS[seeder.random_int(0..ACTIONS_SIZE)])?; + out.write_all(ACTIONS[random_int!(&mut seeder, 0..ACTIONS_SIZE)])?; } if random_value <= self.stutters { match word[0] { @@ -206,4 +232,26 @@ impl<'a> UwUify<'a> { out.write_all(b"\n") }) } -} \ No newline at end of file +} +#[cfg(test)] +mod tests { + #[cfg(feature = "bench")] + extern crate test; + + #[cfg(feature = "bench")] + #[bench] + fn uwu_bench(b: &mut test::Bencher) { + let uwuify = super::UwUify::new( + Some(include_str!("test.txt")), + None, + None, + None, + None, + None, + None, + false, + false, + ); + b.iter(|| uwuify.uwuify()); + } +} diff --git a/src/main.rs b/src/main.rs index e41ce0c..29bd31c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -142,4 +142,4 @@ fn is_between_zero_and_one(input: &str) -> Result<(), &'static str> { return Ok(()); } Err("The value must be between 0.0 and 1.0") -} \ No newline at end of file +} diff --git a/src/seeder.rs b/src/seeder.rs deleted file mode 100644 index 722cd12..0000000 --- a/src/seeder.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::hash::Hasher; - -use ahash::AHasher; -use rand::distributions::uniform::{SampleRange, SampleUniform}; -use rand::{Rng, RngCore, SeedableRng}; -use rand_xoshiro::Xoshiro256Plus; - -pub struct UwUSeeder { - rng: Xoshiro256Plus, -} - -impl UwUSeeder { - pub fn new(word: &[u8], random: bool) -> UwUSeeder { - let rand_u64 = if !random { - let mut hasher = AHasher::new_with_keys(0, 0); - hasher.write(word); - hasher.finish() - } else { - rand::rngs::OsRng::default().next_u64() - }; - - UwUSeeder { - rng: Xoshiro256Plus::seed_from_u64(rand_u64), - } - } - - pub fn random_float(&mut self) -> f64 { - self.rng.gen_range(0.0..1.0) - } - - pub fn random_int>(&mut self, range: R) -> T { - self.rng.gen_range(range) - } -} \ No newline at end of file diff --git a/src/test.txt b/src/test.txt new file mode 100644 index 0000000..d5cf3c3 --- /dev/null +++ b/src/test.txt @@ -0,0 +1,14 @@ +From fairest creatures we desire increase, +That thereby beauty's rose might never die, +But as the riper should by time decease, +His tender heir might bear his memory: +But thou contracted to thine own bright eyes, +Feed'st thy light's flame with self-substantial fuel, +Making a famine where abundance lies, +Thy self thy foe, to thy sweet self too cruel: +Thou that art now the world's fresh ornament, +And only herald to the gaudy spring, +Within thine own bud buriest thy content, +And tender churl mak'st waste in niggarding: +Pity the world, or else this glutton be, +To eat the world's due, by the grave and thee.