5 A template parser supporting includes, translations, Lua code blocks
6 and more. It can be used either as a compiler or as an interpreter.
11 Copyright 2008 Steven Barth <steven@midlink.org>
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at
17 http://www.apache.org/licenses/LICENSE-2.0
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
27 local fs = require "nixio.fs"
28 local sys = require "luci.sys"
29 local util = require "luci.util"
30 local table = require "table"
31 local string = require "string"
32 local config = require "luci.config"
33 local coroutine = require "coroutine"
34 local nixio = require "nixio", require "nixio.util"
36 local tostring, pairs, loadstring = tostring, pairs, loadstring
37 local setmetatable, loadfile = setmetatable, loadfile
38 local getfenv, setfenv, rawget = getfenv, setfenv, rawget
39 local assert, type, error = assert, type, error
41 --- LuCI template library.
42 module "luci.template"
44 config.template = config.template or {}
46 viewdir = config.template.viewdir or util.libpath() .. "/view"
47 compiledir = config.template.compiledir or util.libpath() .. "/view"
51 -- memory: Always compile, do not save compiled files, ignore precompiled
52 -- file: Compile on demand, save compiled files, update precompiled
53 compiler_mode = config.template.compiler_mode or "memory"
56 -- Define the namespace for template modules
57 context = util.threadlocal()
59 --- Manually compile a given template into an executable Lua function
60 -- @param template LuCI template
61 -- @return Lua template function
62 function compile(template)
65 -- Search all <% %> expressions
66 local function expr_add(ws1, skip1, command, skip2, ws2)
67 expr[#expr+1] = command
68 return ( #skip1 > 0 and "" or ws1 ) ..
69 "<%" .. tostring(#expr) .. "%>" ..
70 ( #skip2 > 0 and "" or ws2 )
73 -- Save all expressiosn to table "expr"
74 template = template:gsub("(%s*)<%%(%-?)(.-)(%-?)%%>(%s*)", expr_add)
76 local function sanitize(s)
81 -- Escape and sanitize all the template (all non-expressions)
82 template = sanitize(template)
84 -- Template module header/footer declaration
85 local header = 'write("'
88 template = header .. template .. footer
91 local r_include = '")\ninclude("%s")\nwrite("'
92 local r_i18n = '")\nwrite(translate("%1","%2"))\nwrite("'
93 local r_i18n2 = '")\nwrite(translate("%1", ""))\nwrite("'
94 local r_pexec = '")\nwrite(tostring(%s or ""))\nwrite("'
95 local r_exec = '")\n%s\nwrite("'
97 -- Parse the expressions
98 for k,v in pairs(expr) do
100 v = v:gsub("%%", "%%%%")
103 re = r_include:format(sanitize(string.sub(v, 2)))
106 re = sanitize(v):gsub(":(.-) (.*)", r_i18n)
108 re = sanitize(v):gsub(":(.+)", r_i18n2)
111 re = r_pexec:format(v:sub(2))
115 re = r_exec:format(v)
117 template = template:gsub("<%%"..tostring(k).."%%>", re)
120 return loadstring(template)
123 --- Render a certain template.
124 -- @param name Template name
125 -- @param scope Scope to assign to template (optional)
126 function render(name, scope)
127 return Template(name):render(scope or getfenv(2))
132 Template = util.class()
134 -- Shared template cache to store templates in to avoid unnecessary reloading
135 Template.cache = setmetatable({}, {__mode = "v"})
138 -- Constructor - Reads and compiles the template on-demand
139 function Template.__init__(self, name)
140 local function _encode_filename(str)
142 local function __chrenc( chr )
143 return "%%%02x" % string.byte( chr )
146 if type(str) == "string" then
148 "([^a-zA-Z0-9$_%-%.%+!*'(),])",
156 self.template = self.cache[name]
159 -- Create a new namespace for this template
160 self.viewns = context.viewns
162 -- If we have a cached template, skip compiling and loading
163 if self.template then
167 -- Enforce cache security
168 local cdir = compiledir .. "/" .. sys.process.info("uid")
171 local sourcefile = viewdir .. "/" .. name
172 local compiledfile = cdir .. "/" .. _encode_filename(name) .. ".lua"
175 if compiler_mode == "file" then
176 local tplmt = fs.stat(sourcefile, "mtime") or fs.stat(sourcefile .. ".htm", "mtime")
177 local commt = fs.stat(compiledfile, "mtime")
179 if not fs.stat(cdir, "mtime") then
181 fs.chmod(fs.dirname(cdir), 777)
184 assert(tplmt or commt, "No such template: " .. name)
186 -- Build if there is no compiled file or if compiled file is outdated
187 if not commt or (commt and tplmt and commt < tplmt) then
189 source, err = fs.readfile(sourcefile) or fs.readfile(sourcefile .. ".htm")
192 local compiled, err = compile(source)
194 local f = nixio.open(compiledfile, "w", 600)
195 f:writeall(util.get_bytecode(compiled))
197 self.template = compiled
201 sys.process.info("uid") == fs.stat(compiledfile, "uid")
202 and fs.stat(compiledfile, "modestr") == "rw-------",
203 "Fatal: Cachefile is not sane!"
205 self.template, err = loadfile(compiledfile)
208 elseif compiler_mode == "memory" then
210 source, err = fs.readfile(sourcefile) or fs.readfile(sourcefile .. ".htm")
212 self.template, err = compile(source)
217 -- If we have no valid template throw error, otherwise cache the template
218 if not self.template then
221 self.cache[name] = self.template
226 -- Renders a template
227 function Template.render(self, scope)
228 scope = scope or getfenv(2)
230 -- Put our predefined objects in the scope of the template
231 setfenv(self.template, setmetatable({}, {__index =
233 return rawget(tbl, key) or self.viewns[key] or scope[key]
236 -- Now finally render the thing
237 local stat, err = util.copcall(self.template)
239 error("Error in template %s: %s" % {self.name, err})