feat!: make a project using mix
This commit is contained in:
parent
323d35cad2
commit
222968c495
11 changed files with 301 additions and 139 deletions
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Todo
|
||||||
|
|
||||||
|
**TODO: Add description**
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||||
|
by adding `todo` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
def deps do
|
||||||
|
[
|
||||||
|
{:todo, "~> 0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||||
|
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
||||||
|
be found at <https://hexdocs.pm/todo>.
|
||||||
|
|
29
lib/todo/csv_importer.ex
Normal file
29
lib/todo/csv_importer.ex
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
defmodule Todo.CSVImporter do
|
||||||
|
alias Todo.List
|
||||||
|
|
||||||
|
def import(path) do
|
||||||
|
path
|
||||||
|
|> File.stream!()
|
||||||
|
|> Stream.map(&parse_line/1)
|
||||||
|
|> List.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
|
43
lib/todo/list.ex
Normal file
43
lib/todo/list.ex
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule Todo.List 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
|
67
lib/todo/server.ex
Normal file
67
lib/todo/server.ex
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
defmodule Todo.Server do
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def init(%_{} = todo_list) do
|
||||||
|
{:ok, todo_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def init(entries) do
|
||||||
|
{:ok, Todo.List.new(entries)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def handle_call({:entries, date}, _from, todo_list) do
|
||||||
|
entries = Todo.List.entries(todo_list, date)
|
||||||
|
|
||||||
|
{:reply, entries, todo_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def handle_cast({:add, entry}, todo_list) do
|
||||||
|
new_todo_list = Todo.List.add(todo_list, entry)
|
||||||
|
|
||||||
|
{:noreply, new_todo_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def handle_cast({:update, id, update_fun}, todo_list) do
|
||||||
|
new_todo_list = Todo.List.update(todo_list, id, update_fun)
|
||||||
|
|
||||||
|
{:noreply, new_todo_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl GenServer
|
||||||
|
def handle_cast({:delete, id}, todo_list) do
|
||||||
|
new_todo_list = Todo.List.delete(todo_list, id)
|
||||||
|
|
||||||
|
{:noreply, new_todo_list}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(entries \\ [])
|
||||||
|
|
||||||
|
def start(%_{} = todo_list) do
|
||||||
|
GenServer.start(__MODULE__, todo_list)
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(entries) do
|
||||||
|
GenServer.start(__MODULE__, entries)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add(pid, entry) do
|
||||||
|
GenServer.cast(pid, {:add, entry})
|
||||||
|
end
|
||||||
|
|
||||||
|
def entries(pid, date) do
|
||||||
|
GenServer.call(pid, {:entries, date})
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(pid, id, update_fun) do
|
||||||
|
GenServer.cast(pid, {:update, id, update_fun})
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(pid, id) do
|
||||||
|
GenServer.cast(pid, {:delete, id})
|
||||||
|
end
|
||||||
|
end
|
28
mix.exs
Normal file
28
mix.exs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Todo.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :todo,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.17",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ExUnit.start()
|
19
test/todo/csvimporter_test.exs
Normal file
19
test/todo/csvimporter_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Todo.TestCSVImporter do
|
||||||
|
use ExUnit.Case
|
||||||
|
alias Todo.CSVImporter
|
||||||
|
alias Todo.List
|
||||||
|
|
||||||
|
test "simple correct csv" do
|
||||||
|
file_path = Path.join(__DIR__, "./files/todo_list.csv")
|
||||||
|
list = CSVImporter.import(file_path)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-12-19], title: "Dentist"},
|
||||||
|
%{date: ~D[2024-12-19], title: "Movies"}
|
||||||
|
] = List.entries(list, ~D[2024-12-19])
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-12-20], title: "Shopping"}
|
||||||
|
] = List.entries(list, ~D[2024-12-20])
|
||||||
|
end
|
||||||
|
end
|
89
test/todo/list_test.exs
Normal file
89
test/todo/list_test.exs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
defmodule Todo.TestList do
|
||||||
|
use ExUnit.Case
|
||||||
|
alias Todo.List
|
||||||
|
|
||||||
|
test "simple usage" do
|
||||||
|
list = List.new()
|
||||||
|
assert [] = List.entries(list, ~D[2024-08-04])
|
||||||
|
|
||||||
|
list = List.add(list, %{date: ~D[2024-08-04], title: "Write some Elixir"})
|
||||||
|
|
||||||
|
assert [%{date: ~D[2024-08-04], title: "Write some Elixir"}] =
|
||||||
|
List.entries(list, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "id starts at 1 and increases by 1" do
|
||||||
|
list = List.new()
|
||||||
|
list = List.add(list, %{date: ~D[2024-08-04], title: "Write some Elixir"})
|
||||||
|
|
||||||
|
assert [%{date: ~D[2024-08-04], title: "Write some Elixir", id: 1}] =
|
||||||
|
List.entries(list, ~D[2024-08-04])
|
||||||
|
|
||||||
|
list = List.add(list, %{date: ~D[2024-08-04], title: "Write more Elixir"})
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir", id: 1},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir", id: 2}
|
||||||
|
] =
|
||||||
|
List.entries(list, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "new/1 has expected elements" do
|
||||||
|
list =
|
||||||
|
List.new([
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
] =
|
||||||
|
List.entries(list, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "entries returns correct dates" do
|
||||||
|
list =
|
||||||
|
List.new([
|
||||||
|
%{date: ~D[2024-08-03], title: "Prepare writing Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"},
|
||||||
|
%{date: ~D[2024-08-05], title: "Pause writing Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert [%{date: ~D[2024-08-04]}, %{date: ~D[2024-08-04]}] = List.entries(list, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update/3 updates selected entry" do
|
||||||
|
list =
|
||||||
|
List.new([
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
])
|
||||||
|
|
||||||
|
updated =
|
||||||
|
List.update(list, 1, fn %{title: title} = entry ->
|
||||||
|
%{entry | title: String.upcase(title)}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-08-04], title: "WRITE SOME ELIXIR"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
] =
|
||||||
|
List.entries(updated, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete/2" do
|
||||||
|
list =
|
||||||
|
List.new([
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"},
|
||||||
|
%{date: ~D[2024-08-04], title: "Write more Elixir"}
|
||||||
|
])
|
||||||
|
|
||||||
|
new_list = List.delete(list, 2)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{date: ~D[2024-08-04], title: "Write some Elixir"}
|
||||||
|
] = List.entries(new_list, ~D[2024-08-04])
|
||||||
|
end
|
||||||
|
end
|
139
todo_list.ex
139
todo_list.ex
|
@ -1,139 +0,0 @@
|
||||||
defmodule TodoServer do
|
|
||||||
use GenServer
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def init(%_{} = todo_list) do
|
|
||||||
{:ok, todo_list}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def init(entries) do
|
|
||||||
{:ok, TodoList.new(entries)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def handle_call({:entries, date}, _from, todo_list) do
|
|
||||||
entries = TodoList.entries(todo_list, date)
|
|
||||||
|
|
||||||
{:reply, entries, todo_list}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def handle_cast({:add, entry}, todo_list) do
|
|
||||||
new_todo_list = TodoList.add(todo_list, entry)
|
|
||||||
|
|
||||||
{:no_reply, new_todo_list}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def handle_cast({:update, id, update_fun}, todo_list) do
|
|
||||||
new_todo_list = TodoList.update(todo_list, id, update_fun)
|
|
||||||
|
|
||||||
{:no_reply, new_todo_list}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl GenServer
|
|
||||||
def handle_cast({:delete, id}, todo_list) do
|
|
||||||
new_todo_list = TodoList.delete(todo_list, id)
|
|
||||||
|
|
||||||
{:no_reply, new_todo_list}
|
|
||||||
end
|
|
||||||
|
|
||||||
def start(entries \\ [])
|
|
||||||
|
|
||||||
def start(%_{} = todo_list) do
|
|
||||||
GenServer.start(__MODULE__, todo_list)
|
|
||||||
end
|
|
||||||
|
|
||||||
def start(entries) do
|
|
||||||
GenServer.start(__MODULE__, entries)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add(pid, entry) do
|
|
||||||
GenServer.cast(pid, {:add, entry})
|
|
||||||
end
|
|
||||||
|
|
||||||
def entries(pid, date) do
|
|
||||||
GenServer.call(pid, {:entries, date})
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(pid, id, update_fun) do
|
|
||||||
GenServer.cast(pid, {:update, id, update_fun})
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(pid, id) do
|
|
||||||
GenServer.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
|
|
Loading…
Reference in a new issue