├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── lib ├── emojix.ex └── emojix │ ├── app.ex │ ├── data_loader.ex │ ├── emoji.ex │ └── repo.ex ├── mix.exs ├── mix.lock ├── priv └── dataset_15.2.0.ets └── test ├── emojix_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | mix_test: 7 | name: mix test (Elixir ${{ matrix.elixir }} OTP ${{ matrix.otp }}) 8 | strategy: 9 | matrix: 10 | elixir: ["1.9.1"] 11 | include: 12 | - elixir: "1.9.1" 13 | otp: "22.x" 14 | runs-on: ubuntu-16.04 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions/setup-elixir@v1.0.0 18 | with: 19 | otp-version: ${{ matrix.otp }} 20 | elixir-version: ${{ matrix.elixir }} 21 | - name: Install Dependencies 22 | run: mix deps.get 23 | - name: Run Tests 24 | run: mix test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | emojix-*.tar 24 | 25 | .elixir_ls 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bruno Ukita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emojix 💩 2 | 3 | [![Build Status](https://github.com/ukita/emojix/workflows/CI/badge.svg)](https://github.com/ukita/emojix/actions) 4 | 5 | Simple Elixir library to help you handle emojis. 6 | 7 | ## Installation 8 | 9 | Add it to your deps list in your mix.exs 10 | 11 | ```elixir 12 | def deps do 13 | [ 14 | {:emojix, "~> 0.4.0"} 15 | ] 16 | end 17 | ``` 18 | 19 | ## Usage examples 20 | 21 | ```elixir 22 | iex> Emojix.all() 23 | [ 24 | %Emojix.Emoji{ 25 | description: "flag: Andorra", 26 | hexcode: "1F1E6-1F1E9", 27 | id: 3577, 28 | shortcodes: ["flag_ad"], 29 | tags: ["AD", "flag"], 30 | unicode: "🇦🇩", 31 | variations: [] 32 | }, 33 | %Emojix.Emoji{ 34 | description: "downcast face with sweat", 35 | hexcode: "1F613", 36 | id: 85, 37 | shortcodes: ["shamed"], 38 | tags: ["cold", "face", "sweat"], 39 | unicode: "😓", 40 | variations: [] 41 | }, 42 | ... 43 | ] 44 | iex> Emojix.find_by_shortcode("gleeful") 45 | %Emojix.Emoji{ 46 | description: "grinning face", 47 | hexcode: "1F600", 48 | id: 1, 49 | shortcodes: ["gleeful"], 50 | tags: ["face", "grin"], 51 | unicode: "😀", 52 | variations: [] 53 | } 54 | iex> Emojix.scan("Elixir is awesome!! ✌🏻👍🏽") 55 | [ 56 | %Emojix.Emoji{ 57 | description: "victory hand: light skin tone", 58 | hexcode: "270C-1F3FB", 59 | id: 206, 60 | shortcodes: ["victory_tone1"], 61 | tags: [], 62 | unicode: "✌🏻", 63 | variations: [] 64 | }, 65 | %Emojix.Emoji{ 66 | description: "thumbs up: medium skin tone", 67 | hexcode: "1F44D-1F3FD", 68 | id: 275, 69 | shortcodes: ["thumbsup_tone3", "+1_tone3", "yes_tone3"], 70 | tags: [], 71 | unicode: "👍🏽", 72 | variations: [] 73 | } 74 | ] 75 | ``` 76 | 77 | ## Documentation 78 | 79 | Full API documentation is available here: https://hexdocs.pm/emojix/ 80 | 81 | ## Credits 82 | 83 | Thanks for [@milesj](https://github.com/milesj) for provinding the [emoji datasets](https://github.com/milesj/emojibase). 84 | 85 | ## License 86 | 87 | MIT 88 | -------------------------------------------------------------------------------- /lib/emojix.ex: -------------------------------------------------------------------------------- 1 | defmodule Emojix do 2 | @moduledoc """ 3 | Documentation for Emojix. 4 | """ 5 | 6 | alias Emojix.Repo 7 | 8 | @doc """ 9 | Returns a list of all 3019 Emoji, including variations. 10 | ## Examples 11 | iex> Emojix.all() 12 | [ 13 | %Emojix.Emoji{ 14 | description: "flag: Andorra", 15 | hexcode: "1F1E6-1F1E9", 16 | id: 3577, 17 | shortcodes: ["flag_ad"], 18 | tags: ["AD", "flag"], 19 | unicode: "🇦🇩", 20 | variations: [] 21 | }, 22 | %Emojix.Emoji{ 23 | description: "downcast face with sweat", 24 | hexcode: "1F613", 25 | id: 85, 26 | shortcodes: ["shamed"], 27 | tags: ["cold", "face", "sweat"], 28 | unicode: "😓", 29 | variations: [] 30 | }, 31 | ... 32 | ] 33 | """ 34 | @spec all() :: [Emojix.Emoji.t()] 35 | def all, do: Repo.all() 36 | 37 | @doc """ 38 | Find a emoji by shortcode 39 | ## Examples 40 | iex> Emojix.find_by_shortcode("gleeful") 41 | %Emojix.Emoji{ 42 | description: "grinning face", 43 | hexcode: "1F600", 44 | id: 1, 45 | shortcodes: ["gleeful"], 46 | tags: ["face", "grin"], 47 | unicode: "😀", 48 | variations: [] 49 | } 50 | iex> Emojix.find_by_shortcode("non_valid") 51 | nil 52 | """ 53 | @spec find_by_shortcode(String.t()) :: Emojix.Emoji.t() | nil 54 | def find_by_shortcode(shortcode) do 55 | shortcode |> String.downcase() |> Repo.find_by_shortcode() 56 | end 57 | 58 | @doc """ 59 | Find a emoji by unicode 60 | ## Examples 61 | iex> Emojix.find_by_unicode("😼") 62 | %Emojix.Emoji{ 63 | description: "cat with wry smile", 64 | hexcode: "1F63C", 65 | id: 110, 66 | shortcodes: ["smirking_cat"], 67 | tags: ["cat", "face", "ironic", "smile", "wry"], 68 | unicode: "😼", 69 | variations: [] 70 | } 71 | """ 72 | @spec find_by_unicode(String.t()) :: nil | Emojix.Emoji.t() 73 | def find_by_unicode(unicode) do 74 | unicode 75 | |> String.to_charlist() 76 | |> Enum.map(&Integer.to_string(&1, 16)) 77 | |> Enum.join("-") 78 | |> find_by_hexcode() 79 | end 80 | 81 | @doc """ 82 | Find a emoji by hexcode 83 | ## Examples 84 | iex> Emojix.find_by_hexcode("1F600") 85 | %Emojix.Emoji{ 86 | description: "grinning face", 87 | hexcode: "1F600", 88 | id: 1, 89 | shortcodes: ["gleeful"], 90 | tags: ["face", "grin"], 91 | unicode: "😀", 92 | variations: [] 93 | } 94 | iex> Emojix.find_by_hexcode("FFFFF") 95 | nil 96 | """ 97 | @spec find_by_hexcode(String.t()) :: Emojix.Emoji.t() | nil 98 | def find_by_hexcode(hexcode) do 99 | hexcode |> String.upcase() |> Repo.find_by_hexcode() 100 | end 101 | 102 | @doc """ 103 | Search a emoji by description 104 | ## Examples 105 | iex> Emojix.search_by_description("grinning") 106 | [ 107 | %Emojix.Emoji{ 108 | description: "grinning face with sweat", 109 | hexcode: "1F605", 110 | id: 6, 111 | shortcodes: ["embarassed"], 112 | tags: ["cold", "face", "open", "smile", "sweat"], 113 | unicode: "😅", 114 | variations: [] 115 | }, 116 | %Emojix.Emoji{ 117 | description: "grinning cat with smiling eyes", 118 | hexcode: "1F638", 119 | id: 107, 120 | shortcodes: ["grinning_cat"], 121 | tags: ["cat", "eye", "face", "grin", "smile"], 122 | unicode: "😸", 123 | variations: [] 124 | }, 125 | ... 126 | ] 127 | iex> Emojix.search_by_description("blah") 128 | [] 129 | """ 130 | @spec search_by_description(String.t()) :: [Emojix.Emoji.t()] | [] 131 | def search_by_description(description) do 132 | description |> String.downcase() |> Repo.search_by_description() 133 | end 134 | 135 | @doc """ 136 | Search a emoji by tag 137 | ## Examples 138 | iex> Emojix.search_by_tag("face") 139 | [ 140 | %Emojix.Emoji{ 141 | description: "downcast face with sweat", 142 | hexcode: "1F613", 143 | id: 85, 144 | shortcodes: ["shamed"], 145 | tags: ["cold", "face", "sweat"], 146 | unicode: "😓", 147 | variations: [] 148 | }, 149 | %Emojix.Emoji{ 150 | description: "smiling face with sunglasses", 151 | hexcode: "1F60E", 152 | id: 61, 153 | shortcodes: ["confident"], 154 | tags: ["bright", "cool", "face", "sun", "sunglasses"], 155 | unicode: "😎", 156 | variations: [] 157 | }, 158 | ... 159 | ] 160 | iex> Emojix.search_by_tag("blah") 161 | [] 162 | """ 163 | @spec search_by_tag(String.t()) :: [Emojix.Emoji.t()] | [] 164 | def search_by_tag(tag) do 165 | tag |> String.downcase() |> Repo.search_by_tag() 166 | end 167 | 168 | @doc """ 169 | Returns a new string where each emoji contained in the given string is applied to a function. 170 | ## Examples 171 | iex> Emojix.replace("The 🐶 likes 🦴", fn e -> e.shortcodes end) 172 | "The dog_face likes bone" 173 | """ 174 | @spec replace(String.t(), (Emojix.Emoji.t() -> any)) :: String.t() 175 | def replace(str, fun) do 176 | str 177 | |> String.graphemes() 178 | |> Enum.map(fn grapheme -> 179 | case find_by_unicode(grapheme) do 180 | %Emojix.Emoji{} = emoji -> 181 | fun.(emoji) 182 | 183 | nil -> 184 | grapheme 185 | end 186 | end) 187 | |> Enum.join() 188 | end 189 | 190 | @doc """ 191 | Returns a list of all emoji contained within that string, in order. 192 | 193 | ## Examples 194 | iex> Emojix.scan("Elixir is awesome!! ✌🏻👍🏽") 195 | [ 196 | %Emojix.Emoji{ 197 | description: "victory hand: light skin tone", 198 | hexcode: "270C-1F3FB", 199 | id: 206, 200 | shortcodes: ["victory_tone1"], 201 | tags: [], 202 | unicode: "✌🏻", 203 | variations: [] 204 | }, 205 | %Emojix.Emoji{ 206 | description: "thumbs up: medium skin tone", 207 | hexcode: "1F44D-1F3FD", 208 | id: 275, 209 | shortcodes: ["thumbsup_tone3", "+1_tone3", "yes_tone3"], 210 | tags: [], 211 | unicode: "👍🏽", 212 | variations: [] 213 | } 214 | ] 215 | iex> Emojix.scan("There is no emoji in this sentence") 216 | [] 217 | """ 218 | @spec scan(String.t()) :: [Emojix.Emoji.t()] 219 | def scan(str) do 220 | str 221 | |> String.graphemes() 222 | |> Enum.reverse() 223 | |> _scan() 224 | end 225 | 226 | defp _scan(graphemes, emojis \\ []) 227 | 228 | defp _scan([], emojis), do: Enum.uniq(emojis) 229 | 230 | defp _scan([h | t], emojis) when byte_size(h) >= 3 do 231 | case find_by_unicode(h) do 232 | %Emojix.Emoji{} = emoji -> 233 | _scan(t, [emoji | emojis]) 234 | 235 | nil -> 236 | _scan(t, emojis) 237 | end 238 | end 239 | 240 | defp _scan([_ | t], emojis), do: _scan(t, emojis) 241 | end 242 | -------------------------------------------------------------------------------- /lib/emojix/app.ex: -------------------------------------------------------------------------------- 1 | defmodule Emojix.App do 2 | @moduledoc false 3 | 4 | use Application 5 | 6 | def start(_type, _args) do 7 | children = [Emojix.Repo] 8 | 9 | Supervisor.start_link(children, strategy: :one_for_one) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/emojix/data_loader.ex: -------------------------------------------------------------------------------- 1 | defmodule Emojix.DataLoader do 2 | @moduledoc false 3 | 4 | require Logger 5 | 6 | require IEx 7 | alias Mint.HTTP 8 | 9 | @emoji_version "15.2.0" 10 | @download_host "cdn.jsdelivr.net" 11 | @download_path "/npm/emojibase-data@#{@emoji_version}/en/compact.json" 12 | @download_shortcodes "/npm/emojibase-data@#{@emoji_version}/en/shortcodes/iamcal.json" 13 | @download_legacy_shortcodes "/npm/emojibase-data@#{@emoji_version}/en/shortcodes/emojibase-legacy.json" 14 | 15 | @spec ets_table_path :: Path 16 | def ets_table_path do 17 | Path.join(:code.priv_dir(:emojix), "dataset_#{@emoji_version}.ets") 18 | end 19 | 20 | @spec load_table :: :emoji_table 21 | def load_table do 22 | if table_exists?() do 23 | {:ok, _} = :ets.file2tab(String.to_charlist(ets_table_path())) 24 | 25 | :emoji_table 26 | else 27 | download_and_populate() 28 | end 29 | end 30 | 31 | @spec download_and_populate :: :emoji_table 32 | def download_and_populate do 33 | Logger.debug("Downloading emoji dataset") 34 | json = Jason.decode!(download_file(@download_path), keys: :atoms) 35 | json_shortcodes = Jason.decode!(download_file(@download_shortcodes), keys: :strings) 36 | 37 | json_legacy_shortcodes = 38 | Jason.decode!(download_file(@download_legacy_shortcodes), keys: :strings) 39 | 40 | shortcodes = merge_shortcodes(json_shortcodes, json_legacy_shortcodes) 41 | 42 | create_table(json, shortcodes) 43 | end 44 | 45 | defp download_file(path) do 46 | {:ok, conn} = HTTP.connect(:https, @download_host, 443) 47 | 48 | {:ok, conn, request_ref} = 49 | HTTP.request(conn, "GET", path, [{"content-type", "application/json"}], "") 50 | 51 | {:ok, conn, body} = stream_request(conn, request_ref) 52 | 53 | Mint.HTTP.close(conn) 54 | body 55 | end 56 | 57 | defp merge_shortcodes(shortcodes, legacy_shortcodes) do 58 | Map.merge(shortcodes, legacy_shortcodes, fn _k, v1, v2 -> 59 | (List.wrap(v1 || []) ++ List.wrap(v2 || [])) 60 | |> Enum.uniq() 61 | end) 62 | end 63 | 64 | defp create_table(json, json_shortcodes) do 65 | Logger.debug("Building emoji ets table") 66 | table = :ets.new(:emoji_table, [:named_table]) 67 | 68 | emoji_list = 69 | parse_json(json, json_shortcodes) 70 | |> Enum.reduce([], fn e, acc -> 71 | e.variations ++ [e | acc] 72 | end) 73 | 74 | Logger.debug("Populating table with #{Enum.count(emoji_list)} emojis") 75 | 76 | for emoji <- emoji_list do 77 | :ets.insert(table, {{:hexcodes, emoji.hexcode}, emoji}) 78 | 79 | for shortcode <- emoji.shortcodes do 80 | :ets.insert(table, {{:shortcodes, shortcode}, emoji.hexcode}) 81 | end 82 | end 83 | 84 | :ets.tab2file(:emoji_table, String.to_charlist(ets_table_path())) 85 | 86 | :emoji_table 87 | end 88 | 89 | defp parse_json(json, json_shortcodes) do 90 | json 91 | |> Stream.filter(&Map.has_key?(&1, :order)) 92 | |> Enum.reduce([], fn emoji, list -> 93 | [build_emoji_struct(emoji, json_shortcodes) | list] 94 | end) 95 | end 96 | 97 | defp build_emoji_struct(emoji, json_shortcodes) do 98 | shortcodes = Map.get(json_shortcodes, emoji.hexcode, []) 99 | 100 | %Emojix.Emoji{ 101 | id: emoji.order, 102 | hexcode: emoji.hexcode, 103 | description: emoji.label, 104 | shortcodes: List.wrap(shortcodes), 105 | unicode: emoji.unicode, 106 | tags: Map.get(emoji, :tags, []), 107 | variations: Map.get(emoji, :skins, []) |> Enum.map(&build_emoji_struct(&1, json_shortcodes)) 108 | } 109 | end 110 | 111 | @spec table_exists? :: boolean 112 | defp table_exists? do 113 | File.exists?(ets_table_path()) 114 | end 115 | 116 | defp stream_request(conn, request_ref, body \\ []) do 117 | {conn, body, status} = 118 | receive do 119 | message -> 120 | {:ok, conn, responses} = HTTP.stream(conn, message) 121 | 122 | {body, status} = 123 | Enum.reduce(responses, {body, :incomplete}, fn resp, {body, _status} -> 124 | case resp do 125 | {:data, ^request_ref, data} -> 126 | {body ++ [data], :incomplete} 127 | 128 | {:done, ^request_ref} -> 129 | {body, :done} 130 | 131 | _ -> 132 | {body, :incomplete} 133 | end 134 | end) 135 | 136 | {conn, body, status} 137 | end 138 | 139 | if status == :done do 140 | {:ok, conn, body} 141 | else 142 | stream_request(conn, request_ref, body) 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /lib/emojix/emoji.ex: -------------------------------------------------------------------------------- 1 | defmodule Emojix.Emoji do 2 | @moduledoc """ 3 | Documentation for Emojix.Emoji. 4 | """ 5 | defstruct id: nil, 6 | hexcode: nil, 7 | description: nil, 8 | shortcodes: [], 9 | tags: [], 10 | unicode: nil, 11 | variations: [] 12 | 13 | @type t :: %__MODULE__{ 14 | id: Integer.t(), 15 | hexcode: String.t(), 16 | description: String.t(), 17 | shortcodes: [String.t()], 18 | tags: [String.t()], 19 | unicode: String.t(), 20 | variations: [__MODULE__.t()] 21 | } 22 | end 23 | -------------------------------------------------------------------------------- /lib/emojix/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Emojix.Repo do 2 | @moduledoc false 3 | 4 | use GenServer 5 | 6 | def start_link(_) do 7 | GenServer.start_link(__MODULE__, :ok, name: __MODULE__) 8 | end 9 | 10 | def all() do 11 | GenServer.call(__MODULE__, :all) 12 | end 13 | 14 | def find_by_shortcode(shortcode) do 15 | GenServer.call(__MODULE__, {:find_by, {:shortcodes, shortcode}}) 16 | end 17 | 18 | def find_by_hexcode(hexcode) do 19 | GenServer.call(__MODULE__, {:find_by, {:hexcodes, hexcode}}) 20 | end 21 | 22 | def search_by_description(description) do 23 | GenServer.call(__MODULE__, {:search_by, {:description, description}}) 24 | end 25 | 26 | def search_by_tag(tag) do 27 | GenServer.call(__MODULE__, {:search_by, {:tags, tag}}) 28 | end 29 | 30 | # Callbacks 31 | 32 | def init(_) do 33 | {:ok, Emojix.DataLoader.load_table()} 34 | end 35 | 36 | def handle_call(:all, _from, table) do 37 | {:reply, select_all(table), table} 38 | end 39 | 40 | def handle_call({:find_by, {:hexcodes, _hexcode} = value}, _from, table) do 41 | {:reply, lookup(table, value), table} 42 | end 43 | 44 | def handle_call({:find_by, value}, _from, table) do 45 | case lookup(table, value) do 46 | hexcode when is_binary(hexcode) -> 47 | {:reply, lookup(table, {:hexcodes, hexcode}), table} 48 | 49 | nil -> 50 | {:reply, nil, table} 51 | end 52 | end 53 | 54 | def handle_call({:search_by, {field, value}}, _from, table) do 55 | {:reply, search_by(table, field, value), table} 56 | end 57 | 58 | defp select_all(table) do 59 | ms = [{{{:"$1", :_}, :"$2"}, [{:==, :"$1", :hexcodes}], [:"$2"]}] 60 | :ets.select(table, ms) 61 | end 62 | 63 | defp lookup(table, key) do 64 | case :ets.lookup(table, key) do 65 | [{^key, result}] -> result 66 | _ -> nil 67 | end 68 | end 69 | 70 | defp search_by(table, field, value) when field in [:tags, :shortcodes] do 71 | select_all(table) |> Enum.filter(fn emoji -> Map.get(emoji, field) |> Enum.member?(value) end) 72 | end 73 | 74 | defp search_by(table, field, value) do 75 | select_all(table) 76 | |> Enum.filter(fn emoji -> Map.get(emoji, field) |> String.contains?(value) end) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Emojix.MixProject do 2 | use Mix.Project 3 | 4 | @version "0.4.0" 5 | 6 | def project do 7 | [ 8 | app: :emojix, 9 | version: @version, 10 | elixir: "~> 1.9", 11 | start_permanent: Mix.env() == :prod, 12 | deps: deps(), 13 | package: package(), 14 | description: description() 15 | ] 16 | end 17 | 18 | # Run "mix help compile.app" to learn about applications. 19 | def application do 20 | [ 21 | extra_applications: [:logger], 22 | mod: {Emojix.App, []} 23 | ] 24 | end 25 | 26 | # Run "mix help deps" to learn about dependencies. 27 | defp deps do 28 | [ 29 | {:jason, "~> 1.4"}, 30 | {:castore, "~> 1.0"}, 31 | {:mint, "~> 1.5"}, 32 | {:ex_doc, "~> 0.30", only: :dev, runtime: false} 33 | ] 34 | end 35 | 36 | defp description do 37 | """ 38 | Simple emoji library for Elixir. 💩 39 | """ 40 | end 41 | 42 | defp package do 43 | [ 44 | maintainers: ["Bruno Ukita "], 45 | licenses: ["MIT"], 46 | links: %{"GitHub" => "https://github.com/ukita/emojix"} 47 | ] 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, 4 | "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, 5 | "earmark": {:hex, :earmark, "1.4.0", "397e750b879df18198afc66505ca87ecf6a96645545585899f6185178433cc09", [:mix], [], "hexpm", "4bedcec35de03b5f559fd2386be24d08f7637c374d3a85d3fe0911eecdae838a"}, 6 | "earmark_parser": {:hex, :earmark_parser, "1.4.38", "b42252eddf63bda05554ba8be93a1262dc0920c721f1aaf989f5de0f73a2e367", [:mix], [], "hexpm", "2cd0907795aaef0c7e8442e376633c5b3bd6edc8dbbdc539b22f095501c1cdb6"}, 7 | "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, 8 | "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, 9 | "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, 10 | "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, 11 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, 12 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, 13 | "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, 14 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 15 | } 16 | -------------------------------------------------------------------------------- /priv/dataset_15.2.0.ets: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ukita/emojix/2963a53487bd985943011284230eaa291e126535/priv/dataset_15.2.0.ets -------------------------------------------------------------------------------- /test/emojix_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EmojixTest do 2 | use ExUnit.Case 3 | 4 | test "list all the emojis" do 5 | assert length(Emojix.all()) === 3664 6 | end 7 | 8 | test "find emoji by shortcode" do 9 | test_cases = [ 10 | %{shortcode: "gleeful", expected: "grinning face"}, 11 | %{shortcode: "flag_au", expected: "flag: Australia"}, 12 | %{shortcode: "medium_small_black_square", expected: "black medium-small square"} 13 | ] 14 | 15 | Enum.each(test_cases, fn t -> 16 | emoji = Emojix.find_by_shortcode(t.shortcode) 17 | assert emoji.description === t.expected 18 | end) 19 | end 20 | 21 | test "return nil when emoji not found by shortcode" do 22 | assert is_nil(Emojix.find_by_shortcode("INVALID")) 23 | end 24 | 25 | test "find emoji by unicode" do 26 | test_cases = [ 27 | %{unicode: "👨🏼‍🦱", expected: "man: medium-light skin tone, curly hair"}, 28 | %{unicode: "🇪🇸", expected: "flag: Spain"}, 29 | %{unicode: "🖐🏾", expected: "hand with fingers splayed: medium-dark skin tone"} 30 | ] 31 | 32 | Enum.each(test_cases, fn t -> 33 | emoji = Emojix.find_by_unicode(t.unicode) 34 | assert emoji.description === t.expected 35 | end) 36 | end 37 | 38 | test "return nil when emoji not found by unicode" do 39 | assert is_nil(Emojix.find_by_unicode("INVALID")) 40 | end 41 | 42 | test "find emoji by hexcode" do 43 | test_cases = [ 44 | %{hexcode: "1F1EC-1F1F1", expected: "flag: Greenland"}, 45 | %{hexcode: "1F6A2", expected: "ship"}, 46 | %{hexcode: "1F3DB", expected: "classical building"} 47 | ] 48 | 49 | Enum.each(test_cases, fn t -> 50 | emoji = Emojix.find_by_hexcode(t.hexcode) 51 | assert emoji.description === t.expected 52 | end) 53 | end 54 | 55 | test "return nil when emoji not found by hexcode" do 56 | assert is_nil(Emojix.find_by_hexcode("INVALID")) 57 | end 58 | 59 | test "search a emoji by description" do 60 | test_cases = [ 61 | %{ 62 | description: "brick", 63 | expected: [ 64 | "🧱" 65 | ] 66 | }, 67 | %{ 68 | description: "dog", 69 | expected: ["🐕‍🦺", "🦮", "🌭", "🐕️", "🐶"] 70 | } 71 | ] 72 | 73 | Enum.each(test_cases, fn t -> 74 | emoji_list = Emojix.search_by_description(t.description) 75 | assert Enum.any?(emoji_list, fn e -> Enum.member?(t.expected, e.unicode) end) 76 | end) 77 | end 78 | 79 | test "return empty list when emoji not found by description" do 80 | assert Emojix.search_by_description("BLAHBLEH") == [] 81 | end 82 | 83 | test "search a emoji by tag" do 84 | test_cases = [ 85 | %{ 86 | tag: "boat", 87 | expected: ["🚣", "⛴️", "🚢", "🚤", "⛵️", "🚣‍♀️", "🚣‍♂️", "🛥️", "🛶"] 88 | }, 89 | %{ 90 | tag: "bleed", 91 | expected: ["🩸"] 92 | } 93 | ] 94 | 95 | Enum.each(test_cases, fn t -> 96 | emoji_list = Emojix.search_by_tag(t.tag) 97 | assert Enum.any?(emoji_list, fn e -> Enum.member?(t.expected, e.unicode) end) 98 | end) 99 | end 100 | 101 | test "return empty list when emoji not found by tag" do 102 | assert Emojix.search_by_tag("BLAHBLEH") == [] 103 | end 104 | 105 | test "scan a string and return all the contained emojis" do 106 | test_cases = [ 107 | %{ 108 | text: "⁣ 109 | 🎈🎈 ☁️ 110 | 🎈🎈🎈 111 | ☁️ 🎈🎈🎈🎈 112 | 🎈🎈🎈🎈 113 | ☁️ ⁣🎈🎈🎈 114 | \|/ 115 | 🏠 ☁️ 116 | ☁️ ☁️ 117 | 🌳🌹🏫🌳🏢🏢_🏢🏢🌳🌳 118 | ", 119 | expected: ["🎈", "🏠️", "🌳", "🌹", "🏫", "🏢"] 120 | }, 121 | %{ 122 | text: " 123 | ⁣⚪⚪⚪⚪⚪⚪⚪ 124 | ⚪⚪⚪⚪⚪⚪⚪ 125 | ⁣⚪⚪⚪⚪⚪⚪⚪ 126 | ⚪⚪🔵🔴⚪⚪⚪ 127 | ", 128 | expected: ["⚪️", "🔵", "🔴"] 129 | } 130 | ] 131 | 132 | Enum.each(test_cases, fn t -> 133 | emoji_list = Emojix.scan(t.text) 134 | assert Enum.any?(emoji_list, fn e -> Enum.member?(t.expected, e.unicode) end) 135 | end) 136 | end 137 | 138 | test "return empty list when sentence has no emoji" do 139 | assert Emojix.scan("BLAHBLEH") == [] 140 | end 141 | 142 | test "replace a emoji with a given function" do 143 | test_cases = [ 144 | %{ 145 | string: "🌍 ain't the kind of place to raise your 👨‍👩‍👧‍👦", 146 | expected: ":emoji: ain't the kind of place to raise your :emoji:" 147 | }, 148 | %{ 149 | string: "🚀 👨🏻 🔥 out his fuse up here alone", 150 | expected: ":emoji: :emoji: :emoji: out his fuse up here alone" 151 | } 152 | ] 153 | 154 | Enum.each(test_cases, fn t -> 155 | str = Emojix.replace(t.string, fn _ -> ":emoji:" end) 156 | assert str == t.expected 157 | end) 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------