From f7fba2e82af839d8bf0bd5110b316e8e07c9a3e9 Mon Sep 17 00:00:00 2001 From: Zoey de Souza Pessanha Date: Sun, 14 Jan 2024 13:32:33 -0300 Subject: [PATCH] feat: add `watch command --- lib/exlings/cli.ex | 33 ++++++++++++++++++++++++++++++++- lib/exlings/exercises.ex | 6 +++++- lib/exlings/file_watcher.ex | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 lib/exlings/file_watcher.ex diff --git a/lib/exlings/cli.ex b/lib/exlings/cli.ex index 71d6b88..fb68e5c 100644 --- a/lib/exlings/cli.ex +++ b/lib/exlings/cli.ex @@ -15,8 +15,8 @@ defmodule Exlings.CLI do Nexus.help() defcommand :list, type: :null, doc: "Print all available exercises" - defcommand :verify, type: :null, doc: "Verifies all available exefcises" + defcommand :watch, type: :null, doc: "Waits for a change int oany exercise" @hint_help "Shows a hint for the specified exercise" defcommand :hint, type: :string, required: true, doc: @hint_help @@ -48,6 +48,11 @@ defmodule Exlings.CLI do UI.write(:green, "You passed all the exercises") end + def handle_input(:watch) do + {:ok, _} = Exlings.FileWatcher.start_link(callback: &run_watched_exercise/1) + wait() + end + @impl true def handle_input(:hint, %{value: exercise}) do if e = Exercises.find_by(name: exercise) do @@ -73,6 +78,32 @@ defmodule Exlings.CLI do end end + @spec run_watched_exercise(Path.t()) :: :ok + defp run_watched_exercise(path) do + if e = Exercises.find_by(path: path) do + handle_exercise_result(e) + else + UI.write("Exercise with path #{path} not found!") + end + end + + defp wait do + i = IO.read(:line) + + cond do + String.match?(i, ~r"quit") -> + UI.write(:green, "Bye by exlings o/") + System.halt(0) + + String.match?(i, ~r"exit") -> + UI.write(:green, "Bye by exlings o/") + System.halt(0) + + true -> + wait() + end + end + defp handle_exercise_result(%Exercise{} = e) do case Exercises.run(e) do {:stdout, out} -> success(e, out) diff --git a/lib/exlings/exercises.ex b/lib/exlings/exercises.ex index e5d0b76..dc4eaa6 100644 --- a/lib/exlings/exercises.ex +++ b/lib/exlings/exercises.ex @@ -23,11 +23,15 @@ defmodule Exlings.Exercises do Enum.find(list(), &(Exercise.get_state(&1) == :pending)) end - @spec find_by(name: String.t()) :: Exercise.t() | nil + @spec find_by([name: String.t()] | [path: Path.t()]) :: Exercise.t() | nil def find_by(name: name) do Enum.find(list(), &(&1.name == name)) end + def find_by(path: path) do + Enum.find(list(), &(&1.path =~ path)) + end + @spec run(Exercise.t()) :: {:stdout, binary} | {:stderr, binary} | {:error, binary} def run(%Exercise{} = e) do if exec = System.find_executable("elixir") do diff --git a/lib/exlings/file_watcher.ex b/lib/exlings/file_watcher.ex new file mode 100644 index 0000000..d955237 --- /dev/null +++ b/lib/exlings/file_watcher.ex @@ -0,0 +1,32 @@ +defmodule Exlings.FileWatcher do + @moduledoc false + + use GenServer + + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + @impl true + def init(args) do + priv = List.to_string(:code.priv_dir(:exlings)) + path = Path.join(priv, "exercises") + {:ok, watcher} = FileSystem.start_link(dirs: [path], name: :exlings) + FileSystem.subscribe(:exlings) + {:ok, %{watcher: watcher, callback: args[:callback]}} + end + + @impl true + def handle_info({:file_event, w, {path, e}}, %{watcher: w} = state) do + case e do + [_, :modified] -> state.callback.(Path.basename(path)) + _ -> nil + end + + {:noreply, state} + end + + def handle_info({:file_event, w, :stop}, %{watcher: w} = state) do + {:stop, :normal, state} + end +end