Passing data

Accepted shapes

Data reaches a template as keyword arguments, a NamedTuple, or a Dict keyed by strings or symbols. Keys become strings, because that is how a template names them.

julia> using Liquid

julia> render("{{ x }}"; x = 1)
"1"

julia> render("{{ x }}", (x = 1,))
"1"

julia> render("{{ x }}", Dict("x" => 1))
"1"

julia> render("{{ x }}", Dict(:x => 1))
"1"

Nested values keep their own type, so a Dict inside a NamedTuple works, and an ordered dictionary iterates in its own order.

Lookups

Properties are reached with a dot or a subscript. Subscripts are 0-based, and count from the end when negative.

julia> using Liquid

julia> data = Dict("a" => Dict("b" => Dict("c" => 42)));

julia> render("{{ a.b.c }}", data)
"42"

julia> render("{{ a['b']['c'] }}", data)
"42"

julia> render("{{ xs[0] }} {{ xs[2] }} {{ xs[-1] }}", Dict("xs" => [10, 20, 30]))
"10 30 30"

A key that is not a valid identifier is reached by writing the subscript with no root at all:

julia> using Liquid

julia> render("{{ [\"a b\"] }}", Dict("a b" => "ok"))
"ok"

Virtual properties

Collections answer to size, first and last without those keys existing. A real key of the same name always wins.

julia> using Liquid

julia> render("{{ s.size }} {{ s.first }} {{ s.last }}"; s = "hello")
"5 h o"

julia> render("{{ a.size }} {{ a.first }} {{ a.last }}"; a = [3, 2, 1])
"3 3 1"

julia> render("{{ o.first }}", Dict("o" => Dict("first" => 99)))
"99"

A mapping's first is its first [key, value] pair, and a mapping has no virtual last.

Exposing your own types

A struct is opaque by default. Until its author says otherwise, every property lookup on it resolves to nil — there is no reflection fallback, so a template cannot name a field that was not listed.

julia> using Liquid

julia> struct Product
           title::String
           price::Float64
           cost::Float64
       end

julia> product = Product("widget", 9.99, 3.00);

julia> render("[{{ p.title }}]"; p = product)
"[]"

Opt in with @liquid_drop, naming exactly the fields templates may read. This is the Julia counterpart of Ruby Liquid's Drops.

julia> Liquid.@liquid_drop Product title price

julia> render("{{ p.title }} costs {{ p.price }}"; p = product)
"widget costs 9.99"

julia> render("[{{ p.cost }}]"; p = product)
"[]"

The macro is sugar for a liquid_properties method; write that directly when the list is computed rather than literal:

Liquid.liquid_properties(::Type{Product}) = (:title, :price)

For a computed property, add a liquid_get method and fall back to the default for everything else:

julia> function Liquid.liquid_get(p::Product, key::AbstractString)
           key == "margin" && return p.price - p.cost
           return invoke(Liquid.liquid_get, Tuple{Any,AbstractString}, p, key)
       end;

julia> render("{{ p.margin }}"; p = product)
"6.99"

Rendering to an IO

render with an IO as its first argument writes directly instead of building a string, which is what you want for a large result.

julia> using Liquid

julia> io = IOBuffer();

julia> render(io, parse_template("{{ x }}!"), Dict("x" => "hi"));

julia> String(take!(io))
"hi!"