lib/elixir_latex/renderer.ex

defmodule ElixirLatex.Renderer do
  alias ElixirLatex.Job
  alias ElixirLatex.Attachment

  @xelatexoptions ["-halt-on-error", "-interaction=nonstopmode"]

  defp working_directory do
    Application.get_env(:elixir_latex, :working_directory, "/tmp")
  end

  defp job_directory(job_name) when is_binary(job_name) do
    Path.join(working_directory(), job_name)
  end

  defp job_directory(%Job{job_name: job_name}), do: job_directory(job_name)

  defp create_job_directory(job_name) do
    job_name
    |> job_directory()
    |> File.mkdir_p()
  end

  def write_attachments(%Job{job_name: job_name, attachments: attachments}) do
    base_directory = job_directory(job_name)

    for {_, attachment} <- attachments do
      write_attachment(attachment, base_directory)
    end

    :ok
  end

  def write_attachment(
        %Attachment{filename: filename, extension: extension, body: body},
        base_directory
      ) do
    path = Path.join(base_directory, filename <> "." <> to_string(extension))
    File.write!(path, body)
  end

  defp source_filename(job) do
    Path.join(job_directory(job), "main.tex")
  end

  defp target_filename(job) do
    Path.join(job_directory(job), "main.pdf")
  end

  defp resolve_executable(%Job{renderer: path}) when is_binary(path), do: path

  defp resolve_executable(%Job{renderer: atom}) when is_atom(atom) do
    atom
    |> Atom.to_string()
    |> System.find_executable()
  end

  defp compile(job) do
    System.cmd(resolve_executable(job), @xelatexoptions ++ [source_filename(job)],
      cd: job_directory(job)
    )
  end

  defp clean_up(job) do
    Task.start(fn -> File.rm_rf(job_directory(job)) end)
  end

  def write_source(%Job{body: body} = job) do
    File.write(source_filename(job), body)
  end

  def render_to_pdf(%Job{job_name: job_name} = job) do
    :ok = create_job_directory(job_name)
    :ok = write_attachments(job)
    :ok = write_source(job)

    case compile(job) do
      {_, 0} ->
        {:ok, pdf} = File.read(target_filename(job))
        clean_up(job)
        {:ok, pdf}

      {error, 1} ->
        clean_up(job)
        {:error, error}
    end
  end
end