From b1f98eaf1fc8f86244218fba9097fdd42bd9b2e9 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sat, 28 Oct 2023 14:04:19 -0400 Subject: [PATCH] feat: extract web dev utils --- lib/tableau.ex | 3 ++ lib/tableau/assets.ex | 63 ---------------------------- lib/tableau/code_reloader.ex | 50 ---------------------- lib/tableau/components.ex | 71 -------------------------------- lib/tableau/file_system.ex | 12 ------ lib/tableau/live_reload.ex | 63 ---------------------------- lib/tableau/router.ex | 2 +- lib/tableau/server_supervisor.ex | 6 +-- lib/tableau/websocket.ex | 4 +- mix.exs | 3 +- mix.lock | 1 + 11 files changed, 12 insertions(+), 266 deletions(-) create mode 100644 lib/tableau.ex delete mode 100644 lib/tableau/assets.ex delete mode 100644 lib/tableau/code_reloader.ex delete mode 100644 lib/tableau/components.ex delete mode 100644 lib/tableau/file_system.ex delete mode 100644 lib/tableau/live_reload.ex diff --git a/lib/tableau.ex b/lib/tableau.ex new file mode 100644 index 00000000..cf42a8b --- /dev/null +++ b/lib/tableau.ex @@ -0,0 +1,3 @@ +defmodule Tableau do + defdelegate live_reload(assigns), to: WebDevUtils.Components +end diff --git a/lib/tableau/assets.ex b/lib/tableau/assets.ex deleted file mode 100644 index 72034d6..00000000 --- a/lib/tableau/assets.ex +++ /dev/null @@ -1,63 +0,0 @@ -# module mostly taken from `Phoenix.Endpoint.Watcher` -# copyright belongs to them. - -defmodule Tableau.Assets do - @moduledoc false - require Logger - - def child_spec(args) do - %{ - id: make_ref(), - start: {__MODULE__, :start_link, [args]}, - restart: :transient - } - end - - def start_link({cmd, args}) do - Task.start_link(__MODULE__, :watch, [to_string(cmd), args]) - end - - def watch(_cmd, {mod, fun, args}) do - try do - apply(mod, fun, args) - catch - kind, reason -> - # The function returned a non-zero exit code. - # Sleep for a couple seconds before exiting to - # ensure this doesn't hit the supervisor's - # max_restarts/max_seconds limit. - Process.sleep(2000) - :erlang.raise(kind, reason, __STACKTRACE__) - end - end - - def watch(cmd, args) when is_list(args) do - {args, opts} = Enum.split_while(args, &is_binary(&1)) - opts = Keyword.merge([into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts) - - try do - System.cmd(cmd, args, opts) - catch - :error, :enoent -> - relative = Path.relative_to_cwd(cmd) - - Logger.error( - "Could not start watcher #{inspect(relative)} from #{inspect(cd(opts))}, executable does not exist" - ) - - exit(:shutdown) - else - {_, 0} -> - :ok - - {_, _} -> - # System.cmd returned a non-zero exit code - # sleep for a couple seconds before exiting to ensure this doesn't - # hit the supervisor's max_restarts / max_seconds limit - Process.sleep(2000) - exit(:watcher_command_error) - end - end - - defp cd(opts), do: opts[:cd] || File.cwd!() -end diff --git a/lib/tableau/code_reloader.ex b/lib/tableau/code_reloader.ex deleted file mode 100644 index 2c8474e..00000000 --- a/lib/tableau/code_reloader.ex +++ /dev/null @@ -1,50 +0,0 @@ -defmodule Tableau.CodeReloader do - @moduledoc false - - # Handles automatic code reloading. - - # Taken from [Still](https://github.com/still-ex/still/blob/277f4b546f0abf1ba56167a7ae894a49069b3c6c/lib/still/web/code_reloader.ex), which is taken from [Phoenix](https://github.com/phoenixframework/phoenix/blob/431c51e20d8840fa1f851160b659f78c6bb484c6/lib/phoenix/code_reloader/server.ex). - use GenServer - - require Logger - - def start_link(_) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) - end - - @doc """ - Reloads the code. - """ - def reload do - GenServer.call(__MODULE__, :reload) - end - - def init(_opts) do - {:ok, :nostate} - end - - def handle_call(:reload, from, state) do - froms = all_waiting([from]) - mix_compile(Code.ensure_loaded(Mix.Task)) - Enum.each(froms, &GenServer.reply(&1, :ok)) - - {:noreply, state} - end - - defp all_waiting(acc) do - receive do - {:"$gen_call", from, :reload} -> all_waiting([from | acc]) - after - 0 -> acc - end - end - - defp mix_compile({:error, _reason}) do - Logger.error("Could not find Mix") - end - - defp mix_compile({:module, Mix.Task}) do - Mix.Task.reenable("compile.elixir") - Mix.Task.run("compile.elixir") - end -end diff --git a/lib/tableau/components.ex b/lib/tableau/components.ex deleted file mode 100644 index 861c5c0..00000000 --- a/lib/tableau/components.ex +++ /dev/null @@ -1,71 +0,0 @@ -defmodule Tableau.Components do - @moduledoc """ - Builtin components. - """ - require EEx - - import Tableau.Strung - - @doc """ - A component for triggering live reloading. - - Include this in your root layout to have your site live reload when using `mix tableau.server`. - """ - EEx.function_from_string( - :def, - :live_reload, - ~g''' - <% {:ok, config} = Tableau.Config.new(Application.get_env(:tableau, :config, %{}) |> Map.new()) %> - - '''html, - [:_] - ) -end diff --git a/lib/tableau/file_system.ex b/lib/tableau/file_system.ex deleted file mode 100644 index 0f2c17e..00000000 --- a/lib/tableau/file_system.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Tableau.FileSystem do - @moduledoc false - def child_spec(_) do - file_system_opts = - Keyword.merge([dirs: [Path.absname("")]], name: :tableau_file_watcher, latency: 0) - - %{ - id: FileSystem, - start: {FileSystem, :start_link, [file_system_opts]} - } - end -end diff --git a/lib/tableau/live_reload.ex b/lib/tableau/live_reload.ex deleted file mode 100644 index ec6f5c4..00000000 --- a/lib/tableau/live_reload.ex +++ /dev/null @@ -1,63 +0,0 @@ -defmodule Tableau.LiveReload do - @moduledoc false - require Logger - - def init(opts \\ []) do - name = Keyword.get(opts, :name, :tableau_file_watcher) - - FileSystem.subscribe(name) - end - - def reload!({:file_event, _watcher_pid, {path, _event}}, opts \\ []) do - patterns = Keyword.get(opts, :patterns, []) - debounce = Keyword.get(opts, :debounce, 0) - callback = Keyword.get(opts, :callback, &Function.identity/1) - - if matches_any_pattern?(path, patterns) do - ext = Path.extname(path) - - for {path, ext} <- [{path, ext} | debounce(debounce, [ext], patterns)] do - asset_type = remove_leading_dot(ext) - Logger.debug("Live reload: #{Path.relative_to_cwd(path)}") - - callback.(path) - - send(self(), {:reload, asset_type}) - end - end - end - - defp debounce(0, _exts, _patterns), do: [] - - defp debounce(time, exts, patterns) when is_integer(time) and time > 0 do - Process.send_after(self(), :debounced, time) - debounce(exts, patterns) - end - - defp debounce(exts, patterns) do - receive do - :debounced -> - [] - - {:file_event, _pid, {path, _event}} -> - ext = Path.extname(path) - - if matches_any_pattern?(path, patterns) and ext not in exts do - [{path, ext} | debounce([ext | exts], patterns)] - else - debounce(exts, patterns) - end - end - end - - defp matches_any_pattern?(path, patterns) do - path = to_string(path) - - Enum.any?(patterns, fn pattern -> - String.match?(path, pattern) and not String.match?(path, ~r{(^|/)_build/}) - end) - end - - defp remove_leading_dot("." <> rest), do: rest - defp remove_leading_dot(rest), do: rest -end diff --git a/lib/tableau/router.ex b/lib/tableau/router.ex index 8c456ad..72ebcef 100644 --- a/lib/tableau/router.ex +++ b/lib/tableau/router.ex @@ -31,7 +31,7 @@ defmodule Tableau.Router do end defp recompile(conn, _) do - Tableau.CodeReloader.reload() + WebDevUtils.CodeReloader.reload() conn end diff --git a/lib/tableau/server_supervisor.ex b/lib/tableau/server_supervisor.ex index 51b6618..44f7797 100644 --- a/lib/tableau/server_supervisor.ex +++ b/lib/tableau/server_supervisor.ex @@ -10,9 +10,9 @@ defmodule Tableau.ServerSupervisor do def init(_init_arg) do if Application.get_env(:tableau, :server) do children = [ - Tableau.FileSystem, + WebDevUtils.FileSystem, Tableau.Server, - Tableau.CodeReloader + WebDevUtils.CodeReloader ] Supervisor.init(children ++ asset_children(), strategy: :one_for_one) @@ -23,7 +23,7 @@ defmodule Tableau.ServerSupervisor do def asset_children() do for conf <- Application.get_env(:tableau, :assets, []) do - {Tableau.Assets, conf} + {WebDevUtils.Assets, conf} end end end diff --git a/lib/tableau/websocket.ex b/lib/tableau/websocket.ex index 9f2a472..ad17fda 100644 --- a/lib/tableau/websocket.ex +++ b/lib/tableau/websocket.ex @@ -5,7 +5,7 @@ defmodule Tableau.Websocket do require Logger def init(_args) do - :ok = Tableau.LiveReload.init(name: :tableau_file_watcher) + :ok = WebDevUtils.LiveReload.init() {:ok, []} end @@ -18,7 +18,7 @@ defmodule Tableau.Websocket do end def handle_info({:file_event, _watcher_pid, {_path, _event}} = file_event, state) do - Tableau.LiveReload.reload!(file_event, patterns: @reloader_opts[:patterns]) + WebDevUtils.LiveReload.reload!(file_event, patterns: @reloader_opts[:patterns]) {:ok, state} end diff --git a/mix.exs b/mix.exs index 935e67d..7e3c894 100644 --- a/mix.exs +++ b/mix.exs @@ -33,7 +33,8 @@ defmodule Tableau.MixProject do defp deps do [ {:ex_doc, ">= 0.0.0", only: :dev}, - {:file_system, "~> 0.2"}, + # {:file_system, "~> 0.2"}, + {:web_dev_utils, "~> 0.1"}, {:libgraph, "~> 0.16.0"}, {:bandit, "~> 1.0"}, {:websock_adapter, "~> 0.5"}, diff --git a/mix.lock b/mix.lock index 17726a9..aba3c04 100644 --- a/mix.lock +++ b/mix.lock @@ -20,6 +20,7 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "thousand_island": {:hex, :thousand_island, "1.0.0", "63fc8807d8607c9d74fa670996897c8c8a1f2022c8c68d024182e45249acd756", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "996320c72ba8f34d7be9b02900622e44341649f24359e0f67643e4dda8f23995"}, "tz": {:hex, :tz, "0.26.2", "a40e4bb223344c6fc7b74dda25df1f26b88a30db23fa6e55de843bd79148ccdb", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.5", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "224b0618dd1e032778a094040bc710ef9aff6e2fa8fffc2716299486f27b9e68"}, + "web_dev_utils": {:hex, :web_dev_utils, "0.1.0", "0c41285667b4c9944eaa2c3189f22ec3a915c349c4b879b5ae023e8ca0824657", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "af82e1723fe76c09d817a9a9baf85160f7ed572cb4af854194d3f7609ae92f10"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.4", "7af8408e7ed9d56578539594d1ee7d8461e2dd5c3f57b0f2a5352d610ddde757", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d2c238c79c52cbe223fcdae22ca0bb5007a735b9e933870e241fce66afb4f4ab"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},