diff --git a/lib/display.ex b/lib/display.ex index c0d2b65..e79ab8b 100644 --- a/lib/display.ex +++ b/lib/display.ex @@ -26,6 +26,7 @@ defmodule Display do def format(failure, module, name) do """ + #{intro(module, Tracker.visited)} Now meditate upon #{format_module(module)} #{progress_bar(Tracker.summarize)} ---------------------------------------- @@ -34,6 +35,19 @@ defmodule Display do """ end + defp intro(module, modules) do + if not module in modules do + show_intro(module.intro) + end + end + + defp show_intro(""), do: nil + defp show_intro(message) do + message <> "\n" + |> Colours.green + |> IO.puts + end + def progress_bar(%{current: current, total: total}) do arrow = caluculate_progress(current, total) |> build_arrow diff --git a/lib/execute.ex b/lib/execute.ex index 2af0a5e..be98273 100644 --- a/lib/execute.ex +++ b/lib/execute.ex @@ -1,15 +1,15 @@ defmodule Execute do - def run_module(module, callback \\ fn(_result, _koan) -> nil end) do + def run_module(module, callback \\ fn(_result, _module, _koan) -> nil end) do Enum.reduce_while(module.all_koans, :passed, fn(koan, _) -> module |> run_koan(koan) - |> hook(koan, callback) + |> hook(module, koan, callback) |> continue? end) end - defp hook(result,koan, callback) do - callback.(result, koan) + defp hook(result, module, koan, callback) do + callback.(result, module, koan) result end diff --git a/lib/koans.ex b/lib/koans.ex index 5d5c54c..f089bfb 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -82,11 +82,22 @@ defmodule Koans do end defmacro __before_compile__(env) do - koans = Module.get_attribute(env.module, :koans) |> Enum.reverse + koans = koans(env) quote do def all_koans do unquote(koans) end + + def intro, do: @intro end end + + defp koans(env) do + env.module + |> Module.get_attribute(:koans) + |> Enum.reverse + end + + defp default(nil, n), do: n + defp default(n, _), do: n end diff --git a/lib/koans/01_equalities.ex b/lib/koans/01_equalities.ex index 5a47ecd..121c606 100644 --- a/lib/koans/01_equalities.ex +++ b/lib/koans/01_equalities.ex @@ -1,6 +1,13 @@ defmodule Equalities do use Koans + @intro """ + Welcome to the Elixir koans. + Let these be your first humble steps towards learning a new language. + + The path laid in front of you is one of many. + """ + # Replace ___ with the answer to make the koan pass. koan "We shall contemplate truth by testing reality, via equality" do assert true == ___ diff --git a/lib/koans/02_strings.ex b/lib/koans/02_strings.ex index 6a82fc9..2a6e65d 100644 --- a/lib/koans/02_strings.ex +++ b/lib/koans/02_strings.ex @@ -1,6 +1,8 @@ defmodule Strings do use Koans + @intro "Strings" + koan "Strings are there to represent text" do assert "hello" == ___ end diff --git a/lib/koans/03_atoms.ex b/lib/koans/03_atoms.ex index 596fe79..b565a17 100644 --- a/lib/koans/03_atoms.ex +++ b/lib/koans/03_atoms.ex @@ -1,6 +1,8 @@ defmodule Atoms do use Koans + @intro "Atoms" + koan "Atoms are sort of like strings" do adam = :human assert adam == ___ diff --git a/lib/koans/04_tuples.ex b/lib/koans/04_tuples.ex index c756892..ec79557 100644 --- a/lib/koans/04_tuples.ex +++ b/lib/koans/04_tuples.ex @@ -1,6 +1,8 @@ defmodule Tuples do use Koans + @intro "Tuples" + koan "Tuples can contain different things" do assert {:a, 1, "hi"} == ___ end diff --git a/lib/koans/05_lists.ex b/lib/koans/05_lists.ex index 3e993f2..ca27874 100644 --- a/lib/koans/05_lists.ex +++ b/lib/koans/05_lists.ex @@ -1,6 +1,8 @@ defmodule Lists do use Koans + @intro "Lists" + koan "We can see what is ahead" do assert List.first([1, 2, 3]) == ___ end diff --git a/lib/koans/06_keyword_lists.ex b/lib/koans/06_keyword_lists.ex index 38d0039..c6d73e9 100644 --- a/lib/koans/06_keyword_lists.ex +++ b/lib/koans/06_keyword_lists.ex @@ -1,6 +1,8 @@ defmodule KeywordLists do use Koans + @intro "KeywordLists" + koan "Like maps, keyword lists are key-value pairs" do kw_list = [foo: "bar"] diff --git a/lib/koans/07_maps.ex b/lib/koans/07_maps.ex index 58fe30b..22fa1b0 100644 --- a/lib/koans/07_maps.ex +++ b/lib/koans/07_maps.ex @@ -1,6 +1,8 @@ defmodule Maps do use Koans + @intro "Maps" + @person %{ first_name: "Jon", last_name: "Snow", diff --git a/lib/koans/08_structs.ex b/lib/koans/08_structs.ex index 230eb21..1c33fd5 100644 --- a/lib/koans/08_structs.ex +++ b/lib/koans/08_structs.ex @@ -1,6 +1,8 @@ defmodule Structs do use Koans + @intro "Structs" + defmodule Person do defstruct [:name, :age] end diff --git a/lib/koans/09_pattern_matching.ex b/lib/koans/09_pattern_matching.ex index 5c4a054..e23e04e 100644 --- a/lib/koans/09_pattern_matching.ex +++ b/lib/koans/09_pattern_matching.ex @@ -1,6 +1,8 @@ defmodule PatternMatching do use Koans + @intro "PatternMatching" + koan "One matches one" do assert match?(1, ___) end diff --git a/lib/koans/10_functions.ex b/lib/koans/10_functions.ex index 25e2d0a..8fee618 100644 --- a/lib/koans/10_functions.ex +++ b/lib/koans/10_functions.ex @@ -1,6 +1,8 @@ defmodule Functions do use Koans + @intro "Functions" + def greet(name) do "Hello, #{name}!" end diff --git a/lib/koans/11_enums.ex b/lib/koans/11_enums.ex index 4c98aba..ed8683a 100644 --- a/lib/koans/11_enums.ex +++ b/lib/koans/11_enums.ex @@ -1,6 +1,8 @@ defmodule Enums do use Koans + @intro "Enums" + koan "Knowing how many elements are in a list is important for book-keeping" do assert Enum.count([1, 2, 3]) == ___ end diff --git a/lib/koans/12_processes.ex b/lib/koans/12_processes.ex index 998544e..99d0d17 100644 --- a/lib/koans/12_processes.ex +++ b/lib/koans/12_processes.ex @@ -1,6 +1,8 @@ defmodule Processes do use Koans + @intro "Processes" + koan "You are a process" do assert Process.alive?(self) == ___ end diff --git a/lib/koans/13_tasks.ex b/lib/koans/13_tasks.ex index fe1c840..3ce5298 100644 --- a/lib/koans/13_tasks.ex +++ b/lib/koans/13_tasks.ex @@ -1,6 +1,8 @@ defmodule Tasks do use Koans + @intro "Tasks" + koan "Tasks can be used for asynchronous computations with results" do task = Task.async(fn -> 3 * 3 end) do_other_stuff() diff --git a/lib/koans/14_agents.ex b/lib/koans/14_agents.ex index 7b524ac..eede5a8 100644 --- a/lib/koans/14_agents.ex +++ b/lib/koans/14_agents.ex @@ -1,6 +1,8 @@ defmodule Agents do use Koans + @intro "Agents" + koan "Agents maintain state, so you can ask them about it" do {:ok, pid} = Agent.start_link(fn -> "Hi there" end) assert Agent.get(pid, &(&1)) == ___ diff --git a/lib/runner.ex b/lib/runner.ex index aff090a..7974c47 100644 --- a/lib/runner.ex +++ b/lib/runner.ex @@ -17,6 +17,7 @@ defmodule Runner do ] def koan?(koan), do: Enum.member?(@modules, koan) + def modules, do: @modules def modules_to_run, do: Options.initial_koan |> modules_to_run @@ -31,16 +32,17 @@ defmodule Runner do def run_module(module) do module - |> Execute.run_module(&track/2) + |> Execute.run_module(&track/3) |> display end - defp track(:passed, koan), do: Tracker.completed(koan) - defp track(_, _), do: nil + defp track(:passed, module, koan), do: Tracker.completed(module, koan) + defp track(_, _, _), do: nil defp display({:failed, error, module, name}) do Display.show_failure(error, module, name) :failed end defp display(_), do: :passed + end diff --git a/lib/tracker.ex b/lib/tracker.ex index 4b9e158..ed2ac2f 100644 --- a/lib/tracker.ex +++ b/lib/tracker.ex @@ -4,27 +4,38 @@ defmodule Tracker do |> Enum.flat_map(&(&1.all_koans)) |> Enum.count - Agent.start_link(fn -> {total, MapSet.new()} end, name: __MODULE__) + Agent.start_link(fn -> %{total: total, + koans: MapSet.new(), + visited_modules: MapSet.new()} end, name: __MODULE__) modules end - def get do - Agent.get(__MODULE__, &(&1)) - end + defp get(), do: Agent.get(__MODULE__, &(&1)) - def completed(koan) do - Agent.update(__MODULE__, fn({total, completed}) -> - {total, MapSet.put(completed, koan)} + def completed(module, koan) do + Agent.update(__MODULE__, fn(%{koans: completed, visited_modules: modules} = all) -> + %{ all | koans: MapSet.put(completed, koan), + visited_modules: MapSet.put(modules, module)} end) end + def visited do + summarize[:visited_modules] + end + def complete? do - {total, completed} = get - total == Enum.count(completed) + %{total: total, current: completed} = summarize + total == completed end def summarize, do: get |> summarize - defp summarize({total, completed}) do - %{total: total, current: MapSet.size(completed)} + defp summarize(%{total: total, + koans: completed, + visited_modules: modules}) do + %{ + total: total, + current: MapSet.size(completed), + visited_modules: MapSet.to_list(modules) + } end end diff --git a/test/executor_test.exs b/test/executor_test.exs index 8f8e0f1..394ff2f 100644 --- a/test/executor_test.exs +++ b/test/executor_test.exs @@ -8,6 +8,10 @@ defmodule ExecuteTest do test "stops at the first failing koan" do {:failed, %{file: file, line: line}, SampleKoan, _name} = Execute.run_module(SampleKoan) assert file == 'test/support/sample_koan.ex' - assert line == 4 + assert line == 8 + end + + test "can access intro" do + assert SampleKoan.intro == "There is something\n" end end diff --git a/test/support/passing_koan.ex b/test/support/passing_koan.ex index e40451c..117092f 100644 --- a/test/support/passing_koan.ex +++ b/test/support/passing_koan.ex @@ -1,6 +1,8 @@ defmodule PassingKoan do use Koans + @intro "something" + koan "Hi there" do assert 1 == 1 end diff --git a/test/support/sample_koan.ex b/test/support/sample_koan.ex index 92ab8aa..3af89ca 100644 --- a/test/support/sample_koan.ex +++ b/test/support/sample_koan.ex @@ -1,6 +1,10 @@ defmodule SampleKoan do use Koans + @intro """ + There is something + """ + koan "Thinking more than once" do assert 3 == ___ assert 4 == ___ diff --git a/test/tracker_test.exs b/test/tracker_test.exs index 0ee46f3..8c74be7 100644 --- a/test/tracker_test.exs +++ b/test/tracker_test.exs @@ -5,19 +5,24 @@ defmodule TrackerTest do test "can start" do Tracker.start(@sample_modules) - assert Tracker.summarize == %{total: 2, current: 0} + assert Tracker.summarize == %{total: 2, current: 0, visited_modules: []} end test "can be notified of completed koans" do Tracker.start(@sample_modules) - Tracker.completed(:"Hi there") - assert Tracker.summarize == %{total: 2, current: 1} + Tracker.completed(SampleKoan, :"Hi there") + assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]} 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.summarize == %{total: 2, current: 1} + Tracker.completed(SampleKoan, :"Hi there") + Tracker.completed(SampleKoan, :"Hi there") + assert Tracker.summarize == %{total: 2, current: 1, visited_modules: [SampleKoan]} + end + + test "knows when koans are not complete" do + Tracker.start(@sample_modules) + refute Tracker.complete? end end