Current section

Files

Jump to
todo lib todo.ex
Raw

lib/todo.ex

defmodule TODO do
@moduledoc File.read!(__DIR__ <> "/../README.md")
def config(key, default \\ nil) do
case Application.get_env(:todo, key) do
nil -> default
x -> x
end
end
defmacro __using__(opts) do
print_conf = config(:print, :overdue)
persist = config(:persist, false)
quote do
persist = unquote(persist) || unquote(opts[:persist])
Module.register_attribute(__MODULE__, :todo, accumulate: true, persist: persist)
@before_compile unquote(__MODULE__)
@todo_version Mix.Project.config[:version]
@todo_print_conf (case {Keyword.get(unquote(opts), :print), unquote(print_conf)} do
{:all,_} -> :all
{_,:all} -> :all
_ -> :overdue
end)
defmacrop todo(items) do
# TODO.print_todos(__MODULE__, items, @todo_version, @todo_print_conf)
Module.put_attribute(__MODULE__, :todo, items)
end
end
end
defmacro __before_compile__(env) do
app_version = Module.get_attribute(env.module, :todo_version)
print_conf = Module.get_attribute(env.module, :todo_print_conf)
Module.get_attribute(env.module, :todo)
|> output_todos(env.module, app_version, print_conf)
end
def output_todos(todos, module, app_version, print_conf) do
case todos do
[] -> nil
nil -> nil
items ->
[items]
|> List.flatten
|> Enum.map(&wrap_unversionned/1)
|> Enum.sort(&sort_todos/2)
|> Enum.reduce([], &group_todos/2)
|> Enum.map(fn({version, ts}) -> {version, Enum.reverse ts} end)
|> Enum.reverse
|> format_todos(app_version, print_conf)
|> (fn(x) -> [format_module(module), x] end).()
|> IO.puts
end
end
# put unversionned last
def wrap_unversionned(x={_version, _message}), do: x
def wrap_unversionned(message), do: {:any, message}
def sort_todos({_,_}, {:any, _}), do: true
def sort_todos({:any, _}, {_,_}), do: false
def sort_todos({v1,_}, {v2,_}), do: v1 < v2
def group_todos({same_version, t}, [{same_version,ts}|rest]) do
result = [{same_version,[t|ts]}|rest]
result
end
def group_todos(item, {version,t}) do
# first accumulator is not a list, it's a todo item, wrap and redo
group_todos(item, [{version, [t]}])
end
def group_todos({version, t}, rest) do
[{version,[t]}|rest]
end
def format_todos([], _, _) do
[]
end
def format_todos([{version, ts}|rest], app_version, print_conf) do
current = case display_mode(version, app_version, print_conf) do
:ignore -> []
:info -> [format_version(version), format_messages(ts)]
:warn -> IO.ANSI.format(yellow([format_version(version), Enum.map(ts, &format_message/1)]))
end
[current|format_todos(rest, app_version, print_conf)]
end
def format_module(atom) do
["@todos found in module ", to_string atom]
end
def format_version(:any) do
format_version("Other")
end
def format_version(version) do
["\n * ", to_string(version), " :"]
end
def format_messages([message|[]]) do
# A single message => same line
[" ", message]
end
def format_messages(messages) do
Enum.map(messages, &(["\n - ", &1]))
end
def format_message(message) do
end
def display_mode(:any, _, _) do
:info
end
def display_mode(version, app_version, print_conf) do
version = to_string version
case {Version.compare(version, app_version), print_conf} do
{:gt, :all} ->
# Version when the feature is required is greater than the current
# version, it's not overdue, but we're asked to print all.
:info
{:gt, :overdue} ->
# Same case, but we must print only overdue items, so no-print
:ignore
_ ->
# App version is higher than feature version. The feature is due for a
# past version. This is not good.
:warn
end
end
defp yellow(message) do
[:yellow, message]
end
end