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