#![no_std]
#[cfg(test)]
extern crate alloc;
mod generated;
use core::convert;
use core::fmt;
use core::{cmp, hash};
pub use crate::generated::Group;
#[derive(Debug)]
pub struct Emoji {
emoji: &'static str,
name: &'static str,
unicode_version: UnicodeVersion,
group: Group,
skin_tone: Option<(u16, SkinTone)>,
aliases: Option<&'static [&'static str]>,
variations: &'static [&'static str],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct UnicodeVersion {
major: u32,
minor: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SkinTone {
Default,
Light,
MediumLight,
Medium,
MediumDark,
Dark,
}
impl UnicodeVersion {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
pub const fn major(self) -> u32 {
self.major
}
pub const fn minor(self) -> u32 {
self.minor
}
}
impl Emoji {
pub const fn as_str(&self) -> &str {
self.emoji
}
pub const fn name(&self) -> &str {
self.name
}
pub const fn unicode_version(&self) -> UnicodeVersion {
self.unicode_version
}
pub const fn group(&self) -> Group {
self.group
}
pub fn skin_tone(&self) -> Option<SkinTone> {
self.skin_tone.map(|(_, v)| v)
}
pub fn skin_tones(&self) -> Option<impl Iterator<Item = &'static Self>> {
let (i, _) = self.skin_tone?;
Some(crate::generated::EMOJIS[i as usize..].iter().take(6))
}
pub fn with_skin_tone(&self, skin_tone: SkinTone) -> Option<&'static Self> {
self.skin_tones()?
.find(|emoji| emoji.skin_tone().unwrap() == skin_tone)
}
pub fn shortcode(&self) -> Option<&str> {
self.aliases.and_then(|aliases| aliases.first().copied())
}
}
impl cmp::PartialEq<Emoji> for Emoji {
fn eq(&self, other: &Emoji) -> bool {
self.emoji == other.emoji
}
}
impl cmp::PartialEq<str> for Emoji {
fn eq(&self, s: &str) -> bool {
self.as_str() == s
}
}
impl cmp::PartialEq<&str> for Emoji {
fn eq(&self, s: &&str) -> bool {
self.as_str() == *s
}
}
impl cmp::Eq for Emoji {}
impl hash::Hash for Emoji {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.emoji.hash(state);
}
}
impl convert::AsRef<str> for Emoji {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl fmt::Display for Emoji {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
pub fn iter() -> impl Iterator<Item = &'static Emoji> {
crate::generated::EMOJIS
.iter()
.filter(|emoji| matches!(emoji.skin_tone(), Some(SkinTone::Default) | None))
}
pub fn lookup(query: &str) -> Option<&'static Emoji> {
crate::generated::EMOJIS.iter().find(|&e| {
e == query
|| e.variations.contains(&query)
|| e.aliases
.map(|aliases| aliases.contains(&query))
.unwrap_or(false)
})
}
impl Group {
pub fn emojis(&self) -> impl Iterator<Item = &'static Emoji> {
let group = *self;
iter()
.skip_while(move |emoji| emoji.group != group)
.take_while(move |emoji| emoji.group == group)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use alloc::vec::Vec;
#[test]
fn emoji_partial_eq_str() {
assert_eq!(lookup("đ").unwrap(), "đ");
}
#[test]
fn emoji_display() {
let buf = format!("{}", lookup("đ").unwrap());
assert_eq!(buf.as_str(), "đ");
}
#[test]
fn version_ordering() {
assert!(UnicodeVersion::new(13, 0) >= UnicodeVersion::new(12, 0));
assert!(UnicodeVersion::new(12, 1) >= UnicodeVersion::new(12, 0));
assert!(UnicodeVersion::new(12, 0) >= UnicodeVersion::new(12, 0));
assert!(UnicodeVersion::new(12, 0) < UnicodeVersion::new(12, 1));
assert!(UnicodeVersion::new(11, 0) < UnicodeVersion::new(12, 1));
assert!(UnicodeVersion::new(11, 0) < UnicodeVersion::new(12, 1));
}
#[test]
fn lookup_variation() {
assert_eq!(lookup("âš"), lookup("âšī¸"));
}
#[test]
fn iter_only_default_skin_tones() {
assert!(iter().all(|emoji| matches!(emoji.skin_tone(), Some(SkinTone::Default) | None)));
assert_ne!(
iter()
.filter(|emoji| matches!(emoji.skin_tone(), Some(SkinTone::Default)))
.count(),
0
);
}
#[test]
fn skin_tones() {
let skin_tones = [
SkinTone::Default,
SkinTone::Light,
SkinTone::MediumLight,
SkinTone::Medium,
SkinTone::MediumDark,
SkinTone::Dark,
];
for emoji in iter() {
match emoji.skin_tone() {
Some(_) => {
let emojis: Vec<_> = emoji.skin_tones().unwrap().collect();
assert_eq!(emojis.len(), 6);
let default = emojis[0];
for (emoji, skin_tone) in emojis.iter().zip(skin_tones) {
assert_eq!(emoji.skin_tone().unwrap(), skin_tone, "{:#?}", emojis);
assert_eq!(emoji.with_skin_tone(SkinTone::Default).unwrap(), default);
}
}
None => {
assert!(emoji.skin_tones().is_none());
}
}
}
}
}