diff --git a/.luacheckrc b/.luacheckrc
index 915b7a1..ab9edd1 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -2,7 +2,7 @@ std = {
read_globals = {'box', 'require', 'debug', 'pcall', 'xpcall', 'tostring',
'tonumber', 'type', 'assert', 'ipairs', 'math', 'error', 'string',
'table', 'pairs', 'os', 'io', 'select', 'unpack', 'dofile', 'next',
- 'loadstring', 'setfenv', 'print', 'load',
+ 'loadstring', 'setfenv', 'print', 'load', '_G', 'rawget',
'getmetatable', 'setmetatable', 'SCRIPT_PATH'
},
globals = {'process_request', 'package'}
diff --git a/LICENSE b/LICENSE
index f0e57b1..012c849 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014 - 2017 Aapo Talvensaari
+Copyright (c) 2014 - 2020 Aapo Talvensaari
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
diff --git a/README.md b/README.md
index e934e6d..e16f52d 100644
--- a/README.md
+++ b/README.md
@@ -2,17 +2,20 @@
**template** is a compiling (1) (HTML) templating engine for Tarantool.
-(1) with compilation we mean that templates are translated to Lua functions that you may call or
-`string.dump` as a binary bytecode blobs to disk that can be later utilized with `template` or
-basic `load` and `loadfile` standard Lua functions (see also [Template Precompilation](#template-precompilation)).
-Although, generally you don't need to do that as `template` handles this behind the scenes.
+(1) with compilation we mean that templates are translated to Lua functions that you may call or `string.dump`
+as a binary bytecode blobs to disk that can be later utilized with `template` or basic `load` and
+`loadfile` standard Lua functions (see also [Template Precompilation](#template-precompilation)). Although,
+generally you don't need to do that as `template` handles this behind the scenes.
+
## Hello World with template
```lua
-local template = require "template"
+local template = require("template") -- OR
+local template = require("template.safe") -- return nil, err on errors
+
-- Using template.new
-local view = template.new "view.html"
+local view = template.new("view.html")
view.message = "Hello, World!"
-- render by default print rendered template to stdout
-- use "compile" that returns result as string
@@ -57,37 +60,60 @@ print(result)
## Contents
-* [Template Syntax](#template-syntax)
- * [Reserved Context Keys and Remarks](#reserved-context-keys-and-remarks)
-* [Installation](#installation)
- * [Using LuaRocks or Moonrocks](#using-luarocks-or-moonrocks)
-* [Lua API](#lua-api)
- * [template.caching](#boolean-templatecachingboolean-or-nil)
- * [template.new](#table-templatenewview-layout)
- * [template.compile](#function-boolean-templatecompileview-key)
- * [template.render](#templaterenderview-context-key)
- * [template.parse](#string-templateparseview)
- * [template.precompile](#string-templateprecompileview-path-strip)
- * [template.load](#templateload)
- * [template.print](#templateprint)
-* [Template Precompilation](#template-precompilation)
-* [Template Helpers](#template-helpers)
-* [Usage Examples](#usage-examples)
- * [Template Including](#template-including)
- * [Views with Layouts](#views-with-layouts)
- * [Using Blocks](#using-blocks)
- * [Grandfather-Father-Son Inheritance](#grandfather-father-son-inheritance)
- * [Macros](#macros)
- * [Calling Methods in Templates](#calling-methods-in-templates)
- * [Embedding Angular or other tags / templating inside the Templates](#embedding-angular-or-other-tags--templating-inside-the-templates)
- * [Embedding Markdown inside the Templates](#embedding-markdown-inside-the-templates)
- * [Lua Server Pages (LSP) with OpenResty](#lua-server-pages-lsp-with-openresty)
-* [FAQ](#faq)
-* [Alternatives](#alternatives)
-* [Benchmarks](#benchmarks)
-* [Changes](#changes)
-* [See Also](#see-also)
-* [License](#license)
+- [Template Syntax](#template-syntax)
+ - [Example](#example)
+ - [Reserved Context Keys and Remarks](#reserved-context-keys-and-remarks)
+- [Installation](#installation)
+ - [Using LuaRocks](#using-luarocks)
+- [Lua API](#lua-api)
+ - [template.root](#templateroot)
+ - [template.location](#templatelocation)
+ - [table template.new(view, layout)](#table-templatenewview-layout)
+ - [boolean template.caching(boolean or nil)](#boolean-templatecachingboolean-or-nil)
+ - [function, boolean template.compile(view, cache_key, plain)](#function-boolean-templatecompileview-cache_key-plain)
+ - [function, boolean template.compile_string(view, cache_key)](#function-boolean-templatecompile_stringview-cache_key)
+ - [function, boolean template.compile_file(view, cache_key)](#function-boolean-templatecompile_fileview-cache_key)
+ - [template.visit(func)](#templatevisitfunc)
+ - [string template.process(view, context, cache_key, plain)](#string-templateprocessview-context-cache_key-plain)
+ - [string template.process_string(view, context, cache_key)](#string-templateprocess_stringview-context-cache_key)
+ - [string template.process_file(view, context, cache_key)](#string-templateprocess_fileview-context-cache_key)
+ - [template.render(view, context, cache_key, plain)](#templaterenderview-context-cache_key-plain)
+ - [template.render_string(view, context, cache_key)](#templaterender_stringview-context-cache_key)
+ - [template.render_file(view, context, cache_key)](#templaterender_fileview-context-cache_key)
+ - [string template.parse(view, plain)](#string-templateparseview-plain)
+ - [string template.parse_string(view, plain)](#string-templateparse_stringview-plain)
+ - [string template.parse_file(view, plain)](#string-templateparse_fileview-plain)
+ - [string template.precompile(view, path, strip)](#string-templateprecompileview-path-strip)
+ - [string template.precompile_string(view, path, strip)](#string-templateprecompile_stringview-path-strip)
+ - [string template.precompile_file(view, path, strip)](#string-templateprecompile_fileview-path-strip)
+ - [string template.load(view, plain)](#string-templateloadview-plain)
+ - [string template.load_string(view)](#string-templateload_stringview)
+ - [string template.load_file(view)](#string-templateload_fileview)
+ - [template.print](#templateprint)
+- [Template Precompilation](#template-precompilation)
+- [Template Helpers](#template-helpers)
+ - [Built-in Helpers](#built-in-helpers)
+ - [echo(...)](#echo)
+ - [include(view, context)](#includeview-context)
+ - [Other Ways to Extend](#other-ways-to-extend)
+- [Usage Examples](#usage-examples)
+ - [Template Including](#template-including)
+ - [Views with Layouts](#views-with-layouts)
+ - [Using Blocks](#using-blocks)
+ - [Grandfather-Father-Son Inheritance](#grandfather-father-son-inheritance)
+ - [Macros](#macros)
+ - [Calling Methods in Templates](#calling-methods-in-templates)
+ - [Embedding Angular or other tags / templating inside the Templates](#embedding-angular-or-other-tags--templating-inside-the-templates)
+ - [Embedding Markdown inside the Templates](#embedding-markdown-inside-the-templates)
+ - [Lua Server Pages (LSP)](#lua-server-pages-lsp)
+- [FAQ](#faq)
+- [Alternatives](#alternatives)
+- [Benchmarks](#benchmarks)
+- [Changes](#changes)
+- [Roadmap](#roadmap)
+- [See Also](#see-also)
+- [License](#license)
+
## Template Syntax
@@ -96,13 +122,14 @@ You may use the following tags in templates:
* `{{expression}}`, writes result of expression - html escaped
* `{*expression*}`, writes result of expression
* `{% lua code %}`, executes Lua code
-* `{(template)}`, includes `template` file, you may also supply context for include file `{(file.html, { message = "Hello, World" } )}`
+* `{(template)}`, includes `template` file, you may also supply context for include file `{(file.html, { message = "Hello, World" } )}` (NOTE: you cannot use comma (`,`) in `file.html`, in that case use `{["file,with,comma"]}` instead)
* `{[expression]}`, includes `expression` file (the result of expression), you may also supply context for include file `{["file.html", { message = "Hello, World" } ]}`
* `{-block-}...{-block-}`, wraps inside of a `{-block-}` to a value stored in a `blocks` table with a key `block` (in this case), see [using blocks](https://github.com/tarantool/template#using-blocks). Don't use predefined block names `verbatim` and `raw`.
* `{-verbatim-}...{-verbatim-}` and `{-raw-}...{-raw-}` are predefined blocks whose inside is not processed by the `template` but the content is outputted as is.
* `{# comments #}` everything between `{#` and `#}` is considered to be commented out (i.e. not outputted or executed)
-From templates you may access everything in `context` table, and everything in `template` table. In templates you can also access `context` and `template` by prefixing keys.
+From templates you may access everything in `context` table, and everything in `template` table.
+In templates you can also access `context` and `template` by prefixing keys.
```html
{{message}}
== {{context.message}}
@@ -142,7 +169,8 @@ Say you have this kind of a context table:
local ctx = {["foo:bar"] = "foobar"}
```
-And you want to render the `ctx["foo:bar"]`'s value `foobar` in your template. You have to specify it explicitly by referencing the `context` in your template:
+And you want to render the `ctx["foo:bar"]`'s value `foobar` in your template. You have to specify it explicitly
+by referencing the `context` in your template:
```html
{# {*["foo:bar"]*} won't work, you need to use: #}
@@ -159,8 +187,8 @@ template.render([[
##### A Word About HTML Escaping
-Only strings are escaped, functions are called without arguments (recursively) and results are returned as is, other types are `tostring`ified.
-`nil`s and `box.NULL`s are converted to empty strings `""`.
+Only strings are escaped, functions are called without arguments (recursively) and results are returned as is,
+other types are `tostring`ified. `nil`s and `box.NULL`s are converted to empty strings `""`.
Escaped HTML characters:
@@ -172,9 +200,10 @@ Escaped HTML characters:
* `/` becomes `/`
#### Example
+
##### Lua
```lua
-local template = require "template"
+local template = require("template")
template.render("view.html", {
title = "Testing template",
message = "Hello, World!",
@@ -218,6 +247,7 @@ It is advised that you do not use these keys in your context tables:
* `___`, holds the compiled template, if set you need to use `{{context.___}}`
* `context`, holds the current context, if set you need to use `{{context.context}}`
+* `echo`, holds the echo helper function, if set you need to use `{{context.echo}}`
* `include`, holds the include helper function, if set you need to use `{{context.include}}`
* `layout`, holds the layout by which the view will be decorated, if set you need to use `{{context.layout}}`
* `blocks`, holds the blocks, if set you need to use `{{context.blocks}}` (see: [using blocks](#using-blocks))
@@ -246,13 +276,15 @@ You can load templates from "sub-directories" as well with `{(syntax)}`:
{(users/list.html)}
```
-**Also note that you can provide template either as a file path or as a string. If the file exists, it will be used, otherwise the string is used. See also [`template.load`](#templateload).**
+**Also note that you can provide template either as a file path or as a string. If the file exists,
+it will be used, otherwise the string is used. See also [`template.load`](#templateload).**
-## Installation
-Just place [`template.lua`](https://github.com/tarantool/template/blob/master/lib/template.lua) and
-[`template`](https://github.com/tarantool/template/tree/master/lib/template) directory somewhere in your `package.path`.
+## Installation
+Just place [`template.lua`](https://github.com/tarantool/template/blob/master/lib/template.lua) and
+[`template`](https://github.com/tarantool/template/tree/master/lib/template) directory somewhere in
+your `package.path`.
### Using LuaRocks
@@ -262,21 +294,25 @@ $ tarantoolctl rocks install template
## Lua API
-#### boolean template.caching(boolean or nil)
+#### template.root
-This function enables or disables template caching, or if no parameters are passed, returns current state of template caching. By default template caching is enabled, but you may want to disable it on development or low-memory situations.
+You can setup template root by setting this variable which will be looked for template files:
```lua
-local template = require("template")
--- Get current state of template caching
-local enabled = template.caching()
--- Disable template caching
-template.caching(false)
--- Enable template caching
-template.caching(true)
+local template = require("template").new({
+ root = "/templates"
+})
+template.render_file("test.html")
```
-Please note that if the template was already cached when compiling a template, the cached version will be returned. You may want to flush cache with `template.cache = {}` to ensure that your template really gets recompiled.
+#### template.location
+
+```lua
+local template = require("template").new({
+ location = "/templates"
+})
+template.render_file("test.html")
+```
#### table template.new(view, layout)
@@ -285,8 +321,49 @@ only one method `render`, but the table also has metatable with `__tostring` def
`view` and `layout` arguments can either be strings or file paths, but layout can also be a table created previously
with `template.new`.
+With 2.0 the new can also be used without arguments, which creates a new template instance:
+
+```lua
+local template = require("template").new()
+```
+
+You can also pass a table that is then modified to be a template:
+
+```lua
+local config = {
+ root = "/templates"
+}
+
+local template = require("template").new(config)
+```
+
+This is handy as the `template` created by `new` does not share the cache with the global template returned
+by `require("template")`.
+
+You can also pass a boolean `true` or `false` as a `view` parameter which means that either `safe` or `un-safe`
+version of template is returned:
+
+```lua
+local unsafe = require("template")
+local safe = unsafe.new(true)
+```
+
+There is also a default `safe` implementation available:
+
```lua
-local view = template.new"template.html" -- or
+local safe = require("template.safe")
+-- you can create instance of safe too:
+local safe_instance = safe.new()
+```
+
+`safe` version uses `return nil, err` Lua error handling pattern and `unsafe` just throws the errors, which you
+can catch with `pcall`, `xpcall` or `coroutine.wrap`.
+
+
+Here are examples of using `new` with arguments:
+
+```lua
+local view = template.new("template.html") -- or
local view = template.new("view.html", "layout.html") -- or
local view = template.new[[{{message}}
]] -- or
local view = template.new([[{{message}}
]], [[
@@ -305,61 +382,309 @@ local view = template.new("view.html")
view.message = "Hello, World!"
view:render()
-- You may also replace context on render
-view:render{ title = "Testing template" }
+view:render({ title = "Testing template" })
-- If you want to include view context in replacement context
view:render(setmetatable({ title = "Testing template" }, { __index = view }))
-- To get rendered template as a string, you can use tostring
local result = tostring(view)
```
-#### function, boolean template.compile(view, key, plain)
-Parses, compiles and caches (if caching is enabled) a template and returns the compiled template as a function that takes context as a parameter and returns rendered template as a string. Optionally you may pass `key` that is used as a cache key. If cache key is not provided `view` wil be used as a cache key. If cache key is `no-cache` the template cache will not be checked and the resulting function will not be cached. You may also optionally pass `plain` with a value of `true` if the `view` is plain text string (this will skip `template.load` and binary chunk detection in `template.parse` phase).
+#### boolean template.caching(boolean or nil)
+
+This function enables or disables template caching, or if no parameters are passed, returns current state of
+template caching. By default template caching is enabled, but you may want to disable it on development or
+low-memory situations.
+
+```lua
+local template = require("template")
+-- Get current state of template caching
+local enabled = template.caching()
+-- Disable template caching
+template.caching(false)
+-- Enable template caching
+template.caching(true)
+```
+
+Please note that if the template was already cached when compiling a template, the cached version will be returned.
+You may want to flush cache with `template.cache = {}` to ensure that your template really gets recompiled.
+
+
+#### function, boolean template.compile(view, cache_key, plain)
+
+Parses, compiles and caches (if caching is enabled) a template and returns the compiled template as a function
+that takes context as a parameter and returns rendered template as a string. Optionally you may pass `cache_key` that
+is used as a cache key. If cache key is not provided `view` wil be used as a cache key. If cache key is `no-cache`
+the template cache will not be checked and the resulting function will not be cached. You may also optionally
+pass `plain` with a value of `true` if the `view` is plain text string (this will skip `template.load` and binary
+chunk detection in `template.parse` phase). If `plain` is `false` the template is considered to be a file,
+and all the issues with file reading are considered as errors. If the `plain` is set to `nil` (the default)
+the template does not consider file reading errors as fatal, and returns back the `view` (usually the path of
+the template).
```lua
local func = template.compile("template.html") -- or
local func = template.compile([[{{message}}
]])
```
+
##### Example
+
```lua
-local template = require "template"
+local template = require("template")
local func = template.compile("view.html")
local world = func{ message = "Hello, World!" }
local universe = func{ message = "Hello, Universe!" }
print(world, universe)
```
-Also note the second return value which is a boolean. You may discard it, or use it to determine if the returned function was cached.
+Also note the second return value which is a boolean. You may discard it, or use it to determine if
+the returned function was cached.
+
-#### template.render(view, context, key, plain)
+#### function, boolean template.compile_string(view, cache_key)
-Parses, compiles, caches (if caching is enabled) and outputs template either with `io.write` if available, or `print`. You may optionally also pass `key` that is used as a cache key. If `plain` evaluates to `true`, the `view` is considered to be plain string template (`template.load` and binary chunk detection is skipped on `template.parse`).
+This just calls `template.compile(view, cache_key, true)`
+
+
+#### function, boolean template.compile_file(view, cache_key)
+
+This just calls `template.compile(view, cache_key, false)`
+
+
+#### template.visit(func)
+
+Allows you to register template parser visitor functions. Visitors are called in the order they
+are registered. And once registered, cannot be removed from parser. Perhaps it is easier to show
+how it works:
```lua
-template.render("template.html", { message = "Hello, World!" }) -- or
-template.render([[{{message}}
]], { message = "Hello, World!" })
+local template = require("template.safe").new()
+
+local i = 0
+
+template.visit(function(content, type, name)
+ local trimmed = content:gsub("^%s+", ""):gsub("%s+$", "")
+ if trimmed == "" then return content end
+ i = i + 1
+ print(" visit: ", i)
+ if type then print(" type: ", type) end
+ if name then print(" name: ", name) end
+ print("content: ", trimmed)
+ print()
+ return content
+end)
+
+local func = template.compile([[
+How are you, {{user.name}}?
+
+Here is a new cooking recipe for you!
+
+{% for i, ingredient in ipairs(ingredients) do %}
+ {*i*}. {{ingredient}}
+{% end %}
+{-ad-}`template` the templating engine for Tarantool!{-ad-}
+]])
+
+local content = func{
+ user = {
+ name = "tarantool"
+ },
+ ingredients = {
+ "potatoes",
+ "sausages"
+ }
+}
+
+print(content)
```
-##### Example
+This will output the following:
+
+```
+ visit: 1
+content: How are you,
+
+ visit: 2
+ type: {
+content: user.name
+
+ visit: 3
+content: ?
+
+Here is a new cooking recipe for you!
+
+ visit: 4
+ type: %
+content: for i, ingredient in ipairs(ingredients) do
+
+ visit: 5
+ type: *
+content: i
+
+ visit: 6
+content: .
+
+ visit: 7
+ type: {
+content: ingredient
+
+ visit: 8
+ type: %
+content: end
+
+ visit: 9
+ type: -
+ name: ad
+content: `template` the templating engine for tarantool!
+
+ visit: 10
+content: `template` the templating engine for tarantool!
+
+How are you, tarantool?
+
+Here is a new cooking recipe for you!
+
+ 1. potatoes
+ 2. sausages
+```
+
+The visitor functions should have this signature:
+```
+string function(content, type, name)
+```
+
+If the function doesn't modify the `content` it should return the `content` back, like the visitor
+above does.
+
+Here is a bit more advanced visitor example that handles run-time errors on expressions:
+
```lua
-local template = require "template"
-template.render("view.html", { message = "Hello, World!" })
-template.render("view.html", { message = "Hello, Universe!" })
+local template = require("template").new()
+
+template.render "Calculation: {{i*10}}"
+```
+
+This will runtime error with:
+```
+ERROR: [string "context=... or {}..."]:7: attempt to perform arithmetic on global 'i' (a nil value)
+stack traceback:
+ resty/template.lua:652: in function 'render'
+ a.lua:52: in function 'file_gen'
+ init_worker_by_lua:45: in function
+ [C]: in function 'xpcall'
+ init_worker_by_lua:52: in function
+```
+
+Now let's add a visitor that handles this error:
+
+```lua
+local template = require("template").new()
+
+template.visit(function(content, type)
+ if type == "*" or type == "{" then
+ return "select(3, pcall(function() return nil, " .. content .. " end)) or ''"
+ end
+
+ return content
+end)
+
+template.render "Calculation: {{i*10}}\n"
+template.render("Calculation: {{i*10}}\n", { i = 1 })
+```
+
+This will output:
+
+```
+Calculation:
+Calculation: 10
+```
+
+
+#### string template.process(view, context, cache_key, plain)
+
+Parses, compiles, caches (if caching is enabled) and returns output as string. You may optionally also
+pass `cache_key` that is used as a cache key. If `plain` evaluates to `true`, the `view` is considered
+to be plain string template (`template.load` and binary chunk detection is skipped on `template.parse`).
+If `plain` is `false"` the template is considered to be a file, and all the issues with file reading are
+considered as errors. If the `plain` is set to `nil` (the default) the template does not consider file
+reading errors as fatal, and returns back the `view`.
+
+```lua
+local output = template.process("template.html", { message = "Hello, World!" }) -- or
+local output = template.process([[{{message}}
]], { message = "Hello, World!" })
+```
+
+#### string template.process_string(view, context, cache_key)
+
+This just calls `template.process(view, context, cache_key, true)`
+
+
+#### string template.process_file(view, context, cache_key)
+
+This just calls `template.process(view, context, cache_key, false)`
+
+
+#### template.render(view, context, cache_key, plain)
+
+Parses, compiles, caches (if caching is enabled) and outputs template either with `io.write`.
+You may optionally also pass `cache_key` that is used as a cache key. If `plain` evaluates to
+`true`, the `view` is considered to be plain string template (`template.load` and binary chunk detection
+is skipped on `template.parse`). If `plain` is `false"` the template is considered to be a file, and
+all the issues with file reading are considered as errors. If the `plain` is set to `nil` (the default)
+the template does not consider file reading errors as fatal, and returns back the `view`.
+
+```lua
+template.render("template.html", { message = "Hello, World!" }) -- or
+template.render([[{{message}}
]], { message = "Hello, World!" })
```
+
+#### template.render_string(view, context, cache_key)
+
+This just calls `template.render(view, context, cache_key, true)`
+
+
+#### template.render_file(view, context, cache_key)
+
+This just calls `template.render(view, context, cache_key, false)`
+
+
#### string template.parse(view, plain)
-Parses template file or string, and generates a parsed template string. This may come useful when debugging templates. You should note that if you are trying to parse a binary chunk (e.g. one returned with `template.compile`), `template.parse` will return that binary chunk as is. If optional parameter `plain` evaluates to `true`, the `view` is considered to be plain string, and the `template.load` and binary chunk detection is skipped.
+Parses template file or string, and generates a parsed template string. This may come useful when debugging
+templates. You should note that if you are trying to parse a binary chunk (e.g. one returned with
+`template.compile`), `template.parse` will return that binary chunk as is. If `plain` evaluates to
+`true`, the `view` is considered to be plain string template (`template.load` and binary chunk detection
+is skipped on `template.parse`). If `plain` is `false"` the template is considered to be a file, and
+all the issues with file reading are considered as errors. If the `plain` is set to `nil` (the default)
+the template does not consider file reading errors as fatal, and returns back the `view`.
```lua
local t1 = template.parse("template.html")
local t2 = template.parse([[{{message}}
]])
```
-#### string template.precompile(view, path, strip)
-Precompiles template as a binary chunk. This binary chunk can be written out as a file (and you may use it directly with Lua's `load` and `loadfile`). For convenience you may optionally specify `path` argument to output binary chunk to file. You may also supply `strip` parameter with value of `false` to make precompiled templates to have debug information as well (defaults to `true`).
+#### string template.parse_string(view, plain)
+
+This just calls `template.parse(view, plain, true)`
+
+
+#### string template.parse_file(view, plain)
+
+This just calls `template.parse(view, plain, false)`
+
+
+#### string template.precompile(view, path, strip, plain)
+
+Precompiles template as a binary chunk. This binary chunk can be written out as a file (and you may use it
+directly with Lua's `load` and `loadfile`). For convenience you may optionally specify `path` argument to
+output binary chunk to file. You may also supply `strip` parameter with value of `false` to make precompiled
+templates to have debug information as well (defaults to `true`). The last parameter `plain` means that
+should complilation treat the `view` as `string` (`plain = true`) or as `file path` (`plain = false`) or
+try first as a file, and fallback to `string` (`plain = nil`). In case the `plain=false` (a file) and there
+is error with `file io` the function will also error with an assertion failure.
```lua
local view = [[
@@ -385,35 +710,75 @@ template.render("precompiled-bin.html", {
})
```
-#### template.load
-This field is used to load templates.
-`template.parse` calls this function before it starts parsing the template (assuming that optional `plain` argument in `template.parse` evaluates false (the default). By default there is one loader in `template` for Lua. Users can overwrite this field with their own function. For example you may want to write a template loader function that loads templates from a database.
+#### string template.precompile_string(view, path, strip)
+
+This just calls `template.precompile(view, path, strip, true)`.
+
+
+#### string template.precompile_file(view, path, strip)
+
+This just calls `template.precompile(view, path, strip, false)`.
+
+
+#### string template.load(view, plain)
+
+This field is used to load templates. `template.parse` calls this function before it starts parsing the template
+(assuming that optional `plain` argument in `template.parse` evaluates to `false` or `nil` (the default).
-Default `template.load` for Lua (attached as template.load when used directly with Lua):
+The default `template.load` for Lua (attached as template.load when used directly with Lua):
```lua
-local function load_lua(path)
- -- read_file tries to open file from path, and return its content.
- return read_file(path) or path
+function load(view, plain)
+ if plain == true then return view end
+ local path, root = view, template.root
+ if root and root ~= EMPTY then
+ if byte(root, -1) == SOL then root = sub(root, 1, -2) end
+ if byte(view, 1) == SOL then path = sub(view, 2) end
+ path = root .. "/" .. path
+ end
+ return plain == false and assert(read_file(path)) or read_file(path) or view
end
```
-As you can see, `template` always tries (by default) to load a template from a file even if you provided template as a string. `template`.
-But if you know that your templates are always strings, and not file paths, you may use `plain` argument in `template.compile`, `template.render`, and `template.parse` OR replace `template.load` with the simplest possible template loader there is (but be aware that if your templates use `{(file.html)}` includes,
-those are considered as strings too, in this case `file.html` will be the template string that is parsed) - you could also setup a loader that finds templates in some database system, e.g. Tarantool:
+As you can see, `template` always tries (by default) to load a template from a file
+even if you provided template as a string. `template`.
+But if you know that your templates are always strings, and not file paths, you may use `plain`
+argument in `template.compile`, `template.render`, and `template.parse` OR replace `template.load`
+with the simplest possible template loader there is (but be aware that if your templates use
+`{(file.html)}` includes, those are considered as strings too, in this case `file.html` will
+be the template string that is parsed) - you could also setup a loader that finds templates in
+some database system, e.g. Redis:
```lua
-local template = require "template"
-template.load = function(s) return s end
+local template = require("template")
+template.load = function(view, plain) return view end
```
+If the `plain` parameter is `false` (`nil` is not treated as `false`), all the issues with file
+io are considered assertion errors.
+
+
+#### string template.load_string(view)
+
+This just calls `template.load(view, true)`
+
+
+#### string template.load_file(view)
+
+This just calls `template.load(view, false)`
+
+
#### template.print
-This field contains a function that is used on `template.render()` or `template.new("example.html"):render()` to output the results. By default this holds either `io.write`. You may want to (and are allowed to) overwrite this field, if you want to use your own output function instead. This is also useful if you are using some other framework, e.g. Turbo.lua (http://turbolua.org/).
+This field contains a function that is used on `template.render()` or
+`template.new("example.html"):render()` to output the results. By default this holds either
+`io.write`. You may want to (and are allowed to) overwrite this
+field, if you want to use your own output function instead. This is also useful if you are
+using some other framework, e.g. Turbo.lua (http://turbolua.org/).
```lua
-local template = require "template"
+local template = require("template")
template.print = function(s)
print(s)
@@ -421,27 +786,96 @@ template.print = function(s)
end
```
+
## Template Precompilation
-`template` supports template precompilation. This can be useful when you want to skip template parsing (and Lua interpretation) in production or if you do not want your templates distributed as plain text files on production servers. Also by precompiling, you can ensure that your templates do not contain something, that cannot be compiled (they are syntactically valid Lua). Although templates are cached (even without precompilation), there are some performance (and memory) gains. You could integrate template precompilation in your build (or deployment) scripts (maybe as Gulp, Grunt or Ant tasks).
+`template` supports template precompilation. This can be useful when you want to
+skip template parsing (and Lua interpretation) in production or if you do not want your
+templates distributed as plain text files on production servers. Also by precompiling,
+you can ensure that your templates do not contain something, that cannot be compiled
+(they are syntactically valid Lua). Although templates are cached (even without precompilation),
+there are some performance (and memory) gains. You could integrate template precompilation in
+your build (or deployment) scripts (maybe as Gulp, Grunt or Ant tasks).
+
##### Precompiling template, and output it as a binary file
```lua
-local template = require "template"
+local template = require("template")
local compiled = template.precompile("example.html", "example-bin.html")
```
+
##### Load precompiled template file, and run it with context parameters
```lua
-local template = require "template"
+local template = require("template")
template.render("example-bin.html", { "Jack", "Mary" })
```
+
## Template Helpers
-While `template` does not have much infrastucture or ways to extend it, you still have a few possibilities that you may try.
+### Built-in Helpers
+
+#### echo(...)
+
+Echoes output. This is useful with `{% .. %}`:
+
+```lua
+require("template").render[[
+begin
+{%
+for i=1, 10 do
+ echo("\tline: ", i, "\n")
+end
+%}
+end
+]]
+```
+
+This will output:
+
+```
+begin
+ line: 1
+ line: 2
+ line: 3
+ line: 4
+ line: 5
+ line: 6
+ line: 7
+ line: 8
+ line: 9
+ line: 10
+end
+```
+
+This can also be written as but `echo` might come handy in some cases:
+
+```lua
+require("template").render([[
+begin
+{% for i=1, 10 do %}
+ line: {* i *}
+{% end %}
+end
+]])
+```
+
+
+#### include(view, context)
+
+This is mainly used with internally with `{(view.hmtl)}`, `{["view.hmtl"]}` and
+with blocks `{-block-name-}..{-block-name-}`. If `context` is not given the context
+used to compile parent view is used. This function will compile the `view` and call
+the resulting function with `context` (or the `context` of parent view if not given).
+
+
+### Other Ways to Extend
+
+While `template` does not have much infrastucture or ways to extend it,
+you still have a few possibilities that you may try.
* Adding methods to global `string`, and `table` types (not encouraged, though)
* Wrap your values with something before adding them in context (e.g. proxy-table)
@@ -449,7 +883,8 @@ While `template` does not have much infrastucture or ways to extend it, you stil
* Add local functions either to `template` table or `context` table
* Use metamethods in your tables
-While modifying global types seems convenient, it can have nasty side effects. That's why I suggest you to look at these libraries, and articles first:
+While modifying global types seems convenient, it can have nasty side effects.
+That's why I suggest you to look at these libraries, and articles first:
* Method Chaining Wrapper (http://lua-users.org/wiki/MethodChainingWrapper)
* Moses (https://github.com/Yonaba/Moses)
@@ -457,21 +892,25 @@ While modifying global types seems convenient, it can have nasty side effects. T
You could for example add Moses' or Underscore's `_` to template table or context table.
+
##### Example
```lua
-local _ = require "moses"
-local template = require "template"
+local _ = require("moses")
+local template = require("template")
template._ = _
```
-Then you can use `_` inside your templates. I created one example template helper that can be found from here:
+Then you can use `_` inside your templates. I created one example template helper
+that can be found from here:
https://github.com/tarantool/template/blob/master/lib/template/html.lua
+
##### Lua
```lua
-local template = require "template"
+local template = require("template")
+local html = require "template.html"
template.render([[
@@ -493,6 +932,7 @@ template.render([[
})
```
+
##### Output
```html
@@ -518,16 +958,20 @@ template.render([[
```
+
## Usage Examples
### Template Including
-You may include templates inside templates with `{(template)}` and `{(template, context)}` syntax. The first one uses the current context as a context for included template, and the second one replaces it with a new context. Here is example of using includes and passing a different context to include file:
+You may include templates inside templates with `{(template)}` and `{(template, context)}` syntax.
+The first one uses the current context as a context for included template, and the second one replaces
+it with a new context. Here is example of using includes and passing a different context to include file:
+
##### Lua
```lua
-local template = require "template"
+local template = require("template")
template.render("include.html", { users = {
{ name = "Jane", age = 29 },
{ name = "John", age = 25 }
@@ -567,13 +1011,16 @@ template.render("include.html", { users = {