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

  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

  @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 Assets.read_config(config_file_path) do
      {:ok, config} ->
        Application.put_all_env(config)
        Application.get_all_env(:logger) |> Logger.configure()

        wrapper_file_path = Path.join(assets_dir_path, Assets.wrapper_file_name())
        Assets.create_wrapper_file(wrapper_file_path)
        run_impl(wrapper_file_path)

      :error ->
        :error
    end
  end

  @doc """
  run_impl/2 is the essential function of :file_watch,
  which can be called either as an escript or as a mix task.
  """
  @spec run_impl(wrapper_file_path :: String.t()) :: :ok
  def run_impl(wrapper_file_path) when is_binary(wrapper_file_path) do
    Application.put_env(:file_watch, :main_pid, self())

    start_link(config: Application.get_all_env(:file_watch), wrapper_file_path: wrapper_file_path)

    if on_iex?(), do: :ok, else: receive(do: (:exit -> :ok))
  end

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

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

  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

  use Supervisor

  def start_link(args) when is_list(args) do
    Supervisor.start_link(__MODULE__, args, name: __MODULE__)
  end

  @impl Supervisor
  def init(args) when is_list(args) do
    children = [{FileWatch.FsSubscriber, args}]
    Supervisor.init(children, strategy: :one_for_one)
  end
end