Files
bDS2/lib/bds/rendering/liquid_parser.ex

67 lines
1.9 KiB
Elixir

defmodule BDS.Rendering.LiquidParser do
@moduledoc """
Restricted Liquid parser enforcing the `LiquidTagSubset` invariant
(`specs/template.allium`).
Only the tags used by the bundled starter templates are recognized:
* `{% if %}` / `{% elsif %}` / `{% else %}` / `{% endif %}`
* `{% for %}` / `{% endfor %}`
* `{% assign %}`
* `{% render 'partial', name: value %}`
* `{{ object }}` output (and whitespace-stripped variants `{%- -%}` / `{{- -}}`)
Any tag outside this subset (`unless`, `case`, `capture`, `raw`, `comment`,
`cycle`, `tablerow`, `increment`, `decrement`, `liquid`, `echo`, `include`)
leaves unmatched input and fails the `eos/0` check, producing a parse error.
Pass this module as the second argument to `Liquex.parse/2` (and
`Liquex.parse!/2`) so validation and rendering share the same surface.
"""
import NimbleParsec
@tags [
Liquex.Tag.AssignTag,
Liquex.Tag.ForTag,
Liquex.Tag.IfTag,
Liquex.Tag.RenderTag,
Liquex.Tag.ObjectTag
]
tags_parser = Enum.map(@tags, &tag(&1.parse(), {:tag, &1}))
# Ensure the tags are loaded into scope, otherwise function_exported? will
# return false.
Enum.each(@tags, &Code.ensure_loaded!/1)
liquid_tags_parser =
@tags
|> Enum.filter(&function_exported?(&1, :parse_liquid_tag, 0))
|> Enum.map(&tag(&1.parse_liquid_tag(), {:tag, &1}))
|> choice()
# Special case for leading spaces before `{%-` and `{{-`.
leading_whitespace =
empty()
# credo:disable-for-lines:1
|> Liquex.Parser.Literal.whitespace(1)
|> lookahead(choice([string("{%-"), string("{{-")]))
|> ignore()
base =
choice(
tags_parser ++
[
# credo:disable-for-lines:2
Liquex.Parser.Literal.text(),
leading_whitespace
]
)
defcombinatorp(:document, repeat(base))
defcombinatorp(:liquid_tag_contents, repeat(liquid_tags_parser))
defparsec(:parse, parsec(:document) |> eos())
end