diff --git a/Cargo.lock b/Cargo.lock index e1c7a66..a3cba5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -176,6 +182,7 @@ dependencies = [ "anyhow", "clap", "dialoguer", + "itertools", ] [[package]] @@ -184,6 +191,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 8251df7..47c6f61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" anyhow = "1.0.86" clap = { version = "4.5.17", features = ["derive"] } dialoguer = "0.11.0" +itertools = "0.13.0" diff --git a/src/atbash.rs b/src/atbash.rs deleted file mode 100644 index 0b5c47f..0000000 --- a/src/atbash.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::util; - -pub struct AtabashChipher { - alphabet: Vec, -} - -impl AtabashChipher { - pub fn new(alphabet: &str) -> anyhow::Result { - let alphabet = alphabet.chars().collect::>(); - - util::verify_alphabet(&alphabet)?; - - Ok(Self { alphabet }) - } - - pub fn encode(&self, input: &str) -> anyhow::Result { - let mut output = String::with_capacity(input.len()); - - for c in input.chars() { - let index = self - .alphabet - .iter() - .position(|&x| x == c) - .ok_or(anyhow::anyhow!("cannot encode character {:?}", c))?; - output.push(self.alphabet[self.alphabet.len() - index - 1]); - } - - Ok(output) - } - - pub fn decode(&self, input: &str) -> anyhow::Result { - self.encode(input) - } -} diff --git a/src/caesar.rs b/src/caesar.rs deleted file mode 100644 index daee3ce..0000000 --- a/src/caesar.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::util; - -pub struct CaesarCipher { - alphabet: Vec, - offset: i64, -} - -impl CaesarCipher { - pub fn new(alphabet: &str, offset: i64) -> anyhow::Result { - let alphabet = alphabet.chars().collect::>(); - let offset = offset % alphabet.len() as i64; - - util::verify_alphabet(&alphabet)?; - - Ok(Self { alphabet, offset }) - } - - pub fn encode(&self, input: &str) -> anyhow::Result { - let mut output = String::with_capacity(input.len()); - - for c in input.chars() { - let index = self.get_index(c, self.offset)?; - output.push(self.alphabet[index]) - } - - Ok(output) - } - - pub fn decode(&self, input: &str) -> anyhow::Result { - let mut output = String::with_capacity(input.len()); - - for c in input.chars() { - let index = self.get_index(c, -self.offset)?; - output.push(self.alphabet[index]) - } - - Ok(output) - } - - fn get_index(&self, c: char, offset: i64) -> anyhow::Result { - let mut index = - self.alphabet - .iter() - .position(|&x| x == c) - .ok_or(anyhow::anyhow!("cannot encode character {:?}", c))? as i64 - + offset; - - while index < 0 { - index += self.alphabet.len() as i64; - } - - index %= self.alphabet.len() as i64; - - Ok(index as usize) - } -} diff --git a/src/main.rs b/src/main.rs index 57b2ded..f766104 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,78 +1,68 @@ -use clap::Parser; +use itertools::Itertools; -mod atbash; -#[allow(dead_code)] -mod caesar; -#[allow(dead_code)] -mod polybius_square; mod util; -#[derive(Debug, clap::Parser)] -struct Args { - #[clap(subcommand)] - command: Command, -} - -#[derive(Debug, clap::Subcommand)] -enum Command { - User, - Cli { - #[clap(short, long)] - mode: Mode, - #[clap(short, long)] - input: String, - #[clap(short, long)] - alphabet: String, - }, -} - -#[derive(Debug, Clone, clap::ValueEnum)] -enum Mode { - Encode, - Decode, -} - -fn main() -> anyhow::Result<()> { - let args = Args::parse(); - - match args.command { - Command::User => { - let alphabet = dialoguer::Input::::new() - .with_prompt("Enter alphabet") - .with_initial_text("АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя0123456789 ,.:!?_-+/*='^%#&@$") - .validate_with(|input: &String| { - let chars = input.chars().collect::>(); - util::verify_alphabet(&chars) - }) - .interact_text()?; - let input = dialoguer::Input::::new() - .with_prompt("Enter input") - .interact_text()?; - - let atbash = atbash::AtabashChipher::new(&alphabet)?; - - let encoded = atbash.encode(&input)?; - println!("Result: {}", encoded); - } - Command::Cli { - mode, - input, - alphabet, - } => { - let atbash = atbash::AtabashChipher::new(&alphabet)?; - - match mode { - Mode::Encode => { - let encoded = atbash.encode(&input)?; - println!("{}", encoded); - } - Mode::Decode => { - let decoded = atbash.decode(&input)?; - println!("{}", decoded); - } - } +fn encode(group_size: usize, filler_char: char, input: String) -> String { + let mut input = util::remove_whitespace(util::remove_punctuation(input)); + let len = input.chars().count(); + if len % group_size != 0 { + for _ in 0..(group_size - len % group_size) { + input.push(filler_char); } } + let mut result = String::new(); + for chunk in &input.chars().rev().chunks(group_size) { + for c in chunk { + result.push(c); + } + result.push(' '); + } + + result +} + +fn decode(_group_size: usize, filler_char: char, input: String) -> String { + let result = util::remove_whitespace(util::remove_punctuation(input)) + .chars() + .rev() + .collect::(); + + result.trim_end_matches(filler_char).to_string() +} + +fn main() -> anyhow::Result<()> { + let group_size = dialoguer::Input::::new() + .with_prompt("Enter group size") + .validate_with(|input: &usize| { + if input > &1 { + Ok(()) + } else { + Err("Must be greater than 1".to_string()) + } + }) + .interact_text()?; + let filler_char = dialoguer::Input::::new() + .with_prompt("Filler character") + .default('A') + .interact_text()?; + let input = dialoguer::Input::::new() + .with_prompt("Enter input") + .interact_text()?; + let mode = dialoguer::Select::new() + .with_prompt("Mode") + .item("Encode") + .item("Decode") + .default(0) + .interact()?; + + let result = match mode { + 0 => encode(group_size, filler_char, input), + 1 => decode(group_size, filler_char, input), + _ => unreachable!(), + }; + + println!("Result: {}", result); + Ok(()) } diff --git a/src/polybius_square.rs b/src/polybius_square.rs deleted file mode 100644 index 8e0471f..0000000 --- a/src/polybius_square.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::util; - -pub struct PolybiusSquareCipher { - alphabet: Vec, - rows: usize, - columns: usize, -} - -impl PolybiusSquareCipher { - pub fn new(alphabet: &str, columns: usize, rows: usize) -> anyhow::Result { - let alphabet = alphabet.chars().collect::>(); - util::verify_alphabet(&alphabet)?; - if rows * columns != alphabet.len() { - return Err(anyhow::anyhow!( - "rows * columns must equal the length of the alphabet" - )); - } - - Ok(Self { - alphabet, - rows, - columns, - }) - } - - pub fn encode(&self, input: &str) -> anyhow::Result { - let mut output = String::with_capacity(input.len()); - - for c in input.chars() { - let index = self - .alphabet - .iter() - .position(|&x| x == c) - .ok_or(anyhow::anyhow!("invalid character"))?; - - let row = index / self.columns; - let col = index % self.columns; - - output.push(self.alphabet[((row + 1) % self.rows) * self.columns + col]); - } - - Ok(output) - } - - pub fn decode(&self, input: &str) -> anyhow::Result { - let mut output = String::with_capacity(input.len()); - - for c in input.chars() { - let index = self - .alphabet - .iter() - .position(|&x| x == c) - .ok_or(anyhow::anyhow!("invalid character"))?; - - let mut row = index / self.columns; - let col = index % self.columns; - - if row == 0 { - row = self.rows - 1; - } else { - row -= 1; - } - - output.push(self.alphabet[(row % self.rows) * self.columns + col]); - } - - Ok(output) - } -} diff --git a/src/util.rs b/src/util.rs index 00c33b2..c3c1cad 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,17 +1,7 @@ -pub fn verify_alphabet(alphabet: &[char]) -> anyhow::Result<()> { - if alphabet.len() < 2 { - return Err(anyhow::anyhow!( - "Alphabet must contain at least 2 characters" - )); - } - - let mut alphabet_set = std::collections::HashSet::new(); - for c in alphabet { - if alphabet_set.contains(c) { - return Err(anyhow::anyhow!("Alphabet contains duplicate characters")); - } - alphabet_set.insert(*c); - } - - Ok(()) +pub fn remove_whitespace(input: String) -> String { + input.chars().filter(|c| !c.is_whitespace()).collect() } + +pub fn remove_punctuation(input: String) -> String { + input.chars().filter(|c| !c.is_ascii_punctuation()).collect() +} \ No newline at end of file