lib/exeex.ex

defmodule ExEEx do
  @moduledoc """
  Documentation for ExEEx.
  """
  def adapter() do
    Application.get_env(:exeex, :adapter, ExEEx.Adapter.FileStorage)
  end

  @doc """
  Render template file.

  ## Examples

      iex> ExEEx.render("test/templates/hello.txt", val: "world")
      "Hello, world!\\n"

      iex> ExEEx.render("test/templates/main.txt")
      "This is header\\n---\\nThis is body\\n---\\nThis is footer\\n\\n"

      iex> ExEEx.render("test/templates/main.txt", [{"foo", 3}])
      ** (ExEEx.TemplateError) expected keywords as template parameters

  """
  def render(filename, params \\ [])
  def render(%ExEEx.Template{code: code}, params) when is_list(params) do
    if not Keyword.keyword?(params) do
      raise ExEEx.TemplateError, message: "expected keywords as template parameters"
    end
    {result, _binding} = Code.eval_quoted(code, params)
    result
  end
  def render(filename, params) when is_binary(filename) and is_list(params) do
    compile(filename)
    |> render(params)
  end

  @doc """
  Render template string.

  ## Examples

      iex> ExEEx.render_string("Hello, world!")
      "Hello, world!"

      iex> ExEEx.render_string("<%= include \\"test/templates/hello.txt\\" %>OK", val: "world")
      "Hello, world!
      OK"

      iex> ExEEx.render_string("<%= block \\"header\\" do %>This is default header<% end %>")
      "This is default header"

      iex> ExEEx.render_string("<%= block \\"test\\" %>")
      ""

      iex> ExEEx.render_string("<%= block invalid %>")
      ** (ExEEx.TemplateError) block name should be a string literal: nofile:1
  """
  def render_string(template, params \\ []) when is_list(params) do
    compile_string(template)
    |> render(params)
  end

  @doc """
  Compile template file.

  ## Examples

      iex> ExEEx.compile("test/templates/hello.txt").name
      "hello.txt"
  """
  def compile(filename, opts \\ []) when is_binary(filename) do
    file_path = adapter().expand_path(filename)
    adapter().read(file_path)
    |> compile_string(Keyword.put(opts, :file, file_path))
  end

  @doc """
  Compile template file.

  ## Examples

      iex> ExEEx.compile_string("Hello, world!").name
      :nofile
  """
  def compile_string(source, opts \\ []) when is_binary(source) do
    {dir, name} =
      with nil <- Keyword.get(opts, :file) do
        #
        # インメモリの場合、現在のディレクトリ
        #
        {adapter().expand_path("."), :nofile}
      else
        file ->
          # 絶対パスに変換
          file_path = adapter().expand_path(file)
          #
          # ファイルパスが渡されている場合、ディレクトリとベース名に分割
          #
          ExEEx.Utils.split_path(file_path)
      end
    file_name = to_string(name)
    opts =
      opts
      |> Keyword.put(:file, file_name)
      |> Keyword.put(:dir, dir)
      |> Keyword.put(:includes, [])
      |> Keyword.put(:engine, ExEEx.Engine)
    ExEEx.Engine.BlockStack.init()
    {code, _} =
      EEx.compile_string(source, opts)
      |> ExEEx.Engine.subst_blocks(file_name)
    %ExEEx.Template{
      code: code,
      path: dir,
      name: name
    }
  end
end