119 lines
3.6 KiB
Elixir
119 lines
3.6 KiB
Elixir
defmodule Solid.FileSystem do
|
|
@moduledoc """
|
|
A Solid file system is a way to let your templates retrieve other templates for use with the include tag.
|
|
|
|
You can implement a module that retrieve templates from the database, from the file system using a different path structure, you can provide them as hard-coded inline strings, or any manner that you see fit.
|
|
|
|
You can add additional instance variables, arguments, or methods as needed.
|
|
|
|
Example:
|
|
|
|
```elixir
|
|
file_system = Solid.LocalFileSystem.new(template_path)
|
|
text = Solid.render(template, file_system: {Solid.LocalFileSystem, file_system})
|
|
```
|
|
|
|
This will render the template with a LocalFileSystem implementation rooted at 'template_path'.
|
|
"""
|
|
|
|
# Called by Solid to retrieve a template file
|
|
@callback read_template_file(binary(), options :: any()) :: String.t() | no_return()
|
|
|
|
defmodule Error do
|
|
defexception [:reason]
|
|
|
|
def message(reason) do
|
|
reason
|
|
end
|
|
end
|
|
end
|
|
|
|
defmodule Solid.BlankFileSystem do
|
|
@moduledoc """
|
|
Default file system that raise error on call
|
|
"""
|
|
@behaviour Solid.FileSystem
|
|
|
|
@impl true
|
|
def read_template_file(_template_path, _opts) do
|
|
raise Solid.FileSystem.Error, reason: "This solid context does not allow includes."
|
|
end
|
|
end
|
|
|
|
defmodule Solid.LocalFileSystem do
|
|
@moduledoc """
|
|
This implements an abstract file system which retrieves template files named in a manner similar to Liquid.
|
|
ie. with the template name prefixed with an underscore. The extension ".liquid" is also added.
|
|
|
|
For security reasons, template paths are only allowed to contain letters, numbers, and underscore.
|
|
|
|
**Example:**
|
|
|
|
file_system = Solid.LocalFileSystem.new("/some/path")
|
|
|
|
Solid.LocalFileSystem.full_path(file_system, "mypartial")
|
|
# => "/some/path/_mypartial.liquid"
|
|
|
|
Solid.LocalFileSystem.full_path(file_system,"dir/mypartial")
|
|
# => "/some/path/dir/_mypartial.liquid"
|
|
|
|
Optionally in the second argument you can specify a custom pattern for template filenames.
|
|
`%s` will be replaced with template basename
|
|
Default pattern is "_%s.liquid".
|
|
|
|
**Example:**
|
|
|
|
file_system = Solid.LocalFileSystem.new("/some/path", "%s.html")
|
|
|
|
Solid.LocalFileSystem.full_path( "index", file_system)
|
|
# => "/some/path/index.html"
|
|
|
|
"""
|
|
defstruct [:root, :pattern]
|
|
@behaviour Solid.FileSystem
|
|
|
|
def new(root, pattern \\ "_%s.liquid") do
|
|
%__MODULE__{
|
|
root: root,
|
|
pattern: pattern
|
|
}
|
|
end
|
|
|
|
@impl true
|
|
def read_template_file(template_path, file_system) do
|
|
full_path = full_path(template_path, file_system)
|
|
|
|
if File.exists?(full_path) do
|
|
File.read!(full_path)
|
|
else
|
|
raise Solid.FileSystem.Error, reason: "No such template '#{template_path}'"
|
|
end
|
|
end
|
|
|
|
def full_path(template_path, file_system) do
|
|
if String.match?(template_path, Regex.compile!("^[^./][a-zA-Z0-9_/-]+$")) do
|
|
template_name = String.replace(file_system.pattern, "%s", Path.basename(template_path))
|
|
|
|
full_path =
|
|
if String.contains?(template_path, "/") do
|
|
file_system.root
|
|
|> Path.join(Path.dirname(template_path))
|
|
|> Path.join(template_name)
|
|
|> Path.expand()
|
|
else
|
|
file_system.root
|
|
|> Path.join(template_name)
|
|
|> Path.expand()
|
|
end
|
|
|
|
if String.starts_with?(full_path, Path.expand(file_system.root)) do
|
|
full_path
|
|
else
|
|
raise Solid.FileSystem.Error, reason: "Illegal template path '#{Path.expand(full_path)}'"
|
|
end
|
|
else
|
|
raise Solid.FileSystem.Error, reason: "Illegal template name '#{template_path}'"
|
|
end
|
|
end
|
|
end
|