defmodule ShortUUID do @moduledoc """ ShortUUID is a simple UUID shortener library. ## Installation 1. Add ShortUUID to your list of dependencies in `mix.exs`: ``` def deps do [{:shortuuid, "~> #{ShortUUID.Mixfile.project[:version]}"}] end ``` 2. Optionally configure the alphabet to be used for encoding in `config.exs`: ``` config :shortuuid, alphabet: "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" ``` The default alphabet (above) will translate UUIDs to base57 using lowercase and uppercase letters and digits while avoiding similar-looking characters such as l, 1, I, O and 0. ## Typical usage Use ShortUUID to shorten UUID strings generated by other libraries such as [Ecto](https://hexdocs.pm/ecto/Ecto.UUID.html), [Elixir UUID](https://github.com/zyro/elixir-uuid) and [Erlang UUID](https://github.com/okeuday/uuid). ## Acknowledgments This project was inspired by [skorokithakis/shortuuid](https://github.com/skorokithakis/shortuuid). """ @alphabet (Application.get_env(:shortuuid, :alphabet) || "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") |> String.graphemes |> Enum.sort |> Enum.dedup |> List.to_string @alphabet_graphemes String.graphemes(@alphabet) @alphabet_length String.length(@alphabet) @doc """ Take two integers as arguments and return a pair integers consisting of their quotient and remainder. ## Examples iex> ShortUUID.divmod(324969006592305634633390616021200786553, 57) [5701210641970274291813870456512294500, 53] """ @spec divmod(Integer.t, Integer.t) :: [Integer.t] defp divmod(dividend, divisor) do [Integer.floor_div(dividend, divisor), Integer.mod(dividend, divisor)] end @doc """ Convert the number to a string. """ @spec int_to_string(Integer.t, list()) :: [String.t] defp int_to_string(number, acc \\ []) defp int_to_string(number, acc) when number > 0 do [result, remainder] = divmod(number, @alphabet_length) int_to_string(result, acc ++ [String.at(@alphabet, remainder)]) end defp int_to_string(_number, acc) do List.to_string(acc) end @doc """ Encode a UUID using the chosen alphabet. ## Examples iex> ShortUUID.encode("2a162ee5-02f4-4701-9e87-72762cbce5e2") "keATfB8JP2ggT7U9JZrpV9" """ @spec encode(String.t) :: String.t def encode(uuid) do uuid |> String.replace("-", "") |> String.downcase |> String.to_integer(16) |> int_to_string end @doc """ Convert a shortened UUID back to a full size UUID. ## Examples iex> ShortUUID.decode("keATfB8JP2ggT7U9JZrpV9") "2a162ee5-02f4-4701-9e87-72762cbce5e2" """ @spec decode(String.t) :: String.t def decode(string) do string_to_int(string) |> to_hex_string |> String.downcase |> add_padding |> format_string_to_uuid end defp string_to_int(string) do String.reverse(string) |> String.graphemes |> Enum.reduce(0, fn(char, acc) -> acc * @alphabet_length + Enum.find_index(@alphabet_graphemes, &(&1 == char)) end) end defp to_hex_string(number) do number |> Integer.to_string(16) end defp add_padding(string) do String.pad_leading(string, 32, "0") end defp format_string_to_uuid(string) do "#{time_low(string)}-#{time_mid(string)}-#{time_hi_and_ver(string)}-#{clock_seq(string)}-#{nodeid(string)}" end defp time_low(string) do binary_part(string, 0, 8) end defp time_mid(string) do binary_part(string, 8, 4) end defp time_hi_and_ver(string) do binary_part(string, 12, 4) end defp clock_seq(string) do binary_part(string, 16, 4) end defp nodeid(string) do binary_part(string, 20, 12) end end