* Prepare german translation cleanup
[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 luci.config.template = luci.config.template or {}
34
35 viewdir    = luci.config.template.viewdir or luci.sys.libpath() .. "/view"
36 compiledir = luci.config.template.compiledir or luci.sys.libpath() .. "/view"
37
38
39 -- Compile modes:
40 -- none:        Never compile, only use precompiled data from files
41 -- memory:      Always compile, do not save compiled files, ignore precompiled 
42 -- file:        Compile on demand, save compiled files, update precompiled
43 compiler_mode = luci.config.template.compiler_mode or "memory"
44
45
46 -- Define the namespace for template modules
47 viewns = {
48         write      = io.write,
49         include    = function(name) Template(name):render(getfenv(2)) end,      
50 }
51
52 -- Compiles a given template into an executable Lua module
53 function compile(template)      
54         -- Search all <% %> expressions (remember: Lua table indexes begin with #1)
55         local function expr_add(command)
56                 table.insert(expr, command)
57                 return "<%" .. tostring(#expr) .. "%>"
58         end
59         
60         -- As "expr" should be local, we have to assign it to the "expr_add" scope 
61         local expr = {}
62         luci.util.extfenv(expr_add, "expr", expr)
63         
64         -- Save all expressiosn to table "expr"
65         template = template:gsub("<%%(.-)%%>", expr_add)
66         
67         local function sanitize(s)
68                 s = luci.util.escape(s)
69                 s = luci.util.escape(s, "'")
70                 s = luci.util.escape(s, "\n")
71                 return s
72         end
73         
74         -- Escape and sanitize all the template (all non-expressions)
75         template = sanitize(template)
76
77         -- Template module header/footer declaration
78         local header = "write('"
79         local footer = "')"
80         
81         template = header .. template .. footer
82         
83         -- Replacements
84         local r_include = "')\ninclude('%s')\nwrite('"
85         local r_i18n    = "'..translate('%1','%2')..'"
86         local r_i18n2    = "'..translate('%1', '')..'"
87         local r_pexec   = "'..(%s or '')..'"
88         local r_exec    = "')\n%s\nwrite('"
89         
90         -- Parse the expressions
91         for k,v in pairs(expr) do
92                 local p = v:sub(1, 1)
93                 local re = nil
94                 if p == "+" then
95                         re = r_include:format(sanitize(string.sub(v, 2)))
96                 elseif p == ":" then
97                         if v:find(" ") then
98                                 re = sanitize(v):gsub(":(.-) (.*)", r_i18n)
99                         else
100                                 re = sanitize(v):gsub(":(.+)", r_i18n2)
101                         end
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         return loadstring(template)
111 end
112
113 -- Oldstyle render shortcut
114 function render(name, scope, ...)
115         scope = scope or getfenv(2)
116         local s, t = pcall(Template, name)
117         if not s then
118                 error(t)
119         else
120                 t:render(scope, ...)
121         end
122 end
123
124
125 -- Template class
126 Template = luci.util.class()
127
128 -- Shared template cache to store templates in to avoid unnecessary reloading
129 Template.cache = {}
130
131
132 -- Constructor - Reads and compiles the template on-demand
133 function Template.__init__(self, name)  
134         if self.cache[name] then
135                 self.template = self.cache[name]
136         else
137                 self.template = nil
138         end
139         
140         -- Create a new namespace for this template
141         self.viewns = {}
142         
143         -- Copy over from general namespace
144         for k, v in pairs(viewns) do
145                 self.viewns[k] = v
146         end     
147         
148         -- If we have a cached template, skip compiling and loading
149         if self.template then
150                 return
151         end
152         
153         -- Enforce cache security
154         local cdir = compiledir .. "/" .. luci.sys.process.info("uid")
155         
156         -- Compile and build
157         local sourcefile   = viewdir    .. "/" .. name .. ".htm"
158         local compiledfile = cdir .. "/" .. luci.http.urlencode(name) .. ".lua"
159         local err       
160         
161         if compiler_mode == "file" then
162                 local tplmt = luci.fs.mtime(sourcefile)
163                 local commt = luci.fs.mtime(compiledfile)
164                 
165                 if not luci.fs.mtime(cdir) then
166                         luci.fs.mkdir(cdir, true)
167                         luci.fs.chmod(luci.fs.dirname(cdir), "a+rxw")
168                 end
169                                 
170                 -- Build if there is no compiled file or if compiled file is outdated
171                 if ((commt == nil) and not (tplmt == nil))
172                 or (not (commt == nil) and not (tplmt == nil) and commt < tplmt) then
173                         local source
174                         source, err = luci.fs.readfile(sourcefile)
175                         
176                         if source then
177                                 local compiled, err = compile(source)
178                                 
179                                 luci.fs.writefile(compiledfile, luci.util.dump(compiled))
180                                 self.template = compiled
181                         end
182                 else
183                         self.template, err = loadfile(compiledfile)
184                 end
185                 
186         elseif compiler_mode == "none" then
187                 self.template, err = loadfile(self.compiledfile)
188                 
189         elseif compiler_mode == "memory" then
190                 local source
191                 source, err = luci.fs.readfile(sourcefile)
192                 if source then
193                         self.template, err = compile(source)
194                 end
195                         
196         end
197         
198         -- If we have no valid template throw error, otherwise cache the template
199         if not self.template then
200                 error(err)
201         else
202                 self.cache[name] = self.template
203         end
204 end
205
206
207 -- Renders a template
208 function Template.render(self, scope)
209         scope = scope or getfenv(2)
210         
211         -- Save old environment
212         local oldfenv = getfenv(self.template)
213         
214         -- Put our predefined objects in the scope of the template
215         luci.util.resfenv(self.template)
216         luci.util.updfenv(self.template, scope)
217         luci.util.updfenv(self.template, self.viewns)
218         
219         -- Now finally render the thing
220         self.template()
221         
222         -- Reset environment
223         setfenv(self.template, oldfenv)
224 end