centaurs 0.3.0

A utility crate.
Documentation
use std::collections::HashMap;

const CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

lazy_static::lazy_static! {
    static ref INDEX: HashMap<u8, u8> = {
        CHARSET.iter().enumerate().map(|(index, &value)|{(value, index as u8)}).collect::<HashMap<u8, u8>>()
    };


    static ref VALUE: Vec<u8> = {
        vec!{
            b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9',
            b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
            b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
            b'u', b'v', b'w', b'x', b'y', b'z', b'A', b'B', b'C', b'D',
            b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
            b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X',
            b'Y', b'Z',
        }
    };
}

pub fn value2index(v: u8) -> Option<u8> {
    Some(*INDEX.get(&v)?)
}

pub fn index2value(v: u8) -> Option<u8> {
    Some(*VALUE.get(v as usize)?)
}

pub fn contains(v: u8) -> bool {
    value2index(v).is_some()
}

pub fn decode<S>(value: S) -> Option<u64>
where
    S: AsRef<str>,
{
    decode_bytes(value.as_ref().as_bytes())
}

fn decode_bytes(bytes: &[u8]) -> Option<u64> {
    bytes.iter().try_fold(0u64, |result, c| {
        INDEX.get(c).and_then(|c| {
            result
                .checked_mul(CHARSET.len() as u64)
                .and_then(|v| v.checked_add(*c as u64))
        })
    })
}

pub fn encode(mut value: u64) -> String {
    if (value as usize) < CHARSET.len() {
        let r = VALUE[(value as usize)];
        let s = unsafe { String::from_utf8_unchecked(vec![r]) };
        return s;
    }
    let mut buf = [0u8; 16];
    let mut start_at = buf.len() - 1;
    while value > 0 {
        let index = value % CHARSET.len() as u64;
        value /= CHARSET.len() as u64;
        buf[start_at] = VALUE[(index as usize)];
        start_at -= 1;
    }
    unsafe { String::from_utf8_unchecked(buf[start_at + 1..].to_vec()) }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_decode() {
        fn check(_name: &str, value: &str, expected: impl Into<Option<u64>>) {
            let result = decode(value);
            assert_eq!(expected.into(), result);
        }

        check("Decode-0", "0", 0);
        check("Decode-1", "1", 1);
        check("Decode-9", "9", 9);
        check("Decode-a", "a", 10);
        check("Decode-z", "z", 35);
        check("Decode-A", "A", 36);
        check("Decode-Z", "Z", 61);
        check("Decode-10", "10", 62);
        check("Decode-lYGhA16ahyf", "lYGhA16ahyf", 18446744073709551615);
        check("Decode-lYGhA16ahyg", "lYGhA16ahyg", None);
    }

    #[test]
    fn test_encode() {
        fn check(_name: &str, value: u64, expected: impl Into<String>) {
            let result = encode(value);
            assert_eq!(expected.into(), result);
        }

        check("Encode-0", 0, "0");
        check("Encode-1", 1, "1");
        check("Encode-9", 9, "9");
        check("Encode-10", 10, "a");
        check("Decode-35", 35, "z");
        check("Decode-36", 36, "A");
        check("Decode-61", 61, "Z");
        check("Decode-62", 62, "10");
        check("Decode-lYGhA16ahyf", 18446744073709551615, "lYGhA16ahyf");
    }

    #[test]
    fn test_contains() {
        for &c in CHARSET {
            assert!(contains(c));
        }

        assert!(!contains(b' '));
        assert!(!contains(b'-'));
        assert!(!contains(b'_'));
    }
}