lib/file_watch.ex

defmodule FileWatch do
  @shortdoc FileWatch.MixProject.description()
  @moduledoc """
  #{@shortdoc} v#{FileWatch.MixProject.version()}

  ### Get config template

  config template, #{FileWatch.Assets.config_file_name()}, will be generated under CWD.
  configuration details are described in it.

  ```
  fwatch --config-template
  ```

  ### Start watch

  ```
  fwatch
  ```
  """

  alias FileWatch.Assets

  @doc false
  def main(args) do
    assets_dir_path = File.cwd!()

    case OptionParser.parse(args, strict: [config_template: :boolean]) do
      {[], [], []} ->
        run(assets_dir_path)

      {[config_template: true], [], []} ->
        Path.join(assets_dir_path, Assets.config_file_name())
        |> Assets.create_config_file()

      _ ->
        help()
    end
  end

  @doc false
  @spec run(assets_dir_path :: String.t()) :: :ok | :error
  def run(assets_dir_path) do
    config_file_path = Path.join(assets_dir_path, Assets.config_file_name())

    case load_config(config_file_path) do
      :ok ->
        wrapper_file_path = Path.join(assets_dir_path, Assets.wrapper_file_name())
        run_impl(wrapper_file_path)

      :error ->
        :error
    end
  end

  @doc false
  @spec run_impl(wrapper_file_path :: String.t()) :: :ok
  def run_impl(wrapper_file_path) when is_binary(wrapper_file_path) do
    # NOTE: run_impl/2 is the essential function of :file_watch,
    # which can be called either as an escript or as a mix task.
    Application.put_env(:file_watch, :main_pid, self())
    FileWatch.Supervisor.start_link(wrapper_file_path: wrapper_file_path)
    if on_iex?(), do: :ok, else: receive(do: (:exit -> :ok))
  end

  @doc false
  def exit() do
    Application.fetch_env!(:file_watch, :main_pid) |> send(:exit)
  end

  @doc false
  def load_config(path) do
    case Assets.read_config(path) do
      {:ok, config} ->
        Application.put_all_env(config)
        Application.get_all_env(:logger) |> Logger.configure()
        :ok

      :error ->
        :error
    end
  end

  @doc false
  def highlight(binary) when is_binary(binary) do
    binary |> String.trim_trailing() |> String.split("\n") |> highlight()
  end

  @doc false
  def highlight(lines) when is_list(lines) do
    """
    \n\s\s#{Enum.join(lines, "\n\s\s")}
    """
  end

  defp help(), do: @moduledoc |> highlight() |> IO.puts()

  defp on_iex?() do
    Code.ensure_loaded?(IEx) and IEx.started?()
  end
end