lib/miss/string.ex

defmodule Miss.String do
  @moduledoc """
  Functions to extend the Elixir `String` module.
  """

  @doc ~S"""
  Builds a string with the given values using IO lists.

  In Erlang and Elixir concatenating binaries will copy the concatenated binaries into a new
  binary. Every time you concatenate binaries (`<>`) or use interpolation (`#{}`) you are making
  copies of those binaries.

  To build a string, it is cheaper and more efficient to use IO lists to build the binary just
  once instead of concatenating along the way.

  See the [Elixir IO Data documentation](https://hexdocs.pm/elixir/IO.html#module-io-data) for
  more information.

  All elements in the list must be a binary or convertible to a binary, otherwise an error is
  raised.

  ## Examples

      iex> Miss.String.build(["akira", "hamasaki", "123", "pim", "2010-09-01", "99.99"])
      "akirahamasaki123pim2010-09-0199.99"

      iex> Miss.String.build([:akira, 'hamasaki', 123, [112, 105, 109], ~D[2010-09-01], 99.99])
      "akirahamasaki123pim2010-09-0199.99"

  """
  @spec build(list()) :: String.t()
  def build(values) when is_list(values) do
    values
    |> Enum.map(&value_to_string/1)
    |> IO.iodata_to_binary()
  end

  @spec value_to_string(term()) :: String.t()
  defp value_to_string(value) when is_binary(value), do: value
  defp value_to_string(value), do: String.Chars.to_string(value)

  @doc """
  Builds a string with the given two values using IO lists similar to `Miss.String.build/1`.

  When both values are binary, `#{inspect(__MODULE__)}.build/2` is more efficient than
  `Miss.String.build/1` because it avoids to check if each value is a binary.

  ## Examples

      iex> Miss.String.build("akira", "hamasaki")
      "akirahamasaki"

      iex> Miss.String.build(:akira, 'hamasaki')
      "akirahamasaki"

  """
  @spec build(term(), term()) :: String.t()
  def build(value1, value2)
      when is_binary(value1) and
             is_binary(value2),
      do: IO.iodata_to_binary([value1, value2])

  def build(value1, value2), do: build([value1, value2])

  @doc """
  Builds a string with the given three values using IO lists similar to `Miss.String.build/1`.

  When all the values are binary, `#{inspect(__MODULE__)}.build/3` is more efficient than
  `Miss.String.build/1` because it avoids to check if each value is a binary.

  ## Examples

      iex> Miss.String.build("akira", "hamasaki", "123")
      "akirahamasaki123"

      iex> Miss.String.build(:akira, 'hamasaki', 123)
      "akirahamasaki123"

  """
  @spec build(term(), term(), term()) :: String.t()
  def build(value1, value2, value3)
      when is_binary(value1) and
             is_binary(value2) and
             is_binary(value3),
      do: IO.iodata_to_binary([value1, value2, value3])

  def build(value1, value2, value3), do: build([value1, value2, value3])

  @doc """
  Builds a string with the given four values using IO lists similar to `Miss.String.build/1`.

  When all the values are binary, `#{inspect(__MODULE__)}.build/4` is more efficient than
  `Miss.String.build/1` because it avoids to check if each value is a binary.

  ## Examples

      iex> Miss.String.build("akira", "hamasaki", "123", "pim")
      "akirahamasaki123pim"

      iex> Miss.String.build(:akira, 'hamasaki', 123, [112, 105, 109])
      "akirahamasaki123pim"

  """
  @spec build(term(), term(), term(), term()) :: String.t()
  def build(value1, value2, value3, value4)
      when is_binary(value1) and
             is_binary(value2) and
             is_binary(value3) and
             is_binary(value4),
      do: IO.iodata_to_binary([value1, value2, value3, value4])

  def build(value1, value2, value3, value4), do: build([value1, value2, value3, value4])

  @doc """
  Builds a string with the given five values using IO lists similar to `Miss.String.build/1`.

  When all the values are binary, `#{inspect(__MODULE__)}.build/5` is more efficient than
  `Miss.String.build/1` because it avoids to check if each value is a binary.

  ## Examples

      iex> Miss.String.build("akira", "hamasaki", "123", "pim", "2010-09-01")
      "akirahamasaki123pim2010-09-01"

      iex> Miss.String.build(:akira, 'hamasaki', 123, [112, 105, 109], ~D[2010-09-01])
      "akirahamasaki123pim2010-09-01"

  """
  @spec build(term(), term(), term(), term(), term()) :: String.t()
  def build(value1, value2, value3, value4, value5)
      when is_binary(value1) and
             is_binary(value2) and
             is_binary(value3) and
             is_binary(value4) and
             is_binary(value5),
      do: IO.iodata_to_binary([value1, value2, value3, value4, value5])

  def build(value1, value2, value3, value4, value5),
    do: build([value1, value2, value3, value4, value5])
end