Skip to main content

happy_cracking/crypto/
strtools.rs

1use anyhow::Result;
2use clap::Subcommand;
3
4#[derive(Subcommand)]
5pub enum StrToolsAction {
6    #[command(about = "Reverse a string")]
7    Reverse {
8        #[arg(help = "Input text")]
9        input: String,
10    },
11    #[command(about = "Show ASCII/Unicode code points for each character")]
12    Ord {
13        #[arg(help = "Input text")]
14        input: String,
15    },
16    #[command(about = "Convert space-separated code points to characters")]
17    Chr {
18        #[arg(help = "Space-separated code points (e.g. \"72 101 108 108 111\")")]
19        input: String,
20    },
21}
22
23pub fn run(action: StrToolsAction) -> Result<()> {
24    match action {
25        StrToolsAction::Reverse { input } => {
26            println!("{}", reverse(&input));
27        }
28        StrToolsAction::Ord { input } => {
29            println!("{}", ord(&input));
30        }
31        StrToolsAction::Chr { input } => {
32            println!("{}", chr(&input)?);
33        }
34    }
35    Ok(())
36}
37
38// Reverse a string by Unicode grapheme.
39pub fn reverse(input: &str) -> String {
40    input.chars().rev().collect()
41}
42
43// Return each character with its code point in "A=65 B=66" format.
44pub fn ord(input: &str) -> String {
45    if input.is_empty() {
46        return String::new();
47    }
48
49    // Optimization: avoid format! and collect overhead by pre-allocating
50    // and manually formatting the numbers into a buffer.
51    // Each entry is max 1 char + 1 '=' + 7 digits (max u32 for char is 1114111) + 1 space = 10 chars.
52    let mut out = String::with_capacity(input.len() * 10);
53    let mut first = true;
54
55    for c in input.chars() {
56        if !first {
57            out.push(' ');
58        }
59        first = false;
60
61        out.push(c);
62        out.push('=');
63
64        let mut n = c as u32;
65        if n == 0 {
66            out.push('0');
67        } else {
68            let mut buf = [0u8; 10];
69            let mut i = 10;
70            while n > 0 {
71                i -= 1;
72                buf[i] = (n % 10) as u8 + b'0';
73                n /= 10;
74            }
75            // SAFETY: buf only contains ascii digits b'0'..=b'9'
76            out.push_str(unsafe { std::str::from_utf8_unchecked(&buf[i..]) });
77        }
78    }
79
80    out
81}
82
83// Convert space-separated numeric values to a string.
84pub fn chr(input: &str) -> Result<String> {
85    if input.is_empty() {
86        return Ok(String::new());
87    }
88
89    input
90        .split_whitespace()
91        .map(|s| {
92            let n: u32 = s
93                .parse()
94                .map_err(|_| anyhow::anyhow!("Invalid code point: {}", s))?;
95            char::from_u32(n).ok_or_else(|| anyhow::anyhow!("Invalid Unicode code point: {}", n))
96        })
97        .collect()
98}