defmodule StringNaming.H do
def nested_module(mod, children) do
[funs, mods] =
Enum.reduce(children, [%{}, %{}], fn
{k, v}, [funs, mods] when is_binary(v) -> [Map.put(funs, k, v), mods]
{k, v}, [funs, mods] -> [funs, Map.put(mods, k, v)]
end)
ast =
for {name, value} <- funs do
name =
name |> String.replace(~r/\A(\d)/, "N_\\1") |> Macro.underscore() |> String.to_atom()
quote do: def(unquote(name)(), do: <<String.to_integer(unquote(value), 16)::utf8>>)
end ++
[
quote do
def __all__ do
:functions
|> __MODULE__.__info__()
|> Enum.map(fn
{:__all__, 0} -> nil
{k, 0} -> {k, apply(__MODULE__, k, [])}
_ -> nil
end)
|> Enum.filter(& &1)
end
end
]
Module.create(Module.concat(mod), ast, Macro.Env.location(__ENV__))
StringNaming.H.nesteds(mod, mods)
end
def nesteds(nested \\ [], map_or_code)
def nesteds(nested, %{} = map) do
Enum.each(map, fn
{_key, code} when is_binary(code) ->
:ok
{k, v} ->
mod = :lists.reverse([k | :lists.reverse(nested)])
StringNaming.H.nested_module(mod, v)
end)
end
end
defmodule StringNaming do
@moduledoc ~S"""
The sibling of [`String.Casing`](https://github.com/elixir-lang/elixir/blob/9873e4239f063e044e5d6602e173ebee4f32391d/lib/elixir/unicode/properties.ex#L57),
`String.Break` and `String.Normalizer` from Elixir core.
It parses the [`NamesList.txt`](http://www.unicode.org/Public/UCD/latest/ucd/NamesList.txt) file provided by Consortium, building
the set of nested modules under `StringNaming`. Each nested module is granted with `__all__/0` function that returns all the
available symbols in that particular namespace, as well as with methods returning a symbol by itโs name.
## Examples
iex> StringNaming.AnimalSymbols.monkey
"๐"
iex> StringNaming.FrakturSymbols.Mathematical.Fraktur.Capital.__all__
[a: "๐", b: "๐
", d: "๐", e: "๐", f: "๐", g: "๐", j: "๐",
k: "๐", l: "๐", m: "๐", n: "๐", o: "๐", p: "๐", q: "๐",
s: "๐", t: "๐", u: "๐", v: "๐", w: "๐", x: "๐", y: "๐"]
"""
@categories Enum.uniq(
StringNaming.Defaults.categories() ++
Application.compile_env(:string_naming, :categories, [])
)
data_path = Path.join([__DIR__, "string_naming", "unicode", "NamesList.txt"])
~S"""
0021 EXCLAMATION MARK
= factorial
= bang
x (inverted exclamation mark - 00A1)
x (latin letter retroflex click - 01C3)
"""
extract_prop = fn
_rest, [_category, _names, _props] = acc ->
# TODO make properties available as well
# IO.inspect rest, label: "โ
property"
acc
end
underscore = fn name ->
name
|> String.trim()
|> String.replace(~r/\A(\d)/, "N_\\1")
|> String.replace(~r/[^A-Za-z\d_ ]/, " ")
|> String.split(" ")
|> Enum.filter(&(&1 != ""))
|> Enum.join("_")
|> Macro.underscore()
end
@selected @categories
|> Enum.filter(fn
<<"#", _::binary>> -> false
<<"=", _::binary>> -> false
<<"+", _::binary>> -> false
_ -> true
end)
|> Enum.map(&underscore.(&1))
extract_name = fn
_, <<"<"::binary, _::binary>>, [_category, _names, _props] = acc ->
acc
code, name, [category, names, props] ->
[category, [{code, underscore.(name), category} | names], props]
end
[_category, names, _props] =
Enum.reduce(File.stream!(data_path), ["Unknown", [], %{}], fn
<<";"::binary, _::binary>>, acc ->
acc
<<"@\t"::binary, category::binary>>, [_, names, props] ->
category = underscore.(category)
category = if Enum.member?(@selected, category), do: category, else: ""
[category, names, props]
<<"@"::binary, _::binary>>, acc ->
acc
<<"\t"::binary, rest::binary>>, acc ->
extract_prop.(rest, acc)
code_name, [category, _, _] = acc when category != "" ->
with [code, name] <- :binary.split(code_name, "\t") do
extract_name.(code, name, acc)
end
<<"00", _::binary-size(2), "\t", _::binary>> = code_name, [_, names, props] ->
with [code, name] <- :binary.split(code_name, "\t") do
extract_name.(code, name, ["ascii", names, props])
end
_, acc ->
acc
end)
names_tree =
Enum.reduce(names, %{}, fn {code, name, category}, acc ->
modules = [category | String.split(name, "_")] |> Enum.map(&Macro.camelize/1)
{acc, ^modules} =
Enum.reduce(modules, {acc, []}, fn
key, {acc, keys} ->
keys = :lists.reverse([key | :lists.reverse(keys)])
{_, result} =
get_and_update_in(acc, keys, fn
nil -> {nil, %{}}
map when is_map(map) -> {map, map}
other -> {other, %{}}
end)
{result, keys}
end)
put_in(acc, modules, code)
end)
StringNaming.H.nesteds(["StringNaming"], names_tree)
@doc ~S"""
Returns graphemes for modules that have names matching the regular expression given as a parameter.
The response is a plain keyword list with names taken from concatenated nested module names.
## Examples
iex> StringNaming.graphemes ~r/AnimalFace/
[
animalfaces_bear_face: "๐ป",
animalfaces_cat_face: "๐ฑ",
animalfaces_cow_face: "๐ฎ",
animalfaces_dog_face: "๐ถ",
animalfaces_dragon_face: "๐ฒ",
animalfaces_frog_face: "๐ธ",
animalfaces_hamster_face: "๐น",
animalfaces_horse_face: "๐ด",
animalfaces_monkey_face: "๐ต",
animalfaces_mouse_face: "๐ญ",
animalfaces_panda_face: "๐ผ",
animalfaces_pig_face: "๐ท",
animalfaces_pig_nose: "๐ฝ",
animalfaces_rabbit_face: "๐ฐ",
animalfaces_spouting_whale: "๐ณ",
animalfaces_tiger_face: "๐ฏ",
animalfaces_wolf_face: "๐บ"
]
iex> StringNaming.graphemes ~r/fraktur.small/i
[
fraktursymbols_mathematical_fraktur_small_a: "๐",
fraktursymbols_mathematical_fraktur_small_b: "๐",
fraktursymbols_mathematical_fraktur_small_c: "๐ ",
fraktursymbols_mathematical_fraktur_small_d: "๐ก",
fraktursymbols_mathematical_fraktur_small_e: "๐ข",
fraktursymbols_mathematical_fraktur_small_f: "๐ฃ",
fraktursymbols_mathematical_fraktur_small_g: "๐ค",
fraktursymbols_mathematical_fraktur_small_h: "๐ฅ",
fraktursymbols_mathematical_fraktur_small_i: "๐ฆ",
fraktursymbols_mathematical_fraktur_small_j: "๐ง",
fraktursymbols_mathematical_fraktur_small_k: "๐จ",
fraktursymbols_mathematical_fraktur_small_l: "๐ฉ",
fraktursymbols_mathematical_fraktur_small_m: "๐ช",
fraktursymbols_mathematical_fraktur_small_n: "๐ซ",
fraktursymbols_mathematical_fraktur_small_o: "๐ฌ",
fraktursymbols_mathematical_fraktur_small_p: "๐ญ",
fraktursymbols_mathematical_fraktur_small_q: "๐ฎ",
fraktursymbols_mathematical_fraktur_small_r: "๐ฏ",
fraktursymbols_mathematical_fraktur_small_s: "๐ฐ",
fraktursymbols_mathematical_fraktur_small_t: "๐ฑ",
fraktursymbols_mathematical_fraktur_small_u: "๐ฒ",
fraktursymbols_mathematical_fraktur_small_v: "๐ณ",
fraktursymbols_mathematical_fraktur_small_w: "๐ด",
fraktursymbols_mathematical_fraktur_small_x: "๐ต",
fraktursymbols_mathematical_fraktur_small_y: "๐ถ",
fraktursymbols_mathematical_fraktur_small_z: "๐ท"
]
iex> StringNaming.graphemes ~r/\Aspace/i, false
[
space_medium_mathematical_space: "โ",
space_narrow_no_break_space: "โฏ",
space_ogham_space_mark: "แ",
spaces_em_quad: "โ",
spaces_em_space: "โ",
spaces_en_quad: "โ",
spaces_en_space: "โ",
spaces_figure_space: "โ",
spaces_four_per_em_space: "โ
",
spaces_hair_space: "โ",
spaces_punctuation_space: "โ",
spaces_six_per_em_space: "โ",
spaces_thin_space: "โ",
spaces_three_per_em_space: "โ"
]
"""
def graphemes(%Regex{} = filter, modules_only? \\ true) do
with {:ok, modules} <- :application.get_key(:string_naming, :modules) do
modules
|> Enum.filter(fn m ->
case {modules_only?, to_string(m)} do
{false, _} ->
match?({:module, ^m}, Code.ensure_loaded(m)) and function_exported?(m, :__all__, 0)
{_, <<"Elixir.StringNaming."::binary, name::binary>>} ->
Regex.match?(filter, name)
_ ->
false
end
end)
|> Enum.flat_map(fn m ->
m
|> apply(:__all__, [])
|> Enum.reduce([], fn {k, v}, acc ->
<<"Elixir.StringNaming."::binary, name::binary>> = to_string(m)
name =
name
|> String.split(~r/\W/)
|> Kernel.++([k])
|> Enum.join("_")
if Regex.match?(filter, name),
do: [{name |> String.downcase() |> String.to_atom(), v} | acc],
else: acc
end)
|> Enum.reverse()
end)
end
end
end
:code.purge(StringNaming.H)
:code.delete(StringNaming.H)