lib/eextoheex_cli.ex

defmodule EexToHeex.CLI do
  require Logger

  @eex_extensions [".html.eex", ".html.leex"]
  @ex_extensions [".ex"]

  def main(args \\ []) do
    if length(args) < 2 do
      bad_usage()
    end

    cmd = Enum.at(args, 0)

    paths = Enum.drop(args, 1)

    case cmd do
      "check" ->
        check_all_templates(paths, @eex_extensions, nil, &EexToHeex.eex_to_heex/1)

      "check_inline" ->
        check_all_templates(paths, @ex_extensions, nil, &EexToHeex.ex_to_heex/1)

      "convert" ->
        convert_all_templates(paths, @eex_extensions, ".heex", &EexToHeex.eex_to_heex/1)

      "convert_inline" ->
        convert_all_templates(paths, @ex_extensions, ".ex", &EexToHeex.ex_to_heex/1)

      "run" ->
        run(paths)

      _ ->
        bad_usage()
    end
  end

  defp bad_usage() do
    IO.puts(
      :stderr,
      "Usage: `eextoheex COMMAND PATHS...` where COMMAND is 'check', 'check_inline', 'convert', 'convert_inline', or 'run'"
    )

    System.halt(1)
  end

  defp run(paths) do
    Enum.map(paths, fn path ->
      case to_heex(path) do
        {:error, output, error} ->
          IO.puts(output)
          raise error

        {:ok, output} ->
          IO.puts(output)
      end
    end)
  end

  defp to_heex(path) do
    contents = File.read!(path)

    if has_extension?(path, @ex_extensions) do
      EexToHeex.ex_to_heex(contents)
    else
      EexToHeex.eex_to_heex(contents)
    end
  end

  defp check_all_templates(roots, allowed_extensions, new_extension, conversion_func) do
    templates_helper(roots, allowed_extensions, new_extension, conversion_func)
  end

  defp convert_all_templates(roots, allowed_extensions, new_extension, conversion_func) do
    templates_helper(roots, allowed_extensions, new_extension, conversion_func)
  end

  defp templates_helper(roots, allowed_extensions, new_extension, conversion_func) do
    eex_templates =
      roots
      |> Enum.flat_map(&ls_recursive(&1))
      |> Enum.filter(&has_extension?(&1, allowed_extensions))

    results =
      eex_templates
      |> Enum.map(fn filename ->
        input = File.read!(filename)

        case conversion_func.(File.read!(filename)) do
          {:ok, output} ->
            cond do
              has_extension?(filename, @ex_extensions) and output == input ->
                # Nothing at all has changed, so don't mention this file in the output.
                nil

              new_extension ->
                new_filename = Path.rootname(filename) <> new_extension

                with :ok <- File.rename(filename, new_filename),
                     :ok <- File.write!(new_filename, output) do
                  {:ok, filename}
                else
                  {:error, err} ->
                    Logger.error("Error moving #{filename} to #{new_filename}", err)
                    {:ok, filename}
                end

              true ->
                {:ok, filename}
            end

          {:error, _output, err} ->
            {:error, filename, err}
        end
      end)
      |> Enum.filter(&(&1 != nil))

    grouped = Enum.group_by(results, &elem(&1, 0))
    oks = grouped[:ok] || []
    errors = grouped[:error] || []

    lok = length(oks)
    lerrs = length(errors)

    if lok > 0 do
      IO.puts("conversion worked ok for the following #{lok} templates:")
      IO.puts("")

      Enum.each(oks || [], fn {_, filename} ->
        IO.puts("  " <> strip_path(filename))
      end)
    end

    if lerrs > 0 do
      IO.puts("")
      IO.puts("The following #{lerrs} html.eex templates could not be converted:")
      IO.puts("")
    end

    Enum.each(errors || [], fn {_, filename, err} ->
      IO.puts("  " <> strip_path(filename) <> ":")
      IO.puts("    #{inspect(err)}")
    end)
  end

  defp has_extension?(path, extensions) do
    Enum.any?(extensions, &String.ends_with?(path, &1))
  end

  defp ls_recursive(path) do
    cond do
      File.regular?(path) ->
        [path]

      File.dir?(path) ->
        File.ls!(path)
        |> Enum.map(&Path.join(path, &1))
        |> Enum.map(&ls_recursive/1)
        |> Enum.concat()

      true ->
        []
    end
  end

  defp strip_path(path) do
    Regex.replace(~r[.*/platform/], path, "")
  end
end