package liquid_interpreter
Install
Dune Dependency
Authors
Maintainers
Sources
md5=c6069f115acdb3ba87cde7f8369bd5f0
sha512=7f282ee4ed50d1efaa280db10f5d2dda3142cc9e785c1f58b3d2a2631b2e1c2fea14dd761e8fb3cd158bb6eeaf5de3ef4f2f2cbc7307a4b8951bdf6e2119a584
Description
The interpreter for Liquid
Published: 01 Sep 2023
README
Liquid ML
Shopify's Liquid templating language for the OCaml programming language!
Getting Started
This basic example renders a Liquid file with the default settings. The render is returned as a string.
Default Settings:
open Liquid_ml
let () =
Liquid.render "liquid_templates/test.liquid"
|> Stdio.print_endline
With Settings:
open Liquid_ml
let () =
let settings = Settings.make ~error_policy:Warn ~log_policy:Never in
Liquid.render ~settings "liquid_templates/test.liquid"
|> Stdio.print_endline
You have access to the following settings:
template_directory
The directory that contains template files. This is used both for the initial lookup (ie Liquid.render "yada.liquid") and for the
render
tag used within liquid. Default is project root.
log_directory
Where log files are written too. This must be set if log policy is set to
Verbose
.
error_policy:
Strict
- A Liquid Syntax error will raise an exceptionWarn
- A Liquid Syntax error will print an error messageSilent
- Errors will be ignoredCustom of (handler: string -> unit)
- Accepts a custom handler function
log_policy:
Verbose
- Everything will be loggedMinimal
- The most important things will be loggedNever
- Log nothing
filters:
A function that maps filter names to filter functions
string -> liquid_filter option
context:
Variable Context available in the global scope
value Ctx.t
akavariable_context
preferred_currency:
Used in money formatting filters
Usd
,Eur
,Cad
,Aud
,Gbp
Custom Variable Context
The variable context provides the template with variables accessible in the global scope.
let () =
(* Create an object that can be accessed in Liquid using dot notation (enviroment.language -> "OCaml") *)
let enviroment =
Obj.empty
|> Obj.add "language" (String "OCaml")
|> Obj.add "version" (String "4.14.0")
in
(* HeRe we include our favorite_animal as a string an our enviroment as an object *)
let context =
Ctx.empty
|> Ctx.add "favorite_animal" (String "horse")
|> Ctx.add "enviroment" (Object enviroment)
in
let settings = Settings.make ~context () in
render ~settings "liquid_templates/test.liquid"
|> Stdio.print_endline
Now we can access these variables from the template:
My favorite animal is {{ favorite_animal }}!
This template was rendered using {{ enviroment.language }} Version {{ enviroment.version }}!
This renders as:
My favorite animal is horse!
This template was rendered using OCaml Version 4.14.0
We can perform logic operations using our variables:
(* OCaml *)
let () =
(* Create an object that can be accessed in Liquid using dot notation (enviroment.language -> "OCaml") *)
let context =
Ctx.empty
|> Ctx.add "beatles" (List [String "John"; String "Paul"; String "Ringo"; String "George"])
|> Ctx.add "my_money" (Number 15.0)
|> Ctx.add "apple_price" (Number 5.0)
in
let settings = Settings.make ~context () in
render ~settings "liquid_templates/test.liquid"
|> Stdio.print_endline
{% comment %}Liquid{% endcomment %}
{% for beatle in beatles %}
{{ beatle }} lives in a yellow submarine!
{% endfor %}
{% if my_money > apple_price %}
You bought an apple for {{ apple_price | money }}
{% assign after_purchase = my_money | minus: apple_price %}
You now have {{ after_purchase | money }}
{% else %}
You don't have enough money!
{% endif %}
This template renders to:
John lives in a yellow submarine!
Paul lives in a yellow submarine!
Ringo lives in a yellow submarine!
George lives in a yellow submarine!
You bought an apple for $5.00
You now have $10.00
Execution Context
The type Ctx.t
is used to store the execution context. All variables active in the current scope are stored here. Certain events such as break
and continue
are also stored in the execution context. Ctx.t
is a Stdlib.Map
learn more here: OCaml Map Docs
Custom Filters
A filter is a function that accepts the execution context (value Ctx.t
) and a list of params (value list
) and returns a result of a value
. This is what a filter looks like in Liquid:
{{ "my cool dog" | replace: "dog", "cat" }}
This is transformed into a list of a parameters and passed to the filter. Notice how the value on the left side of the pipe is the first in the list. This is how all filters work. You can think of this filter as function: replace "my cool dog" "dog" "cat"
. This is the parameter list that will be passed to the filter:
[String "my cool dog"; String "dog"; String "cat"]
We then can use pattern matching to type check the filter. If the wrong type / wrong contents are passed into the filter we can return an error. The error will be processed based on the error policy you set. The default is Warn
which causes the filter to return Nil
and print an error message to the console.
Filter Example
open Liquid_ml
(* defined in syntax.ML *)
type liquid_filter = value Ctx.t -> value list -> (value, string) Result.t
let () =
(*
This function accepts a string, anything else will throw an error.
Note: since we discard the tail, extra params aRe simply ignored
*)
let greet _ = function
| String person :: _ -> Ok (String ("Hello " ^ person ^ "!"))
| _ -> Error "greet accepts a string"
in
(* This maps the liquid name to our function *)
let filter_lookup = function
| "greet" -> Some greet
| "say_hello" -> Some greet (* we can create an alias to our filter *)
| _ -> None
in
let settings = Settings.make ~filters:filter_lookup in
render ~settings "liquid_templates/test.liquid"
|> Stdio.print_endline
Lookup Function
A filter lookup function maps filter names (the name in liquid) to the OCaml filter function in the example above we map the string greet
to the function greet
.
Liquid Code:
{{ "John" | greet }}
Render:
Hello John!
Liquid Types
type value =
| Bool of bool
| String of string
| Number of float
| Var of string list
| List of value list
| Date of Date.t
| Object of liquid_object
| Nil
and liquid_object = value Obj.t
These are all the possible values that can be passed to a filter or stored in the execution context. Date is powered by the library Calendar. Object is a custom Stdlib.Map
defined in the file syntax.ML
.
Identifiers are represented as string lists. The ID apple
is represented as ["apple"]
. The ID collection.products[0].title
is represented as ["collection"; "products"; "0"; "title"]
.
When a list of parameters is passed to a filter it will never contain the Var
type. Variables are unpacked before they are passed to filters. If the variable is undefined Nil
is returned.
Compatibility
This is not a complete port of Liquid. Here is a list of everything that has been ported:
Tags
for
if
case
unless
capture
raw
comment (comment tag, hash comments)
render
include
include
section
assign
cycle
style
Filters
Most filters not explicitly labeled "Shopify" in the Liquid Filter Docs have been ported. A complete list can be viewed in
liquid_std/std.ml
Object
forloop
Dependencies (9)
-
liquid_std
= version
-
liquid_parser
= version
-
liquid_syntax
= version
-
re2
>= "v0.13.0"
-
stdio
>= "v0.15.0"
-
core
>= "v0.15.0"
-
base
>= "v0.15.0"
-
dune
>= "2.5"
-
ocaml
>= "4.11"
Dev Dependencies
None
Used by (1)
-
liquid_ml
< "0.1.2"
Conflicts
None