use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{self, satisfy},
combinator::{opt, recognize},
multi::{many1, many_m_n},
sequence::tuple,
IResult,
};
fn variant_selector(c: char) -> bool {
matches!(c, '\u{fe00}'..='\u{fe0f}')
}
fn zero_width_joiner(c: char) -> bool {
c == '\u{200d}'
}
fn single_char_emoji_core(c: char) -> bool {
matches!(c,
| '\u{2700}'..='\u{27bf}'
| '\u{2600}'..='\u{26ff}'
| '\u{3299}' | '\u{3297}'
| '\u{303d}' | '\u{3030}'
| '\u{24c2}'
| '\u{203c}' | '\u{2049}'
| '\u{25aa}'..='\u{25ab}' | '\u{25b6}' | '\u{25c0}' | '\u{25fb}'..='\u{25fe}'
| '\u{00a9}' | '\u{00ae}'
| '\u{2122}' | '\u{2139}'
| '\u{2b05}' | '\u{2b06}' | '\u{2b07}' | '\u{2b1b}' | '\u{2b1c}' | '\u{2b50}' | '\u{2b55}'
| '\u{231a}' | '\u{231b}' | '\u{2328}' | '\u{23cf}' | '\u{23e9}'..='\u{23f3}' | '\u{23f8}'..='\u{23fa}'
| '\u{2934}' | '\u{2935}'
| '\u{2190}'..='\u{2199}'
| 'π
°' | 'π
±' | 'π
Ύ'| 'π
Ώ' | 'π' | 'π'..='π'
| 'π' | 'π'| 'π―' | 'π²'..='πΆ' | 'πΈ'..='πΊ' | 'π' | 'π'
| 'π'..='π‘' | 'π€'..='π' | 'π'..='π'| 'π'..='π' | 'π'..='π°' | 'π³'..='π΅' | 'π·'..='π½' | 'πΏ'..='π½'
| 'π'..='π' | 'π'..='π§'| 'π―' | 'π°' | 'π³'..='πΊ' | 'π' | 'π'..='π' | 'π' | 'π' | 'π' | 'π€' | 'π₯'
| 'π¨' | 'π±' | 'π²' | 'πΌ' | 'π'..='π' | 'π'..='π' | 'π' | 'π' | 'π‘' |'π£' | 'π¨' | 'π―' | 'π³' | 'πΊ'..='πΏ'
| 'π'..='π'
| 'π'..='π
' | 'π'..='π' | 'π'..='π₯' | 'π©' | 'π«'..='π°' | 'π³'..='πΌ'
| 'π '..='π«'
| 'π€'..='π€Ί' | 'π€Ό'..='π₯
' | 'π₯'..='π§Ώ'
| 'π©°'..='π«Έ'
| 'π' | 'π°'
)
}
fn emoji_core(input: &str) -> IResult<&str, &str> {
alt((
recognize(tuple((
complete::char('π΄'),
many1(satisfy(|c| matches!(c, '\u{e0061}'..='\u{e007a}'))),
complete::char('\u{e007f}'),
))),
recognize(tuple((
satisfy(|c| matches!(c, 'π¦'..='πΏ')),
satisfy(|c| matches!(c, 'π¦'..='πΏ')),
))),
recognize(satisfy(single_char_emoji_core)),
recognize(tuple((
satisfy(|c| ('\u{0023}'..='\u{0039}').contains(&c)),
opt(complete::char('\u{fe0f}')),
complete::char('\u{20e3}'),
))),
tag("π"),
tag("π"),
tag("ποΈ"),
tag("π·οΈ"),
tag("β©οΈ"),
tag("βͺοΈ"),
))(input)
}
fn emoji_modifier(c: char) -> bool {
matches!(c, 'π»' | 'πΌ' | 'π½' | 'πΎ' | 'πΏ')
}
const USIZE_MAX_COMPOSITE_LEN: usize = 10;
macro_rules! emoji_with_variant {
() => {
tuple((
emoji_core,
opt(satisfy(variant_selector)),
opt(satisfy(emoji_modifier)),
))
};
}
pub fn emoji(input: &str) -> IResult<&str, &str> {
recognize(tuple((
emoji_with_variant!(),
many_m_n(
0,
USIZE_MAX_COMPOSITE_LEN,
tuple((satisfy(zero_width_joiner), emoji_with_variant!())),
),
)))(input)
}
pub fn get_first_emoji(text: &str) -> Option<&str> {
if let Ok((_, emoji)) = emoji(text) {
Some(emoji)
} else {
None
}
}
pub fn count_emojis_if_only_contains_emoji(input: &str) -> Option<u32> {
let mut remainder = input;
let mut count: u32 = 0;
while let Ok((new_remainder, _)) = emoji(remainder) {
remainder = new_remainder;
count = count.saturating_add(1);
}
if !remainder.is_empty() {
return None;
}
if count == 0 {
None
} else {
Some(count)
}
}
#[cfg(test)]
mod emoji_test {
mod emoji_char {
use crate::parser::is_emoji::emoji;
#[test]
fn some_emojis() {
assert!(emoji("π₯").is_ok());
}
#[test]
fn not_emoji() {
assert!(emoji("A").is_err());
}
#[test]
fn keycap() {
assert!(emoji("#οΈβ£").is_ok());
}
#[test]
fn flag() {
assert!(emoji("π¦π¨").is_ok());
}
#[test]
fn mahjong() {
assert!(emoji("π").is_ok());
}
#[test]
fn playing_card() {
assert!(emoji("π").is_ok());
}
#[test]
fn supplemental_arrows() {
assert!(emoji("‴").is_ok());
assert!(emoji("‡").is_ok());
}
#[test]
fn test_variant_emoji() {
assert!(emoji("ποΈββοΈ").is_ok());
assert!(emoji("π€Ήπ½").is_ok());
assert!(emoji("ππΏ").is_ok());
}
#[test]
fn test_composite_emoji() {
assert!(emoji("β€οΈβπ₯").is_ok());
assert!(emoji("πβπ¦Ί").is_ok());
assert!(emoji("π©βπ©βπ§").is_ok());
assert!(emoji("π§πΏβπ€βπ§πΏ").is_ok());
assert!(emoji("π©π½ββ€οΈβπ¨π½").is_ok());
}
}
mod exported_methods {
use crate::parser::is_emoji::{count_emojis_if_only_contains_emoji, get_first_emoji};
#[test]
fn test_get_first_emoji() {
assert_eq!(get_first_emoji("#οΈβ£ Hashtag"), Some("#οΈβ£"));
assert_eq!(get_first_emoji("#οΈβ£Hashtag"), Some("#οΈβ£"));
assert_eq!(get_first_emoji("#οΈβ£πHashtag"), Some("#οΈβ£"));
assert_eq!(get_first_emoji("Hashtag #οΈβ£"), None);
assert_eq!(get_first_emoji("'#οΈβ£"), None);
assert_eq!(get_first_emoji("β€οΈβπ₯Hashtag"), Some("β€οΈβπ₯"));
assert_eq!(get_first_emoji("π©π½ββ€οΈβπ¨π½Hashtag"), Some("π©π½ββ€οΈβπ¨π½"));
assert_eq!(get_first_emoji("πͺπΈπ§"), Some("πͺπΈ"));
}
#[test]
fn test_string_contains_only_emojis_and_count() {
assert_eq!(count_emojis_if_only_contains_emoji("#οΈβ£"), Some(1));
assert_eq!(
count_emojis_if_only_contains_emoji("π©π½ββ€οΈβπ¨π½Hashtag"),
None
);
assert_eq!(count_emojis_if_only_contains_emoji("β€οΈβπ₯"), Some(1));
assert_eq!(count_emojis_if_only_contains_emoji("π©π½ββ€οΈβπ¨π½"), Some(1));
assert_eq!(
count_emojis_if_only_contains_emoji("π©π½ββ€οΈβπ¨π½π©π½ββ€οΈβπ¨π½"),
Some(2)
);
assert_eq!(
count_emojis_if_only_contains_emoji("π©π½ββ€οΈβπ¨π½β€οΈβπ₯π©π½ββ€οΈβπ¨π½"),
Some(3)
);
assert_eq!(count_emojis_if_only_contains_emoji("π¨βπ¦°"), Some(1));
assert_eq!(count_emojis_if_only_contains_emoji("π¨βπ¦³"), Some(1));
assert_eq!(
count_emojis_if_only_contains_emoji("πͺπΈπ§π§π§π§π§π§π§"),
Some(8)
);
}
}
}