lib/r_list/active_support.ex

defmodule RList.ActiveSupport do
  @moduledoc """
  Summarized all of List functions in Rails.ActiveSupport.
  If a function with the same name already exists in Elixir, that is not implemented.
  Defines all of here functions when `use RList.ActiveSupport`.
  """
  @spec __using__(any) :: list
  defmacro __using__(_opts) do
    RUtils.define_all_functions!(__MODULE__)
  end

  alias RList.Support

  # https://www.rubydoc.info/gems/activesupport/Array
  # [:as_json, :compact_blank!, :deep_dup, :excluding, :extract!, :extract_options!, :fifth, :forty_two, :fourth, :from, :in_groups, :in_groups_of, :including, :inquiry, :second, :second_to_last, :split, :sum, :third, :third_to_last, :to, :to_default_s, :to_formatted_s, :to_param, :to_query, :to_s, :to_sentence, :to_xml]
  # |> RUtils.required_functions([List, RList.Ruby, REnum])
  # × as_json
  # × deep_dup
  # ✔ fifth
  # ✔ forty_two
  # ✔ fourth
  # ✔ from
  # ✔ in_groups
  # ✔ in_groups_of
  # inquiry
  # ✔ second
  # ✔ second_to_last
  # ✔ third
  # ✔ third_to_last
  # ✔ to
  # ✔ to_default_s
  # × to_formatted_s
  # to_param
  # to_query
  # ✔ to_sentence
  # to_xml

  @doc """
  Returns the tail of the list from position.
  ## Examples
      iex> ~w[a b c d]
      iex> |> RList.from(0)
      ["a", "b", "c", "d"]

      iex> ~w[a b c d]
      iex> |> RList.from(2)
      ["c", "d"]

      iex> ~w[a b c d]
      iex> |> RList.from(10)
      []

      iex> ~w[]
      iex> |> RList.from(0)
      []

      iex> ~w[a b c d]
      iex> |> RList.from(-2)
      ["c", "d"]

      iex> ~w[a b c d]
      iex> |> RList.from(-10)
      []
  """
  @spec from(list(), integer()) :: list()
  def from(list, position) do
    list
    |> Enum.slice(position..Enum.count(list))
  end

  @doc """
  Returns the beginning of the list up to position.
  ## Examples
      iex> ~w[a b c d]
      iex> |> RList.to(0)
      ["a"]

      iex> ~w[a b c d]
      iex> |> RList.to(2)
      ["a", "b", "c"]

      iex> ~w[a b c d]
      iex> |> RList.to(10)
      ["a", "b", "c", "d"]

      iex> ~w[]
      iex> |> RList.to(0)
      []

      iex> ~w[a b c d]
      iex> |> RList.to(-2)
      ["a", "b", "c"]

      iex> ~w[a b c d]
      iex> |> RList.to(-10)
      []
  """
  @spec to(list(), integer()) :: list()
  def to(list, position) do
    list
    |> Enum.slice(0..position)
  end

  @doc """
  Equal to `Enum.at(list, 1)`.
  ## Examples
      iex> ~w[a b c d]
      iex> |> RList.second()
      "b"
  """
  @spec second(list()) :: any()
  def second(list) do
    Enum.at(list, 1)
  end

  @doc """
  Equal to `Enum.at(list, 2)`.
  ## Examples
      iex> ~w[a b c d]
      iex> |> RList.third()
      "c"
  """
  @spec third(list()) :: any()
  def third(list) do
    Enum.at(list, 2)
  end

  @doc """
  Equal to `Enum.at(list, 3)`.
  ## Examples
      iex> ~w[a b c d]
      iex> |> RList.fourth()
      "d"
  """
  @spec fourth(list()) :: any()
  def fourth(list) do
    Enum.at(list, 3)
  end

  @doc """
  Equal to `Enum.at(list, 4)`.
  ## Examples
      iex> ~w[a b c d e]
      iex> |> RList.fifth()
      "e"
  """
  @spec fifth(list()) :: any()
  def fifth(list) do
    Enum.at(list, 4)
  end

  @doc """
  Equal to `Enum.at(list, 41)`. Also known as accessing "the reddit".
  ## Examples
      iex> 1..42
      iex> |> RList.forty_two()
      42
  """
  @spec forty_two(list()) :: any()
  def forty_two(list) do
    Enum.at(list, 41)
  end

  @doc """
  Equal to `Enum.at(list, -2)`.
  ## Examples
      iex> ~w[a b c d e]
      iex> |> RList.second_to_last()
      "d"
  """
  @spec second_to_last(list()) :: any()
  def second_to_last(list) do
    Enum.at(list, -2)
  end

  @doc """
  Equal to `Enum.at(list, -3)`.
  ## Examples
      iex> ~w[a b c d e]
      iex> |> RList.third_to_last()
      "c"
  """
  @spec third_to_last(list()) :: any()
  def third_to_last(list) do
    Enum.at(list, -3)
  end

  @doc """
  Converts the list to a comma-separated sentence where the last element is joined by the connector word.

  You can pass the following options to change the default behavior. If you pass an option key that doesn't exist in the list below, it will raise an

  ** Options **
  * `:words_connector` - The sign or word used to join all but the last
    element in lists with three or more elements (default: ", ").
  * `:last_word_connector` - The sign or word used to join the last element
    in lists with three or more elements (default: ", and ").
  * `:two_words_connector` - The sign or word used to join the elements
    in lists with two elements (default: " and ").

  ## Examples
      iex> ["one", "two"]
      iex> |> RList.to_sentence()
      "one and two"

      iex> ["one", "two", "three"]
      iex> |> RList.to_sentence()
      "one, two, and three"

      iex> ["one", "two"]
      iex> |> RList.to_sentence(two_words_connector: "-")
      "one-two"

      iex> ["one", "two", "three"]
      iex> |> RList.to_sentence(words_connector: " or ", last_word_connector: " or at least ")
      "one or two or at least three"

      iex> ["one", "two", "three"]
      iex> |> RList.to_sentence()
      "one, two, and three"
  """
  @spec to_sentence(list(), list(keyword()) | nil) :: String.t()
  def to_sentence(list, opts \\ []) do
    words_connector = Keyword.get(opts, :words_connector) || ", "
    two_words_connector = Keyword.get(opts, :two_words_connector) || " and "
    last_word_connector = Keyword.get(opts, :last_word_connector) || ", and "

    case Enum.count(list) do
      0 -> ""
      1 -> "#{Enum.at(list, 0)}"
      2 -> "#{Enum.at(list, 0)}#{two_words_connector}#{Enum.at(list, 1)}"
      _ -> "#{to(list, -2) |> Enum.join(words_connector)}#{last_word_connector}#{List.last(list)}"
    end
  end

  @doc """
  Splits or iterates over the list in number of groups, padding any remaining slots with fill_with unless it is false.
  ## Examples
      iex> ~w[1 2 3 4 5 6 7 8 9 10]
      iex> |> RList.in_groups(3)
      [
        ["1", "2", "3", "4"],
        ["5", "6", "7", nil],
        ["8", "9", "10", nil]
      ]

      iex> ~w[1 2 3 4 5 6 7 8 9 10]
      iex> |> RList.in_groups(3, " ")
      [
        ["1", "2", "3", "4"],
        ["5", "6", "7", " "],
        ["8", "9", "10", " "]
      ]

      iex> ~w[1 2 3 4 5 6 7]
      iex> |> RList.in_groups(3, false)
      [
        ["1", "2", "3"],
        ["4", "5"],
        ["6", "7"]
      ]
  """
  @spec in_groups(list(), non_neg_integer(), any() | nil) :: list()
  def in_groups(list, number, fill_with \\ nil) do
    division = div(Enum.count(list), number)
    modulo = rem(Enum.count(list), number)
    range = 0..(number - 1)

    length_list =
      range
      |> Enum.map(&(division + if(modulo > 0 && modulo > &1, do: 1, else: 0)))
      |> IO.inspect()

    range
    |> Enum.reduce([], fn index, acc ->
      length = length_list |> Enum.at(index)

      group =
        Enum.slice(
          list,
          length_list
          |> Enum.take(index)
          |> Enum.sum(),
          length
        )

      if fill_with != false && modulo > 0 && length == division do
        acc ++ [group ++ [fill_with]]
      else
        acc ++ [group]
      end
    end)
  end

  @doc """
  Splits or iterates over the list in groups of size number, padding any remaining slots with fill_with unless it is +false+.
  ## Examples
      iex> ~w[1 2 3 4 5 6 7 8 9 10]
      iex> |> RList.in_groups_of(3)
      [
        ["1", "2", "3"],
        ["4", "5", "6"],
        ["7", "8", "9"],
        ["10", nil, nil]
      ]

      iex> ~w[1 2 3 4 5]
      iex> |> RList.in_groups_of(2, " ")
      [
        ["1", "2"],
        ["3", "4"],
        ["5", " "]
      ]

      iex> ~w[1 2 3 4 5]
      iex> |> RList.in_groups_of(2, false)
      [
        ["1", "2"],
        ["3", "4"],
        ["5"]
      ]
  """
  @spec in_groups_of(list(), non_neg_integer(), any() | nil) :: list()
  def in_groups_of(list, number, fill_with \\ nil) do
    if(fill_with == false) do
      list
    else
      padding = rem(number - rem(Enum.count(list), number), number)
      list ++ Support.new(fill_with, padding)
    end
    |> REnum.each_slice(number)
    |> Enum.to_list()
  end

  defdelegate to_default_s(list), to: Kernel, as: :inspect
end