lib/mix/tasks/compile/fortune_compiler.ex

# SPDX-FileCopyrightText: 2023 Frank Hunleth, Masatoshi Nishiguchi
#
# SPDX-License-Identifier: Apache-2.0
defmodule Mix.Tasks.Compile.FortuneCompiler do
  @moduledoc """
  Compile fortune files

  This compiler parses specially formatted files, indexes them, and saves the results
  to the `priv/fortune` directory. To be precise, this does what the Unix `strfile`
  command does.

  By default, input files should be placed in the `fortune` directory in a project.
  Each fortune (aka string) is separated by a `%` line like the following:

  ```text
  My first fortune
  %
  Another string or fortune
  and this can be multiple lines too.
  %
  The last string
  ```

  # Configuration

  * `:fortunec_paths` - directories to find fortune files. Defaults to `["fortune"]`
  """
  use Mix.Task

  alias Fortune.StrfileWriter

  @recursive true

  @doc false
  @spec run([String.t()]) :: :ok
  def run(_args) do
    check_priv()

    paths = Mix.Project.config()[:fortunec_paths] || ["fortune"]
    inputs = Enum.flat_map(paths, &find_files/1)

    Enum.each(inputs, &process_file/1)
  end

  defp find_files(path) do
    case File.ls(path) do
      {:ok, paths} -> Enum.map(paths, &Path.join(path, &1))
      {:error, _any} -> []
    end
  end

  defp check_priv() do
    priv_path = Path.join([Mix.Project.app_path(), "priv"])

    case File.read_link(priv_path) do
      {:ok, _} ->
        Mix.raise("""
        Cannot compile fortunes and use the priv directory at the same time.

        One way of fixing this is to move the contents of the priv directory to
        a different directory, e.g., assets. Then in the `mix.exs`, copy the
        contents of the assets directory to the priv directory at compilation
        time.
        """)

      _ ->
        :ok
    end
  end

  defp process_file(file) do
    priv_path = Path.join([Mix.Project.app_path(), "priv", "fortune"])
    File.mkdir_p!(priv_path)

    StrfileWriter.create(file, priv_path)
  end
end