Table of Contents
This chapter describes the API to Aspect and not the template language. It will be most useful as reference to those implementing the template interface to the application and not those who are creating Aspect templates.
Basic API Usage #
Get Aspect from aspect.template
package
local aspect = require("aspect.template").new(options)
options
variable store Aspect configuration.
Aspect uses a loader aspect.loader
to locate templates
aspect.loader = function (name)
local templates = {
index = 'Hello {{ name }}!',
}
return templates[name]
end
The display()
method loads the template passed as a first argument and renders it with the variables passed as a second argument.
aspect:display("dashboard.tpl", {
title = "hello world",
data = app:get_data()
})
Render result #
local output, err = aspect:display(template, vars)
output
isaspect.output
object and contains rendering information, even if the rendering failed. If you useaspect:render
methodoutput
contains rendered string:local output, err = aspect:render(template, vars) if not err then io.write(tostring(output)) end
err
isaspect.error
object and contains error information.nil
if no errors.
Render Templates #
- To render the template with some variables, call the
render()
method:local result, error = aspect:render('index.html', {the = 'variables', go = 'here'})
- If a template defines blocks, they can be rendered individually via the
render_block()
:local result, error = aspect:render_block('index.html', 'block_name', {the = 'variables', go = 'here'})
- If a template defines macros, they can be rendered individually via the
render_macro()
:local result, error = aspect:render_macro('index.html', 'macro_name', {the = 'variables', go = 'here'})
-
The
display()
,display_block()
,display_macro()
methods are shortcuts to output the rendered template.aspect:display('index.html', {the = 'variables', go = 'here'}) aspect:display_block('index.html', 'block_name', {the = 'variables', go = 'here'}) aspect:display_macro('index.html', 'macro_name', {the = 'variables', go = 'here'})
Also method has some options for output:
aspect:render('index.html', vars, {chunk_size = 8198, print = ngx.print})
Possible options are:
chunk_size
(number) - buffer size before sending data toprint
. By default -nil
, buffer disabled.print
(callable) - callback used to send data. By default -ngx.print or print
.
Note. render
functions and display
functions returns aspect.output
object with template result
(if result not displayed) and more useful information
Debug Templates #
If compilation errors or template execution fail, you can get an aspect.error
error object that contains fields:
- code — code or type of error
syntax
- template syntax errorcompile
- template compilation error not related to syntax (unknown filter, wrong arguments, etc)compiler
- internal compiler’s errorruntime
- error executing template
- name — name of the template in which the error was occurred
- line — line of the template in which the error was occurred
- message — the error message
- context — fragment of the template where the error occurred (
runtime
andcompiler
errors have no context) - callstack — trace template calls as a string.
- traceback — Lua trace back
The error object aspect.error
can be converted to a string with a detailed description of the error.
local output, error = aspect:render("launch.view")
if error then
print(tostring(error))
end
To get more information (lua code, used variables e.g.) about the template, use the builder object aspect.compiler
local builder, error = aspect:compile("launch.view")
if builder then
print("Lua code of the template launch.view:\n", builder:get_code())
else
print("Error occurred: ", tostring(error))
end
Or just use the --dump
option on the CLI:
aspect --dump /path/to/launch.view
Options #
While creating aspect.template
instance, you can pass an table of options as the constructor argument:
--- main options
local aspect = require("aspect.template").new({
cache = true,
debug = true
})
--- render options
aspect:display("main.view", vars, {
time_zone = 3 * 3600
})
Option debug
#
debug = boolean
When set to true
, templates generates notices in some obscure situations. Also:
- enables
dump
function. - enables compile-time and runtime notices.
Context:
- main options
- render options
Option loader
#
loader = func
where func
is
--- @param name string the template name
--- @param tpl aspect.template current aspect instance
--- @return string aspect template code
--- @return string error message
function func(name, tpl) end
loader
is used for loading templates from any resource such as the file system or databases.
Return nil, nil
if no template found.
Option luacode_save
#
luacode_save = func
where func
is
--- @param name string the template name
--- @param luacode string lua code of template
--- @param tpl aspect.template current aspect instance
function func(name, luacode, tpl) end
Function is used for saving compiled lua code of the template.
Option luacode_load
#
luacode_load = func
where func
is
--- @param name string the template name
--- @param tpl aspect.template current aspect instance
--- @return string|nil lua code of template
function func(name, tpl) end
Function is used for loading compiled lua code of the template.
Option bytecode_save
#
bytecode_save = func
where func
is
--- @param name string the template name
--- @param bytecode string bytecode of template
--- @param tpl aspect.template current aspect instance
function func(name, bytecode, tpl) end
Function is used for saving byte-code of the template.
Option bytecode_load
#
luacode_load = func
where func
is
--- @param name string the template name
--- @param tpl aspect.template current aspect instance
--- @return string|nil bytecode of template
function func(name, tpl) end
Function is used for loading byte-code of the template.
Option autoescape
#
autoescape = boolean
Option enables or disables auto-escaping with ‘html’ strategy.
Context:
- main options
- render options
Option env
#
env = table_or_function
Option sets the environment to be used by all templates (for current Aspect instance).
Context:
- main options
- render options
Option cache
#
cache = table_or_boolean
Option enables or disables in-memory cache of templates objects. If this parameter is a table, then it will be used to store the cache.
true
- enable cachefalse
ornil
— disable cache, load templates every call.table
(using astable<string,aspect.view>
) with custom view storage or object with__newindex
and__index
(default)
Context:
- main options
Option cache_version
#
cache_version = any
Option set (lua code or byte-code) cache version. If change version all cache will be invalidated.
Context:
- main options
Option time_zone
#
time_zone = number
Option sets UTC offset in seconds. For example Tokyo (+9 UTC) has offset 9 * 60 * 60
or 32400
. See date.
Context:
- main options
- render options
Option locale
#
locale = string
Option set locale ISO identifier. For example en
, de
, ru
Context:
- main options
- render options
Option build_stats
#
build_stats = boolean
Option enables runtime statistics aggregator. Statistic stores in aspect.template
object after compilation of the template.
Context:
- render options
Global Config #
Configure JSON #
Customize JSON encoder and decoder
local json = require("aspect.config").json
json.encode = json_encode_function
json.decode = json_decode_function
Configure UTF8 #
Customize UTF8 binding
local utf8 = require("aspect.config").utf8
utf8.len = utf8_len_function
utf8.lower = utf8_lower_function
utf8.upper = utf8_upper_function
utf8.sub = utf8_sub_function
utf8.match = utf8_match_function
Configure default environment #
Cache #
Aspect templating engine has 3 cache levels:
- lua code cache — normal (Aspect’s functions
luacode_load
andluacode_save
). - byte code cache — fast (Aspect’s functions
bytecode_load
andbytecode_save
). aspect.view
object cache (internal) — fastest (Aspect’s propertycache
)
Cache stages:
- Require the template
- Get
aspect.view
from internal (table in theaspect.template
) cache. If not found … - … get bytcode using
bytecode_load
function. If failed … - … get lua code using
luacode_load
function. If failed … - … get template source code using
loader
function. Compile it. - Save lua code using
luacode_save
function. - Save bytcode using
bytecode_save
function. - Save
aspect.view
into internal (table in theaspect.template
) cache. - Render the template
local aspect = require("aspect.template")
local template = aspect.new({
cache = true -- enable internal lua in-memory cache
})
template.bytecode_load = function (name, aspect)
-- load bytecode from nginx shared dictionary
return ngx.shared["cache"]:get(name)
end
template.luacode_load = function (name, aspect)
-- load lua code from redis
return redis:get(name)
end
template.luacode_save = function (name, luacode, aspect)
-- save lua code into redis
redis:set(name, luacode)
end
template.bytecode_save = function (name, bytecode, aspect)
-- save bytecode into nginx shared dictionary
ngx.shared["cache"]:set(name, bytecode)
end
Use custom table for internal cache:
local aspect = require("aspect.template")
local template = aspect.new({
cache = app.views -- use own app.views table for storing aspect.view objects
})
Loaders #
Loader should be callback or callable object.
File system loader #
aspect.loader = require("aspect.loader.filesystem").new("/var/project/templates")
The code bellow
aspect:display("pages/about.html", vars)
loads /var/project/templates/pages/about.html
template.
Resty loader #
aspect.loader = require("aspect.loader.resty").new("/.templates/")
The code bellow
aspect:display("pages/about.html", vars)
loads /.templates/pages/about.html
template (via ngx.location.capture).
Array loader #
local tpls = require("aspect.loader.array").new()
tpls["dashboard.tpl"] = [[ ... template ... ]]
tpls["theme.tpl"] = [[<html> ... template ... </html>]]
aspect.loader = tpls
Extending #
Add tags #
Add inline tag {% foo %}
:
local tags = require("aspect.tags")
--- @param compiler aspect.compiler
--- @param tok aspect.tokenizer
--- @return string|table of lua code
function tags.foo(compiler, tok)
-- ...
end
Add block tag {% bar %} ... {% endbar %}
:
local tags = require("aspect.tags")
--- @param compiler aspect.compiler
--- @param tok aspect.tokenizer
--- @return string|table of lua code
function tags.bar(compiler, tok)
-- ...
local tag = compiler:push_tag('bar')
--- tag.buz = ...
-- ...
end
--- @param compiler aspect.compiler
--- @param tok aspect.tokenizer
--- @return string|table of lua code
function tags.endbar(compiler, tok)
-- ...
local tag = compiler:pop_tag('bar')
-- ...
end
See aspect.tags for more examples.
Add filters #
local filters = require("aspect.filters")
filters.add("foo", {
input = "any", -- input value type
output = "string", -- output value type
-- define foo's arguments
args = {
[1] = {name = "arg1", type = "string"},
[2] = {name = "arg2", type = "number"}
}
}, function (v, arg1, arg2)
end)
See aspect.filters for more examples.
Add functions #
Add function {{ foo(arg1=x, arg2=y) }}
:
local funcs = require("aspect.funcs")
--- add {{ foo(arg1, arg2) }}
funcs.add("foo", {
-- Define foo's arguments
args = {
[1] = {name = "arg1", type = "string"},
[2] = {name = "arg2", type = "number"},
},
--- Optional. The function will be called when the template is compiled.
--- @param compiler aspect.compiler
--- @param args table
parser = function (compiler, args)
-- ...
end
}, function (__, args)
-- ...
end)
See aspect.funcs for more examples.
Add tests #
Add tests foo
, bar
and baz
local tests = require("aspect.tests")
tests.add('foo', function (__, v)
-- return boolean
end)
tests.add('bar', function (__, v, arg)
-- return boolean
end, true)
tests.add('baz', function (__, v, arg)
-- return boolean
end, "quux")
Result:
{{ a is foo }}
{{ a is bar(c) }}
{{ a is baz quux(c) }}
See aspect.tests for more examples.
Add operators #
For example add bitwise operator &
(using bitop package):
local ops = require("aspect.ast.ops")
-- Push new operator to the operators array
--- Define 'and' bitwise operator.
--- @see aspect.ast.op
table.insert(ops, {
token = "&", -- one-symbol-token for parser
order = 7, -- operator precedence (1 - high priority, 14 - low priority)
l = "number", -- left operand should be number
r = "number", -- right operand should be number
out = "number", -- result of the operator is number
type = "binary", -- operator with two operands
pack = function (left, right) -- build lua code
return "bit.band(" .. left .. ", " .. right .. ")"
end
})
Don’t forget add bitop
package to global environment using config.env:
require("aspect.config").env.bit = bitop or require("bitop")
See aspect.ast.ops for more examples.
Behaviors #
Condition behaviour #
Cast specific tables and userdata to false.
For persistent tables and userdata:
local cjson = require("cjson.safe")
local is_false = require("aspect.config").is_false
--- cjson.null the same thing every time
is_false[cjson.null] = true -- use userdata as key
For runtime tables and userdata use their metatable:
local cbson = require("cbson")
local is_false = require("aspect.config").is_false
--- cbson.null() creates every time new 'null' userdata object
--- metatable will be used for comparison
is_false[getmetatable(cbson.null())] = true
Add zero-string behaviour.
{% set zero = "0" %} {# zero as string #}
{% if zero %}
Unacceptable condition!
{% end %}
Example do nothing because zero
will be casted to true
("0"
not ‘empty’).
Add behaviour for 0
local is_false = require("aspect.config").is_false
is_false['0'] = true
Now example output Unacceptable condition!
because zero
will be casted to false.
Empty string behaviour #
Configure aspect.config.is_empty_string
table. Indicate which values are empty string or values with specific metatable.
local is_empty_string = require("aspect.config").is_empty_string
is_empty_string[ngx.null] = true
is_empty_string[getmetatable(cbson.null())] = true
Number behaviour #
Configure aspect.config.is_n
table. Indicate which objects can behave like numbers.
local is_n = require("aspect.config").is_n
is_n[getmetatable(cbson.number(0))] = 0
Custom escaper #
Add custom escaper via config, using escaper name:
require("aspect.config").escapers.csv = function(value)
-- ... returns escaped value
end
{{ data.raw|e("csv") }}
Date processing #
strtotime #
Parse about any textual datetime description into a Unix timestamp:
local strtotime = require("aspect.date").strtotime
local ts, info = strtotime("2009-02-13 23:31:30")
Table info
contains more information about date (day, month, year, hour, min, sec, time zone offset)
Date localization #
Add or change month and day of the week localizations.
Month localization
For parser (required utf8 module)
local months = require("aspect.config").date.months
months["дек"] = 12 -- add short name of december on russian
months["декабрь"] = 12 -- add long name of december on russian
months["dic"] = 12 -- add short name of december on spain
months["diciembre"] = 12 -- add long name of december on spain
-- ...
For formatter (utf8 module NOT required)
local months_locale = require("aspect.config").date.months_locale
months_locale["ru"] = { -- add Russian name of month
[1] = {"Янв", "Январь"}, -- {"Jan", "January"}
[2] = {"Фев", "Февраль"}, -- {"Feb", "February"}
[3] = {"Мар", "Март"}, -- {"Mar", "March"}
--- ...
}
There 1 - january, 12 - december. Index 1 — short name of month, index 2 - full name of month.
Week localization
For formatter (utf8 module NOT required)
local week_locale = require("aspect.config").date.week_locale
week_locale["ru"] = { -- add Russian day of the week
[1] = {"Пн", "Понедельник"}, -- {"Mon", "Monday"}
[2] = {"Вт", "Вторник"}, -- {"Tue", "Tuesday"}
[3] = {"Ср", "Среда"}, -- {"Wed", "Wednesday"}
--- ...
}
There 1 - monday, 7 - sunday (see ISO8601 part 2.2.8)
Date parser #
Add or change date parsers. For example add parser for date like 2009Y02M13D23h31m30s+0230z
(it is 2009-02-13 23:31:30 UTC+02:30
)
local date = require("aspect.date")
table.insert(date.parsers.date, { -- parse date segment
pattern = "(%d%d%d%d)Y(%d%d)M(%d%d)D",
match = function(y, m, d)
return {year = tonumber(y), month = tonumber(m), day = tonumber(d)}
end
})
table.insert(date.parsers.time, { -- parse time segment
pattern = "(%d%d)h(%d%d)m(%d%d)s",
match = function(h, m, s)
return {hour = tonumber(h), min = tonumber(m), sec = tonumber(s)}
end
})
table.insert(date.parsers.zone, { -- parse offset/timezone offset
pattern = "([+-]%d%d)(%d?%d?)z",
match = function(h, m)
return {offset = tonumber(h) * 60 * 60 + (tonumber(m) or 0) * 60} -- seconds
end
})
How parsers work:
- take
date
parser.- Iterate by patterns.
- When the pattern matches, the
match
function will be called. Match
function returns table likeos.date("*t")
if success, nil if failed (if nil resume 1.1)
- take
time
parser. search continues with the next character after matcheddate
- Iterate by patterns.
- When the pattern matches, the
match
function will be called. Match
function returns table likeos.date("*t")
if success, nil if failed (if nil resume 2.1)
- take
zone
parser. search continues with the next character after matchedtime
- Iterate by patterns.
- When the pattern matches, the
match
function will be called. Match
function returns table with keyoffset
if success, nil if failed (if nil resume 3.1)
- calculate timestamp
See date.parsers
for more information.
Date format aliases #
Add symbol R
($R
) witch is %I:%M:%S %p
local aliases = require("aspect.config").date.aliases
aliases["R"] = "%I:%M:%S %p"
Iterator and countable objects #
The Aspect
implements custom iterators as in Lua 5.2+ - through the metatable and __pairs()
function.
Works for all lua/luajit versions.
For example see range iterator.
As in Lua 5.2+, the Aspect allows to determine the length of objects through the __len()
function.
Works for all lua/luajit versions.