X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=blobdiff_plain;f=libs%2Fweb%2Fluasrc%2Ftemplate.lua;h=962c2ea8863809ebe688006d3f451880e73e8ada;hp=61e4e39ec97d685d5c4fa5dcbacbe1312d7874d3;hb=24c4cce3ae278c0511a65aded38ef83b2e49d3d4;hpb=211c6394a0d145d3215893b827241723bb358a13 diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua index 61e4e39ec..962c2ea88 100644 --- a/libs/web/luasrc/template.lua +++ b/libs/web/luasrc/template.lua @@ -23,183 +23,68 @@ See the License for the specific language governing permissions and limitations under the License. ]]-- -module("luci.template", package.seeall) -require("luci.config") -require("luci.util") -require("luci.fs") -require("luci.http") +local util = require "luci.util" +local config = require "luci.config" +local tparser = require "luci.template.parser" -luci.config.template = luci.config.template or {} +local tostring, pairs, loadstring = tostring, pairs, loadstring +local setmetatable, loadfile = setmetatable, loadfile +local getfenv, setfenv, rawget = getfenv, setfenv, rawget +local assert, type, error = assert, type, error -viewdir = luci.config.template.viewdir or luci.sys.libpath() .. "/view" -compiledir = luci.config.template.compiledir or luci.sys.libpath() .. "/view" +--- LuCI template library. +module "luci.template" - --- Compile modes: --- none: Never compile, only use precompiled data from files --- memory: Always compile, do not save compiled files, ignore precompiled --- file: Compile on demand, save compiled files, update precompiled -compiler_mode = luci.config.template.compiler_mode or "memory" +config.template = config.template or {} +viewdir = config.template.viewdir or util.libpath() .. "/view" -- Define the namespace for template modules -viewns = { - write = io.write, - include = function(name) Template(name):render(getfenv(2)) end, -} - --- Compiles a given template into an executable Lua module -function compile(template) - -- Search all <% %> expressions (remember: Lua table indexes begin with #1) - local function expr_add(command) - table.insert(expr, command) - return "<%" .. tostring(#expr) .. "%>" - end - - -- As "expr" should be local, we have to assign it to the "expr_add" scope - local expr = {} - luci.util.extfenv(expr_add, "expr", expr) - - -- Save all expressiosn to table "expr" - template = template:gsub("<%%(.-)%%>", expr_add) - - local function sanitize(s) - s = luci.util.escape(s) - s = luci.util.escape(s, "'") - s = luci.util.escape(s, "\n") - return s - end - - -- Escape and sanitize all the template (all non-expressions) - template = sanitize(template) +context = util.threadlocal() - -- 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) - 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)) - else - re = r_exec:format(v) - end - template = template:gsub("<%%"..tostring(k).."%%>", re) - end - - return loadstring(template) -end - --- Oldstyle render shortcut -function render(name, scope, ...) - scope = scope or getfenv(2) - local s, t = pcall(Template, name) - if not s then - error(t) - else - t:render(scope, ...) - end +--- Render a certain template. +-- @param name Template name +-- @param scope Scope to assign to template (optional) +function render(name, scope) + return Template(name):render(scope or getfenv(2)) end -- Template class -Template = luci.util.class() +Template = util.class() -- Shared template cache to store templates in to avoid unnecessary reloading -Template.cache = {} +Template.cache = setmetatable({}, {__mode = "v"}) -- Constructor - Reads and compiles the template on-demand function Template.__init__(self, name) - if self.cache[name] then - self.template = self.cache[name] - else - self.template = nil - end + + self.template = self.cache[name] + self.name = name -- Create a new namespace for this template - self.viewns = {} - - -- Copy over from general namespace - for k, v in pairs(viewns) do - self.viewns[k] = v - 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 .. "/" .. luci.sys.process.info("uid") - - -- Compile and build - local sourcefile = viewdir .. "/" .. name .. ".htm" - local compiledfile = cdir .. "/" .. luci.http.urlencode(name) .. ".lua" - local err - - if compiler_mode == "file" then - local tplmt = luci.fs.mtime(sourcefile) - local commt = luci.fs.mtime(compiledfile) - - if not luci.fs.mtime(cdir) then - luci.fs.mkdir(cdir, true) - luci.fs.chmod(luci.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 = luci.fs.readfile(sourcefile) - - if source then - local compiled, err = compile(source) - - luci.fs.writefile(compiledfile, luci.util.dump(compiled)) - 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("Failed to load template '" .. name .. "'.\n" .. + "Error while parsing template '" .. sourcefile .. "'.\n" .. + "A syntax error occured near '" .. + (err or "(nil)"):gsub("\t", "\\t"):gsub("\n", "\\n") .. "'.") else - self.template, err = loadfile(compiledfile) - end - - elseif compiler_mode == "none" then - self.template, err = loadfile(self.compiledfile) - - elseif compiler_mode == "memory" then - local source - source, err = luci.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 @@ -208,17 +93,16 @@ 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 - luci.util.resfenv(self.template) - luci.util.updfenv(self.template, scope) - luci.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 - self.template() - - -- Reset environment - setfenv(self.template, oldfenv) + local stat, err = util.copcall(self.template) + if not stat then + error("Failed to execute template '" .. self.name .. "'.\n" .. + "A runtime error occured: " .. tostring(err or "(nil)")) + end end