Packages

fnord

0.8.25

AI code archaeology

Current section

Files

Jump to
fnord lib task_server.ex
Raw

lib/task_server.ex

defmodule TaskServer do
defstruct [
:next_id,
:lists
]
@type task :: %{
id: non_neg_integer | binary,
outcome: :todo | :done | :failed,
data: any,
result: any
}
@type t :: %__MODULE__{
next_id: non_neg_integer,
lists: %{
non_neg_integer => list(task)
}
}
use GenServer
# ----------------------------------------------------------------------------
# Client API
# ----------------------------------------------------------------------------
@spec start_link(any) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@spec start_list() :: non_neg_integer
def start_list() do
GenServer.call(__MODULE__, :start_list)
end
@spec get_list(non_neg_integer) :: list(task) | {:error, :not_found}
def get_list(list_id) do
GenServer.call(__MODULE__, {:get_list, list_id})
end
@spec add_task(non_neg_integer, binary, any) :: :ok
def add_task(list_id, task_id, task_data) do
GenServer.cast(__MODULE__, {:add_task, list_id, task_id, task_data})
end
@spec complete_task(non_neg_integer, binary, any) :: :ok
def complete_task(list_id, task_id, result) do
GenServer.cast(__MODULE__, {:complete_task, list_id, task_id, :done, result})
end
@spec fail_task(non_neg_integer, binary, any) :: :ok
def fail_task(list_id, task_id, msg) do
GenServer.cast(__MODULE__, {:complete_task, list_id, task_id, :failed, msg})
end
@spec as_string(non_neg_integer | list(task), boolean) :: binary
def as_string(subject, detail? \\ false)
def as_string(list_id, detail?) when is_integer(list_id) do
list_id
|> get_list()
|> case do
{:error, :not_found} -> "List #{list_id} not found"
tasks -> as_string(tasks, detail?)
end
end
def as_string([], _), do: ""
def as_string([task | tasks], detail?) do
as_string(task, detail?) <> "\n" <> as_string(tasks, detail?)
end
def as_string(%{id: id, outcome: :todo}, _), do: "- [ ] #{id}"
def as_string(%{id: id, outcome: :done}, false), do: "- [✓] #{id}"
def as_string(%{id: id, outcome: :failed}, false), do: "- [✗] #{id}"
def as_string(%{id: id, outcome: :done, result: result}, true) do
"- [✓] #{id}: #{result}"
end
def as_string(%{id: id, outcome: :failed, result: result}, true) do
"- [✗] #{id}: #{result}"
end
# ----------------------------------------------------------------------------
# Server Callbacks
# ----------------------------------------------------------------------------
def init(_) do
{:ok, %__MODULE__{next_id: 1, lists: %{}}}
end
def handle_call(:start_list, _from, %{lists: lists, next_id: next_id} = state) do
{:reply, next_id,
%{
state
| lists: Map.put(lists, next_id, []),
next_id: next_id + 1
}}
end
def handle_call({:get_list, list_id}, _from, state) do
state.lists
|> Map.get(list_id)
|> case do
nil ->
{:reply, {:error, :not_found}, state}
tasks ->
tasks
|> Enum.reverse()
|> then(&{:reply, &1, state})
end
end
def handle_cast({:add_task, list_id, task_id, task_data}, state) do
# does not create lists on demand: ignore invalid list IDs
case Map.fetch(state.lists, list_id) do
:error ->
{:noreply, state}
{:ok, tasks} ->
task = %{
id: task_id,
data: task_data,
outcome: :todo,
result: nil
}
new_list = [task | tasks]
{:noreply, %{state | lists: Map.put(state.lists, list_id, new_list)}}
end
end
def handle_cast({:complete_task, list_id, task_id, outcome, result}, state) do
state
|> Map.get(:lists, %{})
|> Map.fetch(list_id)
|> case do
:error ->
{:noreply, state}
{:ok, tasks} ->
tasks
|> Enum.map(fn
%{id: ^task_id} = task -> %{task | outcome: outcome, result: result}
task -> task
end)
|> then(&{:noreply, %{state | lists: Map.put(state.lists, list_id, &1)}})
end
end
end