Files
bDS2/lib/bds/rendering/template_selection.ex

163 lines
5.1 KiB
Elixir

defmodule BDS.Rendering.TemplateSelection do
@moduledoc false
import Ecto.Query
alias BDS.Frontmatter
alias BDS.Projects
alias BDS.Rendering.FileSystem
alias BDS.Rendering.Filters
alias BDS.Repo
alias BDS.StarterTemplates
alias BDS.Templates.Template
@spec load_template_source(String.t(), atom(), String.t() | nil) ::
{:ok, String.t()} | {:error, term()}
def load_template_source(project_id, kind, slug) do
project = Projects.get_project!(project_id)
case select_template(project_id, kind, slug) do
%Template{} = template ->
case published_template_body(template) do
{:ok, _source} = ok ->
ok
{:error, reason} = error ->
maybe_load_bundled_template_source(project, kind, slug, template, reason, error)
end
nil ->
load_bundled_template_source(project, kind, slug)
end
end
defp select_template(project_id, kind, slug) when is_binary(slug) and slug != "" do
Repo.one(
from template in Template,
where:
template.project_id == ^project_id and template.kind == ^kind and
template.status == :published and
template.enabled == true and template.slug == ^slug,
limit: 1
)
end
defp select_template(project_id, :post, nil) do
case StarterTemplates.default_slug(:post) do
nil ->
nil
default_slug ->
Repo.one(
from template in Template,
where:
template.project_id == ^project_id and template.kind == :post and
template.status == :published and
template.enabled == true and template.slug == ^default_slug,
limit: 1
)
end
end
defp select_template(project_id, kind, nil) do
Repo.one(
from template in Template,
where:
template.project_id == ^project_id and template.kind == ^kind and
template.status == :published and
template.enabled == true,
order_by: [desc: template.created_at, desc: template.slug],
limit: 1
)
end
defp published_template_body(%Template{content: content}) when is_binary(content),
do: {:ok, content}
defp published_template_body(%Template{} = template) do
project = Projects.get_project!(template.project_id)
full_path = Path.join(Projects.project_data_dir(project), template.file_path)
case File.read(full_path) do
{:ok, contents} ->
case Frontmatter.parse_document(contents) do
{:ok, %{body: body}} -> {:ok, body}
{:error, reason} -> {:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
@spec render_template(String.t(), String.t(), map()) ::
{:ok, String.t()} | {:error, String.t()}
def render_template(project_id, source, assigns) do
with {:ok, template_ast} <- Liquex.parse(source),
{:ok, _rendered} = ok <- safe_liquex_render(template_ast, project_id, assigns) do
ok
else
{:error, reason, line} when is_integer(line) -> {:error, "#{reason} at line #{line}"}
{:error, _message} = error -> error
end
end
defp safe_liquex_render(template_ast, project_id, assigns) do
project = Projects.get_project!(project_id)
context =
Liquex.Context.new(assigns,
static_environment: assigns,
filter_module: Filters,
file_system: FileSystem.new(StarterTemplates.template_roots(project))
)
{result, _context} = Liquex.render!(template_ast, context)
{:ok, IO.iodata_to_binary(result)}
rescue
e in Liquex.Error -> {:error, e.message}
end
defp load_bundled_template_source(project, kind, slug) do
desired_slug = bundled_template_slug(kind, slug)
with true <- is_binary(desired_slug),
file_system = project |> StarterTemplates.template_roots() |> FileSystem.new(),
{:ok, source} <- FileSystem.try_read(file_system, desired_slug) do
case Frontmatter.parse_document(source) do
{:ok, %{body: body}} -> {:ok, body}
{:error, :invalid_frontmatter} -> {:ok, source}
end
else
false -> {:error, :template_not_found}
{:error, :enoent} -> {:error, :template_not_found}
end
end
defp maybe_load_bundled_template_source(project, kind, slug, template, reason, error)
when reason in [:enoent, :template_not_found] do
if template.content in [nil, ""] and StarterTemplates.default_template?(kind, template.slug) do
load_bundled_template_source(project, kind, slug)
else
error
end
end
defp maybe_load_bundled_template_source(_project, _kind, _slug, _template, _reason, error),
do: error
defp bundled_template_slug(_kind, slug) when is_binary(slug) and slug != "", do: slug
defp bundled_template_slug(kind, _slug), do: StarterTemplates.default_slug(kind)
@spec template_render_context(String.t()) :: Liquex.Context.t()
def template_render_context(project_id) do
project = Projects.get_project!(project_id)
Liquex.Context.new(%{},
static_environment: %{},
filter_module: Filters,
file_system: FileSystem.new(StarterTemplates.template_roots(project))
)
end
end