lib/populator/populator.ex

defmodule Platem.Populator do
  @moduledoc """
  This module is a GenServer responsible for populating a template.
  """

  use GenServer
  alias Platem.Template
  alias Platem.Value
  alias Platem.Field
  alias Platem.Page

  @doc false
  def start_link(state) do
    GenServer.start_link(__MODULE__, state)
  end

  @doc """
  Find a field with the same name.

  # Examples
      iex> Platem.Populator.find_field("name", [%Platem.Field{name: "name", default: "default"}])
      %Platem.Field{name: "name", default: "default"}
  """
  def find_field(name, []), do: name

  def find_field(name, [%Field{name: field_name} = field | tail]) do
    cond do
      field_name == name ->
        field

      true ->
        find_field(name, tail)
    end
  end

  @doc """
  Populates a template with the given values.

  # Example
      iex> Platem.Populator.populate(%Platem.Template{template: "{{name}}", fields: [%Platem.Field{name: "name", default: "default"}], clause: {"{{", "}}"}}, [%Platem.Value{name: "name", value: "value"}])
      %Platem.Page{html: "value", published: false}
  """
  def populate(%Template{} = template, []), do: %Page{html: template.template, published: false}

  def populate(
        %Template{template: template_string, fields: fields, clause: {open, close}} = template,
        [%Value{name: name, value: value} | tail]
      ) do
    replace =
      case value do
        nil ->
          %Field{default: default} = find_field(name, fields)
          default

        _ ->
          value
      end

    template
    |> Map.put(:template, String.replace(template_string, "#{open}#{name}#{close}", replace))
    |> populate(tail)
  end

  @impl true
  def init(state) do
    {:ok, state}
  end

  @impl true
  def handle_call(:populate, _from, {template, values} = state) do
    {:reply, populate(template, values), state}
  end
end