bottomify 1.2.0

Fantastic (maybe) CLI for translating between bottom and human-readable text
Documentation
use std::error::Error;
use std::fmt;

include!(concat!(env!("OUT_DIR"), "/maps.rs"));

#[derive(Debug)]
pub struct TranslationError {
    pub why: String,
}

impl fmt::Display for TranslationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.why)
    }
}

impl Error for TranslationError {}

pub fn encode_byte(value: u8) -> &'static str {
    &BYTE_TO_EMOJI[value as usize]
}

pub fn decode_byte(input: &dyn AsRef<str>) -> Result<u8, TranslationError> {
    let input_ref = input.as_ref();
    let result = EMOJI_TO_BYTE.get(input_ref).ok_or_else(|| TranslationError {
        why: format!("Cannot decode character {}", input_ref),
    })?;
    Ok(*result)
}

pub fn encode_string(input: &dyn AsRef<str>) -> String {
    input.as_ref().bytes().map(encode_byte).collect::<String>()
}

pub fn decode_string(input: &dyn AsRef<str>) -> Result<String, TranslationError> {
    let input = input.as_ref();
    let result = {
        // Older versions used a ZWSP as a character separator, instead of `πŸ‘‰πŸ‘ˆ`.
        let split_char = input
            .chars()
            .find(|&c| c == '\u{200b}' || c == 'πŸ‘‰');

        if let Some('\u{200b}') = split_char {
            input.trim_end_matches("\u{200B}").split("\u{200B}")
        } else {
            input.trim_end_matches("πŸ‘‰πŸ‘ˆ").split("πŸ‘‰πŸ‘ˆ")
        }
    }
    .map(|c| decode_byte(&c))
    .collect::<Result<Vec<u8>, _>>()?;

    Ok(String::from_utf8_lossy(&result).to_string())
}

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

    #[test]
    fn test_string_encode() {
        assert_eq!(
            encode_string(&"Test"),
            "πŸ’–βœ¨βœ¨βœ¨,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨πŸ₯ΊπŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆ"
        );
    }

    #[test]
    fn test_byte_encode() {
        assert_eq!(encode_byte(b'h'), "πŸ’–πŸ’–,,,,πŸ‘‰πŸ‘ˆ",);
    }

    #[test]
    fn test_char_decode() {
        assert_eq!(decode_byte(&"πŸ’–πŸ’–,,,,").unwrap(), b'h',);
    }

    #[test]
    fn test_string_decode() {
        // Test that we haven't killed backwards-compat
        assert_eq!(
            decode_string(&"πŸ’–βœ¨βœ¨βœ¨,,,,\u{200B}πŸ’–πŸ’–,\u{200B}πŸ’–πŸ’–βœ¨πŸ₯Ί\u{200B}πŸ’–πŸ’–βœ¨πŸ₯Ί,\u{200B}")
                .unwrap(),
            "Test"
        );
        assert_eq!(
            decode_string(&"πŸ’–βœ¨βœ¨βœ¨,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨πŸ₯ΊπŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆ").unwrap(),
            "Test"
        );
    }

    #[test]
    fn test_unicode_string_encode() {
        assert_eq!(
            encode_string(&"πŸ₯Ί"),
            "πŸ«‚βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–βœ¨πŸ₯ΊπŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆ"
        );
        assert_eq!(
            encode_string(&"γŒγ‚“γ°γ‚Œ"),
            "πŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆ\
            πŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆ\
            πŸ’–πŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆ"
        );
    }

    #[test]
    fn test_unicode_string_decode() {
        assert_eq!(
            decode_string(&"πŸ«‚βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–βœ¨πŸ₯ΊπŸ‘‰πŸ‘ˆπŸ’–πŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆ")
                .unwrap(),
            "πŸ₯Ί",
        );
        assert_eq!(
            decode_string(
                &"πŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆ\
            πŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,,,,πŸ‘‰πŸ‘ˆ\
            πŸ’–πŸ’–πŸ’–βœ¨βœ¨πŸ₯Ί,πŸ‘‰πŸ‘ˆπŸ«‚βœ¨βœ¨πŸ₯Ί,,πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆπŸ’–πŸ’–βœ¨βœ¨βœ¨βœ¨πŸ‘‰πŸ‘ˆ"
            )
            .unwrap(),
            "γŒγ‚“γ°γ‚Œ",
        );
    }

    #[test]
    fn test_embedded_null_byte() {
        assert_eq!(
            encode_string(&"\0"),
            "β€οΈπŸ‘‰πŸ‘ˆ",
        );
        assert_eq!(
            decode_string(&"β€οΈπŸ‘‰πŸ‘ˆ")
                .unwrap(),
            "\0",
        );
    }
}