Merge pull request #80 from elixirkoans/add-a-progress-bar

Add a progress bar
This commit is contained in:
Uku Taht
2016-04-26 23:28:44 +01:00
8 changed files with 128 additions and 26 deletions

View File

@@ -2,6 +2,7 @@ defmodule Display do
alias IO.ANSI
@current_dir File.cwd!
@no_value :ex_unit_no_meaningful_value
@progress_bar_length 30
def invalid_koan(koan, modules) do
koans_names = module_names(modules)
@@ -22,21 +23,36 @@ defmodule Display do
def show_failure(failure, module, name) do
IO.puts("Now meditate upon #{format_module(module)}")
IO.puts("---------------------------------------")
IO.puts(progress_bar(Tracker.get))
IO.puts(bar())
IO.puts(name)
IO.puts(format_failure(failure))
end
defp bar() do
"----------------------------------------"
end
def progress_bar(%{current: current, total: total}) do
arrow = caluculate_progress(current, total) |> build_arrow
"|" <> String.ljust(arrow, @progress_bar_length) <> "| #{current} of #{total}"
end
defp caluculate_progress(current, total) do
round( (current/total) * @progress_bar_length)
end
defp build_arrow(0), do: ""
defp build_arrow(length) do
String.duplicate("=", length-1) <> ">"
end
def show_compile_error(error) do
IO.puts("")
format_error(error) |> IO.puts
end
def considering(module) do
IO.puts("Considering #{format_module(module)}...")
module
end
def clear_screen do
if Options.clear_screen? do
IO.puts(ANSI.clear)
@@ -50,14 +66,9 @@ defmodule Display do
#{format_red(message)}
"""
end
defp format_failure(%{error: %ExUnit.AssertionError{expr: expr}, file: file, line: line}) do
"""
#{format_cyan("Assertion failed in #{file}:#{line}")}
#{format_red(Macro.to_string(expr))}
"""
format_assertion_error(expr, file, line)
end
defp format_failure(%{error: error, file: file, line: line}) do
"""
#{format_cyan("Error in #{file}:#{line}")}
@@ -65,6 +76,14 @@ defmodule Display do
"""
end
defp format_assertion_error(error, file, line) do
"""
#{format_cyan("Assertion failed in #{file}:#{line}")}
#{format_red(Macro.to_string(error))}
"""
end
defp format_error(error) do
trace = System.stacktrace |> Enum.take(2)
format_red(Exception.format(:error, error, trace))

View File

@@ -1,12 +1,18 @@
defmodule Execute do
def run_module(module) do
def run_module(module, callback \\ fn(_result, _koan) -> nil end) do
Enum.reduce_while(module.all_koans, :passed, fn(koan, _) ->
module
|> run_koan(koan)
|> hook(koan, callback)
|> continue?
end)
end
defp hook(result,koan, callback) do
callback.(result, koan)
result
end
defp continue?(:passed), do: {:cont, :passed}
defp continue?(result), do: {:halt, result}

View File

@@ -8,9 +8,22 @@ defmodule Mix.Tasks.Meditate do
Watcher.start
Options.start(args)
Runner.run
Options.initial_koan
|> ok?
|> Runner.modules_to_run
|> Tracker.start
|> Runner.run
:timer.sleep(:infinity)
end
defp ok?(koan) do
if Runner.koan?(koan) do
koan
else
Display.invalid_koan(koan, Runner.modules)
end
end
end

View File

@@ -15,31 +15,27 @@ defmodule Runner do
]
def koan?(koan), do: Enum.member?(@modules, koan)
def modules, do: @modules
def run do
Options.initial_koan
|>run
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 run(start_module) when start_module in @modules, do: run(start_module, @modules)
def run(start_module), do: Display.invalid_koan(start_module, @modules)
def run(start_module, modules) do
def run(modules) do
Display.clear_screen()
modules
|> Enum.drop_while( &(&1 != start_module))
|> Enum.take_while( &(run_module(&1) == :passed))
end
def run_module(module) do
module
|> Display.considering
|> Execute.run_module
|> Execute.run_module(&track/2)
|> display
end
defp track(:passed, koan), do: Tracker.completed(koan)
defp track(_, _), do: nil
defp display({:failed, error, module, name}) do
Display.show_failure(error, module, name)
:failed

25
lib/tracker.ex Normal file
View File

@@ -0,0 +1,25 @@
defmodule Tracker do
def start(modules) do
total = modules
|> Enum.flat_map(&(&1.all_koans))
|> Enum.count
Agent.start_link(fn -> {total, MapSet.new()} end, name: __MODULE__)
modules
end
def get do
Agent.get(__MODULE__, &(&1))
|> summarize
end
def completed(koan) do
Agent.update(__MODULE__, fn({total, completed}) ->
{total, MapSet.put(completed, koan)}
end)
end
def summarize({total, completed}) do
%{total: total, current: MapSet.size(completed)}
end
end

View File

@@ -7,6 +7,7 @@ defmodule Watcher do
Code.load_file(file)
|> Enum.map(&(elem(&1, 0)))
|> Enum.find(&Runner.koan?/1)
|> Runner.modules_to_run
|> Runner.run
rescue
e -> Display.show_compile_error(e)

19
test/display_test.exs Normal file
View File

@@ -0,0 +1,19 @@
defmodule DisplayTest do
use ExUnit.Case
test "empty bar" do
bar = Display.progress_bar(%{total: 12, current: 0})
assert bar == "| | 0 of 12"
end
test "puts counter on the right until half the koans are complete" do
bar = Display.progress_bar(%{total: 12, current: 3})
assert bar == "|=======> | 3 of 12"
end
test "full bar" do
bar = Display.progress_bar(%{total: 12, current: 12})
assert bar == "|=============================>| 12 of 12"
end
end

23
test/tracker_test.exs Normal file
View File

@@ -0,0 +1,23 @@
defmodule TrackerTest do
use ExUnit.Case
@sample_modules [SampleKoan, PassingKoan]
test "can start" do
Tracker.start(@sample_modules)
assert Tracker.get == %{total: 2, current: 0}
end
test "can be notified of completed koans" do
Tracker.start(@sample_modules)
Tracker.completed(:"Hi there")
assert Tracker.get == %{total: 2, current: 1}
end
test "multiple comletions of the same koan count only once" do
Tracker.start(@sample_modules)
Tracker.completed(:"Hi there")
Tracker.completed(:"Hi there")
assert Tracker.get == %{total: 2, current: 1}
end
end