Merge pull request #80 from elixirkoans/add-a-progress-bar
Add a progress bar
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
25
lib/tracker.ex
Normal 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
|
||||
@@ -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
19
test/display_test.exs
Normal 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
23
test/tracker_test.exs
Normal 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
|
||||
Reference in New Issue
Block a user