Packages
fermo
0.17.0
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.9
0.13.8
0.13.7
0.13.6
0.13.5
0.13.4
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.4
0.2.2
0.2.1
0.2.0
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A static site generator
Current section
Files
Jump to
Current section
Files
lib/fermo/assets.ex
defmodule Fermo.Assets do
@moduledoc """
Loads the asset manifest and provides helpers to build
paths to the assets.
"""
use GenServer
@asset_path Application.compile_env(
:fermo,
:asset_path,
"build"
)
@asset_extensions Application.compile_env(
:fermo,
:asset_extensions,
~w(.css .ico .jpg .jpeg .js .png .txt)
)
@digested_filename ~r(-[a-z0-9]{32}\.[a-z0-9]+$)
@live_asset_base Application.compile_env(
:fermo,
:live_asset_base,
"/"
)
@name :fermo_assets
def start_link(args \\ %{}) do
GenServer.start_link(__MODULE__, args, name: @name)
end
@impl true
def init(args) do
{:ok, args}
end
def create_manifest() do
with {:ok, files} <- list_files(),
{:ok, metadata} <- build_metadata(files),
{:ok} <- copy_to_digested(metadata),
{:ok, manifest} <- to_manifest(metadata) do
GenServer.call(@name, {:put, manifest})
{:ok}
else
{:error, reason} ->
raise reason
end
end
defp list_files do
files =
@asset_path
|> Path.join("**")
|> Path.wildcard()
|> Enum.filter(&is_asset?/1)
{:ok, files}
end
defp is_asset?(path) do
with false <- File.dir?(path),
filename <- Path.basename(path),
false <- Regex.match?(@digested_filename, filename),
extension <- Path.extname(path),
true <- extension in @asset_extensions do
true
else
_ -> false
end
end
defp build_metadata(files) do
metadata =
files
|> Enum.map(fn file ->
relative_filename = Path.relative_to(file, @asset_path)
content = File.read!(file)
digest = Base.encode16(:erlang.md5(content), case: :lower)
extension = Path.extname(file)
root = Path.rootname(file, extension)
digested_filename = "#{root}-#{digest}#{extension}"
relative_digested_filename = Path.relative_to(digested_filename, @asset_path)
%{
filename: file,
relative_filename: relative_filename,
digest: digest,
digested_filename: digested_filename,
asset_path: "/#{relative_digested_filename}",
extension: extension
}
end)
{:ok, metadata}
end
defp copy_to_digested(metadata) do
metadata
|> Enum.each(fn item ->
File.cp!(item.filename, item.digested_filename)
end)
{:ok}
end
defp to_manifest(metadata) do
manifest =
metadata
|> Enum.map(fn item ->
{item.relative_filename, item}
end)
|> Enum.into(%{})
{:ok, manifest}
end
def manifest do
GenServer.call(@name, {:manifest})
end
def path("/" <> name) do
GenServer.call(@name, {:path, name})
end
def path(name) do
GenServer.call(@name, {:path, name})
end
def path!(name) do
{:ok, path} = path(name)
path
end
@impl true
def handle_call({:put, state}, _from, _state) do
{:reply, {:ok}, state}
end
def handle_call({:manifest}, _from, state) do
{:reply, {:ok, state}, state}
end
def handle_call({:path, name}, _from, state) do
if Map.has_key?(state, name) do
item = state[name]
{:reply, {:ok, item.asset_path}, state}
else
{:reply, {:error, "'#{name}' not found in manifest"}, state}
end
end
defmacro asset_path(name) do
quote do
context = var!(context)
if context[:page][:live] do
live_asset_path(unquote(name))
else
static_asset_path(unquote(name))
end
end
end
def static_asset_path("https://" <> _path = url) do
url
end
def static_asset_path(filename) do
path!(filename)
end
@doc """
If your assets pipeline has a hot reloading dev server
(e.g. with Webpack), set `:live_asset_base` to
the base URL of the webserver, e.g. '//localhost:8080'
"""
def live_asset_path(filename) do
manifest_path = path!(filename)
Path.join(@live_asset_base, manifest_path)
end
# TODO: make this a context aware macro
def font_path("https://" <> _path = url) do
url
end
def font_path(filename) do
path!("/fonts/#{filename}")
end
defmacro image_path("https://" <> _path = url) do
quote do
static_image_path(unquote(url))
end
end
defmacro image_path(name) do
quote do
context = var!(context)
if context[:page][:live] do
live_image_path(unquote(name))
else
static_image_path(unquote(name))
end
end
end
defmacro image_tag(filename, attributes) do
quote do
if String.starts_with?(unquote(filename), "https://") do
image_tag_with_attributes(unquote(filename), unquote(attributes))
else
context = var!(context)
url = if context[:page][:live] do
live_image_path(unquote(filename))
else
static_image_path(unquote(filename))
end
image_tag_with_attributes(url, unquote(attributes))
end
end
end
def image_tag_with_attributes(url, attributes) do
attribs = Enum.map(attributes, fn ({k, v}) ->
"#{k}=\"#{v}\""
end)
"<img src=\"#{url}\" #{Enum.join(attribs, " ")}/>"
end
def static_image_path("https://" <> _path = url) do
url
end
def static_image_path("/" <> filename) do
path!("/images/#{filename}")
end
def static_image_path(filename) do
path!("/images/#{filename}")
end
def live_image_path(filename) do
live_asset_path("images/#{filename}")
end
defmacro javascript_path(name) do
quote do
context = var!(context)
if context[:page][:live] do
live_javascript_path(unquote(name))
else
static_javascript_path(unquote(name))
end
end
end
# TODO: handle user-supplied attributes, e.g. defer="true"
defmacro javascript_include_tag(name) do
quote do
context = var!(context)
url = if context[:page][:live] do
live_javascript_path(unquote(name))
else
static_javascript_path(unquote(name))
end
"<script src=\"#{url}\" type=\"text/javascript\"></script>"
end
end
def static_javascript_path("https://" <> _path = url) do
url
end
def static_javascript_path(name) do
path!("/#{name}.js")
end
def live_javascript_path(name) do
live_asset_path("/#{name}.js")
end
defmacro stylesheet_path(name) do
quote do
context = var!(context)
if context[:page][:live] do
live_stylesheet_path(unquote(name))
else
static_stylesheet_path(unquote(name))
end
end
end
defmacro stylesheet_link_tag(name) do
quote do
context = var!(context)
url = if context[:page][:live] do
live_stylesheet_path(unquote(name))
else
static_stylesheet_path(unquote(name))
end
"<link href=\"#{url}\" media=\"all\" rel=\"stylesheet\" />"
end
end
def static_stylesheet_path("https://" <> _path = url) do
url
end
def static_stylesheet_path(name) do
path!("/#{name}.css")
end
def live_stylesheet_path(name) do
live_asset_path("/#{name}.css")
end
end