From 7b494e16df07dc280dbd2cb8a3d0cbc8f7d49961 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 22:41:02 +0100 Subject: [PATCH 1/9] To enable adding the intro, the tracker knows visited modules. --- lib/tracker.ex | 29 ++++++++++++++++++----------- test/tracker_test.exs | 17 +++++++++++------ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/lib/tracker.ex b/lib/tracker.ex index 4b9e158..c384042 100644 --- a/lib/tracker.ex +++ b/lib/tracker.ex @@ -4,27 +4,34 @@ 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 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/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 From 149cc471957b374cab92e6dc94ca16bbe7772db0 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 22:47:47 +0100 Subject: [PATCH 2/9] Cleanup how koan modules get their intros --- lib/koans.ex | 23 ++++++++++++++++++++++- test/executor_test.exs | 6 +++++- test/support/sample_koan.ex | 4 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/koans.ex b/lib/koans.ex index 5d5c54c..0830a06 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -82,11 +82,32 @@ defmodule Koans do end defmacro __before_compile__(env) do - koans = Module.get_attribute(env.module, :koans) |> Enum.reverse + koans = koans(env) + intro = extract_into(env) quote do def all_koans do unquote(koans) end + + def intro do + unquote(intro) + end end end + + defp koans(env) do + env.module + |> Module.get_attribute(:koans) + |> Enum.reverse + end + + defp extract_into(env) do + env.module + |> Module.get_attribute(:intro) + |> default("") + |> String.strip + end + + defp default(nil, n), do: n + defp default(n, _), do: n end diff --git a/test/executor_test.exs b/test/executor_test.exs index 8f8e0f1..f3681c6 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" end 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 == ___ From 3d79284aab58eac114d2c08a5edaaae0cfc2e278 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 22:49:04 +0100 Subject: [PATCH 3/9] Shows into the first time a module is visted. --- lib/display.ex | 12 ++++++++++++ lib/execute.ex | 8 ++++---- lib/runner.ex | 14 +++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/display.ex b/lib/display.ex index c0d2b65..21ee774 100644 --- a/lib/display.ex +++ b/lib/display.ex @@ -20,6 +20,18 @@ defmodule Display do defp name("Elixir." <> module), do: module defp name(module), do: name(Atom.to_string(module)) + def intro(module) do + show_intro(module.intro) + module + end + + defp show_intro(""), do: nil + defp show_intro(message) do + message <> "\n" + |> Colours.green + |> IO.puts + end + def show_failure(failure, module, name) do IO.puts(format(failure, module, name)) end 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/runner.ex b/lib/runner.ex index aff090a..2b73429 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,23 @@ 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 + intro(module, Tracker.summarize) Display.show_failure(error, module, name) :failed end defp display(_), do: :passed + + defp intro(module, %{visited_modules: modules}) do + if ! module in modules do + Display.intro(module) + end + end end From 751f849585955cebe5fc611de193b348750518c3 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 22:57:00 +0100 Subject: [PATCH 4/9] Collect all intro printing into display --- lib/display.ex | 15 +++++++++------ lib/runner.ex | 6 ------ lib/tracker.ex | 4 ++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/display.ex b/lib/display.ex index 21ee774..004a51a 100644 --- a/lib/display.ex +++ b/lib/display.ex @@ -20,9 +20,15 @@ defmodule Display do defp name("Elixir." <> module), do: module defp name(module), do: name(Atom.to_string(module)) - def intro(module) do - show_intro(module.intro) - module + def show_failure(failure, module, name) do + intro(module, Tracker.visited) + IO.puts(format(failure, module, name)) + end + + defp intro(module, modules) do + if ! module in modules do + show_intro(module.intro) + end end defp show_intro(""), do: nil @@ -32,9 +38,6 @@ defmodule Display do |> IO.puts end - def show_failure(failure, module, name) do - IO.puts(format(failure, module, name)) - end def format(failure, module, name) do """ diff --git a/lib/runner.ex b/lib/runner.ex index 2b73429..7974c47 100644 --- a/lib/runner.ex +++ b/lib/runner.ex @@ -40,15 +40,9 @@ defmodule Runner do defp track(_, _, _), do: nil defp display({:failed, error, module, name}) do - intro(module, Tracker.summarize) Display.show_failure(error, module, name) :failed end defp display(_), do: :passed - defp intro(module, %{visited_modules: modules}) do - if ! module in modules do - Display.intro(module) - end - end end diff --git a/lib/tracker.ex b/lib/tracker.ex index c384042..ed2ac2f 100644 --- a/lib/tracker.ex +++ b/lib/tracker.ex @@ -19,6 +19,10 @@ defmodule Tracker do end) end + def visited do + summarize[:visited_modules] + end + def complete? do %{total: total, current: completed} = summarize total == completed From b90c748f20d93949a1364e7a4714591ae2ac6dc0 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 23:00:46 +0100 Subject: [PATCH 5/9] Adds an example (cheesy!) first intro. --- lib/koans/01_equalities.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/koans/01_equalities.ex b/lib/koans/01_equalities.ex index 5a47ecd..d831bd5 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 layed infront of you us but 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 == ___ From c0a670257da3d2e2afbffb7e77ce9004ff8fb26a Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 23:40:31 +0100 Subject: [PATCH 6/9] Fixes sample intro. --- lib/koans/01_equalities.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/koans/01_equalities.ex b/lib/koans/01_equalities.ex index d831bd5..121c606 100644 --- a/lib/koans/01_equalities.ex +++ b/lib/koans/01_equalities.ex @@ -5,7 +5,7 @@ defmodule Equalities do Welcome to the Elixir koans. Let these be your first humble steps towards learning a new language. - The path layed infront of you us but one of many. + The path laid in front of you is one of many. """ # Replace ___ with the answer to make the koan pass. From 94ab9826aeed2ce2161ac2f09e14e25363419180 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Tue, 10 May 2016 23:41:22 +0100 Subject: [PATCH 7/9] Make intro/2 more readable by making 'not' more explicit. --- lib/display.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/display.ex b/lib/display.ex index 004a51a..caab02e 100644 --- a/lib/display.ex +++ b/lib/display.ex @@ -26,7 +26,7 @@ defmodule Display do end defp intro(module, modules) do - if ! module in modules do + if not module in modules do show_intro(module.intro) end end From f5477f0fb09011d6db7e3da2c1009b0d24cb5dd5 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Wed, 11 May 2016 21:44:18 +0100 Subject: [PATCH 8/9] All modules have an intro. --- lib/koans.ex | 12 +----------- lib/koans/02_strings.ex | 2 ++ lib/koans/03_atoms.ex | 2 ++ lib/koans/04_tuples.ex | 2 ++ lib/koans/05_lists.ex | 2 ++ lib/koans/06_keyword_lists.ex | 2 ++ lib/koans/07_maps.ex | 2 ++ lib/koans/08_structs.ex | 2 ++ lib/koans/09_pattern_matching.ex | 2 ++ lib/koans/10_functions.ex | 2 ++ lib/koans/11_enums.ex | 2 ++ lib/koans/12_processes.ex | 2 ++ lib/koans/13_tasks.ex | 2 ++ lib/koans/14_agents.ex | 2 ++ test/executor_test.exs | 2 +- test/support/passing_koan.ex | 2 ++ 16 files changed, 30 insertions(+), 12 deletions(-) diff --git a/lib/koans.ex b/lib/koans.ex index 0830a06..f089bfb 100644 --- a/lib/koans.ex +++ b/lib/koans.ex @@ -83,15 +83,12 @@ defmodule Koans do defmacro __before_compile__(env) do koans = koans(env) - intro = extract_into(env) quote do def all_koans do unquote(koans) end - def intro do - unquote(intro) - end + def intro, do: @intro end end @@ -101,13 +98,6 @@ defmodule Koans do |> Enum.reverse end - defp extract_into(env) do - env.module - |> Module.get_attribute(:intro) - |> default("") - |> String.strip - end - defp default(nil, n), do: n defp default(n, _), do: n end 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/test/executor_test.exs b/test/executor_test.exs index f3681c6..394ff2f 100644 --- a/test/executor_test.exs +++ b/test/executor_test.exs @@ -12,6 +12,6 @@ defmodule ExecuteTest do end test "can access intro" do - assert SampleKoan.intro == "There is something" + 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 From 3ef70518882710b3de36d2927800726aa3487b28 Mon Sep 17 00:00:00 2001 From: Felipe Sere Date: Fri, 13 May 2016 07:51:31 +0100 Subject: [PATCH 9/9] Moves showing the intro into the formatting of a failure --- lib/display.ex | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/display.ex b/lib/display.ex index caab02e..e79ab8b 100644 --- a/lib/display.ex +++ b/lib/display.ex @@ -21,10 +21,20 @@ defmodule Display do defp name(module), do: name(Atom.to_string(module)) def show_failure(failure, module, name) do - intro(module, Tracker.visited) IO.puts(format(failure, module, name)) end + def format(failure, module, name) do + """ + #{intro(module, Tracker.visited)} + Now meditate upon #{format_module(module)} + #{progress_bar(Tracker.summarize)} + ---------------------------------------- + #{name} + #{format_failure(failure)} + """ + end + defp intro(module, modules) do if not module in modules do show_intro(module.intro) @@ -38,17 +48,6 @@ defmodule Display do |> IO.puts end - - def format(failure, module, name) do - """ - Now meditate upon #{format_module(module)} - #{progress_bar(Tracker.summarize)} - ---------------------------------------- - #{name} - #{format_failure(failure)} - """ - end - def progress_bar(%{current: current, total: total}) do arrow = caluculate_progress(current, total) |> build_arrow