Current section
Files
Jump to
Current section
Files
lib/emoshi.ex
defmodule Emoshi do
@moduledoc """
Main module to query and access emoji data.
See https://www.unicode.org/reports/tr51/ for information about emojis.
"""
@enforce_keys [:slug, :name, :status, :emoji]
defstruct [:slug, :name, :status, :emoji, :group, :subgroup]
@type emoji_status() :: :unqualified | :fully_qualified | :minimally_qualified | :component
@type t() :: %__MODULE__{
emoji: String.t(),
slug: String.t(),
name: String.t(),
status: emoji_status(),
group: String.t(),
subgroup: String.t()
}
@doc """
Returns the specs version used to generate the module
"""
@spec version :: String.t()
def version do
Emoshi.Emoshis.version()
end
@doc """
Returns all `t:Emoshi.t/0` where the slug matches the input argument.
Unlike `closest/2` this function checks for substring comparison.
## Options
* `:ignore_variations` - `t:boolean/0`. Whether to ignore variations such as skin color.
Defaults to `true`.
* `:take` - `t:pos_integer/0`. The maximum number of emojis to retrieve. Keep in mind
that unlike `closest/2` which always returns the specified `:take` number, this function
may return fewer results if there are not enough matches.
Defaults to `5`
"""
@spec search(String.t(), Keyword.t()) :: list(Emoshi.t())
def search(search_slug, opts \\ []) do
ignore_variations = Keyword.get(opts, :ignore_variations, true)
take = Keyword.get(opts, :take, 5)
normalized = slugify(search_slug)
Emoshi.Emoshis.emojis()
|> filter_variations(ignore_variations)
|> Enum.filter(fn %Emoshi{slug: slug} -> String.contains?(slug, normalized) end)
|> Enum.take(take)
end
# Duplicating the function here because the `Generate` modules are not
# included in runtime, and it's not worth moving to another place
defp slugify(name) when is_binary(name) do
name
|> String.downcase()
|> String.normalize(:nfd)
|> String.replace(~r/[^a-z0-9\s-]/u, " ")
|> String.replace(~r/[\s-]+/, "-")
end
@doc """
Returns the closest emojis by slug.
Uses `String.jaro_distance/2` internally to find the closest emojis.
## Options
* `:ignore_variations` - `t:boolean/0`. Whether to ignore variations such as skin color.
Defaults to `true`.
* `:take`- `t:pos_integer/0`. The number of emojis to retrieve.
Defaults to `5`
"""
@spec closest(String.t(), Keyword.t()) :: list(Emoshi.t())
def closest(search_slug, opts \\ []) do
ignore_variations = Keyword.get(opts, :ignore_variations, true)
n = Keyword.get(opts, :take, 5)
Emoshi.Emoshis.emojis()
|> filter_variations(ignore_variations)
|> Enum.sort_by(fn %Emoshi{slug: slug} -> String.jaro_distance(slug, search_slug) end, :desc)
|> Enum.take(n)
end
defp filter_variations(emojis, false), do: emojis
defp filter_variations(emojis, true) do
Enum.filter(emojis, fn %Emoshi{name: name} -> not String.contains?(name, "skin tone") end)
end
@doc """
Returns all the emojis' groups
"""
@spec groups() :: list(String.t())
def groups do
Map.keys(Emoshi.Emoshis.groups())
end
@doc """
Returns whether an emoji group exists
"""
@spec group?(String.t()) :: boolean()
def group?(group_name) do
Map.has_key?(Emoshi.Emoshis.groups(), group_name)
end
@doc """
Returns all the subgroups for a group or `nil` if the group does not exist
"""
@spec subgroups(String.t()) :: list(String.t()) | nil
def subgroups(group) do
Map.get(Emoshi.Emoshis.groups(), group)
end
@doc """
Returns all emojis for the given group
"""
@spec for_group(String.t()) :: list(Emoshi.t())
def for_group(group) when is_binary(group) do
for_groups([group])
end
@doc """
Returns all emojis for the given groups
"""
@spec for_groups(nonempty_list(String.t())) :: list(Emoshi.t())
def for_groups(groups) when is_list(groups) do
Emoshi.Emoshis.emojis()
|> Enum.filter(fn %Emoshi{group: g} -> Enum.member?(groups, g) end)
end
@doc """
Returns all emojis for the given group and subgroup(s).
Accepts both a single subgroup and a list of subgroups
"""
@spec for_subgroups(String.t(), String.t() | nonempty_list(String.t())) :: list(Emoshi.t())
def for_subgroups(group, subgroups) when is_binary(group) and is_list(subgroups) do
Emoshi.Emoshis.emojis()
|> Enum.filter(fn %Emoshi{group: g, subgroup: sg} ->
g == group and Enum.member?(subgroups, sg)
end)
end
def for_subgroups(group, subgroup) when is_binary(group) and is_binary(subgroup) do
for_subgroups(group, [subgroup])
end
end