lib/chaperon/action/loop.ex

defmodule Chaperon.Action.Loop do
  @moduledoc """
  Action that executes a given `Chaperon.Actionable` repeatedly for a given
  `duration`.
  """

  defstruct action: nil,
            duration: 0,
            started: nil

  @type duration :: non_neg_integer
  @type t :: %Chaperon.Action.Loop{
          action: Chaperon.Actionable,
          duration: duration,
          started: DateTime.t()
        }
end

defimpl Chaperon.Actionable, for: Chaperon.Action.Loop do
  def run(loop = %{started: nil}, session) do
    %{loop | started: DateTime.utc_now()}
    |> run(session)
  end

  def run(loop = %{action: a, duration: d}, session) do
    now = DateTime.utc_now() |> DateTime.to_unix(:millisecond)
    s = loop.started |> DateTime.to_unix(:millisecond)

    if now - s > d do
      {:ok, _, session} = loop |> abort(session)
      {:ok, session}
    else
      with {:ok, session} <- Chaperon.Actionable.run(a, session) do
        run(loop, session)
      end
    end
  end

  def abort(loop, session) do
    {:ok, %{loop | started: nil}, session}
  end
end

defimpl String.Chars, for: Chaperon.Action.Loop do
  def to_string(%{action: action, duration: duration}) do
    "Loop[#{action}, #{duration}]"
  end
end