ice 0.4.0

A simple CTF tool store
Documentation
use anyhow::{anyhow, Result};

pub fn rot13(s: &str) -> String {
    s.chars()
        .map(|c| match c {
            'a'..='m' | 'A'..='M' => ((c as u8) + 13) as char,
            'n'..='z' | 'N'..='Z' => ((c as u8) - 13) as char,
            _ => c,
        })
        .collect()
}

pub fn caesar(s: &str, shift: Option<u8>) -> Result<String> {
    match shift {
        Some(n) => {
            let result: String = s
                .chars()
                .map(|c| match c {
                    'a'..='z' => (((c as u8 - 97 + n) % 26) + 97) as char,
                    'A'..='Z' => (((c as u8 - 65 + n) % 26) + 65) as char,
                    _ => c,
                })
                .collect();
            Ok(result)
        }
        None => {
            let results: String = (0..26)
                .map(|i| {
                    let result: String = s
                        .chars()
                        .map(|c| match c {
                            'a'..='z' => (((c as u8 - 97 + i) % 26) + 97) as char,
                            'A'..='Z' => (((c as u8 - 65 + i) % 26) + 65) as char,
                            _ => c,
                        })
                        .collect();
                    format!("[{:02}] {}\n", i, result)
                })
                .collect();
            Ok(results.trim().to_string())
        }
    }
}

pub fn vigenere(s: &str, key: &str, decrypt: bool) -> Result<String> {
    let key: String = key
        .chars()
        .filter(|&c| c.is_ascii_alphabetic())
        .collect::<String>()
        .to_ascii_lowercase();

    if key.is_empty() {
        return Err(anyhow!("Key must contain at least one letter"));
    }

    let key_len = key.len();
    let mut index = 0;

    let result: String = s
        .chars()
        .map(|c| {
            if c.is_ascii_alphabetic() {
                let first = if c.is_ascii_lowercase() { b'a' } else { b'A' };
                let shift = key.as_bytes()[index % key_len] - b'a';
                index += 1;
                let c_val = c as u8;
                let shifted = if decrypt {
                    (c_val + 26 - first - shift) % 26
                } else {
                    (c_val - first + shift) % 26
                };
                (first + shifted) as char
            } else {
                c
            }
        })
        .collect();

    Ok(result)
}

pub fn rot47(s: &str) -> String {
    s.chars()
        .map(|c| {
            let code = c as u8;
            if (33..=126).contains(&code) {
                ((code - 33 + 47) % 94 + 33) as char
            } else {
                c
            }
        })
        .collect()
}

pub fn rot18(s: &str) -> String {
    rot13(s)
        .chars()
        .map(|c| match c {
            '0'..='4' => ((c as u8) + 5) as char,
            '5'..='9' => ((c as u8) - 5) as char,
            _ => c,
        })
        .collect()
}