edgurgel/solid


Liquid template engine in Elixir

https://hexdocs.pm/solid

License: MIT

Language: Elixir

Keywords: elixir, liquid, nimble-parsec, parser, template-engine, template-engine-liquid


Solid

Build Status Module Version Hex Docs Total Download License Last Updated

Solid is an implementation in Elixir of the template language Liquid. It uses nimble_parsec to generate the parser.

Basic Usage

iex> template = "My name is {{ user.name }}"
iex> {:ok, template} = Solid.parse(template)
iex> Solid.render!(template, %{ "user" => %{ "name" => "José" } }) |> to_string
"My name is José"

Installation

The package can be installed with:

def deps do
  [{:solid, "~> 0.14"}]
end

Custom tags

To implement a new tag you need to create a new module that implements the Tag behaviour:

defmodule MyCustomTag do
  import NimbleParsec
  @behaviour Solid.Tag

  @impl true
  def spec(_parser) do
    space = Solid.Parser.Literal.whitespace(min: 0)

    ignore(string("{%"))
    |> ignore(space)
    |> ignore(string("my_tag"))
    |> ignore(space)
    |> ignore(string("%}"))
  end

  @impl true
  def render(tag, _context, _options) do
    [text: "my first tag"]
  end
end
  • spec defines how to parse your tag;
  • render defines how to render your tag.

Now we need to add the tag to the parser

defmodule MyParser do
  use Solid.Parser.Base, custom_tags: [MyCustomTag]
end

And finally pass the custom parser as an option:

"{% my_tag %}"
|> Solid.parse!(parser: MyParser)
|> Solid.render()

Custom filters

While calling Solid.render one can pass a module with custom filters:

defmodule MyCustomFilters do
  def add_one(x), do: x   1
end

"{{ number | add_one }}"
|> Solid.parse!()
|> Solid.render(%{ "number" => 41}, custom_filters: MyCustomFilters)
|> IO.puts()
# 42

Extra options can be passed as last argument to custom filters if an extra argument is accepted:

defmodule MyCustomFilters do
  def asset_url(path, opts) do
    opts[:host] <> path
  end
end

opts = [custom_filters: MyCustomFilters, host: "http://example.com"]

"{{ file_path | asset_url }}"
|> Solid.parse!()
|> Solid.render(%{ "file_path" => "/styles/app.css"}, opts)
|> IO.puts()
# http://example.com/styles/app.css

Strict rendering

Solid.render/3 doesn't raise or return errors unless strict_variables: true or strict_filters: true are passed as options.

If there are any missing variables/filters Solid.render/3 returns {:error, errors, result} where errors is the list of collected errors and result is the rendered template.

Solid.render!/3 raises if strict_variables: true is passed and there are missing variables. Solid.render!/3 raises if strict_filters: true is passed and there are missing filters.

Caching

In order to cache render-ed templates, you can write your own cache adapter. It should implement behaviour Solid.Caching. By default it uses Solid.Caching.NoCache trivial adapter.

If you want to use for example Cachex for that such implemention would look like:

defmodule CachexCache do
  @behaviour Solid.Caching

  @impl true
  def get(key) do
    case Cachex.get(:your_cache_name, key) do
      {_, nil} -> {:error, :not_found}
      {:ok, value} -> {:ok, value}
      {:error, error_msg} -> {:error, error_msg}
    end
  end

  @impl true
  def put(key, value) do
    case Cachex.put(:my_cache, key, value) do
      {:ok, true} -> :ok
      {:error, error_msg} -> {:error, error_msg}
    end
  end
end

And then pass it as an option to render cache_module: CachexCache.

Using structs in context

In order to pass structs to context you need to implement protocol Solid.Matcher for that. That protocol consist of one function def match(data, keys). First argument is struct being provided and second is list of string, which are keys passed after . to the struct.

For example:

defmodule UserProfile do
  defstruct [:full_name]

  defimpl Solid.Matcher do
    def match(user_profile, ["full_name"]), do: {:ok, user_profile.full_name}
  end
end

defmodule User do
  defstruct [:email]

  def load_profile(%User{} = _user) do
    # implementation omitted
    %UserProfile{full_name: "John Doe"}
  end

  defimpl Solid.Matcher do
    def match(user, ["email"]), do: {:ok, user.email}
    def match(user, ["profile" | keys]), do: user |> User.load_profile() |> @protocol.match(keys)
  end
end

template = ~s({{ user.email}}: {{ user.profile.full_name }})
context = %{
  "user" => %User{email: "[email protected]"}
}

template |> Solid.parse!() |> Solid.render!(context) |> to_string()
# => [email protected]: John Doe

Contributing

When adding new functionality or fixing bugs consider adding a new test case here inside test/cases. These cases are tested against the Ruby gem so we can try to stay as close as possible to the original implementation.

TODO

  • Integration tests using Liquid gem to build fixtures; #3
  • All the standard filters #8
  • Support to custom filters #11
  • Tags (if, case, unless, etc)
    • for
      • else
      • break
      • continue
      • limit
      • offset
      • Range (3..5)
      • reversed
      • forloop object
    • raw #18
    • cycle #17
    • capture #19
    • increment #16
    • decrement #16
  • Boolean operators #2
  • Whitespace control #10

Copyright (c) 2016-2022 Eduardo Gurgel Pinho

This work is free. You can redistribute it and/or modify it under the terms of the MIT License. See the LICENSE.md file for more details.

Project Statistics

Sourcerank 9
Repository Size 307 KB
Stars 189
Forks 37
Watchers 5
Open issues 15
Dependencies 1
Contributors 26
Tags 23
Created
Last updated
Last pushed

Top Contributors See all

Eduardo Gurgel Benjamin Milde John Barker Philipp Joel Ambass Dung Nguyen Manuel Zubieta Stephen Bussey Jason Schweier Kian-Meng Ang Billy Ceskavich Jeroen Visser Frédérick Capovilla Mark Glenn Piotr Szmielew Chris Bell Karl Seguin Tyler Cross Jacky Alciné Arjan Scherpenisse

Packages Referencing this Repo

solid
Liquid Template engine
Latest release 0.15.2 - Updated - 189 stars

Recent Tags See all

v0.15.2 November 14, 2023
v0.15.1 November 07, 2023
v0.15.0 September 06, 2023
v0.14.1 January 22, 2023
v0.14.0 November 02, 2022
v0.13.0 August 08, 2022
v0.12.0 March 01, 2022
v0.11.0 February 26, 2022
v0.10.0 September 20, 2021
v0.9.0 July 31, 2021
v0.8.1 May 30, 2021
v0.8.0 May 08, 2021
v0.7.1 February 21, 2021
v0.7.0 February 13, 2021
v0.6.1 November 17, 2020

Something wrong with this page? Make a suggestion

Last synced: 2023-11-14 19:57:11 UTC

Login to resync this repository