lib/lexical/formats.ex

defmodule Lexical.Formats do
  @moduledoc """
  A collection of formatting functions
  """

  @type unit :: :millisecond | :second
  @type time_opt :: {:unit, unit}
  @type time_opts :: [time_opt]

  @doc """
  Formats an elapsed time to either seconds or milliseconds

  Examples:

  ```
  Format.seconds(500, unit: :millisecond)
  "0.5 seconds"
  ```

  ```
  Format.format(1500, unit: :millisecond)
  "1.4 seconds"
  ```

  ```
  Format.format(1500)
  "15 ms"
  ```
  """
  @spec time(time :: non_neg_integer(), opts :: time_opts) :: String.t()
  def time(time, opts \\ []) do
    units = Keyword.get(opts, :unit, :microsecond)
    millis = to_milliseconds(time, units)

    cond do
      millis >= 1000 ->
        "#{Float.round(millis / 1000, 1)} seconds"

      millis >= 1.0 ->
        "#{trunc(millis)} ms"

      true ->
        "#{millis} ms"
    end
  end

  @doc """
  Formats a name of a module
  Both elixir and erlang modules will format like they appear in elixir source code.

  ```
   Format.format(MyModule)
   "MyModule"
   ```

   ```
  Formats.module(Somewhat.Nested.Module)
  "Somewhat.Nested.Module"
  ```

  ```
  Format.format(:erlang_module)
  ":erlang_module"
  ```
  """
  @spec module(atom()) :: String.t()
  def module(module_name) when is_atom(module_name) do
    string_name = Atom.to_string(module_name)

    if String.contains?(string_name, ".") do
      module_name
      |> Module.split()
      |> Enum.join(".")
    else
      # erlang module_name
      ":#{string_name}"
    end
  end

  def module(module_name) when is_binary(module_name) do
    module_name
  end

  defp to_milliseconds(micros, :microsecond) do
    micros / 1000
  end

  defp to_milliseconds(millis, :millisecond) do
    millis
  end

  def plural(count, singular, plural) do
    case count do
      0 -> templatize(count, plural)
      1 -> templatize(count, singular)
      _n -> templatize(count, plural)
    end
  end

  def mfa(module, function, arity) do
    "#{module(module)}.#{function}/#{arity}"
  end

  defp templatize(count, template) do
    count_string = Integer.to_string(count)
    String.replace(template, "${count}", count_string)
  end
end