diff --git a/.scalafmt.conf b/.scalafmt.conf index 6df54a4..2e1c291 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -4,3 +4,4 @@ maxColumn = 100 newlines.source = keep rewrite.trailingCommas.style = keep docstrings.style = Asterisk +project.layout = StandardConvention diff --git a/README.md b/README.md index 82261fa..de3eadc 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,16 @@ scala> "yay".emoji res1: com.lightbend.emoji.Emoji = 😃 ``` +## Scala 3 + +Imports for extension methods are slightly shorter in Scala 3. + +The default `ShortCodes` is `given` in `ShortCodes`. +``` +scala> import com.lightbend.emoji.Emoji.* +scala> import com.lightbend.emoji.ShortCodes.{given, *} +``` + ## Similar Works These libraries have not been evaluated, and they may work or not: diff --git a/build.sbt b/build.sbt index 3ee456d..604d26e 100644 --- a/build.sbt +++ b/build.sbt @@ -28,6 +28,7 @@ scalaVersion := crossScalaVersions.value.head libraryDependencies ++= Seq( "org.scalatest" %% "scalatest-wordspec" % "3.2.12" % Test, "org.scalatest" %% "scalatest-shouldmatchers" % "3.2.12" % Test, + "org.scalameta" %% "munit" % "1.0.0-M1" % Test, ) scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-Xfatal-warnings") ++ ( @@ -46,15 +47,6 @@ Test / scalacOptions ~= (_ filterNot Set( "-Xfatal-warnings" )) -Compile / unmanagedSourceDirectories += { - val sourceDir = (Compile / sourceDirectory).value - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, n)) if n >= 13 => sourceDir / "scala-2.13+" - case Some((3, _)) => sourceDir / "scala-2.13+" - case _ => sourceDir / "scala-2.13-" - } -} - console / initialCommands := { """import com.lightbend.emoji._ |import com.lightbend.emoji.Emoji.Implicits._ diff --git a/src/main/scala-2.13-/com/lightbend/emoji/ScalaVersionSpecific.scala b/src/main/scala-2.12/com/lightbend/emoji/ScalaVersionSpecific.scala similarity index 100% rename from src/main/scala-2.13-/com/lightbend/emoji/ScalaVersionSpecific.scala rename to src/main/scala-2.12/com/lightbend/emoji/ScalaVersionSpecific.scala diff --git a/src/main/scala-2.13+/com/lightbend/emoji/ScalaVersionSpecific.scala b/src/main/scala-2.13/com/lightbend/emoji/ScalaVersionSpecific.scala similarity index 100% rename from src/main/scala-2.13+/com/lightbend/emoji/ScalaVersionSpecific.scala rename to src/main/scala-2.13/com/lightbend/emoji/ScalaVersionSpecific.scala diff --git a/src/main/scala/com/lightbend/emoji/Emoji.scala b/src/main/scala-2/com/lightbend/emoji/Emoji.scala similarity index 100% rename from src/main/scala/com/lightbend/emoji/Emoji.scala rename to src/main/scala-2/com/lightbend/emoji/Emoji.scala diff --git a/src/main/scala/com/lightbend/emoji/ShortCodes.scala b/src/main/scala-2/com/lightbend/emoji/ShortCodes.scala similarity index 100% rename from src/main/scala/com/lightbend/emoji/ShortCodes.scala rename to src/main/scala-2/com/lightbend/emoji/ShortCodes.scala diff --git a/src/main/scala-3/com/lightbend/emoji/Emoji.scala b/src/main/scala-3/com/lightbend/emoji/Emoji.scala new file mode 100644 index 0000000..689aedb --- /dev/null +++ b/src/main/scala-3/com/lightbend/emoji/Emoji.scala @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015-2022 Lightbend Inc. + */ +package com.lightbend.emoji + +import scala.util.Try + +/** The value class representing a codepoint in the Emoji set. + * + * @param codePoint the codepoint representing the Emoji character + */ +class Emoji private (val codePoint: Int) extends AnyVal: + + /** Returns the sequence of characters (usually surrogate pairs). + */ + def chars: Array[Char] = Character.toChars(codePoint) + + /** Returns the emoji's Unicode name. + */ + def name: String = Emoji.name(this.codePoint) + + /** Returns the hexadecimal code. + */ + def toHexString: String = Emoji.toHexString(this.codePoint) + + /** A convenience method that prepends "0x" before toHexString + */ + def hex: String = "0x" + toHexString + + /** Returns the emoji as a String. Use this if you want smiley faces. + */ + override def toString = new String(chars) + +/** This is the singleton object for Emoji. + */ +object Emoji: + + // Don't want this to conflict with shortCode.emoji + extension (string: String) def codePointEmoji: Emoji = Emoji( + Try(Integer.parseInt(string, 16)).recover { + case e: NumberFormatException => + val stripped = string.replace("0x", "") + Integer.parseInt(stripped, 16) + }.getOrElse { + throw new EmojiNotFound("Cannot parse emoji from hexadecimal string") + } + ) + + extension (codePoint: Int) def emoji: Emoji = Emoji(codePoint) + + /** Returns the emoji for the given array of characters, throws EmojiNotFound + */ + def apply(chars: Array[Char]): Emoji = apply(Character.codePointAt(chars, 0)) + + /** Returns the emoji given a string containing the codepoint. + */ + def apply(string: String): Emoji = apply(string.codePointAt(0)) + + /** Returns the emoji for this codepoint if found, throws EmojiNotFound otherwise. + */ + def apply(codePoint: Int): Emoji = new Emoji(validated(codePoint)) + + /** Returns Some(emoji) if found, None otherwise. + */ + def get(codePoint: Int): Option[Emoji] = + if isEmoji(codePoint) then Some(new Emoji(codePoint)) else None + + /** Returns true if this is a valid Unicode codepoint, false otherwise. + */ + def isEmoji(codePoint: Int): Boolean = + // there is no codeblock for unicode. + // val block = UnicodeBlock.of(codePoint) + // block == Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS_AND_PICTOGRAPHS || + // block == Character.UnicodeBlock.EMOTICONS || + // block == Character.UnicodeBlock.TRANSPORT_AND_MAP_SYMBOLS || + // block == Character.UnicodeBlock.MISCELLANEOUS_SYMBOLS || + // block == Character.UnicodeBlock.DINGBATS + Character.isValidCodePoint(codePoint) + + /** Throws an exception if this is not a valid Unicode codepoint. + */ + def validated(codePoint: Int): Int = + if isEmoji(codePoint) then codePoint else throw new EmojiNotFound("Code point is not emoji!") + + /** Returns the unicode name for this codePoint. Throws EmojiNotFound if this is not a valid codepoint. + * + * @param codePoint the codePoint. + * @return the unicode description of the emoji. + */ + def name(codePoint: Int): String = + Option(Character.getName(codePoint)).getOrElse { + throw new EmojiNotFound("No name found for this codePoint") + } + + /** Returns the hexadecimal string of the code point. + */ + def toHexString(codePoint: Int): String = codePoint.toHexString +end Emoji + +class EmojiNotFound(msg: String) extends RuntimeException(msg) diff --git a/src/main/scala-3/com/lightbend/emoji/ScalaVersionSpecific.scala b/src/main/scala-3/com/lightbend/emoji/ScalaVersionSpecific.scala new file mode 100644 index 0000000..9728b86 --- /dev/null +++ b/src/main/scala-3/com/lightbend/emoji/ScalaVersionSpecific.scala @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2015-2022 Lightbend Inc. + */ +package com.lightbend.emoji + +object ScalaVersionSpecific: + def checkLengths(sc: StringContext, args: Seq[Any]): Unit = + StringContext.checkLengths(args, sc.parts) diff --git a/src/main/scala-3/com/lightbend/emoji/ShortCodes.scala b/src/main/scala-3/com/lightbend/emoji/ShortCodes.scala new file mode 100644 index 0000000..f5eac49 --- /dev/null +++ b/src/main/scala-3/com/lightbend/emoji/ShortCodes.scala @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2015-2022 Lightbend Inc. + */ +package com.lightbend.emoji + +import scala.util.chaining.given + +import ScalaVersionSpecific.checkLengths + +/** An emoji to shortcode mapping. This is a class that should be declared and used as an implicit + * value, so that shortcode mappings don't have to be global across an application. + * + * "import ShortCodes.Defaults.given" to import the default shortcode mapping. "import + * ShortCodes.Implicits.given" to enrich Emoji and String with shortcode methods. + */ +class ShortCodes(template: Option[ShortCodes] = None): + + private val emojiToShortCodes = collection.mutable.Map[Emoji, collection.Set[String]]() + + private val shortCodeToEmoji = collection.mutable.Map[String, Emoji]() + + template.foreach { t => + for ((emoji, shortCodes) <- t.emojiToShortCodes) { + shortCodes.foreach(code => entry(emoji, code)) + } + } + + /** Defines a mapping between an emoji and a short code. Emojis may have multiple short code mappings. + */ + def entry(emoji: Emoji, shortCode: String): Unit = + emojiToShortCodes.get(emoji) match { + case Some(shortCodes) => + emojiToShortCodes += (emoji -> (shortCodes ++ Set(shortCode))) + case None => + emojiToShortCodes += (emoji -> Set(shortCode)) + } + shortCodeToEmoji += (shortCode -> emoji) + + /** Returns the short codes for this emoji. + */ + def shortCodes(emoji: Emoji): Option[collection.Set[String]] = emojiToShortCodes.get(emoji) + + /** Returns Some(emoji) if a short code is defined, None otherwise + */ + def emoji(shortCode: String): Option[Emoji] = shortCodeToEmoji.get(shortCode) + + /** Returns the set of emojis that have short codes. + */ + def emojis: collection.Set[Emoji] = emojiToShortCodes.keySet + + /** Returns the set of short codes mapped to emojis. + */ + def shortCodes: collection.Set[String] = shortCodeToEmoji.keySet + + /** Removes emoji from the shortcodes mapping. This removes all the codes that map to the emoji as well. + */ + def removeEmoji(emoji: Emoji): Unit = + emojiToShortCodes.remove(emoji).foreach { shortCodes => + shortCodes.foreach { shortCode => + shortCodeToEmoji.remove(shortCode) + } + } + + /** Removes a shortcode from the mapping. This does not remove the emoji. + */ + def removeCode(shortCode: String): Unit = + shortCodeToEmoji.remove(shortCode).foreach { emoji => + emojiToShortCodes.get(emoji).map { codes => + val set = codes diff Set(shortCode) + if (set.isEmpty) { + emojiToShortCodes.remove(emoji) + } else { + emojiToShortCodes.put(emoji, set) + } + } + } + + /** Completely removes the emoji and shortcodes from the mapping. + */ + def clear(): Unit = + emojiToShortCodes.clear() + shortCodeToEmoji.clear() +end ShortCodes + +/** Companion object for shortcodes. + */ +object ShortCodes: + + /** Returns the in-scope implicit shortcodes mapping. + */ + def current(using shortCodes: ShortCodes): ShortCodes = shortCodes + + extension (shortCodes: ShortCodes) def update(emoji: Emoji, shortCode: String): Unit = + shortCodes.entry(emoji, shortCode) + + /** Maps short codes onto Emoji and String, so you can say "+1".emoji and thumbsUpEmoji.shortCodes. + */ + extension (shortCode: String) def emoji(using shortCodes: ShortCodes): Emoji = + shortCodes.emoji(shortCode).getOrElse { throw new EmojiNotFound("No emoji found for short code") } + + extension (emoji: Emoji) def shortCodes(using shortCodes: ShortCodes): Option[collection.Set[String]] = + shortCodes.shortCodes(emoji) + + private val colonSyntax = ":([a-zA-Z0-9_+-]+):".r + + import StringContext.InvalidEscapeException + + // Emojilator + extension (sc: StringContext) def e(args: Any*)(using shortCodes: ShortCodes): String = + def emojify(s: String): String = colonSyntax.replaceAllIn( + s, + m => + try m.group(1).emoji.toString + catch case _: EmojiNotFound => m.matched + ) + checkLengths(sc, args) + val sb = new java.lang.StringBuilder + def process(part: String): String = emojify(StringContext.processEscapes(part)) + def partly(part: String): Unit = + try sb.append(process(part)) + catch + case e: InvalidEscapeException + if e.index < part.length - 1 && part.charAt(e.index + 1) == ':' => + sb.append(process(part.substring(0, e.index))) + sb.append(":") + partly(part.substring(e.index + 2)) + val pi = sc.parts.iterator + val ai = args.iterator + partly(pi.next()) + while ai.hasNext do + sb.append(ai.next()) + partly(pi.next()) + sb.toString + end extension + + /** The default shortcodes mapping, as used by emoji-cheat-sheet. + */ + given ShortCodes = ShortCodes().tap(defaults) + + def defaults(sc: ShortCodes) = + import sc.* + + entry(Emoji(0x1f44d), ("+1")) + entry(Emoji(0x1f44e), ("-1")) + entry(Emoji(0x1f4af), ("100")) + entry(Emoji(0x1f522), ("1234")) + entry(Emoji(0x1f3b1), ("8ball")) + entry(Emoji(0x1f170), ("a")) + entry(Emoji(0x1f18e), ("ab")) + entry(Emoji(0x1f524), ("abc")) + entry(Emoji(0x1f521), ("abcd")) + entry(Emoji(0x1f251), ("accept")) + entry(Emoji(0x1f6a1), ("aerial_tramway")) + entry(Emoji(0x02708), ("airplane")) + entry(Emoji(0x023f0), ("alarm_clock")) + entry(Emoji(0x1f47d), ("alien")) + entry(Emoji(0x1f691), ("ambulance")) + entry(Emoji(0x02693), ("anchor")) + entry(Emoji(0x1f47c), ("angel")) + entry(Emoji(0x1f4a2), ("anger")) + entry(Emoji(0x1f620), ("angry")) + entry(Emoji(0x1f627), ("anguished")) + entry(Emoji(0x1f41c), ("ant")) + entry(Emoji(0x1f34e), ("apple")) + entry(Emoji(0x02652), ("aquarius")) + entry(Emoji(0x02648), ("aries")) + entry(Emoji(0x025c0), ("arrow_backward")) + entry(Emoji(0x023ec), ("arrow_double_down")) + entry(Emoji(0x023eb), ("arrow_double_up")) + entry(Emoji(0x02b07), ("arrow_down")) + entry(Emoji(0x1f53d), ("arrow_down_small")) + entry(Emoji(0x025b6), ("arrow_forward")) + entry(Emoji(0x02935), ("arrow_heading_down")) + entry(Emoji(0x02934), ("arrow_heading_up")) + entry(Emoji(0x02b05), ("arrow_left")) + entry(Emoji(0x02199), ("arrow_lower_left")) + entry(Emoji(0x02198), ("arrow_lower_right")) + entry(Emoji(0x027a1), ("arrow_right")) + entry(Emoji(0x021aa), ("arrow_right_hook")) + entry(Emoji(0x02b06), ("arrow_up")) + entry(Emoji(0x02195), ("arrow_up_down")) + entry(Emoji(0x1f53c), ("arrow_up_small")) + entry(Emoji(0x02196), ("arrow_upper_left")) + entry(Emoji(0x02197), ("arrow_upper_right")) + entry(Emoji(0x1f503), ("arrows_clockwise")) + entry(Emoji(0x1f504), ("arrows_counterclockwise")) + entry(Emoji(0x1f3a8), ("art")) + entry(Emoji(0x1f69b), ("articulated_lorry")) + entry(Emoji(0x1f632), ("astonished")) + entry(Emoji(0x1f45f), ("athletic_shoe")) + entry(Emoji(0x1f3e7), ("atm")) + entry(Emoji(0x1f171), ("b")) + entry(Emoji(0x1f476), ("baby")) + entry(Emoji(0x1f37c), ("baby_bottle")) + entry(Emoji(0x1f424), ("baby_chick")) + entry(Emoji(0x1f6bc), ("baby_symbol")) + entry(Emoji(0x1f519), ("back")) + entry(Emoji(0x1f6c4), ("baggage_claim")) + entry(Emoji(0x1f388), ("balloon")) + entry(Emoji(0x02611), ("ballot_box_with_check")) + entry(Emoji(0x1f38d), ("bamboo")) + entry(Emoji(0x1f34c), ("banana")) + entry(Emoji(0x0203c), ("bangbang")) + entry(Emoji(0x1f3e6), ("bank")) + entry(Emoji(0x1f4ca), ("bar_chart")) + entry(Emoji(0x1f488), ("barber")) + entry(Emoji(0x026be), ("baseball")) + entry(Emoji(0x1f3c0), ("basketball")) + entry(Emoji(0x1f6c0), ("bath")) + entry(Emoji(0x1f6c1), ("bathtub")) + entry(Emoji(0x1f50b), ("battery")) + entry(Emoji(0x1f43b), ("bear")) + entry(Emoji(0x1f41d), ("bee")) + entry(Emoji(0x1f37a), ("beer")) + entry(Emoji(0x1f37b), ("beers")) + entry(Emoji(0x1f41e), ("beetle")) + entry(Emoji(0x1f530), ("beginner")) + entry(Emoji(0x1f514), ("bell")) + entry(Emoji(0x1f371), ("bento")) + entry(Emoji(0x1f6b4), ("bicyclist")) + entry(Emoji(0x1f6b2), ("bike")) + entry(Emoji(0x1f459), ("bikini")) + entry(Emoji(0x1f426), ("bird")) + entry(Emoji(0x1f382), ("birthday")) + entry(Emoji(0x026ab), ("black_circle")) + entry(Emoji(0x1f0cf), ("black_joker")) + entry(Emoji(0x02b1b), ("black_large_square")) + entry(Emoji(0x025fe), ("black_medium_small_square")) + entry(Emoji(0x025fc), ("black_medium_square")) + entry(Emoji(0x02712), ("black_nib")) + entry(Emoji(0x025aa), ("black_small_square")) + entry(Emoji(0x1f532), ("black_square_button")) + entry(Emoji(0x1f33c), ("blossom")) + entry(Emoji(0x1f421), ("blowfish")) + entry(Emoji(0x1f4d8), ("blue_book")) + entry(Emoji(0x1f699), ("blue_car")) + entry(Emoji(0x1f499), ("blue_heart")) + entry(Emoji(0x1f60a), ("blush")) + entry(Emoji(0x1f417), ("boar")) + entry(Emoji(0x026f5), ("boat")) + entry(Emoji(0x1f4a3), ("bomb")) + entry(Emoji(0x1f4d6), ("book")) + entry(Emoji(0x1f516), ("bookmark")) + entry(Emoji(0x1f4d1), ("bookmark_tabs")) + entry(Emoji(0x1f4da), ("books")) + entry(Emoji(0x1f4a5), ("boom")) + entry(Emoji(0x1f462), ("boot")) + entry(Emoji(0x1f490), ("bouquet")) + entry(Emoji(0x1f647), ("bow")) + entry(Emoji(0x1f3b3), ("bowling")) + entry(Emoji(0x1f466), ("boy")) + entry(Emoji(0x1f35e), ("bread")) + entry(Emoji(0x1f470), ("bride_with_veil")) + entry(Emoji(0x1f309), ("bridge_at_night")) + entry(Emoji(0x1f4bc), ("briefcase")) + entry(Emoji(0x1f494), ("broken_heart")) + entry(Emoji(0x1f41b), ("bug")) + entry(Emoji(0x1f4a1), ("bulb")) + entry(Emoji(0x1f685), ("bullettrain_front")) + entry(Emoji(0x1f684), ("bullettrain_side")) + entry(Emoji(0x1f68c), ("bus")) + entry(Emoji(0x1f68f), ("busstop")) + entry(Emoji(0x1f464), ("bust_in_silhouette")) + entry(Emoji(0x1f465), ("busts_in_silhouette")) + entry(Emoji(0x1f335), ("cactus")) + entry(Emoji(0x1f370), ("cake")) + entry(Emoji(0x1f4c6), ("calendar")) + entry(Emoji(0x1f4f2), ("calling")) + entry(Emoji(0x1f42b), ("camel")) + entry(Emoji(0x1f4f7), ("camera")) + entry(Emoji(0x0264b), ("cancer")) + entry(Emoji(0x1f36c), ("candy")) + entry(Emoji(0x1f520), ("capital_abcd")) + entry(Emoji(0x02651), ("capricorn")) + entry(Emoji(0x1f697), ("car")) + entry(Emoji(0x1f4c7), ("card_index")) + entry(Emoji(0x1f3a0), ("carousel_horse")) + entry(Emoji(0x1f431), ("cat")) + entry(Emoji(0x1f408), ("cat2")) + entry(Emoji(0x1f4bf), ("cd")) + entry(Emoji(0x1f4b9), ("chart")) + entry(Emoji(0x1f4c9), ("chart_with_downwards_trend")) + entry(Emoji(0x1f4c8), ("chart_with_upwards_trend")) + entry(Emoji(0x1f3c1), ("checkered_flag")) + entry(Emoji(0x1f352), ("cherries")) + entry(Emoji(0x1f338), ("cherry_blossom")) + entry(Emoji(0x1f330), ("chestnut")) + entry(Emoji(0x1f414), ("chicken")) + entry(Emoji(0x1f6b8), ("children_crossing")) + entry(Emoji(0x1f36b), ("chocolate_bar")) + entry(Emoji(0x1f384), ("christmas_tree")) + entry(Emoji(0x026ea), ("church")) + entry(Emoji(0x1f3a6), ("cinema")) + entry(Emoji(0x1f3aa), ("circus_tent")) + entry(Emoji(0x1f307), ("city_sunrise")) + entry(Emoji(0x1f306), ("city_sunset")) + entry(Emoji(0x1f191), ("cl")) + entry(Emoji(0x1f44f), ("clap")) + entry(Emoji(0x1f3ac), ("clapper")) + entry(Emoji(0x1f4cb), ("clipboard")) + entry(Emoji(0x1f550), ("clock1")) + entry(Emoji(0x1f559), ("clock10")) + entry(Emoji(0x1f565), ("clock1030")) + entry(Emoji(0x1f55a), ("clock11")) + entry(Emoji(0x1f566), ("clock1130")) + entry(Emoji(0x1f55b), ("clock12")) + entry(Emoji(0x1f567), ("clock1230")) + entry(Emoji(0x1f55c), ("clock130")) + entry(Emoji(0x1f551), ("clock2")) + entry(Emoji(0x1f55d), ("clock230")) + entry(Emoji(0x1f552), ("clock3")) + entry(Emoji(0x1f55e), ("clock330")) + entry(Emoji(0x1f553), ("clock4")) + entry(Emoji(0x1f55f), ("clock430")) + entry(Emoji(0x1f554), ("clock5")) + entry(Emoji(0x1f560), ("clock530")) + entry(Emoji(0x1f555), ("clock6")) + entry(Emoji(0x1f561), ("clock630")) + entry(Emoji(0x1f556), ("clock7")) + entry(Emoji(0x1f562), ("clock730")) + entry(Emoji(0x1f557), ("clock8")) + entry(Emoji(0x1f563), ("clock830")) + entry(Emoji(0x1f558), ("clock9")) + entry(Emoji(0x1f564), ("clock930")) + entry(Emoji(0x1f4d5), ("closed_book")) + entry(Emoji(0x1f510), ("closed_lock_with_key")) + entry(Emoji(0x1f302), ("closed_umbrella")) + entry(Emoji(0x02601), ("cloud")) + entry(Emoji(0x02663), ("clubs")) + entry(Emoji(0x1f378), ("cocktail")) + entry(Emoji(0x02615), ("coffee")) + entry(Emoji(0x1f630), ("cold_sweat")) + entry(Emoji(0x1f4a5), ("collision")) + entry(Emoji(0x1f4bb), ("computer")) + entry(Emoji(0x1f38a), ("confetti_ball")) + entry(Emoji(0x1f616), ("confounded")) + entry(Emoji(0x1f615), ("confused")) + entry(Emoji(0x03297), ("congratulations")) + entry(Emoji(0x1f6a7), ("construction")) + entry(Emoji(0x1f477), ("construction_worker")) + entry(Emoji(0x1f3ea), ("convenience_store")) + entry(Emoji(0x1f36a), ("cookie")) + entry(Emoji(0x1f192), ("cool")) + entry(Emoji(0x1f46e), ("cop")) + entry(Emoji(0x000a9), ("copyright")) + entry(Emoji(0x1f33d), ("corn")) + entry(Emoji(0x1f46b), ("couple")) + entry(Emoji(0x1f491), ("couple_with_heart")) + entry(Emoji(0x1f48f), ("couplekiss")) + entry(Emoji(0x1f42e), ("cow")) + entry(Emoji(0x1f404), ("cow2")) + entry(Emoji(0x1f4b3), ("credit_card")) + entry(Emoji(0x1f319), ("crescent_moon")) + entry(Emoji(0x1f40a), ("crocodile")) + entry(Emoji(0x1f38c), ("crossed_flags")) + entry(Emoji(0x1f451), ("crown")) + entry(Emoji(0x1f622), ("cry")) + entry(Emoji(0x1f63f), ("crying_cat_face")) + entry(Emoji(0x1f52e), ("crystal_ball")) + entry(Emoji(0x1f498), ("cupid")) + entry(Emoji(0x027b0), ("curly_loop")) + entry(Emoji(0x1f4b1), ("currency_exchange")) + entry(Emoji(0x1f35b), ("curry")) + entry(Emoji(0x1f36e), ("custard")) + entry(Emoji(0x1f6c3), ("customs")) + entry(Emoji(0x1f300), ("cyclone")) + entry(Emoji(0x1f483), ("dancer")) + entry(Emoji(0x1f46f), ("dancers")) + entry(Emoji(0x1f361), ("dango")) + entry(Emoji(0x1f3af), ("dart")) + entry(Emoji(0x1f4a8), ("dash")) + entry(Emoji(0x1f4c5), ("date")) + entry(Emoji(0x1f333), ("deciduous_tree")) + entry(Emoji(0x1f3ec), ("department_store")) + entry(Emoji(0x1f4a0), ("diamond_shape_with_a_dot_inside")) + entry(Emoji(0x02666), ("diamonds")) + entry(Emoji(0x1f61e), ("disappointed")) + entry(Emoji(0x1f625), ("disappointed_relieved")) + entry(Emoji(0x1f4ab), ("dizzy")) + entry(Emoji(0x1f635), ("dizzy_face")) + entry(Emoji(0x1f6af), ("do_not_litter")) + entry(Emoji(0x1f436), ("dog")) + entry(Emoji(0x1f415), ("dog2")) + entry(Emoji(0x1f4b5), ("dollar")) + entry(Emoji(0x1f38e), ("dolls")) + entry(Emoji(0x1f42c), ("dolphin")) + entry(Emoji(0x1f6aa), ("door")) + entry(Emoji(0x1f369), ("doughnut")) + entry(Emoji(0x1f409), ("dragon")) + entry(Emoji(0x1f432), ("dragon_face")) + entry(Emoji(0x1f457), ("dress")) + entry(Emoji(0x1f42a), ("dromedary_camel")) + entry(Emoji(0x1f4a7), ("droplet")) + entry(Emoji(0x1f4c0), ("dvd")) + entry(Emoji(0x1f4e7), ("e-mail")) + entry(Emoji(0x1f442), ("ear")) + entry(Emoji(0x1f33e), ("ear_of_rice")) + entry(Emoji(0x1f30d), ("earth_africa")) + entry(Emoji(0x1f30e), ("earth_americas")) + entry(Emoji(0x1f30f), ("earth_asia")) + entry(Emoji(0x1f373), ("egg")) + entry(Emoji(0x1f346), ("eggplant")) + entry(Emoji(0x02734), ("eight_pointed_black_star")) + entry(Emoji(0x02733), ("eight_spoked_asterisk")) + entry(Emoji(0x1f50c), ("electric_plug")) + entry(Emoji(0x1f418), ("elephant")) + entry(Emoji(0x02709), ("email")) + entry(Emoji(0x1f51a), ("end")) + entry(Emoji(0x02709), ("envelope")) + entry(Emoji(0x1f4e9), ("envelope_with_arrow")) + entry(Emoji(0x1f4b6), ("euro")) + entry(Emoji(0x1f3f0), ("european_castle")) + entry(Emoji(0x1f3e4), ("european_post_office")) + entry(Emoji(0x1f332), ("evergreen_tree")) + entry(Emoji(0x02757), ("exclamation")) + entry(Emoji(0x1f611), ("expressionless")) + entry(Emoji(0x1f453), ("eyeglasses")) + entry(Emoji(0x1f440), ("eyes")) + entry(Emoji(0x1f44a), ("facepunch")) + entry(Emoji(0x1f3ed), ("factory")) + entry(Emoji(0x1f342), ("fallen_leaf")) + entry(Emoji(0x1f46a), ("family")) + entry(Emoji(0x023e9), ("fast_forward")) + entry(Emoji(0x1f4e0), ("fax")) + entry(Emoji(0x1f628), ("fearful")) + entry(Emoji(0x1f43e), ("feet")) + entry(Emoji(0x1f3a1), ("ferris_wheel")) + entry(Emoji(0x1f4c1), ("file_folder")) + entry(Emoji(0x1f525), ("fire")) + entry(Emoji(0x1f692), ("fire_engine")) + entry(Emoji(0x1f386), ("fireworks")) + entry(Emoji(0x1f313), ("first_quarter_moon")) + entry(Emoji(0x1f31b), ("first_quarter_moon_with_face")) + entry(Emoji(0x1f41f), ("fish")) + entry(Emoji(0x1f365), ("fish_cake")) + entry(Emoji(0x1f3a3), ("fishing_pole_and_fish")) + entry(Emoji(0x0270a), ("fist")) + entry(Emoji(0x1f38f), ("flags")) + entry(Emoji(0x1f526), ("flashlight")) + entry(Emoji(0x1f42c), ("flipper")) + entry(Emoji(0x1f4be), ("floppy_disk")) + entry(Emoji(0x1f3b4), ("flower_playing_cards")) + entry(Emoji(0x1f633), ("flushed")) + entry(Emoji(0x1f301), ("foggy")) + entry(Emoji(0x1f3c8), ("football")) + entry(Emoji(0x1f463), ("footprints")) + entry(Emoji(0x1f374), ("fork_and_knife")) + entry(Emoji(0x026f2), ("fountain")) + entry(Emoji(0x1f340), ("four_leaf_clover")) + entry(Emoji(0x1f193), ("free")) + entry(Emoji(0x1f364), ("fried_shrimp")) + entry(Emoji(0x1f35f), ("fries")) + entry(Emoji(0x1f438), ("frog")) + entry(Emoji(0x1f626), ("frowning")) + entry(Emoji(0x026fd), ("fuelpump")) + entry(Emoji(0x1f315), ("full_moon")) + entry(Emoji(0x1f31d), ("full_moon_with_face")) + entry(Emoji(0x1f3b2), ("game_die")) + entry(Emoji(0x1f48e), ("gem")) + entry(Emoji(0x0264a), ("gemini")) + entry(Emoji(0x1f47b), ("ghost")) + entry(Emoji(0x1f381), ("gift")) + entry(Emoji(0x1f49d), ("gift_heart")) + entry(Emoji(0x1f467), ("girl")) + entry(Emoji(0x1f310), ("globe_with_meridians")) + entry(Emoji(0x1f410), ("goat")) + entry(Emoji(0x026f3), ("golf")) + entry(Emoji(0x1f347), ("grapes")) + entry(Emoji(0x1f34f), ("green_apple")) + entry(Emoji(0x1f4d7), ("green_book")) + entry(Emoji(0x1f49a), ("green_heart")) + entry(Emoji(0x02755), ("grey_exclamation")) + entry(Emoji(0x02754), ("grey_question")) + entry(Emoji(0x1f62c), ("grimacing")) + entry(Emoji(0x1f601), ("grin")) + entry(Emoji(0x1f600), ("grinning")) + entry(Emoji(0x1f482), ("guardsman")) + entry(Emoji(0x1f3b8), ("guitar")) + entry(Emoji(0x1f52b), ("gun")) + entry(Emoji(0x1f487), ("haircut")) + entry(Emoji(0x1f354), ("hamburger")) + entry(Emoji(0x1f528), ("hammer")) + entry(Emoji(0x1f439), ("hamster")) + entry(Emoji(0x0270b), ("hand")) + entry(Emoji(0x1f45c), ("handbag")) + entry(Emoji(0x1f4a9), ("hankey")) + entry(Emoji(0x1f425), ("hatched_chick")) + entry(Emoji(0x1f423), ("hatching_chick")) + entry(Emoji(0x1f3a7), ("headphones")) + entry(Emoji(0x1f649), ("hear_no_evil")) + entry(Emoji(0x02764), ("heart")) + entry(Emoji(0x1f49f), ("heart_decoration")) + entry(Emoji(0x1f60d), ("heart_eyes")) + entry(Emoji(0x1f63b), ("heart_eyes_cat")) + entry(Emoji(0x1f493), ("heartbeat")) + entry(Emoji(0x1f497), ("heartpulse")) + entry(Emoji(0x02665), ("hearts")) + entry(Emoji(0x02714), ("heavy_check_mark")) + entry(Emoji(0x02797), ("heavy_division_sign")) + entry(Emoji(0x1f4b2), ("heavy_dollar_sign")) + entry(Emoji(0x02757), ("heavy_exclamation_mark")) + entry(Emoji(0x02796), ("heavy_minus_sign")) + entry(Emoji(0x02716), ("heavy_multiplication_x")) + entry(Emoji(0x02795), ("heavy_plus_sign")) + entry(Emoji(0x1f681), ("helicopter")) + entry(Emoji(0x1f33f), ("herb")) + entry(Emoji(0x1f33a), ("hibiscus")) + entry(Emoji(0x1f506), ("high_brightness")) + entry(Emoji(0x1f460), ("high_heel")) + entry(Emoji(0x1f52a), ("hocho")) + entry(Emoji(0x1f36f), ("honey_pot")) + entry(Emoji(0x1f41d), ("honeybee")) + entry(Emoji(0x1f434), ("horse")) + entry(Emoji(0x1f3c7), ("horse_racing")) + entry(Emoji(0x1f3e5), ("hospital")) + entry(Emoji(0x1f3e8), ("hotel")) + entry(Emoji(0x02668), ("hotsprings")) + entry(Emoji(0x0231b), ("hourglass")) + entry(Emoji(0x023f3), ("hourglass_flowing_sand")) + entry(Emoji(0x1f3e0), ("house")) + entry(Emoji(0x1f3e1), ("house_with_garden")) + entry(Emoji(0x1f62f), ("hushed")) + entry(Emoji(0x1f368), ("ice_cream")) + entry(Emoji(0x1f366), ("icecream")) + entry(Emoji(0x1f194), ("id")) + entry(Emoji(0x1f250), ("ideograph_advantage")) + entry(Emoji(0x1f47f), ("imp")) + entry(Emoji(0x1f4e5), ("inbox_tray")) + entry(Emoji(0x1f4e8), ("incoming_envelope")) + entry(Emoji(0x1f481), ("information_desk_person")) + entry(Emoji(0x02139), ("information_source")) + entry(Emoji(0x1f607), ("innocent")) + entry(Emoji(0x02049), ("interrobang")) + entry(Emoji(0x1f4f1), ("iphone")) + entry(Emoji(0x1f3ee), ("izakaya_lantern")) + entry(Emoji(0x1f383), ("jack_o_lantern")) + entry(Emoji(0x1f5fe), ("japan")) + entry(Emoji(0x1f3ef), ("japanese_castle")) + entry(Emoji(0x1f47a), ("japanese_goblin")) + entry(Emoji(0x1f479), ("japanese_ogre")) + entry(Emoji(0x1f456), ("jeans")) + entry(Emoji(0x1f602), ("joy")) + entry(Emoji(0x1f639), ("joy_cat")) + entry(Emoji(0x1f511), ("key")) + entry(Emoji(0x1f51f), ("keycap_ten")) + entry(Emoji(0x1f458), ("kimono")) + entry(Emoji(0x1f48b), ("kiss")) + entry(Emoji(0x1f617), ("kissing")) + entry(Emoji(0x1f63d), ("kissing_cat")) + entry(Emoji(0x1f61a), ("kissing_closed_eyes")) + entry(Emoji(0x1f618), ("kissing_heart")) + entry(Emoji(0x1f619), ("kissing_smiling_eyes")) + entry(Emoji(0x1f428), ("koala")) + entry(Emoji(0x1f201), ("koko")) + entry(Emoji(0x1f3ee), ("lantern")) + entry(Emoji(0x1f535), ("large_blue_circle")) + entry(Emoji(0x1f537), ("large_blue_diamond")) + entry(Emoji(0x1f536), ("large_orange_diamond")) + entry(Emoji(0x1f317), ("last_quarter_moon")) + entry(Emoji(0x1f31c), ("last_quarter_moon_with_face")) + entry(Emoji(0x1f606), ("laughing")) + entry(Emoji(0x1f343), ("leaves")) + entry(Emoji(0x1f4d2), ("ledger")) + entry(Emoji(0x1f6c5), ("left_luggage")) + entry(Emoji(0x02194), ("left_right_arrow")) + entry(Emoji(0x021a9), ("leftwards_arrow_with_hook")) + entry(Emoji(0x1f34b), ("lemon")) + entry(Emoji(0x0264c), ("leo")) + entry(Emoji(0x1f406), ("leopard")) + entry(Emoji(0x0264e), ("libra")) + entry(Emoji(0x1f688), ("light_rail")) + entry(Emoji(0x1f517), ("link")) + entry(Emoji(0x1f444), ("lips")) + entry(Emoji(0x1f484), ("lipstick")) + entry(Emoji(0x1f512), ("lock")) + entry(Emoji(0x1f50f), ("lock_with_ink_pen")) + entry(Emoji(0x1f36d), ("lollipop")) + entry(Emoji(0x027bf), ("loop")) + entry(Emoji(0x1f4e2), ("loudspeaker")) + entry(Emoji(0x1f3e9), ("love_hotel")) + entry(Emoji(0x1f48c), ("love_letter")) + entry(Emoji(0x1f505), ("low_brightness")) + entry(Emoji(0x024c2), ("m")) + entry(Emoji(0x1f50d), ("mag")) + entry(Emoji(0x1f50e), ("mag_right")) + entry(Emoji(0x1f004), ("mahjong")) + entry(Emoji(0x1f4eb), ("mailbox")) + entry(Emoji(0x1f4ea), ("mailbox_closed")) + entry(Emoji(0x1f4ec), ("mailbox_with_mail")) + entry(Emoji(0x1f4ed), ("mailbox_with_no_mail")) + entry(Emoji(0x1f468), ("man")) + entry(Emoji(0x1f472), ("man_with_gua_pi_mao")) + entry(Emoji(0x1f473), ("man_with_turban")) + entry(Emoji(0x1f45e), ("mans_shoe")) + entry(Emoji(0x1f341), ("maple_leaf")) + entry(Emoji(0x1f637), ("mask")) + entry(Emoji(0x1f486), ("massage")) + entry(Emoji(0x1f356), ("meat_on_bone")) + entry(Emoji(0x1f4e3), ("mega")) + entry(Emoji(0x1f348), ("melon")) + entry(Emoji(0x1f4dd), ("memo")) + entry(Emoji(0x1f6b9), ("mens")) + entry(Emoji(0x1f687), ("metro")) + entry(Emoji(0x1f3a4), ("microphone")) + entry(Emoji(0x1f52c), ("microscope")) + entry(Emoji(0x1f30c), ("milky_way")) + entry(Emoji(0x1f690), ("minibus")) + entry(Emoji(0x1f4bd), ("minidisc")) + entry(Emoji(0x1f4f4), ("mobile_phone_off")) + entry(Emoji(0x1f4b8), ("money_with_wings")) + entry(Emoji(0x1f4b0), ("moneybag")) + entry(Emoji(0x1f412), ("monkey")) + entry(Emoji(0x1f435), ("monkey_face")) + entry(Emoji(0x1f69d), ("monorail")) + entry(Emoji(0x1f314), ("moon")) + entry(Emoji(0x1f393), ("mortar_board")) + entry(Emoji(0x1f5fb), ("mount_fuji")) + entry(Emoji(0x1f6b5), ("mountain_bicyclist")) + entry(Emoji(0x1f6a0), ("mountain_cableway")) + entry(Emoji(0x1f69e), ("mountain_railway")) + entry(Emoji(0x1f42d), ("mouse")) + entry(Emoji(0x1f401), ("mouse2")) + entry(Emoji(0x1f3a5), ("movie_camera")) + entry(Emoji(0x1f5ff), ("moyai")) + entry(Emoji(0x1f4aa), ("muscle")) + entry(Emoji(0x1f344), ("mushroom")) + entry(Emoji(0x1f3b9), ("musical_keyboard")) + entry(Emoji(0x1f3b5), ("musical_note")) + entry(Emoji(0x1f3bc), ("musical_score")) + entry(Emoji(0x1f507), ("mute")) + entry(Emoji(0x1f485), ("nail_care")) + entry(Emoji(0x1f4db), ("name_badge")) + entry(Emoji(0x1f454), ("necktie")) + entry(Emoji(0x0274e), ("negative_squared_cross_mark")) + entry(Emoji(0x1f610), ("neutral_face")) + entry(Emoji(0x1f195), ("new")) + entry(Emoji(0x1f311), ("new_moon")) + entry(Emoji(0x1f31a), ("new_moon_with_face")) + entry(Emoji(0x1f4f0), ("newspaper")) + entry(Emoji(0x1f196), ("ng")) + entry(Emoji(0x1f515), ("no_bell")) + entry(Emoji(0x1f6b3), ("no_bicycles")) + entry(Emoji(0x026d4), ("no_entry")) + entry(Emoji(0x1f6ab), ("no_entry_sign")) + entry(Emoji(0x1f645), ("no_good")) + entry(Emoji(0x1f4f5), ("no_mobile_phones")) + entry(Emoji(0x1f636), ("no_mouth")) + entry(Emoji(0x1f6b7), ("no_pedestrians")) + entry(Emoji(0x1f6ad), ("no_smoking")) + entry(Emoji(0x1f6b1), ("non-potable_water")) + entry(Emoji(0x1f443), ("nose")) + entry(Emoji(0x1f4d3), ("notebook")) + entry(Emoji(0x1f4d4), ("notebook_with_decorative_cover")) + entry(Emoji(0x1f3b6), ("notes")) + entry(Emoji(0x1f529), ("nut_and_bolt")) + entry(Emoji(0x02b55), ("o")) + entry(Emoji(0x1f17e), ("o2")) + entry(Emoji(0x1f30a), ("ocean")) + entry(Emoji(0x1f419), ("octopus")) + entry(Emoji(0x1f362), ("oden")) + entry(Emoji(0x1f3e2), ("office")) + entry(Emoji(0x1f197), ("ok")) + entry(Emoji(0x1f44c), ("ok_hand")) + entry(Emoji(0x1f646), ("ok_woman")) + entry(Emoji(0x1f474), ("older_man")) + entry(Emoji(0x1f475), ("older_woman")) + entry(Emoji(0x1f51b), ("on")) + entry(Emoji(0x1f698), ("oncoming_automobile")) + entry(Emoji(0x1f68d), ("oncoming_bus")) + entry(Emoji(0x1f694), ("oncoming_police_car")) + entry(Emoji(0x1f696), ("oncoming_taxi")) + entry(Emoji(0x1f4d6), ("open_book")) + entry(Emoji(0x1f4c2), ("open_file_folder")) + entry(Emoji(0x1f450), ("open_hands")) + entry(Emoji(0x1f62e), ("open_mouth")) + entry(Emoji(0x026ce), ("ophiuchus")) + entry(Emoji(0x1f4d9), ("orange_book")) + entry(Emoji(0x1f4e4), ("outbox_tray")) + entry(Emoji(0x1f402), ("ox")) + entry(Emoji(0x1f4e6), ("package")) + entry(Emoji(0x1f4c4), ("page_facing_up")) + entry(Emoji(0x1f4c3), ("page_with_curl")) + entry(Emoji(0x1f4df), ("pager")) + entry(Emoji(0x1f334), ("palm_tree")) + entry(Emoji(0x1f43c), ("panda_face")) + entry(Emoji(0x1f4ce), ("paperclip")) + entry(Emoji(0x1f17f), ("parking")) + entry(Emoji(0x0303d), ("part_alternation_mark")) + entry(Emoji(0x026c5), ("partly_sunny")) + entry(Emoji(0x1f6c2), ("passport_control")) + entry(Emoji(0x1f43e), ("paw_prints")) + entry(Emoji(0x1f351), ("peach")) + entry(Emoji(0x1f350), ("pear")) + entry(Emoji(0x1f4dd), ("pencil")) + entry(Emoji(0x0270f), ("pencil2")) + entry(Emoji(0x1f427), ("penguin")) + entry(Emoji(0x1f614), ("pensive")) + entry(Emoji(0x1f3ad), ("performing_arts")) + entry(Emoji(0x1f623), ("persevere")) + entry(Emoji(0x1f64d), ("person_frowning")) + entry(Emoji(0x1f471), ("person_with_blond_hair")) + entry(Emoji(0x1f64e), ("person_with_pouting_face")) + entry(Emoji(0x0260e), ("phone")) + entry(Emoji(0x1f437), ("pig")) + entry(Emoji(0x1f416), ("pig2")) + entry(Emoji(0x1f43d), ("pig_nose")) + entry(Emoji(0x1f48a), ("pill")) + entry(Emoji(0x1f34d), ("pineapple")) + entry(Emoji(0x02653), ("pisces")) + entry(Emoji(0x1f355), ("pizza")) + entry(Emoji(0x1f447), ("point_down")) + entry(Emoji(0x1f448), ("point_left")) + entry(Emoji(0x1f449), ("point_right")) + entry(Emoji(0x0261d), ("point_up")) + entry(Emoji(0x1f446), ("point_up_2")) + entry(Emoji(0x1f693), ("police_car")) + entry(Emoji(0x1f429), ("poodle")) + entry(Emoji(0x1f4a9), ("poop")) + entry(Emoji(0x1f3e3), ("post_office")) + entry(Emoji(0x1f4ef), ("postal_horn")) + entry(Emoji(0x1f4ee), ("postbox")) + entry(Emoji(0x1f6b0), ("potable_water")) + entry(Emoji(0x1f45d), ("pouch")) + entry(Emoji(0x1f357), ("poultry_leg")) + entry(Emoji(0x1f4b7), ("pound")) + entry(Emoji(0x1f63e), ("pouting_cat")) + entry(Emoji(0x1f64f), ("pray")) + entry(Emoji(0x1f478), ("princess")) + entry(Emoji(0x1f44a), ("punch")) + entry(Emoji(0x1f49c), ("purple_heart")) + entry(Emoji(0x1f45b), ("purse")) + entry(Emoji(0x1f4cc), ("pushpin")) + entry(Emoji(0x1f6ae), ("put_litter_in_its_place")) + entry(Emoji(0x02753), ("question")) + entry(Emoji(0x1f430), ("rabbit")) + entry(Emoji(0x1f407), ("rabbit2")) + entry(Emoji(0x1f40e), ("racehorse")) + entry(Emoji(0x1f4fb), ("radio")) + entry(Emoji(0x1f518), ("radio_button")) + entry(Emoji(0x1f621), ("rage")) + entry(Emoji(0x1f683), ("railway_car")) + entry(Emoji(0x1f308), ("rainbow")) + entry(Emoji(0x0270b), ("raised_hand")) + entry(Emoji(0x1f64c), ("raised_hands")) + entry(Emoji(0x1f64b), ("raising_hand")) + entry(Emoji(0x1f40f), ("ram")) + entry(Emoji(0x1f35c), ("ramen")) + entry(Emoji(0x1f400), ("rat")) + entry(Emoji(0x0267b), ("recycle")) + entry(Emoji(0x1f697), ("red_car")) + entry(Emoji(0x1f534), ("red_circle")) + entry(Emoji(0x000ae), ("registered")) + entry(Emoji(0x0263a), ("relaxed")) + entry(Emoji(0x1f60c), ("relieved")) + entry(Emoji(0x1f501), ("repeat")) + entry(Emoji(0x1f502), ("repeat_one")) + entry(Emoji(0x1f6bb), ("restroom")) + entry(Emoji(0x1f49e), ("revolving_hearts")) + entry(Emoji(0x023ea), ("rewind")) + entry(Emoji(0x1f380), ("ribbon")) + entry(Emoji(0x1f35a), ("rice")) + entry(Emoji(0x1f359), ("rice_ball")) + entry(Emoji(0x1f358), ("rice_cracker")) + entry(Emoji(0x1f391), ("rice_scene")) + entry(Emoji(0x1f48d), ("ring")) + entry(Emoji(0x1f680), ("rocket")) + entry(Emoji(0x1f3a2), ("roller_coaster")) + entry(Emoji(0x1f413), ("rooster")) + entry(Emoji(0x1f339), ("rose")) + entry(Emoji(0x1f6a8), ("rotating_light")) + entry(Emoji(0x1f4cd), ("round_pushpin")) + entry(Emoji(0x1f6a3), ("rowboat")) + entry(Emoji(0x1f3c9), ("rugby_football")) + entry(Emoji(0x1f3c3), ("runner")) + entry(Emoji(0x1f3c3), ("running")) + entry(Emoji(0x1f3bd), ("running_shirt_with_sash")) + entry(Emoji(0x1f202), ("sa")) + entry(Emoji(0x02650), ("sagittarius")) + entry(Emoji(0x026f5), ("sailboat")) + entry(Emoji(0x1f376), ("sake")) + entry(Emoji(0x1f461), ("sandal")) + entry(Emoji(0x1f385), ("santa")) + entry(Emoji(0x1f4e1), ("satellite")) + entry(Emoji(0x1f606), ("satisfied")) + entry(Emoji(0x1f3b7), ("saxophone")) + entry(Emoji(0x1f3eb), ("school")) + entry(Emoji(0x1f392), ("school_satchel")) + entry(Emoji(0x02702), ("scissors")) + entry(Emoji(0x0264f), ("scorpius")) + entry(Emoji(0x1f631), ("scream")) + entry(Emoji(0x1f640), ("scream_cat")) + entry(Emoji(0x1f4dc), ("scroll")) + entry(Emoji(0x1f4ba), ("seat")) + entry(Emoji(0x03299), ("secret")) + entry(Emoji(0x1f648), ("see_no_evil")) + entry(Emoji(0x1f331), ("seedling")) + entry(Emoji(0x1f367), ("shaved_ice")) + entry(Emoji(0x1f411), ("sheep")) + entry(Emoji(0x1f41a), ("shell")) + entry(Emoji(0x1f6a2), ("ship")) + entry(Emoji(0x1f455), ("shirt")) + entry(Emoji(0x1f4a9), ("shit")) + entry(Emoji(0x1f45e), ("shoe")) + entry(Emoji(0x1f6bf), ("shower")) + entry(Emoji(0x1f4f6), ("signal_strength")) + entry(Emoji(0x1f52f), ("six_pointed_star")) + entry(Emoji(0x1f3bf), ("ski")) + entry(Emoji(0x1f480), ("skull")) + entry(Emoji(0x1f634), ("sleeping")) + entry(Emoji(0x1f62a), ("sleepy")) + entry(Emoji(0x1f3b0), ("slot_machine")) + entry(Emoji(0x1f539), ("small_blue_diamond")) + entry(Emoji(0x1f538), ("small_orange_diamond")) + entry(Emoji(0x1f53a), ("small_red_triangle")) + entry(Emoji(0x1f53b), ("small_red_triangle_down")) + entry(Emoji(0x1f604), ("smile")) + entry(Emoji(0x1f638), ("smile_cat")) + entry(Emoji(0x1f603), ("smiley")) + entry(Emoji(0x1f63a), ("smiley_cat")) + entry(Emoji(0x1f608), ("smiling_imp")) + entry(Emoji(0x1f60f), ("smirk")) + entry(Emoji(0x1f63c), ("smirk_cat")) + entry(Emoji(0x1f6ac), ("smoking")) + entry(Emoji(0x1f40c), ("snail")) + entry(Emoji(0x1f40d), ("snake")) + entry(Emoji(0x1f3c2), ("snowboarder")) + entry(Emoji(0x02744), ("snowflake")) + entry(Emoji(0x026c4), ("snowman")) + entry(Emoji(0x1f62d), ("sob")) + entry(Emoji(0x026bd), ("soccer")) + entry(Emoji(0x1f51c), ("soon")) + entry(Emoji(0x1f198), ("sos")) + entry(Emoji(0x1f509), ("sound")) + entry(Emoji(0x1f47e), ("space_invader")) + entry(Emoji(0x02660), ("spades")) + entry(Emoji(0x1f35d), ("spaghetti")) + entry(Emoji(0x02747), ("sparkle")) + entry(Emoji(0x1f387), ("sparkler")) + entry(Emoji(0x02728), ("sparkles")) + entry(Emoji(0x1f496), ("sparkling_heart")) + entry(Emoji(0x1f64a), ("speak_no_evil")) + entry(Emoji(0x1f50a), ("speaker")) + entry(Emoji(0x1f4ac), ("speech_balloon")) + entry(Emoji(0x1f6a4), ("speedboat")) + entry(Emoji(0x02b50), ("star")) + entry(Emoji(0x1f31f), ("star2")) + entry(Emoji(0x1f303), ("stars")) + entry(Emoji(0x1f689), ("station")) + entry(Emoji(0x1f5fd), ("statue_of_liberty")) + entry(Emoji(0x1f682), ("steam_locomotive")) + entry(Emoji(0x1f372), ("stew")) + entry(Emoji(0x1f4cf), ("straight_ruler")) + entry(Emoji(0x1f353), ("strawberry")) + entry(Emoji(0x1f61b), ("stuck_out_tongue")) + entry(Emoji(0x1f61d), ("stuck_out_tongue_closed_eyes")) + entry(Emoji(0x1f61c), ("stuck_out_tongue_winking_eye")) + entry(Emoji(0x1f31e), ("sun_with_face")) + entry(Emoji(0x1f33b), ("sunflower")) + entry(Emoji(0x1f60e), ("sunglasses")) + entry(Emoji(0x02600), ("sunny")) + entry(Emoji(0x1f305), ("sunrise")) + entry(Emoji(0x1f304), ("sunrise_over_mountains")) + entry(Emoji(0x1f3c4), ("surfer")) + entry(Emoji(0x1f363), ("sushi")) + entry(Emoji(0x1f69f), ("suspension_railway")) + entry(Emoji(0x1f613), ("sweat")) + entry(Emoji(0x1f4a6), ("sweat_drops")) + entry(Emoji(0x1f605), ("sweat_smile")) + entry(Emoji(0x1f360), ("sweet_potato")) + entry(Emoji(0x1f3ca), ("swimmer")) + entry(Emoji(0x1f523), ("symbols")) + entry(Emoji(0x1f489), ("syringe")) + entry(Emoji(0x1f389), ("tada")) + entry(Emoji(0x1f38b), ("tanabata_tree")) + entry(Emoji(0x1f34a), ("tangerine")) + entry(Emoji(0x02649), ("taurus")) + entry(Emoji(0x1f695), ("taxi")) + entry(Emoji(0x1f375), ("tea")) + entry(Emoji(0x0260e), ("telephone")) + entry(Emoji(0x1f4de), ("telephone_receiver")) + entry(Emoji(0x1f52d), ("telescope")) + entry(Emoji(0x1f3be), ("tennis")) + entry(Emoji(0x026fa), ("tent")) + entry(Emoji(0x1f4ad), ("thought_balloon")) + entry(Emoji(0x1f44e), ("thumbsdown")) + entry(Emoji(0x1f44d), ("thumbsup")) + entry(Emoji(0x1f3ab), ("ticket")) + entry(Emoji(0x1f42f), ("tiger")) + entry(Emoji(0x1f405), ("tiger2")) + entry(Emoji(0x1f62b), ("tired_face")) + entry(Emoji(0x02122), ("tm")) + entry(Emoji(0x1f6bd), ("toilet")) + entry(Emoji(0x1f5fc), ("tokyo_tower")) + entry(Emoji(0x1f345), ("tomato")) + entry(Emoji(0x1f445), ("tongue")) + entry(Emoji(0x1f51d), ("top")) + entry(Emoji(0x1f3a9), ("tophat")) + entry(Emoji(0x1f69c), ("tractor")) + entry(Emoji(0x1f6a5), ("traffic_light")) + entry(Emoji(0x1f683), ("train")) + entry(Emoji(0x1f686), ("train2")) + entry(Emoji(0x1f68a), ("tram")) + entry(Emoji(0x1f6a9), ("triangular_flag_on_post")) + entry(Emoji(0x1f4d0), ("triangular_ruler")) + entry(Emoji(0x1f531), ("trident")) + entry(Emoji(0x1f624), ("triumph")) + entry(Emoji(0x1f68e), ("trolleybus")) + entry(Emoji(0x1f3c6), ("trophy")) + entry(Emoji(0x1f379), ("tropical_drink")) + entry(Emoji(0x1f420), ("tropical_fish")) + entry(Emoji(0x1f69a), ("truck")) + entry(Emoji(0x1f3ba), ("trumpet")) + entry(Emoji(0x1f455), ("tshirt")) + entry(Emoji(0x1f337), ("tulip")) + entry(Emoji(0x1f422), ("turtle")) + entry(Emoji(0x1f4fa), ("tv")) + entry(Emoji(0x1f500), ("twisted_rightwards_arrows")) + entry(Emoji(0x1f495), ("two_hearts")) + entry(Emoji(0x1f46c), ("two_men_holding_hands")) + entry(Emoji(0x1f46d), ("two_women_holding_hands")) + entry(Emoji(0x1f239), ("u5272")) + entry(Emoji(0x1f234), ("u5408")) + entry(Emoji(0x1f23a), ("u55b6")) + entry(Emoji(0x1f22f), ("u6307")) + entry(Emoji(0x1f237), ("u6708")) + entry(Emoji(0x1f236), ("u6709")) + entry(Emoji(0x1f235), ("u6e80")) + entry(Emoji(0x1f21a), ("u7121")) + entry(Emoji(0x1f238), ("u7533")) + entry(Emoji(0x1f232), ("u7981")) + entry(Emoji(0x1f233), ("u7a7a")) + entry(Emoji(0x02614), ("umbrella")) + entry(Emoji(0x1f612), ("unamused")) + entry(Emoji(0x1f51e), ("underage")) + entry(Emoji(0x1f513), ("unlock")) + entry(Emoji(0x1f199), ("up")) + entry(Emoji(0x0270c), ("v")) + entry(Emoji(0x1f6a6), ("vertical_traffic_light")) + entry(Emoji(0x1f4fc), ("vhs")) + entry(Emoji(0x1f4f3), ("vibration_mode")) + entry(Emoji(0x1f4f9), ("video_camera")) + entry(Emoji(0x1f3ae), ("video_game")) + entry(Emoji(0x1f3bb), ("violin")) + entry(Emoji(0x0264d), ("virgo")) + entry(Emoji(0x1f30b), ("volcano")) + entry(Emoji(0x1f19a), ("vs")) + entry(Emoji(0x1f6b6), ("walking")) + entry(Emoji(0x1f318), ("waning_crescent_moon")) + entry(Emoji(0x1f316), ("waning_gibbous_moon")) + entry(Emoji(0x026a0), ("warning")) + entry(Emoji(0x0231a), ("watch")) + entry(Emoji(0x1f403), ("water_buffalo")) + entry(Emoji(0x1f349), ("watermelon")) + entry(Emoji(0x1f44b), ("wave")) + entry(Emoji(0x03030), ("wavy_dash")) + entry(Emoji(0x1f312), ("waxing_crescent_moon")) + entry(Emoji(0x1f314), ("waxing_gibbous_moon")) + entry(Emoji(0x1f6be), ("wc")) + entry(Emoji(0x1f629), ("weary")) + entry(Emoji(0x1f492), ("wedding")) + entry(Emoji(0x1f433), ("whale")) + entry(Emoji(0x1f40b), ("whale2")) + entry(Emoji(0x0267f), ("wheelchair")) + entry(Emoji(0x02705), ("white_check_mark")) + entry(Emoji(0x026aa), ("white_circle")) + entry(Emoji(0x1f4ae), ("white_flower")) + entry(Emoji(0x02b1c), ("white_large_square")) + entry(Emoji(0x025fd), ("white_medium_small_square")) + entry(Emoji(0x025fb), ("white_medium_square")) + entry(Emoji(0x025ab), ("white_small_square")) + entry(Emoji(0x1f533), ("white_square_button")) + entry(Emoji(0x1f390), ("wind_chime")) + entry(Emoji(0x1f377), ("wine_glass")) + entry(Emoji(0x1f609), ("wink")) + entry(Emoji(0x1f43a), ("wolf")) + entry(Emoji(0x1f469), ("woman")) + entry(Emoji(0x1f45a), ("womans_clothes")) + entry(Emoji(0x1f452), ("womans_hat")) + entry(Emoji(0x1f6ba), ("womens")) + entry(Emoji(0x1f61f), ("worried")) + entry(Emoji(0x1f527), ("wrench")) + entry(Emoji(0x0274c), ("x")) + entry(Emoji(0x1f49b), ("yellow_heart")) + entry(Emoji(0x1f4b4), ("yen")) + entry(Emoji(0x1f60b), ("yum")) + entry(Emoji(0x026a1), ("zap")) + entry(Emoji(0x1f4a4), ("zzz")) + end defaults +end ShortCodes diff --git a/src/test/scala/com/lightbend/emoji/EmojiSpec.scala b/src/test/scala-2/com/lightbend/emoji/EmojiSpec.scala similarity index 98% rename from src/test/scala/com/lightbend/emoji/EmojiSpec.scala rename to src/test/scala-2/com/lightbend/emoji/EmojiSpec.scala index e43cf41..3bf1340 100644 --- a/src/test/scala/com/lightbend/emoji/EmojiSpec.scala +++ b/src/test/scala-2/com/lightbend/emoji/EmojiSpec.scala @@ -3,8 +3,6 @@ */ package com.lightbend.emoji -import scala.language.implicitConversions - import org.scalatest.matchers.should.Matchers._ import org.scalatest.wordspec.AnyWordSpec diff --git a/src/test/scala/com/lightbend/emoji/ShortCodesSpec.scala b/src/test/scala-2/com/lightbend/emoji/ShortCodesSpec.scala similarity index 98% rename from src/test/scala/com/lightbend/emoji/ShortCodesSpec.scala rename to src/test/scala-2/com/lightbend/emoji/ShortCodesSpec.scala index 675beda..a3b6596 100644 --- a/src/test/scala/com/lightbend/emoji/ShortCodesSpec.scala +++ b/src/test/scala-2/com/lightbend/emoji/ShortCodesSpec.scala @@ -3,8 +3,6 @@ */ package com.lightbend.emoji -import scala.language.implicitConversions - import org.scalatest.matchers.should.Matchers._ import org.scalatest.wordspec.AnyWordSpec diff --git a/src/test/scala-3/com/lightbend/emoji/EmojiSuite.scala b/src/test/scala-3/com/lightbend/emoji/EmojiSuite.scala new file mode 100644 index 0000000..923087a --- /dev/null +++ b/src/test/scala-3/com/lightbend/emoji/EmojiSuite.scala @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015-2022 Lightbend Inc. + */ +package com.lightbend.emoji + +import Emoji.* + +class EmojiSuite extends munit.FunSuite: + + def assertFalse(b: Boolean) = assert(!b) + + val ramen = Emoji(0x1f35c) + val smiley = Emoji(codePoint = 128515) + + test("hex maps correctly") { + assertEquals(ramen.hex, "0x1f35c") + } + + test("toString shows Unicode value") { + assertEquals(ramen.toString, "\ud83c\udf5c") + } + + test("name returns the name") { + assertEquals(ramen.name, "STEAMING BOWL") + } + + test("equals is reflexive and symmetric") { + val codePoint = 128515 + val e1 = Emoji(codePoint) + val e2 = Emoji(codePoint) + assert(e1.equals(e1)) + assert(e1.equals(e2)) + assert(e2.equals(e1)) + } + + test("equals knows when it's not equal") { + val e = Emoji(codePoint = 128515) + assertFalse(Emoji(e.codePoint + 1).equals(e)) + assertFalse(Emoji(e.codePoint - 1).equals(e)) + } + + test("hashCode is the codepoint hash") { + val e = Emoji(codePoint = 128515) + assertEquals(e.hashCode, e.hashCode) + assertEquals(Emoji(e.codePoint).hashCode, e.hashCode) + assertFalse(Emoji(e.codePoint + 1).hashCode == e.hashCode) + assertFalse(Emoji(e.codePoint - 1).hashCode == e.hashCode) + } + + test("smiling face with open mouth is \uD83D\uDE03") { + assertEquals(smiley.name, "SMILING FACE WITH OPEN MOUTH") + assertEquals(smiley.codePoint, 128515) + assertEquals(smiley.toString, "\uD83D\uDE03") + assertEquals(Emoji(smiley.toString).toString, smiley.toString) + assertEquals(Emoji(smiley.chars).toString, smiley.toString) + assertEquals(Emoji(smiley.codePoint).toString, smiley.toString) + } + + test("map hexcode correctly") { + assertEquals("1f35c".codePointEmoji, ramen) + } + + test("map hexcode correctly using a leading 0x") { + assertEquals("0x1f35c".codePointEmoji, ramen) + } + + test("map raw integers correctly") { + assertEquals(0x1f35c.emoji, ramen) + } diff --git a/src/test/scala-3/com/lightbend/emoji/ShortCodesSuite.scala b/src/test/scala-3/com/lightbend/emoji/ShortCodesSuite.scala new file mode 100644 index 0000000..41a666b --- /dev/null +++ b/src/test/scala-3/com/lightbend/emoji/ShortCodesSuite.scala @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2015-2022 Lightbend Inc. + */ +package com.lightbend.emoji + +import ShortCodes.{given, *} + +class ShortCodesSuite extends munit.FunSuite: + + test("map emoji to shortcodes using the implicit class") { + val pointRightEmoji = Emoji(0x1f449) + assertEquals(pointRightEmoji.shortCodes, Some(Set("point_right"))) + } + + test("map shortcode to emoji using the implicit class") { + val pointRightEmoji = new String(Character.toChars(0x1f449)) + assertEquals("point_right".emoji.toString, s"$pointRightEmoji") + } + + test("default short codes sanity check") { + val current = ShortCodes.current + assert(current.shortCodes.nonEmpty) + assert(current.emojis.nonEmpty) + current.shortCodes.foreach { shortCode => + current.emoji(shortCode) match { + case Some(emoji) => assert(Emoji.isEmoji(emoji.codePoint)) + case None => fail(s"Unable to find Emoji for shortCode '${shortCode}") + } + } + } + + test("pick up the current shortCode mapping") { + val shortCodes = ShortCodes.current + assertEquals(shortCodes, ShortCodes.given_ShortCodes) + } + + test("find emoji given a short code") { + val maybeEmoji = ShortCodes.current.emoji("point_right") + assertEquals(maybeEmoji, Some(Emoji(0x1f449))) + } + + test("find short codes given an emoji") { + val maybeShortCodes = ShortCodes.current.shortCodes(Emoji(0x1f449)) + assertEquals(maybeShortCodes, Some(Set("point_right"))) + } + + test("define a new custom shortcode mapping") { + given newShortCodes: ShortCodes = new ShortCodes(Some(ShortCodes.given_ShortCodes)) + + val stuckOutTongue = Emoji(0x1f61b) // aka "stuck_out_tongue" + newShortCodes.entry(stuckOutTongue, "silly") + + val silly = ShortCodes.current.emoji("silly").get + assertEquals(stuckOutTongue, silly) + } + + test("use assignment syntax for custom shortcode mapping") { + given newShortCodes: ShortCodes = new ShortCodes(Some(ShortCodes.given_ShortCodes)) + + val stuckOutTongue = Emoji(0x1f61b) // aka "stuck_out_tongue" + newShortCodes(stuckOutTongue) = "silly" + + val silly = ShortCodes.current.emoji("silly").get + assertEquals(stuckOutTongue, silly) + } + + test("allow short names between colons") { + val eye = "I" + val heart = "heart".emoji + + assertEquals(e"$eye :heart: Scala", s"I $heart Scala") + } + + test("ignore short names in interpolated args") { + val eye = "I" + val heart = ":heart:" + assertEquals(e"$eye $heart Scala", s"I :heart: Scala") + } + + test("accept double colon as double colon") { + val heart = "heart".emoji + assertEquals(e"List of fave emojis = :heart: :: Nil", s"List of fave emojis = $heart :: Nil") + } + + test("accept colon not followed by emoji char as literal colon") { + val smiley = "smiley".emoji + assertEquals(e"Dear Customer: Have a nice day! :) :smiley:", s"Dear Customer: Have a nice day! :) $smiley") + } + + test("also accept backslash-escaped colon as literal colon") { + val eye = "I" + val heart = "heart".emoji + assertEquals(e"$eye :heart:\: Scala", s"I $heart: Scala") + assertEquals(e"$eye \:heart:\: Scala", s"I :heart:: Scala") + assertEquals(e"$eye \::heart:\: Scala", s"I :$heart: Scala") + } + + test("not go kaput on bad short name") { + val upper = "+1".emoji + assertEquals(e":+1: Loved it! So much! :++1:", s"$upper Loved it! So much! :++1:") + } + + test("gently ignore bad characters") { + val upper = "+1".emoji + assertEquals(e":+1: Love the idea of using :left arrow: in for comprehensions!", + s"$upper Love the idea of using :left arrow: in for comprehensions!") + }