defmodule Iter.Iterable.WhileChunker do defstruct iterable: nil, acc: nil, chunk_fun: nil, after_fun: nil @moduledoc """ An iterable that chunks based on a chunk function. """ alias Iter.{Impl, IntoIterable, Iterable} @type acc :: any @type chunk :: any @type t :: %__MODULE__{ iterable: Iterable.t(), acc: nil, chunk_fun: (Iterable.element(), acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}), after_fun: (acc -> {:cont, chunk, acc} | {:cont, acc}) } @doc """ Creates an iterable that chunks based on a chunk function. """ @spec new( Iterable.t(), any, (Iterable.element(), acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}), (acc -> {:cont, chunk, acc} | {:cont, acc}) ) :: t def new(iterable, acc, chunk_fun, after_fun) when is_function(chunk_fun, 2) and is_function(after_fun, 1), do: %__MODULE__{iterable: iterable, acc: acc, chunk_fun: chunk_fun, after_fun: after_fun} defimpl Iterable do use Impl @doc false @impl true def next(chunk_while) do case Iterable.next(chunk_while.iterable) do {:ok, element, iterable} -> handle_chunk_fun(chunk_while, element, iterable) :done -> handle_after_fun(chunk_while, chunk_while.acc) end end defp handle_chunk_fun(chunk_while, element, iterable) do case chunk_while.chunk_fun.(element, chunk_while.acc) do {:cont, chunk, acc} -> {:ok, chunk, %{chunk_while | acc: acc, iterable: iterable}} {:cont, acc} -> next(%{chunk_while | acc: acc, iterable: iterable}) {:halt, acc} -> handle_after_fun(chunk_while, acc) end end defp handle_after_fun(chunk_while, acc) do case chunk_while.after_fun.(acc) do {:cont, chunk, _acc} -> {:ok, chunk, Iterable.Empty.new()} {:cont, _acc} -> :done end end end defimpl IntoIterable do @doc false def into_iterable(self), do: self end end