1
0
This commit is contained in:
2024-09-26 10:58:48 +03:00
parent eeb9de0ebc
commit b3687183a0
7 changed files with 83 additions and 245 deletions

16
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -1,34 +0,0 @@
use crate::util;
pub struct AtabashChipher {
alphabet: Vec<char>,
}
impl AtabashChipher {
pub fn new(alphabet: &str) -> anyhow::Result<Self> {
let alphabet = alphabet.chars().collect::<Vec<_>>();
util::verify_alphabet(&alphabet)?;
Ok(Self { alphabet })
}
pub fn encode(&self, input: &str) -> anyhow::Result<String> {
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<String> {
self.encode(input)
}
}

View File

@@ -1,56 +0,0 @@
use crate::util;
pub struct CaesarCipher {
alphabet: Vec<char>,
offset: i64,
}
impl CaesarCipher {
pub fn new(alphabet: &str, offset: i64) -> anyhow::Result<Self> {
let alphabet = alphabet.chars().collect::<Vec<_>>();
let offset = offset % alphabet.len() as i64;
util::verify_alphabet(&alphabet)?;
Ok(Self { alphabet, offset })
}
pub fn encode(&self, input: &str) -> anyhow::Result<String> {
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<String> {
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<usize> {
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)
}
}

View File

@@ -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::<String>::new()
.with_prompt("Enter alphabet")
.with_initial_text("АаБбВвГгДдЕеЁёЖжЗзИиЙйКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭэЮюЯя0123456789 ,.:!?_-+/*='^%#&@$")
.validate_with(|input: &String| {
let chars = input.chars().collect::<Vec<_>>();
util::verify_alphabet(&chars)
})
.interact_text()?;
let input = dialoguer::Input::<String>::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::<String>();
result.trim_end_matches(filler_char).to_string()
}
fn main() -> anyhow::Result<()> {
let group_size = dialoguer::Input::<usize>::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::<char>::new()
.with_prompt("Filler character")
.default('A')
.interact_text()?;
let input = dialoguer::Input::<String>::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(())
}

View File

@@ -1,69 +0,0 @@
use crate::util;
pub struct PolybiusSquareCipher {
alphabet: Vec<char>,
rows: usize,
columns: usize,
}
impl PolybiusSquareCipher {
pub fn new(alphabet: &str, columns: usize, rows: usize) -> anyhow::Result<Self> {
let alphabet = alphabet.chars().collect::<Vec<_>>();
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<String> {
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<String> {
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)
}
}

View File

@@ -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()
}