View Source Alembic.Loader (alembic v0.1.0)

Resolves template names to files across one or more configured template root directories and reads their contents. Supports multiple roots (checked in order, first match wins), automatic extension appending when a template name has none, and path-traversal protection.

File.read/1 (and File.stat/2) are called directly on each candidate path — never File.exists?/1 first. Checking existence and then reading is a TOCTOU race; attempting the read/stat and handling its error is the correct approach.

Summary

Functions

Builds a (name -> {:ok, source} | {:error, reason}) function capturing opts — used for dependency injection into Alembic.Inheritance.resolve_chain/3.

Reads a template's content by name, walking opts[:roots] (or config :alembic, :template_roots) in order.

Resolves name to an absolute path across the configured roots, without reading its content. Added beyond issue 1.5.1's own public API: the top-level pipeline (Alembic.render_file/3, issue 1.5.3) needs the fully resolved path — not just its content — to use as Alembic.Cache's cache key, since the cache is keyed by a real, statable filesystem path, not a root-relative template name.

Returns the mtime of a resolved template — used by Alembic.Cache to detect a stale entry.

Types

@type reason() ::
  {:template_not_found, [String.t()]} | {:path_traversal_detected, String.t()}

Functions

Link to this function

build_loader(opts \\ [])

View Source
@spec build_loader(keyword()) :: (String.t() ->
                              {:ok, String.t()} | {:error, reason()})

Builds a (name -> {:ok, source} | {:error, reason}) function capturing opts — used for dependency injection into Alembic.Inheritance.resolve_chain/3.

Examples

iex> loader = Alembic.Loader.build_loader(roots: ["test/fixtures/templates"])
iex> {:ok, content} = loader.("base.html")
iex> content =~ "Alembic"
true
@spec load(
  String.t(),
  keyword()
) :: {:ok, String.t()} | {:error, reason()}

Reads a template's content by name, walking opts[:roots] (or config :alembic, :template_roots) in order.

Examples

iex> {:ok, content} = Alembic.Loader.load("base.html", roots: ["test/fixtures/templates"])
iex> content =~ "Alembic"
true

iex> {:error, {:template_not_found, paths}} = Alembic.Loader.load("nope.html", roots: ["test/fixtures/templates"])
iex> Enum.any?(paths, &String.ends_with?(&1, "nope.html"))
true
Link to this function

resolve_path(name, opts \\ [])

View Source
@spec resolve_path(
  String.t(),
  keyword()
) :: {:ok, String.t()} | {:error, reason()}

Resolves name to an absolute path across the configured roots, without reading its content. Added beyond issue 1.5.1's own public API: the top-level pipeline (Alembic.render_file/3, issue 1.5.3) needs the fully resolved path — not just its content — to use as Alembic.Cache's cache key, since the cache is keyed by a real, statable filesystem path, not a root-relative template name.

Examples

iex> {:ok, path} = Alembic.Loader.resolve_path("base.html", roots: ["test/fixtures/templates"])
iex> Path.type(path)
:absolute
@spec stat(
  String.t(),
  keyword()
) :: {:ok, DateTime.t()} | {:error, reason() | File.posix()}

Returns the mtime of a resolved template — used by Alembic.Cache to detect a stale entry.

Examples

iex> {:ok, mtime} = Alembic.Loader.stat("base.html", roots: ["test/fixtures/templates"])
iex> match?(%DateTime{}, mtime)
true