Merge pull request #193 from marosluuce/convert-to-application

Convert to application
This commit is contained in:
Felipe Seré
2017-08-22 07:26:37 +01:00
committed by GitHub
11 changed files with 167 additions and 124 deletions

View File

@@ -8,6 +8,7 @@
* kamidev * kamidev
* Mahmut Surekci * Mahmut Surekci
* Makis Otman * Makis Otman
* Nathan Walker
* Rabea Gleissner * Rabea Gleissner
* Ria Cataquian * Ria Cataquian
* Simone D'Amico * Simone D'Amico

View File

@@ -1,9 +1,30 @@
defmodule Display do defmodule Display do
use GenServer
alias IO.ANSI alias IO.ANSI
alias Display.ProgressBar alias Display.{ProgressBar, Intro, Failure, Notifications}
alias Display.Intro
alias Display.Failure def start_link do
alias Display.Notifications GenServer.start_link(__MODULE__, %{clear_screen: true}, name: __MODULE__)
end
def disable_clear do
GenServer.cast(__MODULE__, :disable_clear)
end
def handle_cast(:disable_clear, state) do
{:noreply, %{state | clear_screen: false}}
end
def handle_cast(:clear_screen, state = %{clear_screen: true}) do
IO.puts(ANSI.clear)
IO.puts(ANSI.home)
{:noreply, state}
end
def handle_cast(:clear_screen, state) do
{:noreply, state}
end
def invalid_koan(koan, modules) do def invalid_koan(koan, modules) do
Notifications.invalid_koan(koan, modules) Notifications.invalid_koan(koan, modules)
@@ -15,17 +36,6 @@ defmodule Display do
|> IO.puts |> IO.puts
end end
def format(failure, module, name) do
"""
#{Intro.intro(module, Tracker.visited)}
Now meditate upon #{format_module(module)}
#{ProgressBar.progress_bar(Tracker.summarize)}
----------------------------------------
#{name}
#{Failure.format_failure(failure)}
"""
end
def show_compile_error(error) do def show_compile_error(error) do
Failure.show_compile_error(error) Failure.show_compile_error(error)
|> IO.puts |> IO.puts
@@ -37,10 +47,18 @@ defmodule Display do
end end
def clear_screen do def clear_screen do
if Options.clear_screen? do GenServer.cast(__MODULE__, :clear_screen)
IO.puts(ANSI.clear) end
IO.puts(ANSI.home)
end defp format(failure, module, name) do
"""
#{Intro.intro(module, Tracker.visited)}
Now meditate upon #{format_module(module)}
#{ProgressBar.progress_bar(Tracker.summarize)}
----------------------------------------
#{name}
#{Failure.format_failure(failure)}
"""
end end
defp format_module(module) do defp format_module(module) do

17
lib/elixir_koans.ex Normal file
View File

@@ -0,0 +1,17 @@
defmodule ElixirKoans do
use Application
def start(_type, _args) do
import Supervisor.Spec
children = [
worker(Display, []),
worker(Tracker, []),
worker(Runner, []),
worker(Watcher, [])
]
opts = [strategy: :one_for_one, name: ElixirKoans.Supervisor]
Supervisor.start_link(children, opts)
end
end

View File

@@ -1,30 +1,37 @@
defmodule Mix.Tasks.Meditate do defmodule Mix.Tasks.Meditate do
use Mix.Task use Mix.Task
alias Options
@shortdoc "Start the koans" @shortdoc "Start the koans"
def run(args) do def run(args) do
Application.ensure_all_started(:elixir_koans) Application.ensure_all_started(:elixir_koans)
Code.compiler_options(ignore_module_conflict: true) Code.compiler_options(ignore_module_conflict: true)
{:ok, watcher} = Watcher.start
Process.monitor(watcher)
Options.start(args) {parsed, _, _} = OptionParser.parse(args)
Options.initial_koan modules = parsed
|> initial_module
|> ok? |> ok?
|> Runner.modules_to_run |> Runner.modules_to_run
|> Tracker.start
|> Runner.run
if Tracker.complete? do Tracker.set_total(modules)
Display.congratulate Tracker.notify_on_complete(self())
exit(:normal)
end
receive do set_clear_screen(parsed)
{:DOWN, _references, :process, ^watcher, _reason} -> nil Runner.run(modules)
Tracker.wait_until_complete()
Display.congratulate()
end
defp initial_module(parsed) do
name = Keyword.get(parsed, :koan, "Equalities")
String.to_atom("Elixir."<> name)
end
defp set_clear_screen(parsed) do
if Keyword.has_key?(parsed, :no_clear_screen) do
Display.disable_clear()
end end
end end

View File

@@ -1,32 +0,0 @@
defmodule Options do
@defaults %{
clear_screen: true,
initial_koan: Equalities,
}
def start(args) do
Agent.start_link(fn -> parse(args) end, name: __MODULE__)
end
def clear_screen? do
Agent.get(__MODULE__, fn(options) ->
Map.fetch!(options, :clear_screen)
end)
end
def initial_koan() do
Agent.get(__MODULE__, fn(options) ->
Map.fetch!(options, :initial_koan)
end)
end
defp parse(args) do
Enum.reduce(args, @defaults, fn(arg, acc) ->
Map.merge(acc, parse_argument(arg))
end)
end
def parse_argument("--koan="<>module), do: %{ initial_koan: String.to_atom("Elixir."<> module)}
def parse_argument("--no-clear-screen"), do: %{ clear_screen: false}
def parse_argument(_), do: %{}
end

View File

@@ -1,4 +1,5 @@
defmodule Runner do defmodule Runner do
use GenServer
def koan?(koan) do def koan?(koan) do
Keyword.has_key?(koan.__info__(:exports), :all_koans) Keyword.has_key?(koan.__info__(:exports), :all_koans)
@@ -24,17 +25,40 @@ defmodule Runner do
String.to_integer(number) String.to_integer(number)
end end
def modules_to_run, do: Options.initial_koan |> modules_to_run
def modules_to_run(start_module), do: Enum.drop_while(modules(), &(&1 != start_module)) def modules_to_run(start_module), do: Enum.drop_while(modules(), &(&1 != start_module))
def run(modules) do def start_link do
Display.clear_screen() GenServer.start_link(__MODULE__, [], name: __MODULE__)
modules
|> Enum.take_while( &(run_module(&1) == :passed))
end end
def run_module(module) do def handle_cast({:run, modules}, _) do
flush()
send(self(), :run_modules)
{:noreply, modules}
end
def handle_info(:run_modules, []) do
{:noreply, []}
end
def handle_info(:run_modules, [module | rest]) do
Display.clear_screen()
case run_module(module) do
:passed ->
send(self(), :run_modules)
{:noreply, rest}
_ ->
{:noreply, []}
end
end
def run(modules) do
GenServer.cast(__MODULE__, {:run, modules})
end
defp run_module(module) do
module module
|> Execute.run_module(&track/3) |> Execute.run_module(&track/3)
|> display |> display
@@ -49,4 +73,11 @@ defmodule Runner do
end end
defp display(_), do: :passed defp display(_), do: :passed
defp flush do
receive do
_ -> flush()
after
0 -> :ok
end
end
end end

View File

@@ -1,22 +1,47 @@
defmodule Tracker do defmodule Tracker do
def start(modules) do alias __MODULE__
total = modules
|> Enum.flat_map(&(&1.all_koans))
|> Enum.count
Agent.start_link(fn -> %{total: total, defstruct total: 0,
koans: MapSet.new(), koans: MapSet.new(),
visited_modules: MapSet.new()} end, name: __MODULE__) visited_modules: MapSet.new(),
modules on_complete: :noop
def start_link do
Agent.start_link(fn -> %Tracker{} end, name: __MODULE__)
end end
defp get(), do: Agent.get(__MODULE__, &(&1)) def notify_on_complete(pid) do
Agent.update(__MODULE__, fn state -> %{state | on_complete: pid} end)
end
def set_total(modules) do
total = modules
|> Enum.flat_map(&(&1.all_koans))
|> Enum.count
Agent.update(__MODULE__, fn _ -> %Tracker{total: total} end)
end
def completed(module, koan) do def completed(module, koan) do
Agent.update(__MODULE__, fn(%{koans: completed, visited_modules: modules} = all) -> Agent.update(__MODULE__, &mark_koan_completed(&1, module, koan))
%{ all | koans: MapSet.put(completed, koan), if complete?() do
visited_modules: MapSet.put(modules, module)} Agent.cast(__MODULE__, fn state ->
end) send(state.on_complete, {self(), :complete})
state
end)
end
end
def wait_until_complete() do
pid = Process.whereis(Tracker)
receive do
{^pid, :complete} -> :ok
end
end
defp mark_koan_completed(state, module, koan) do
%{state | koans: MapSet.put(state.koans, koan),
visited_modules: MapSet.put(state.visited_modules, module)}
end end
def visited do def visited do
@@ -28,14 +53,13 @@ defmodule Tracker do
total == completed total == completed
end end
def summarize, do: get() |> summarize() def summarize do
defp summarize(%{total: total, state = Agent.get(__MODULE__, &(&1))
koans: completed,
visited_modules: modules}) do
%{ %{
total: total, total: state.total,
current: MapSet.size(completed), current: MapSet.size(state.koans),
visited_modules: MapSet.to_list(modules) visited_modules: MapSet.to_list(state.visited_modules)
} }
end end
end end

View File

@@ -1,14 +1,13 @@
defmodule Watcher do defmodule Watcher do
use ExFSWatch, dirs: ["lib/koans"] use ExFSWatch, dirs: ["lib/koans"]
def start_link do
start()
end
def callback(file, events) do def callback(file, events) do
if Enum.member?(events, :modified) do if Enum.member?(events, :modified) do
file |> normalize |> reload file |> normalize |> reload
if Tracker.complete? do
Display.congratulate
exit(:normal)
end
end end
end end

View File

@@ -10,7 +10,8 @@ defmodule Koans.Mixfile do
end end
def application do def application do
[applications: [:exfswatch, :logger]] [mod: {ElixirKoans, []},
applications: [:exfswatch, :logger]]
end end
defp deps do defp deps do

View File

@@ -1,23 +0,0 @@
defmodule OptionTest do
use ExUnit.Case, async: true
test "has default options" do
Options.start([])
assert Options.clear_screen?
end
test "override clearing of screen" do
Options.start(["--no-clear-screen"])
refute Options.clear_screen?
end
test "can target specifc koans" do
Options.start(["--koan=Strings"])
assert Options.initial_koan() == Strings
end
test "ignores unknown options" do
Options.start(["--foo"])
assert Options.clear_screen?
end
end

View File

@@ -4,25 +4,25 @@ defmodule TrackerTest do
@sample_modules [SampleKoan, PassingKoan] @sample_modules [SampleKoan, PassingKoan]
test "can start" do test "can start" do
Tracker.start(@sample_modules) Tracker.set_total(@sample_modules)
assert Tracker.summarize == %{total: 2, current: 0, visited_modules: []} assert Tracker.summarize == %{total: 2, current: 0, visited_modules: []}
end end
test "can be notified of completed koans" do test "can be notified of completed koans" do
Tracker.start(@sample_modules) Tracker.set_total(@sample_modules)
Tracker.completed(SampleKoan, :"Hi there") Tracker.completed(SampleKoan, :"Hi there")
assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]} assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]}
end end
test "multiple comletions of the same koan count only once" do test "multiple comletions of the same koan count only once" do
Tracker.start(@sample_modules) Tracker.set_total(@sample_modules)
Tracker.completed(SampleKoan, :"Hi there") Tracker.completed(SampleKoan, :"Hi there")
Tracker.completed(SampleKoan, :"Hi there") Tracker.completed(SampleKoan, :"Hi there")
assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]} assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]}
end end
test "knows when koans are not complete" do test "knows when koans are not complete" do
Tracker.start(@sample_modules) Tracker.set_total(@sample_modules)
refute Tracker.complete? refute Tracker.complete?
end end
end end