Skip to content

Instantly share code, notes, and snippets.

@Exadra37
Last active September 25, 2022 19:55
Show Gist options
  • Save Exadra37/0cbaf090c152049bdbdffbd331fc7b08 to your computer and use it in GitHub Desktop.
Save Exadra37/0cbaf090c152049bdbdffbd331fc7b08 to your computer and use it in GitHub Desktop.
Elixir Tags Unify module
defmodule Utils.TagsUnify do
@moduledoc """
This module unifies the given tags, by removing duplicates and merging the
ones that are similar.
## Examples
iex> "My Elixir Status, my-elixir-status, myelixirstatus, MyElixirStatus" |> Utils.TagsUnify.string()
"MyElixirStatus"
"""
alias Utils.TagsUnify
alias Utils.UniqueCaseInsensitive
defstruct [
split_separator: ",",
join_separator: ", ",
replace_chars: "_-"
]
@doc """
Unify the given string of tags, without duplicates, and with similar tags
being merged.
## Examples:
iex> Utils.TagsUnify.test_tags_string |> Utils.TagsUnify.string
"Elixir, MyElixirStatus, PhoenixFramework, Phoenix"
"""
def string(tags, %TagsUnify{} = opts \\ %TagsUnify{}) when is_binary(tags) do
tags
|> string_to_list(opts)
|> Enum.join(opts.join_separator)
end
@doc """
Unify the given string of tags into a list, without duplicates, and with
similar tags being merged.
## Examples
iex> Utils.TagsUnify.test_tags_string |> Utils.TagsUnify.string_to_list()
["Elixir", "MyElixirStatus", "PhoenixFramework", "Phoenix"]
"""
def string_to_list(tags, %TagsUnify{} = opts \\ %TagsUnify{}) when is_binary(tags) do
tags
|> String.split(~r[#{opts.split_separator}], trim: true)
|> _unify_tags(opts)
end
@doc """
Unify the given list of tags, without duplicates, and with similar tags being
merged.
## Examples
iex> Utils.TagsUnify.test_tags_list |> Utils.TagsUnify.list()
["Elixir", "MyElixirStatus", "PhoenixFramework", "Phoenix"]
"""
def list(tags, %TagsUnify{} = opts \\ %TagsUnify{}) when is_list(tags) do
tags
|> _unify_tags(opts)
end
@doc """
Unify the given list of tags into a string, without duplicates, and with
similar tags being merged.
## Examples
iex> Utils.TagsUnify.test_tags_list |> Utils.TagsUnify.list_to_string()
"Elixir, MyElixirStatus, PhoenixFramework, Phoenix"
"""
def list_to_string(tags, %TagsUnify{} = opts \\ %TagsUnify{}) when is_list(tags) do
tags
|> _unify_tags(opts)
|> Enum.join(", ")
end
defp _unify_tags(tags, %TagsUnify{} = opts) when is_list(tags) do
tags
|> Enum.map(&_unify_tag(&1, opts))
|> UniqueCaseInsensitive.list()
|> Enum.reject(fn tag -> String.length(tag) == 0 end)
end
defp _unify_tag(tag, %TagsUnify{} = opts) when is_binary(tag) do
tag
|> String.replace(~r/\s+/, " ")
|> _replace_chars(opts.replace_chars, " ")
|> _title_case()
|> _replace_chars(" ", "")
# @TODO maybe pluralize or singularize?
# + @link https://hexdocs.pm/inflex/1.8.0/Inflex.html
# + @link https://hex.pm/packages?_utf8=%E2%9C%93&search=inflex&sort=recent_downloads
# + @link https://hex.pm/packages?search=inflector&sort=recent_downloads
end
# @link https://www.rosettacode.org/wiki/Strip_a_set_of_characters_from_a_string#Elixir
defp _replace_chars(str, match, replace) do
String.replace(str, ~r/[#{match}]/, replace)
end
# @link https://kozmicluis.com/title-case-capitalize-sentence/
defp _title_case(sentence) do
sentence
|> String.split
|> Stream.map(&_upcase_first_char/1)
|> Enum.join(" ")
end
# @link https://stackoverflow.com/a/58677092/6454622
defp _upcase_first_char(<<first::utf8, rest::binary>>) do
String.upcase(<<first::utf8>>) <> rest
end
@test_tags_string " elixir, My Elixir Status,my elixir Status, phoenix framework , phoenixframework, phoenix , , , Elixir , My-Elixir-Status,myelixirstatus,MyELixirStatus,my-elixir-status,myElixirStatus, my Elixir status, my elixir status, Elixir"
@doc false
def test_tags_string(), do: @test_tags_string
@doc false
def test_tags_list(), do: @test_tags_string |> String.split(",")
end
defmodule Utils.UniqueCaseInsensitive do
@moduledoc """
Module to take a string or list and return it without duplicates in a case
insensitive way.
To bear in mind that `"Elixir, Elixir., Elixir ."` will have each of its words
to be considered as unique, but if you want each word to be considered has the
same word, then you are better using `Utils.TagsUnify.string()`.
## Examples
iex> "MyELixirStatus, my elixir status, Phoenix, Phoenix" |> Utils.UniqueCaseInsensitive.string()
"MyELixirStatus, my elixir status, Phoenix"
iex> "MyELixirStatus, my elixir status, Phoenix, Phoenix." |> Utils.UniqueCaseInsensitive.string()
"MyELixirStatus, my elixir status, Phoenix, Phoenix."
iex> "MyELixirStatus, my elixir status, Phoenix, Phoenix ." |> Utils.UniqueCaseInsensitive.string()
"MyELixirStatus, my elixir status, Phoenix, Phoenix ."
"""
@doc """
Remove all duplicate words from a string, by preserving the first word in a
case insensitive way. By default separates the word by a comma.
## Examples
iex> Utils.UniqueCaseInsensitive.test_string |> Utils.UniqueCaseInsensitive.string()
"Elixir, MyElixirStatus, My Elixir Status"
"""
def string(value, separator \\ ",") when is_binary(value) do
value
#|> String.replace(~r/\W+/, "") # Only words?
|> String.split(separator)
|> _unique_list()
|> Enum.join(separator)
end
@doc """
Remove all duplicate words from the given list in a case insensitive way.
## Examples
iex> Utils.UniqueCaseInsensitive.test_string_list |> Utils.UniqueCaseInsensitive.list()
["Elixir", "MyElixirStatus", "My Elixir Status"]
"""
def list(values) when is_list(values) do
_unique_list(values)
|> Enum.map(&String.trim(&1))
end
# @link https://www.rosettacode.org/wiki/Remove_duplicate_elements#Elixir
defp _unique_list(values) when is_list(values) do
values
|> _unique([], [])
end
defp _unique([], _tags, unique_tags) do
Enum.reverse(unique_tags)
end
defp _unique([head | tail], tags, unique_tags) do
tag =
head
|> String.trim()
|> String.replace(~r/\s+/, " ") # "My Elixir Status" |> "My Elixir Status"
|> String.downcase()
case tag in tags do
true ->
_unique(tail, tags, unique_tags)
_ ->
_unique(tail, [tag | tags], [head | unique_tags])
end
end
@test_string "Elixir, elixir, MyElixirStatus, myelixirstatus, myElixirStatus, My Elixir Status, My Elixir Status"
@doc false
def test_string(), do: @test_string
@doc false
def test_string_list(), do: @test_string |> String.split(",")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment