Skip to content

Commit

Permalink
Added ~LIQUID sigil
Browse files Browse the repository at this point in the history
  • Loading branch information
Valian authored and edgurgel committed Mar 3, 2025
1 parent 1821814 commit ad26606
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 0 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,24 @@ iex> Solid.render!(template, %{ "user" => %{ "name" => "José" } }) |> to_string
"My name is José"
```

## Sigil Support

Solid provides a `~LIQUID` sigil for validating and compiling templates at compile time:

```elixir
import Solid.Sigil

# Validates syntax at compile time
template = ~LIQUID"""
Hello, {{ name }}!
"""

# Use the compiled template
Solid.render!(template, %{"name" => "World"})
```

The sigil will raise helpful CompileError messages with line numbers and context when templates contain syntax errors.

## Installation

The package can be installed with:
Expand Down
71 changes: 71 additions & 0 deletions lib/solid/sigil.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
defmodule Solid.Sigil do
@moduledoc """
Provides the `~LIQUID` sigil for validating and compiling Liquid templates using Solid.
This sigil validates the template at compile time and returns a compiled Solid template.
If the template has syntax errors, it will raise a CompileError with detailed information.
## Examples
iex> import Solid.Sigil
iex> template = ~LIQUID\"\"\"
...> Hello, {{ name }}!
...> \"\"\"
iex> Solid.render(template, %{"name" => "World"})
{:ok, "Hello, World!"}
"""

# Import Solid to use parse! function
require Solid

# Custom sigil for validating and compiling Liquid templates using Solid
defmacro sigil_LIQUID({:<<>>, _meta, [string]}, _modifiers) do
line = __CALLER__.line
file = __CALLER__.file

try do
# Validate the template during compile time
parsed_template = Solid.parse!(string)

# Return the parsed template
Macro.escape(parsed_template)
rescue
e in Solid.TemplateError ->
# Extract template line number (first element of the tuple)
template_line = elem(e.line, 0)
# Calculate actual line number in the file
actual_line = line + template_line

# Extract just the problematic portion of the template
template_lines = String.split(string, "\n")
context_start = max(0, template_line - 2)
context_end = min(length(template_lines), template_line + 2)

context_lines =
template_lines
|> Enum.slice(context_start, context_end - context_start)
|> Enum.with_index(line + context_start + 1)
|> Enum.map_join("\n", fn {line_text, idx} ->
indicator = if idx == actual_line, do: "→ ", else: " "
"#{indicator}#{idx}: #{line_text}"
end)

# Prepare a more helpful error message
message = """
Liquid template syntax error at line #{actual_line}:
#{context_lines}
Error: #{e.reason}
"""

# Re-raise with better context
reraise %CompileError{
file: file,
line: actual_line,
description: message
},
__STACKTRACE__
end
end
end
61 changes: 61 additions & 0 deletions test/sigil_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule Solid.SigilTest do
use ExUnit.Case, async: true

import Solid.Sigil

describe "sigil_LIQUID/2" do
test "compiles valid templates" do
template = ~LIQUID"Hello, {{ name }}!"

assert template == Solid.parse!("Hello, {{ name }}!")
end

test "raises CompileError for unclosed tag" do
code = """
import Solid.Sigil
~LIQUID"Hello, {{ name!"
"""

assert_raise CompileError, ~r/Liquid template syntax error/, fn ->
Code.eval_string(code)
end
end

test "raises CompileError for invalid tag" do
code = """
import Solid.Sigil
~LIQUID"{% invalid_tag %}"
"""

assert_raise CompileError, ~r/Liquid template syntax error/, fn ->
Code.eval_string(code)
end
end

test "raises CompileError for unbalanced tags" do
code = """
import Solid.Sigil
~LIQUID"{% if condition %}No closing endif"
"""

assert_raise CompileError, ~r/Liquid template syntax error/, fn ->
Code.eval_string(code)
end
end

test "error message includes line number and contextual information" do
code = """
import Solid.Sigil
~LIQUID\"\"\"
Line 1 is fine
Line 2 has {{ an error
Line 3 is also fine
\"\"\"
"""

assert_raise CompileError, ~r/Line 2 has {{ an error/, fn ->
Code.eval_string(code)
end
end
end
end

0 comments on commit ad26606

Please sign in to comment.