# Solid

[![Build Status](](
[![Module Version](](
[![Hex Docs](](
[![Total Download](](
[![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 {{ }}"
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"}]

## 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(space)
    |> ignore(string("my_tag"))
    |> ignore(space)
    |> ignore(string("%}"))

  @impl true
  def render(tag, _context, _options) do
    [text: "my first tag"]

- `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]

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

"{{ 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

opts = [custom_filters: MyCustomFilters, host: ""]

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

## 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.

## 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.


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

## Copyright and License

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 [](./ file for more details.