]]--
-local fs = require"luci.fs"
-local sys = require "luci.sys"
local util = require "luci.util"
-local table = require "table"
-local string = require "string"
local config = require "luci.config"
-local coroutine = require "coroutine"
+local tparser = require "luci.template.parser"
local tostring, pairs, loadstring = tostring, pairs, loadstring
local setmetatable, loadfile = setmetatable, loadfile
-local getfenv, setfenv = getfenv, setfenv
+local getfenv, setfenv, rawget = getfenv, setfenv, rawget
local assert, type, error = assert, type, error
--- LuCI template library.
module "luci.template"
config.template = config.template or {}
-
-viewdir = config.template.viewdir or util.libpath() .. "/view"
-compiledir = config.template.compiledir or util.libpath() .. "/view"
-
-
--- Compile modes:
--- memory: Always compile, do not save compiled files, ignore precompiled
--- file: Compile on demand, save compiled files, update precompiled
-compiler_mode = config.template.compiler_mode or "memory"
+viewdir = config.template.viewdir or util.libpath() .. "/view"
-- Define the namespace for template modules
context = util.threadlocal()
-viewns = {
- include = function(name) Template(name):render(getfenv(2)) end,
-}
-
---- Manually compile a given template into an executable Lua function
--- @param template LuCI template
--- @return Lua template function
-function compile(template)
- local expr = {}
-
- -- Search all <% %> expressions
- local function expr_add(ws1, skip1, command, skip2, ws2)
- table.insert(expr, command)
- return ( #skip1 > 0 and "" or ws1 ) ..
- "<%" .. tostring(#expr) .. "%>" ..
- ( #skip2 > 0 and "" or ws2 )
- end
-
- -- Save all expressiosn to table "expr"
- template = template:gsub("(%s*)<%%(%-?)(.-)(%-?)%%>(%s*)", expr_add)
-
- local function sanitize(s)
- s = "%q" % s
- return s:sub(2, #s-1)
- end
-
- -- Escape and sanitize all the template (all non-expressions)
- template = sanitize(template)
-
- -- Template module header/footer declaration
- local header = 'write("'
- local footer = '")'
-
- template = header .. template .. footer
-
- -- Replacements
- local r_include = '")\ninclude("%s")\nwrite("'
- local r_i18n = '"..translate("%1","%2").."'
- local r_i18n2 = '"..translate("%1", "").."'
- local r_pexec = '"..(%s or "").."'
- local r_exec = '")\n%s\nwrite("'
-
- -- Parse the expressions
- for k,v in pairs(expr) do
- local p = v:sub(1, 1)
- v = v:gsub("%%", "%%%%")
- local re = nil
- if p == "+" then
- re = r_include:format(sanitize(string.sub(v, 2)))
- elseif p == ":" then
- if v:find(" ") then
- re = sanitize(v):gsub(":(.-) (.*)", r_i18n)
- else
- re = sanitize(v):gsub(":(.+)", r_i18n2)
- end
- elseif p == "=" then
- re = r_pexec:format(v:sub(2))
- elseif p == "#" then
- re = ""
- else
- re = r_exec:format(v)
- end
- template = template:gsub("<%%"..tostring(k).."%%>", re)
- end
-
- return loadstring(template)
-end
-
--- Render a certain template.
-- @param name Template name
-- @param scope Scope to assign to template (optional)
-- Constructor - Reads and compiles the template on-demand
-function Template.__init__(self, name, srcfile, comfile)
- local function _encode_filename(str)
-
- local function __chrenc( chr )
- return "%%%02x" % string.byte( chr )
- end
-
- if type(str) == "string" then
- str = str:gsub(
- "([^a-zA-Z0-9$_%-%.%+!*'(),])",
- __chrenc
- )
- end
-
- return str
- end
+function Template.__init__(self, name)
self.template = self.cache[name]
self.name = name
-- Create a new namespace for this template
- self.viewns = {sink=self.sink}
-
- -- Copy over from general namespace
- util.update(self.viewns, viewns)
- if context.viewns then
- util.update(self.viewns, context.viewns)
- end
+ self.viewns = context.viewns
-- If we have a cached template, skip compiling and loading
- if self.template then
- return
- end
-
- -- Enforce cache security
- local cdir = compiledir .. "/" .. sys.process.info("uid")
-
- -- Compile and build
- local sourcefile = srcfile or (viewdir .. "/" .. name .. ".htm")
- local compiledfile = comfile or (cdir .. "/" .. _encode_filename(name) .. ".lua")
- local err
-
- if compiler_mode == "file" then
- local tplmt = fs.mtime(sourcefile)
- local commt = fs.mtime(compiledfile)
-
- if not fs.mtime(cdir) then
- fs.mkdir(cdir, true)
- fs.chmod(fs.dirname(cdir), "a+rxw")
- end
-
- -- Build if there is no compiled file or if compiled file is outdated
- if ((commt == nil) and not (tplmt == nil))
- or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then
- local source
- source, err = fs.readfile(sourcefile)
-
- if source then
- local compiled, err = compile(source)
-
- fs.writefile(compiledfile, util.get_bytecode(compiled))
- fs.chmod(compiledfile, "a-rwx,u+rw")
- self.template = compiled
- end
+ if not self.template then
+
+ -- Compile template
+ local err
+ local sourcefile = viewdir .. "/" .. name .. ".htm"
+
+ self.template, _, err = tparser.parse(sourcefile)
+
+ -- If we have no valid template throw error, otherwise cache the template
+ if not self.template then
+ error(err)
else
- assert(
- sys.process.info("uid") == fs.stat(compiledfile, "uid")
- and fs.stat(compiledfile, "mode") == "rw-------",
- "Fatal: Cachefile is not sane!"
- )
- self.template, err = loadfile(compiledfile)
- end
-
- elseif compiler_mode == "memory" then
- local source
- source, err = fs.readfile(sourcefile)
- if source then
- self.template, err = compile(source)
+ self.cache[name] = self.template
end
-
- end
-
- -- If we have no valid template throw error, otherwise cache the template
- if not self.template then
- error(err)
- else
- self.cache[name] = self.template
end
end
function Template.render(self, scope)
scope = scope or getfenv(2)
- -- Save old environment
- local oldfenv = getfenv(self.template)
-
-- Put our predefined objects in the scope of the template
- util.resfenv(self.template)
- util.updfenv(self.template, scope)
- util.updfenv(self.template, self.viewns)
+ setfenv(self.template, setmetatable({}, {__index =
+ function(tbl, key)
+ return rawget(tbl, key) or self.viewns[key] or scope[key]
+ end}))
-- Now finally render the thing
local stat, err = util.copcall(self.template)
if not stat then
- setfenv(self.template, oldfenv)
error("Error in template %s: %s" % {self.name, err})
end
-
- -- Reset environment
- setfenv(self.template, oldfenv)
end