text_utils/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::fmt::Write;
4
5/// Applies ROT13 to a byte, rotating letters by 13 and digits by 5.
6#[must_use]
7pub fn rot13_byte(byte: u8) -> u8 {
8    match byte {
9        b'a'..=b'z' => b'a' + (byte - b'a' + 13) % 26,
10        b'A'..=b'Z' => b'A' + (byte - b'A' + 13) % 26,
11        b'0'..=b'9' => b'0' + (byte - b'0' + 5) % 10,
12        _ => byte,
13    }
14}
15
16/// Replaces smart quotes with ASCII equivalents.
17#[must_use]
18pub fn replace_quotes(text: &str) -> String {
19    text.chars()
20        .map(|c| match c {
21            '\u{201c}' | '\u{201d}' => '"',
22            '\u{2018}' | '\u{2019}' => '\'',
23            _ => c,
24        })
25        .collect()
26}
27
28/// Converts decimal digit sequences in text to hexadecimal.
29///
30/// # Panics
31///
32/// Panics if a decimal number in the text is too large to fit in a `u64`.
33#[must_use]
34pub fn hexify(mut text: &str) -> String {
35    let mut result = String::new();
36    while let Some(begin) = text.find(|c: char| c.is_ascii_digit()) {
37        let next = begin + 1;
38        let rest = &text[next..];
39        let end = next
40            + rest
41                .find(|c: char| !c.is_ascii_digit())
42                .unwrap_or(rest.len());
43        let value: u64 = text[begin..end]
44            .parse()
45            .expect("Can't parse ASCII digits as u64; value too big?");
46        write!(&mut result, "{}{value:x}", &text[..begin]).expect("write to string should succeed");
47        text = &text[end..];
48    }
49    result + text
50}
51
52/// Converts a character to its hex representation if possible.
53/// Returns `None` for non-hex-representable alphabetic characters.
54#[must_use]
55pub fn to_hex_char(c: char) -> Option<char> {
56    match c {
57        'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' => Some(c),
58        'i' | 'I' => Some('1'),
59        'o' | 'O' => Some('0'),
60        _ if c.is_alphabetic() => None,
61        _ => Some(c),
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_rot13_byte() {
71        assert_eq!(rot13_byte(b'a'), b'n');
72        assert_eq!(rot13_byte(b'n'), b'a');
73        assert_eq!(rot13_byte(b'A'), b'N');
74        assert_eq!(rot13_byte(b'0'), b'5');
75        assert_eq!(rot13_byte(b' '), b' ');
76    }
77
78    #[test]
79    fn test_replace_quotes() {
80        assert_eq!(replace_quotes("\u{201c}hello\u{201d}"), "\"hello\"");
81        assert_eq!(replace_quotes("\u{2018}world\u{2019}"), "'world'");
82    }
83
84    #[test]
85    fn test_hexify() {
86        assert_eq!(hexify(""), "");
87        assert_eq!(hexify("hello"), "hello");
88        assert_eq!(hexify("42"), "2a");
89        assert_eq!(hexify("172.31.64.0/20"), "ac.1f.40.0/14");
90    }
91
92    #[test]
93    fn test_to_hex_char() {
94        assert_eq!(to_hex_char('a'), Some('a'));
95        assert_eq!(to_hex_char('i'), Some('1'));
96        assert_eq!(to_hex_char('o'), Some('0'));
97        assert_eq!(to_hex_char('g'), None);
98        assert_eq!(to_hex_char(' '), Some(' '));
99    }
100}