Skip to main content

cipher_salad/cipher/
caesar.rs

1use crate::{
2    Alphabet, ForeignGraphemesPolicy,
3    cipher::CipherOperation::{self, Decrypt, Encrypt},
4};
5use unicode_segmentation::UnicodeSegmentation;
6
7pub struct Caesar {
8    foreign_policy: ForeignGraphemesPolicy,
9    alphabet: Alphabet,
10}
11
12impl Caesar {
13    #[must_use]
14    pub fn new(alphabet: Alphabet, foreign_policy: ForeignGraphemesPolicy) -> Self {
15        Self {
16            foreign_policy,
17            alphabet,
18        }
19    }
20
21    fn cipher(&self, contents: &str, key: usize, operation: CipherOperation) -> String {
22        let key = key % self.alphabet.length();
23
24        contents
25            .graphemes(true)
26            .map(|grapheme| (grapheme, self.alphabet.index_of(grapheme)))
27            .filter_map(|(grapheme, index)| match index {
28                Some(grapheme_index) => {
29                    let ciphered_index = match operation {
30                        Encrypt => (grapheme_index + key).rem_euclid(self.alphabet.length()),
31                        Decrypt => (grapheme_index as isize - key as isize)
32                            .rem_euclid(self.alphabet.length() as isize)
33                            as usize,
34                    };
35                    self.alphabet.grapheme_at(ciphered_index).cloned()
36                }
37                None => match self.foreign_policy {
38                    ForeignGraphemesPolicy::Include => Some(grapheme.to_string()),
39                    ForeignGraphemesPolicy::Exclude => Some(String::new()),
40                },
41            })
42            .collect()
43    }
44
45    #[must_use]
46    pub fn encrypt(&self, contents: &str, key: usize) -> String {
47        self.cipher(contents, key, CipherOperation::Encrypt)
48    }
49
50    #[must_use]
51    pub fn decrypt(&self, ciphertext: &str, key: usize) -> String {
52        self.cipher(ciphertext, key, CipherOperation::Decrypt)
53    }
54}