defmodule ServerProcess do def start(callback_module) do spawn(fn -> initial_state = callback_module.init() loop(callback_module, initial_state) end) end defp loop(callback_module, current_state) do receive do {:call, request, caller} -> {response, new_state} = callback_module.handle_call(request, current_state) send(caller, {:response, response}) loop(callback_module, new_state) {:cast, request} -> new_state = callback_module.handle_cast(request, current_state) loop(callback_module, new_state) end end def call(pid, message) do send(pid, {:call, message, self()}) receive do {:response, response} -> response end end def cast(pid, message) do send(pid, {:cast, message}) nil end end defmodule TodoServer do def init(entries) do TodoList.new(entries) end def handle_call({:entries, date}, todo_list) do entries = TodoList.entries(todo_list, date) {entries, todo_list} end def handle_call(invalid_message, todo_list) do IO.puts("Invalid message: #{IO.inspect(invalid_message)}") todo_list end def handle_cast({:add, entry}, todo_list) do TodoList.add(todo_list, entry) end def handle_cast({:update, id, update_fun}, todo_list) do TodoList.update(todo_list, id, update_fun) end def handle_cast({:delete, id}, todo_list) do TodoList.delete(todo_list, id) end def handle_cast(invalid_message, todo_list) do IO.puts("Invalid message: #{IO.inspect(invalid_message)}") todo_list end def add(pid, entry) do ServerProcess.cast(pid, {:add, entry}) end def entries(pid, date) do ServerProcess.call(pid, {:entries, date}) end def update(pid, id, update_fun) do ServerProcess.cast(pid, {:update, id, update_fun}) end def delete(pid, id) do ServerProcess.cast(pid, {:delete, id}) end end defmodule TodoList do defstruct entries: %{}, next_id: 1 def new(entries \\ []) do Enum.reduce(entries, %__MODULE__{}, &add(&2, &1)) end def add( %__MODULE__{entries: entries, next_id: next_id} = todo_list, %{date: _, title: _} = entry ) do new_entry = Map.put(entry, :id, next_id) new_entries = Map.put(entries, next_id, new_entry) %__MODULE__{todo_list | entries: new_entries, next_id: next_id + 1} end def entries(%__MODULE__{} = todo_list, date) do todo_list.entries |> Map.filter(fn {_, entry} -> entry.date == date end) |> Map.values() end def update(%__MODULE__{entries: entries} = todo_list, id, update_fun) when is_function(update_fun, 1) do case Map.fetch(entries, id) do :error -> todo_list {:ok, entry} -> new_entry = update_fun.(entry) new_entries = Map.put(entries, id, new_entry) %__MODULE__{todo_list | entries: new_entries} end end def delete(%__MODULE__{entries: entries} = todo_list, id) when is_number(id) do new_entries = Map.delete(entries, id) %__MODULE__{todo_list | entries: new_entries} end end defmodule TodoList.CSVImporter do def import(path) do path |> File.stream!() |> Stream.map(&parse_line/1) |> TodoList.new() end defp parse_line(line) do line |> String.trim() |> String.split(",") |> create_entry() end defp create_entry([date, title]) do final_date = parse_date(date) %{date: final_date, title: title} end defp parse_date(string) do [year, month, day] = string |> String.split("-") |> Enum.map(&String.to_integer/1) Date.new!(year, month, day) end end