summaryrefslogtreecommitdiffhomepage
path: root/libs/web/luasrc/template.lua
diff options
context:
space:
mode:
authorSteven Barth <steven@midlink.org>2008-05-27 12:23:39 +0000
committerSteven Barth <steven@midlink.org>2008-05-27 12:23:39 +0000
commitd35a620e9f5665a94967f4bd02c93581a1dd7e00 (patch)
treecbe7a9b9fe71e40d850c649361c87608d56ea48b /libs/web/luasrc/template.lua
parent5df565faba5eb0c26a7ce0e2d6e9092bf71cb1e8 (diff)
commit 4f6198094cf4134179d1f9c9fa8f79759a27c87e
Author: Felix Fietkau <nbd@openwrt.org> Date: Tue May 27 13:56:12 2008 +0200 rename src/ to luasrc/
Diffstat (limited to 'libs/web/luasrc/template.lua')
-rw-r--r--libs/web/luasrc/template.lua220
1 files changed, 220 insertions, 0 deletions
diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua
new file mode 100644
index 0000000000..369aa0a30b
--- /dev/null
+++ b/libs/web/luasrc/template.lua
@@ -0,0 +1,220 @@
+--[[
+LuCI - Template Parser
+
+Description:
+A template parser supporting includes, translations, Lua code blocks
+and more. It can be used either as a compiler or as an interpreter.
+
+FileId: $Id$
+
+License:
+Copyright 2008 Steven Barth <steven@midlink.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+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")
+
+viewdir = luci.sys.libpath() .. "/view/"
+
+
+-- 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 = "memory"
+
+
+-- This applies to compiler modes "always" and "smart"
+--
+-- Produce compiled lua code rather than lua sourcecode
+-- WARNING: Increases template size heavily!!!
+-- This produces the same bytecode as luac but does not have a strip option
+compiler_enable_bytecode = false
+
+
+-- 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)
+
+ -- 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_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
+ re = sanitize(v):gsub(":(.-) (.+)", r_i18n)
+ elseif p == "=" then
+ re = r_pexec:format(v:sub(2))
+ else
+ re = r_exec:format(v)
+ end
+ template = template:gsub("<%%"..tostring(k).."%%>", re)
+ end
+
+ if compiler_enable_bytecode then
+ tf = loadstring(template)
+ template = string.dump(tf)
+ end
+
+ return 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
+end
+
+
+-- Template class
+Template = luci.util.class()
+
+-- Shared template cache to store templates in to avoid unnecessary reloading
+Template.cache = {}
+
+
+-- 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
+
+ -- 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
+
+ -- If we have a cached template, skip compiling and loading
+ if self.template then
+ return
+ end
+
+ -- Compile and build
+ local sourcefile = viewdir .. name .. ".htm"
+ local compiledfile = viewdir .. name .. ".lua"
+ local err
+
+ if compiler_mode == "file" then
+ local tplmt = luci.fs.mtime(sourcefile)
+ local commt = luci.fs.mtime(compiledfile)
+
+ -- 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 = compile(source)
+ luci.fs.writefile(compiledfile, compiled)
+ self.template, err = loadstring(compiled)
+ end
+ 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 = loadstring(compile(source))
+ 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
+
+
+-- Renders a template
+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)
+
+ -- Now finally render the thing
+ self.template()
+
+ -- Reset environment
+ setfenv(self.template, oldfenv)
+end