commit 4f6198094cf4134179d1f9c9fa8f79759a27c87e
[project/luci.git] / libs / web / luasrc / template.lua
1 --[[
2 LuCI - Template Parser
3
4 Description:
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.
7
8 FileId: $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
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
16
17 http://www.apache.org/licenses/LICENSE-2.0
18
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.
24
25 ]]--
26 module("luci.template", package.seeall)
27
28 require("luci.config")
29 require("luci.util")
30 require("luci.fs")
31 require("luci.http")
32
33 viewdir = luci.sys.libpath() .. "/view/"
34
35
36 -- Compile modes:
37 -- none: Never compile, only use precompiled data from files
38 -- memory: Always compile, do not save compiled files, ignore precompiled
39 -- file: Compile on demand, save compiled files, update precompiled
40 compiler_mode = "memory"
41
42
43 -- This applies to compiler modes "always" and "smart"
44 --
45 -- Produce compiled lua code rather than lua sourcecode
46 -- WARNING: Increases template size heavily!!!
47 -- This produces the same bytecode as luac but does not have a strip option
48 compiler_enable_bytecode = false
49
50
51 -- Define the namespace for template modules
52 viewns = {
53 write = io.write,
54 include = function(name) Template(name):render(getfenv(2)) end,
55 }
56
57 -- Compiles a given template into an executable Lua module
58 function compile(template)
59 -- Search all <% %> expressions (remember: Lua table indexes begin with #1)
60 local function expr_add(command)
61 table.insert(expr, command)
62 return "<%" .. tostring(#expr) .. "%>"
63 end
64
65 -- As "expr" should be local, we have to assign it to the "expr_add" scope
66 local expr = {}
67 luci.util.extfenv(expr_add, "expr", expr)
68
69 -- Save all expressiosn to table "expr"
70 template = template:gsub("<%%(.-)%%>", expr_add)
71
72 local function sanitize(s)
73 s = luci.util.escape(s)
74 s = luci.util.escape(s, "'")
75 s = luci.util.escape(s, "\n")
76 return s
77 end
78
79 -- Escape and sanitize all the template (all non-expressions)
80 template = sanitize(template)
81
82 -- Template module header/footer declaration
83 local header = "write('"
84 local footer = "')"
85
86 template = header .. template .. footer
87
88 -- Replacements
89 local r_include = "')\ninclude('%s')\nwrite('"
90 local r_i18n = "'..translate('%1','%2')..'"
91 local r_pexec = "'..(%s or '')..'"
92 local r_exec = "')\n%s\nwrite('"
93
94 -- Parse the expressions
95 for k,v in pairs(expr) do
96 local p = v:sub(1, 1)
97 local re = nil
98 if p == "+" then
99 re = r_include:format(sanitize(string.sub(v, 2)))
100 elseif p == ":" then
101 re = sanitize(v):gsub(":(.-) (.+)", r_i18n)
102 elseif p == "=" then
103 re = r_pexec:format(v:sub(2))
104 else
105 re = r_exec:format(v)
106 end
107 template = template:gsub("<%%"..tostring(k).."%%>", re)
108 end
109
110 if compiler_enable_bytecode then
111 tf = loadstring(template)
112 template = string.dump(tf)
113 end
114
115 return template
116 end
117
118 -- Oldstyle render shortcut
119 function render(name, scope, ...)
120 scope = scope or getfenv(2)
121 local s, t = pcall(Template, name)
122 if not s then
123 error(t)
124 else
125 t:render(scope, ...)
126 end
127 end
128
129
130 -- Template class
131 Template = luci.util.class()
132
133 -- Shared template cache to store templates in to avoid unnecessary reloading
134 Template.cache = {}
135
136
137 -- Constructor - Reads and compiles the template on-demand
138 function Template.__init__(self, name)
139 if self.cache[name] then
140 self.template = self.cache[name]
141 else
142 self.template = nil
143 end
144
145 -- Create a new namespace for this template
146 self.viewns = {}
147
148 -- Copy over from general namespace
149 for k, v in pairs(viewns) do
150 self.viewns[k] = v
151 end
152
153 -- If we have a cached template, skip compiling and loading
154 if self.template then
155 return
156 end
157
158 -- Compile and build
159 local sourcefile = viewdir .. name .. ".htm"
160 local compiledfile = viewdir .. name .. ".lua"
161 local err
162
163 if compiler_mode == "file" then
164 local tplmt = luci.fs.mtime(sourcefile)
165 local commt = luci.fs.mtime(compiledfile)
166
167 -- Build if there is no compiled file or if compiled file is outdated
168 if ((commt == nil) and not (tplmt == nil))
169 or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then
170 local source
171 source, err = luci.fs.readfile(sourcefile)
172
173 if source then
174 local compiled = compile(source)
175 luci.fs.writefile(compiledfile, compiled)
176 self.template, err = loadstring(compiled)
177 end
178 else
179 self.template, err = loadfile(compiledfile)
180 end
181
182 elseif compiler_mode == "none" then
183 self.template, err = loadfile(self.compiledfile)
184
185 elseif compiler_mode == "memory" then
186 local source
187 source, err = luci.fs.readfile(sourcefile)
188 if source then
189 self.template, err = loadstring(compile(source))
190 end
191
192 end
193
194 -- If we have no valid template throw error, otherwise cache the template
195 if not self.template then
196 error(err)
197 else
198 self.cache[name] = self.template
199 end
200 end
201
202
203 -- Renders a template
204 function Template.render(self, scope)
205 scope = scope or getfenv(2)
206
207 -- Save old environment
208 local oldfenv = getfenv(self.template)
209
210 -- Put our predefined objects in the scope of the template
211 luci.util.resfenv(self.template)
212 luci.util.updfenv(self.template, scope)
213 luci.util.updfenv(self.template, self.viewns)
214
215 -- Now finally render the thing
216 self.template()
217
218 -- Reset environment
219 setfenv(self.template, oldfenv)
220 end