From 876c6a864629d1d2580edca58e36653f6182ab2d Mon Sep 17 00:00:00 2001 From: sgoudham Date: Fri, 28 Jan 2022 01:42:16 +0000 Subject: [PATCH] Port over code from uwuify --- src/main.rs | 71 +++++++++++++ src/uwuify.rs | 203 +++++++++++++++++++++++++++++++++++++ src/uwuify/constants.rs | 39 +++++++ src/uwuify/io.rs | 80 +++++++++++++++ src/uwuify/progress_bar.rs | 29 ++++++ src/uwuify/seeder.rs | 35 +++++++ 6 files changed, 457 insertions(+) create mode 100644 src/main.rs create mode 100644 src/uwuify.rs create mode 100644 src/uwuify/constants.rs create mode 100644 src/uwuify/io.rs create mode 100644 src/uwuify/progress_bar.rs create mode 100644 src/uwuify/seeder.rs diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7c3f6f4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,71 @@ +use clap::{ArgGroup, ErrorKind, IntoApp, Parser}; + +use crate::uwuify::UwUify; + +mod uwuify; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +#[clap(group(ArgGroup::new("uwu").required(true).args(& ["text", "infile"]),))] +struct Args { + /// Text to uwu'ify + #[clap(short, long, required_unless_present_all = ["infile", "outfile"], display_order = 1)] + text: Option, + + /// The file to uwu'ify + #[clap(short, long, parse(from_os_str), conflicts_with = "text", requires = "outfile", value_name = "FILE", display_order = 2)] + infile: Option, + + /// The file to output uwu'ified text + #[clap(short, long, value_name = "FILE", display_order = 3)] + outfile: Option, + + /// The modifier to determine how many words to be uwu'ified + #[clap(short, long, value_name = "VALUE", default_value = "1", validator = is_between_zero_and_one, display_order = 4)] + words: f32, + + /// The modifier for uwu faces e.g hello -> hewwo + #[clap(short, long, value_name = "VALUE", default_value = "0.05", validator = is_between_zero_and_one, display_order = 5)] + faces: f32, + + /// The modifier for actions e.g *shuffles over* + #[clap(short, long, value_name = "VALUE", default_value = "0.125", validator = is_between_zero_and_one, display_order = 6)] + actions: f32, + + /// The modifier for stutters e.g b-baka! + #[clap(short, long, value_name = "VALUE", default_value = "0.225", validator = is_between_zero_and_one, display_order = 7)] + stutters: f32, + + /// Flag to enable/disable random uwu'ifying + #[clap(short, long, display_order = 8)] + random: bool, +} + +fn main() { + let args = Args::parse(); + let matches = Args::into_app().get_matches(); + + let supplied_at_runtime = modifiers_supplied_at_runtime(matches.occurrences_of("faces"), matches.occurrences_of("actions"), matches.occurrences_of("stutters")); + let uwuify = UwUify::new(args.text, args.infile, args.outfile, supplied_at_runtime, args.words, args.faces, args.actions, args.stutters, args.random); + match uwuify.uwuify() { + Ok(_) => (), + Err(err) => { + let mut app = Args::into_app(); + app.error(ErrorKind::DisplayHelp, err.to_string()).exit(); + } + } +} + +fn is_between_zero_and_one(input: &str) -> Result<(), String> { + let value = match input.parse::() { + Ok(value) => value, + Err(_) => return Err(String::from("The value must be a decimal number")) + }; + + if (0.0..=1.0).contains(&value) { return Ok(()); } + Err(String::from("The value must be between 0.0 and 1.0")) +} + +fn modifiers_supplied_at_runtime(faces_occurrences: u64, actions_occurrences: u64, stutters_occurrences: u64) -> bool { + faces_occurrences > 0 || actions_occurrences > 0 || stutters_occurrences > 0 +} \ No newline at end of file diff --git a/src/uwuify.rs b/src/uwuify.rs new file mode 100644 index 0000000..bd56fb4 --- /dev/null +++ b/src/uwuify.rs @@ -0,0 +1,203 @@ +use std::io::Error; +use std::path::PathBuf; +use linkify::{LinkFinder, LinkKind}; + +use crate::uwuify::constants::ACTIONS; +use crate::uwuify::constants::ACTIONS_SIZE; +use crate::uwuify::constants::FACES; +use crate::uwuify::constants::FACES_SIZE; +use crate::uwuify::io::{UwUInFile, UwUOutFile}; +use crate::uwuify::progress_bar::UwUProgressBar; +use crate::uwuify::seeder::UwUSeeder; + +mod constants; +mod seeder; +mod progress_bar; +mod io; + +#[derive(Debug)] +struct Modifiers { + supplied_at_runtime: bool, + words: f32, + faces: f32, + actions: f32, + stutters: f32, +} + +#[derive(Debug)] +pub struct UwUify { + text: String, + input: PathBuf, + output: String, + modifiers: Modifiers, + random: bool, + linkify: LinkFinder, +} + +impl UwUify { + pub fn new(text: Option, + infile: Option, + outfile: Option, + supplied_at_runtime: bool, + words: f32, + faces: f32, + actions: f32, + stutters: f32, + random: bool) -> UwUify { // I dislike this + + let mut linkify = LinkFinder::new(); + linkify.kinds(&[LinkKind::Email, LinkKind::Url]); + linkify.url_must_have_scheme(false); + + UwUify { + text: text.unwrap_or_default(), + input: infile.unwrap_or_default(), + output: outfile.unwrap_or_default(), + modifiers: Modifiers { supplied_at_runtime, words, faces, actions, stutters }, + random, + linkify, + } + } + + pub fn uwuify(&self) -> Result<(), Error> { + // Handle Text + if !self.text.is_empty() { + let uwu_text = self.uwuify_sentence(&self.text); + + // Handle Text Output + if !self.output.is_empty() { + let mut uwu_out_file = match UwUOutFile::new(&self.output) { + Ok(uwu_out_file) => uwu_out_file, + Err(err) => return Err(err) + }; + let mut uwu_progress_bar = UwUProgressBar::new(uwu_text.len() as u64); + + match uwu_out_file.write_string(&uwu_text) { + Ok(_) => (), + Err(err) => return Err(err), + }; + + uwu_progress_bar.update_progess(uwu_text.len()); + uwu_progress_bar.finish("UwU'ifying Complete!"); + } else { + println!("{}", uwu_text); + } + } else { + // Handle File I/O + let mut uwu_in_file = match UwUInFile::new(&self.input) { + Ok(uwu_in_file) => uwu_in_file, + Err(err) => return Err(err), + }; + let mut uwu_out_file = match UwUOutFile::new(&self.output) { + Ok(uwu_out_file) => uwu_out_file, + Err(err) => return Err(err) + }; + let mut uwu_progress_bar = UwUProgressBar::new(uwu_in_file.get_file_bytes()); + + loop { + let bytes_read_in = match uwu_in_file.read_until_newline() { + Ok(bytes_read_in) => bytes_read_in, + Err(err) => return Err(err), + }; + if bytes_read_in == 0 { break; } + + let utf8_str = uwu_in_file.get_buffer_as_utf8_str(); + let uwu_sentence = self.uwuify_sentence(&utf8_str); + match uwu_out_file.write_string_with_newline(&uwu_sentence) { + Ok(_) => (), + Err(err) => return Err(err), + }; + + uwu_progress_bar.update_progess(bytes_read_in); + uwu_in_file.clear_buffer(); + } + + uwu_progress_bar.finish("UwU'ifying Complete!"); + } + + Ok(()) + } + + fn uwuify_sentence(&self, text: &str) -> String { + text + .split_whitespace() + .map(|word| { + let uwu_word = self.uwuify_word(word.to_string()); + self.uwuify_spaces(uwu_word) + }) + .collect::>() + .join(" ") + } + + fn uwuify_word(&self, word: String) -> String { + if self.linkify.links(&word).count() > 0 { + return word; + } + + let mut seeder = UwUSeeder::new(&word, self.random); + if seeder.random() > self.modifiers.words { return word; } + + let word_bytes = word.as_bytes(); + let uwu_text_count = word.len(); + let mut uwu_text = String::new(); + + for index in 0..uwu_text_count { + let previous_previous_char = *word_bytes.get(index - 2).unwrap_or_else(|| &word_bytes[0]) as char; + let previous_char = *word_bytes.get(index - 1).unwrap_or_else(|| &word_bytes[0]) as char; + let current_char = word_bytes[index] as char; + + match current_char { + 'L' | 'R' => uwu_text.push('W'), + 'l' | 'r' => uwu_text.push('w'), + 'E' | 'e' => match previous_char { + 'N' | 'n' => uwu_text.push_str(format!("y{}", current_char).as_str()), + 'v' => match previous_previous_char { + 'o' => { + uwu_text.pop(); + uwu_text.pop(); + uwu_text.push_str("uv"); + } + _ => uwu_text.push(current_char) + } + _ => uwu_text.push(current_char) + } + 'A' | 'I' | 'O' | 'U' | 'a' | 'i' | 'o' | 'u' => match previous_char { + 'N' | 'n' => uwu_text.push_str(format!("y{}", current_char).as_str()), + _ => uwu_text.push(current_char) + } + _ => uwu_text.push(current_char) + } + } + + uwu_text + } + + fn uwuify_spaces(&self, mut word: String) -> String { + 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 { + word = format!("{} {}", FACES[seeder.random_int(0, FACES_SIZE)], word); + } else if random_value <= self.modifiers.actions { + word = format!("{} {}", ACTIONS[seeder.random_int(0, ACTIONS_SIZE)], word); + } else if random_value <= self.modifiers.stutters { + let first_char_stutter = format!("{}-", word.chars().next().unwrap()); + word = format!("{}{}", first_char_stutter.repeat(seeder.random_int(1, 2)), word); + } + } else { + if random_value <= self.modifiers.stutters { + let first_char_stutter = format!("{}-", word.chars().next().unwrap()); + word = format!("{}{}", first_char_stutter.repeat(seeder.random_int(1, 2)), word); + } + if random_value <= self.modifiers.faces { + word = format!("{} {}", FACES[seeder.random_int(0, FACES_SIZE)], word); + } + if random_value <= self.modifiers.actions { + word = format!("{} {}", ACTIONS[seeder.random_int(0, ACTIONS_SIZE)], word); + } + } + + word + } +} \ No newline at end of file diff --git a/src/uwuify/constants.rs b/src/uwuify/constants.rs new file mode 100644 index 0000000..6193bff --- /dev/null +++ b/src/uwuify/constants.rs @@ -0,0 +1,39 @@ +pub const FACES_SIZE: i32 = 15; +pub const FACES: [&str; FACES_SIZE as usize] = [ + "OwO", + "UwU", + ">w<", + "^w^", + "ÚwÚ", + "^-^", + ":3", + "x3", + "xDD", + ";;w;;", + ">_<", + ">_>", + "^.^", + ":33", + "uWu", +]; + +pub const ACTIONS_SIZE: i32 = 17; +pub const ACTIONS: [&str; ACTIONS_SIZE as usize] = [ + "*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*", +]; \ No newline at end of file diff --git a/src/uwuify/io.rs b/src/uwuify/io.rs new file mode 100644 index 0000000..43a0823 --- /dev/null +++ b/src/uwuify/io.rs @@ -0,0 +1,80 @@ +use std::fs::File; +use std::io::{BufRead, BufReader, BufWriter, Error, Write}; +use std::path::{Path}; + +pub struct UwUInFile { + file_bytes: u64, + reader: BufReader, + buffer: Vec, +} + +pub struct UwUOutFile { + writer: BufWriter, +} + +impl UwUInFile { + pub fn new(path: &Path) -> Result { + let file = match File::open(path) { + Ok(file) => file, + Err(err) => return Err(err) + }; + let file_metadata = match file.metadata() { + Ok(file_metadata) => file_metadata, + Err(err) => return Err(err) + }; + let file_bytes = file_metadata.len(); + let reader = BufReader::new(file); + let buffer = Vec::new(); + + Ok(UwUInFile { file_bytes, reader, buffer }) + } + + pub fn read_until_newline(&mut self) -> Result { + match self.reader.read_until(b'\n', &mut self.buffer) { + Ok(byte_vec) => Ok(byte_vec), + Err(err) => Err(err), + } + } + + pub fn get_buffer_as_utf8_str(&self) -> String { + String::from_utf8_lossy(&self.buffer).to_string() + } + + pub fn clear_buffer(&mut self) { + self.buffer.clear(); + } + + pub fn get_file_bytes(&self) -> u64 { + self.file_bytes + } +} + +impl UwUOutFile { + pub fn new(path: &str) -> Result { + let file = match File::create(path) { + Ok(file) => file, + Err(err) => return Err(err) + }; + let writer = BufWriter::new(file); + + Ok(UwUOutFile { writer }) + } + + pub fn exists(path: &str) -> bool { + Path::new(path).exists() + } + + pub fn write_string_with_newline(&mut self, write_str: &str) -> Result<(), Error> { + match self.writer.write_all(format!("{}\n", write_str).as_bytes()) { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } + + pub fn write_string(&mut self, write_str: &str) -> Result<(), Error> { + match self.writer.write_all(write_str.as_bytes()) { + Ok(_) => Ok(()), + Err(err) => Err(err), + } + } +} \ No newline at end of file diff --git a/src/uwuify/progress_bar.rs b/src/uwuify/progress_bar.rs new file mode 100644 index 0000000..ad40ec6 --- /dev/null +++ b/src/uwuify/progress_bar.rs @@ -0,0 +1,29 @@ +use indicatif::{ProgressBar, ProgressStyle}; + +pub struct UwUProgressBar { + downloaded_bytes: u64, + progress_bar: ProgressBar, +} + +impl UwUProgressBar { + 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("#>-")); + + UwUProgressBar { + downloaded_bytes: 0, + progress_bar, + } + } + + pub fn update_progess(&mut self, bytes_read_in: usize) { + self.downloaded_bytes += bytes_read_in as u64; + self.progress_bar.set_position(self.downloaded_bytes); + } + + pub fn finish(&self, message: &'static str) { + self.progress_bar.finish_with_message(message); + } +} \ No newline at end of file diff --git a/src/uwuify/seeder.rs b/src/uwuify/seeder.rs new file mode 100644 index 0000000..861d37c --- /dev/null +++ b/src/uwuify/seeder.rs @@ -0,0 +1,35 @@ +use rand::{Rng, rngs::ThreadRng, thread_rng}; +use rand_pcg::Pcg32; +use rand_seeder::Seeder; + +pub struct UwUSeeder { + seeder: Pcg32, + rng: ThreadRng, + random: bool, +} + +impl UwUSeeder { + pub fn new(word: &str, random: bool) -> UwUSeeder { + UwUSeeder { + seeder: Seeder::from(word).make_rng(), + rng: thread_rng(), + random, + } + } + + pub fn random(&mut self) -> f32 { + if self.random { + self.rng.gen_range(0.0..1.0) + } else { + self.seeder.gen_range(0.0..1.0) + } + } + + pub fn random_int(&mut self, min: i32, max: i32) -> usize { + if self.random { + self.rng.gen_range(min..max) as usize + } else { + self.seeder.gen_range(min..max) as usize + } + } +} \ No newline at end of file