rustwemoji_parser/
lib.rs

1#[cfg(feature = "discord")]
2use regex::Regex;
3use rustwemoji::get;
4
5#[cfg(feature = "discord")]
6const RE_DISCORD_EMOJI: &str = r"<a?:[a-zA-Z0-9_]+:([0-9]{17,19})>";
7
8/// Tokens parsed
9#[derive(Debug, PartialEq, Eq)]
10pub enum Token {
11    /// Text token
12    Text(String),
13    /// Emoji token(bytes of png)
14    Emoji(Vec<u8>),
15    #[cfg(feature = "discord")]
16    /// Custom emoji token(url)
17    CustomEmoji(String),
18}
19
20impl Token {
21    pub fn new_text(s: impl Into<String>) -> Self {
22        Self::Text(s.into())
23    }
24    pub fn new_emoji(s: impl Into<Vec<u8>>) -> Self {
25        Self::Emoji(s.into())
26    }
27    #[cfg(feature = "discord")]
28    pub fn new_custom_emoji(s: String) -> Self {
29        let s = format!("https://cdn.discordapp.com/emojis/{}.png?size=96", s);
30        Self::CustomEmoji(s)
31    }
32}
33
34#[cfg(not(feature = "discord"))]
35fn raw_parse(s: String) -> Vec<Token> {
36    raw_parse_emoji(s)
37}
38
39#[cfg(not(feature = "async"))]
40pub fn parse(s: String) -> Vec<Token> {
41    raw_parse(s)
42}
43
44#[cfg(feature = "tokio")]
45/// Parse a string to tokens
46pub async fn parse(s: String) -> Result<Vec<Token>, tokio::task::JoinError> {
47    tokio::task::spawn(async { raw_parse(s) }).await
48}
49
50#[cfg(feature = "async-std")]
51/// Parse a string to tokens
52pub async fn parse(s: String) -> Vec<Token> {
53    async_std::task::spawn(async { raw_parse(s) }).await
54}
55
56fn raw_parse_emoji(s: String) -> Vec<Token> {
57    let s = s.chars();
58    s.map(|f| f.to_string())
59        .map(|f| {
60            if let Some(v) = get(&f) {
61                Token::new_emoji(v)
62            } else {
63                Token::new_text(f)
64            }
65        })
66        .collect::<Vec<_>>()
67}
68
69#[cfg(feature = "discord")]
70fn raw_parse(s: String) -> Vec<Token> {
71    let mut tokens = Vec::new();
72    let re = Regex::new(RE_DISCORD_EMOJI).unwrap();
73    let mut last = 0;
74    for m in re.find_iter(&s) {
75        let (start, end) = (m.range().start, m.range().end);
76        let text = s[last..start].to_string();
77        let emoji = s[start..end].to_string();
78        let id = re
79            .captures(&emoji)
80            .unwrap()
81            .get(1)
82            .unwrap()
83            .as_str()
84            .to_string();
85        tokens.extend(raw_parse_emoji(text));
86        tokens.push(Token::new_custom_emoji(id));
87        last = end;
88    }
89    let text = s[last..].to_string();
90    tokens.extend(raw_parse_emoji(text));
91    tokens
92}
93
94#[cfg(test)]
95mod test {
96    #[cfg(all(feature = "discord", feature = "async"))]
97    use super::*;
98    #[cfg(all(feature = "discord", feature = "async-std"))]
99    #[async_std::test]
100    async fn test_parse() {
101        let s = "Hello <a:pepega:123456789012345678> World".to_string();
102        let tokens = parse(s).await;
103        assert_eq!(
104            tokens,
105            vec![
106                Token::new_text("H"),
107                Token::new_text("e"),
108                Token::new_text("l"),
109                Token::new_text("l"),
110                Token::new_text("o"),
111                Token::new_text(" "),
112                Token::new_custom_emoji("123456789012345678".to_string()),
113                Token::new_text(" "),
114                Token::new_text("W"),
115                Token::new_text("o"),
116                Token::new_text("r"),
117                Token::new_text("l"),
118                Token::new_text("d"),
119            ]
120        );
121    }
122    #[cfg(all(feature = "discord", feature = "tokio"))]
123    #[tokio::test]
124    async fn test_parse() {
125        let s = "Hello <a:pepega:123456789012345678> World".to_string();
126        let tokens = parse(s).await.unwrap();
127        assert_eq!(
128            tokens,
129            vec![
130                Token::new_text("H"),
131                Token::new_text("e"),
132                Token::new_text("l"),
133                Token::new_text("l"),
134                Token::new_text("o"),
135                Token::new_text(" "),
136                Token::new_custom_emoji("123456789012345678".to_string()),
137                Token::new_text(" "),
138                Token::new_text("W"),
139                Token::new_text("o"),
140                Token::new_text("r"),
141                Token::new_text("l"),
142                Token::new_text("d"),
143            ]
144        );
145    }
146}
147
148// Will make a compile error with both async-syd and tokio enabled
149#[cfg(all(feature = "async-std", feature = "tokio"))]
150compile_error!("You can only enable one of the async features");