lib/cldr/utils/string.ex

defmodule Cldr.String do
  @moduledoc """
  Functions that operate on a `String.t` that are not provided
  in the standard lib.
  """

  @doc """
  Hash a string using a polynomial rolling hash function.

  See https://cp-algorithms.com/string/string-hashing.html for
  a description of the algoithim.
  """

  @p 99991
  @m trunc(1.0e9) + 9
  def hash(string) do
    {hash, _} =
      string
      |> String.to_charlist()
      |> Enum.reduce({0, 1}, fn char, {hash, p_pow} ->
        hash = rem(hash + char * p_pow, @m)
        p_pow = rem(p_pow * @p, @m)
        {hash, p_pow}
      end)

    hash
  end

  @doc """
  Replaces "-" with "_" in a string.

  ## Examples

      iex> Cldr.String.to_underscore("this-one")
      "this_one"

  """
  def to_underscore(string) when is_binary(string) do
    String.replace(string, "-", "_")
  end

  @doc """
  This is the code of Macro.underscore with modifications:

  The change is to cater for strings in the format:

      This_That

  which in Macro.underscore gets formatted as:

      this__that (note the double underscore)

  when we actually want:

      that_that

  """
  def underscore(atom) when is_atom(atom) do
    "Elixir." <> rest = Atom.to_string(atom)
    underscore(rest)
  end

  def underscore(<<h, t::binary>>) do
    <<to_lower_char(h)>> <> do_underscore(t, h)
  end

  def underscore("") do
    ""
  end

  # h is upper case, next char is not uppercase, or a _ or .  => and prev != _
  defp do_underscore(<<h, t, rest::binary>>, prev)
       when h >= ?A and h <= ?Z and not (t >= ?A and t <= ?Z) and t != ?. and t != ?_ and
              prev != ?_ do
    <<?_, to_lower_char(h), t>> <> do_underscore(rest, t)
  end

  # h is uppercase, previous was not uppercase or _
  defp do_underscore(<<h, t::binary>>, prev)
       when h >= ?A and h <= ?Z and not (prev >= ?A and prev <= ?Z) and prev != ?_ do
    <<?_, to_lower_char(h)>> <> do_underscore(t, h)
  end

  # h is .
  defp do_underscore(<<?., t::binary>>, _) do
    <<?/>> <> underscore(t)
  end

  # Any other char
  defp do_underscore(<<h, t::binary>>, _) do
    <<to_lower_char(h)>> <> do_underscore(t, h)
  end

  defp do_underscore(<<>>, _) do
    <<>>
  end

  def to_upper_char(char) when char >= ?a and char <= ?z, do: char - 32
  def to_upper_char(char), do: char

  def to_lower_char(char) when char >= ?A and char <= ?Z, do: char + 32
  def to_lower_char(char), do: char
end