defmodule Serum.Page do
@moduledoc """
Defines a struct describing a normal page.
## Fields
* `file`: Source path
* `type`: Type of source file
* `title`: Page title
* `label`: Page label
* `group`: A group the page belongs to
* `order`: Order of the page within its group
* `url`: Absolute URL of the page within the website
* `output`: Destination path
* `data`: Source data
"""
@type t :: %__MODULE__{
file: binary(),
type: binary(),
title: binary(),
label: binary(),
group: binary(),
order: integer(),
url: binary(),
output: binary(),
data: binary(),
extras: map(),
template: binary() | nil
}
alias Serum.Fragment
alias Serum.Renderer
alias Serum.Result
alias Serum.Template
alias Serum.Template.Storage, as: TS
defstruct [
:file,
:type,
:title,
:label,
:group,
:order,
:url,
:output,
:data,
:extras,
:template
]
@spec new(binary(), {map(), map()}, binary(), map()) :: t()
def new(path, {header, extras}, data, proj) do
page_dir = (proj.src == "." && "pages") || Path.join(proj.src, "pages")
filename = Path.relative_to(path, page_dir)
type = get_type(filename)
{url, output} =
with name <- String.replace_suffix(filename, type, ".html") do
{Path.join(proj.base_url, name), Path.join(proj.dest, name)}
end
__MODULE__
|> struct(header)
|> Map.merge(%{
file: path,
type: type,
url: url,
output: output,
data: data,
extras: extras
})
end
@spec compact(t()) :: map()
def compact(%__MODULE__{} = page) do
page
|> Map.drop(~w(__struct__ data file output type)a)
|> Map.put(:type, :page)
end
@spec get_type(binary) :: binary
defp get_type(filename) do
case Path.extname(filename) do
".eex" ->
filename
|> Path.basename(".eex")
|> Path.extname()
|> Kernel.<>(".eex")
"" ->
""
ext ->
ext
end
end
@spec to_fragment(t()) :: Result.t(Fragment.t())
def to_fragment(page) do
metadata = compact(page)
template_name = page.template || "page"
bindings = [page: metadata, contents: page.data]
with %Template{} = template <- TS.get(template_name, :template),
{:ok, html} <- Renderer.render_fragment(template, bindings) do
Fragment.new(page.file, page.output, metadata, html)
else
nil -> {:error, "the template \"#{template_name}\" is not available"}
{:error, _} = error -> error
end
end
defimpl Fragment.Source do
alias Serum.Page
alias Serum.Result
@spec to_fragment(Page.t()) :: Result.t(Fragment.t())
def to_fragment(page) do
Page.to_fragment(page)
end
end
end