use core::fmt;
use crate::emojis::Emoji;
#[cfg(feature = "alloc")]
use alloc::string::String;
pub fn parse_alias(inp: &str) -> Option<&'static Emoji> {
if inp.starts_with(':') && inp.ends_with(':') && inp.is_ascii() && inp.len() > 2 {
parse_pure_alias(&inp[1..(inp.len() - 1)])
} else {
None
}
}
fn parse_pure_alias(inp: &str) -> Option<&'static Emoji> {
cfg_if::cfg_if! {
if #[cfg(feature = "alloc")] {
crate::alias::GEMOJI_MAP.get(inp).cloned()
} else {
crate::matching::matching(inp)
}
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "alloc")))]
pub fn parse_text(text: &str) -> String {
EmojiTextParser::new(text).collect()
}
#[derive(Debug, Clone)]
pub struct EmojiTextParser<'a> {
original: &'a str,
next_pos: usize,
emoji_fragment_start: bool,
}
impl<'a> EmojiTextParser<'a> {
pub fn new(original: &'a str) -> Self {
Self {
original,
next_pos: 0,
emoji_fragment_start: false,
}
}
fn is_valid_emoji_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '_' || c == '+' || c == '-'
}
fn text_until_next_colon(&mut self, start_idx: usize, skip: usize) -> &'a str {
if let Some(colon_idx) = self.original[(start_idx + skip)..].find(':') {
let true_colon_idx = start_idx + skip + colon_idx;
self.emoji_fragment_start = true;
self.next_pos = true_colon_idx + 1;
&self.original[start_idx..true_colon_idx]
} else {
self.emoji_fragment_start = false;
self.next_pos = self.original.len();
&self.original[start_idx..]
}
}
}
impl<'a> Iterator for EmojiTextParser<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.emoji_fragment_start {
debug_assert!(self.next_pos > 0);
let start_idx = self.next_pos - 1;
let chars = self.original[self.next_pos..].char_indices();
for (i, c) in chars {
if c == ':' {
let current_pos = self.next_pos + i;
let emoji_name = &self.original[start_idx..=current_pos];
if let Some(e) = crate::parse_alias(emoji_name) {
self.emoji_fragment_start = false;
self.next_pos = current_pos + 1;
return Some(e.grapheme);
} else {
self.emoji_fragment_start = true;
self.next_pos = current_pos + 1;
return Some(&self.original[start_idx..current_pos]);
}
} else if Self::is_valid_emoji_char(c) {
} else {
return Some(self.text_until_next_colon(start_idx, 1));
}
}
self.emoji_fragment_start = false;
self.next_pos = self.original.len();
Some(&self.original[start_idx..])
} else if self.next_pos < self.original.len() {
Some(self.text_until_next_colon(self.next_pos, 0))
} else {
None
}
}
}
impl<'a> fmt::Display for EmojiTextParser<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let copy = self.clone();
for frag in copy {
write!(fmt, "{}", frag)?
}
Ok(())
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::prelude::v1::*;
use super::*;
#[test]
fn parser_test() {
let input = "Hello :waving_hand:, I am a :technologist:.";
let mut parser = EmojiTextParser::new(input);
assert_eq!(Some("Hello "), parser.next());
assert_eq!(Some("👋"), parser.next());
assert_eq!(Some(", I am a "), parser.next());
assert_eq!(Some("🧑💻"), parser.next());
assert_eq!(Some("."), parser.next());
assert_eq!(None, parser.next());
}
#[test]
fn parser_misspelled() {
let input = "Hello :wavinghand:, I am a :tchnologist:."; let mut parser = EmojiTextParser::new(input);
assert_eq!(Some("Hello "), parser.next());
assert_eq!(Some(":wavinghand"), parser.next());
assert_eq!(Some(":, I am a "), parser.next());
assert_eq!(Some(":tchnologist"), parser.next());
assert_eq!(Some(":."), parser.next());
assert_eq!(None, parser.next());
}
#[test]
fn parser_thumbs() {
let input = ":thumbs_up::+1::-1::thumbs_down:";
let output = "👍👍👎👎";
let parser = EmojiTextParser::new(input);
assert_eq!(output, &parser.collect::<String>());
}
#[test]
fn parser_nothing() {
let input = "";
let mut parser = EmojiTextParser::new(input);
assert_eq!(None, parser.next());
}
#[test]
fn parser_corner_cases() {
let input = "100: :100:100:100: :100";
let output = "100: 💯100💯 :100";
let parser = EmojiTextParser::new(input);
assert_eq!(output, &parser.collect::<String>());
}
#[test]
fn parser_no_emoji() {
let input = "Hello :: I am: a technologist, :=: :).";
let parser = EmojiTextParser::new(input);
assert_eq!(input, &parser.collect::<String>());
}
#[test]
fn parser_no_colons() {
let input = "Hello, I am a technologist.";
let parser = EmojiTextParser::new(input);
assert_eq!(input, &parser.collect::<String>());
}
#[test]
fn parser_single_colon() {
let input = ":";
let parser = EmojiTextParser::new(input);
assert_eq!(input, &parser.collect::<String>());
}
#[test]
fn parser_only_colons() {
let input = ":::";
let parser = EmojiTextParser::new(input);
assert_eq!(input, &parser.collect::<String>());
}
#[test]
fn parser_double_colons() {
let input = "abc::technologist::def";
let output = "abc:🧑💻:def";
let parser = EmojiTextParser::new(input);
assert_eq!(output, &parser.collect::<String>());
}
#[test]
fn parser_many_colons() {
let input = "abc:::technologist:::def";
let output = "abc::🧑💻::def";
let parser = EmojiTextParser::new(input);
assert_eq!(output, &parser.collect::<String>());
}
#[test]
fn parse_alias_test() {
assert_eq!(
Some(&crate::flat::FLAG_ECUADOR),
parse_alias(":flag_ecuador:")
);
}
#[test]
fn parse_alias_none() {
assert_eq!(None, parse_alias(":hebele:"));
}
}