defmodule Mix.Tasks.Drab.Install do use Mix.Task @shortdoc "Installs Drab in the current Phoenix project" @moduledoc """ Installs Drab in the current Phoenix application. It does inject lines of code to the existing *.ex, *.exs and *.eex files, so it is wise to commit before continuing. It assumes that the Phoenix application was generated by `mix phx.new`. In case you have a non standard application, it is better to do it with manual install. Usage: mix drab.install """ @impl true def run(_args) do validate_phoenix_version() app = app_name() Mix.shell().info("Checking prerequisites for #{inspect(app)}") app_html = find_file("lib", "app.html.eex") user_socket = find_file("lib", "user_socket.ex") config = find_file("config", "config.exs") dev_config = find_file("config", "dev.exs") if Mix.shell().yes?("The installer is going to modify those files. OK to proceed?") do update("app.html.eex", app_html) update("user_socket.ex", user_socket) update("config.exs", config, app) update("dev.exs", dev_config) end Mix.shell().info """ Drab has been successfully installed in your Phoenix application. Now it is time to create your first commander, for example, for PageController: mix drab.gen.commander Page """ end defp app_name() do app = Mix.Project.config()[:app] unless app do Mix.shell().error("Can't find the application name.") Mix.shell().info(""" If your web application is under an umbrella, please change directory there and try again. """) Mix.raise("Giving up.") end app end defp update(file, path, app \\ nil) defp update("app.html.eex", file, _app) do inject = "<%= Drab.Client.run(@conn) %>" inject_to_file(file, " ", " #{inject}\n ") end defp update("user_socket.ex", file, _app) do inject = "use Drab.Socket" inject_to_file(file, " use Phoenix.Socket", " use Phoenix.Socket\n #{inject}") end defp update("dev.exs", file, _app) do inject = "templates/.*(eex|drab)$" inject_to_file file, "templates/.*(eex)$", inject end defp update("config.exs", file, app) do inject = "\nconfig :phoenix, :template_engines,\n drab: Drab.Live.Engine\n" # if under an umbrella umbrella = with {:ok, f} <- File.read("../../mix.exs"), true <- String.contains?(f, "apps_path:") do "\nconfig :drab, main_phoenix_app: #{inspect(app)}\n" else _ -> "" end unless inject_string_already_there(file, inject) do File.write!(file, inject <> umbrella, [:append]) end end defp inject_to_file(file, search_for, replace_with) do unless inject_string_already_there(file, replace_with) do f = File.read!(file) replaced = String.replace(f, search_for, replace_with) if replaced == f do Mix.shell().error("Installer could not update #{file}. Please install Drab manually.") else File.write!(file, replaced, [:write]) end end end defp inject_string_already_there(file, inject) do f = File.read!(file) contains = String.contains?(f, inject) if contains do Mix.shell().info(" Drab is already installed in #{file}, skipping.") end contains end defp find_file(dir, name) do file = case Path.wildcard("#{dir}/**/#{name}") do [] -> ask_for_file_path(name) [filename] -> filename names -> choose_file(names, name) end Mix.shell().info " #{file}" file end defp ask_for_file_path(name) do Mix.shell().error("Can't find #{name}. Please specify the full path.") path = String.trim(Mix.shell().prompt(">")) if File.exists?(path) do path else ask_for_file_path(name) end end defp choose_file(names, name) do Mix.shell().error("Multiple #{name} found, please copy/paste the full path of correct one.") Mix.shell().info(Enum.join(names, "\n")) path = String.trim(Mix.shell().prompt(">")) if File.exists?(path) do path else choose_file(names, name) end end defp validate_phoenix_version do unless phoenix13?() do Mix.raise """ Only Phoenix 1.3 is supported, please proceed with manual install. """ end end defp phoenix13?(), do: Regex.match?(~r/^1.3/, to_string(Application.spec(:phoenix, :vsn))) end