luci-base: break circular luci.config <> luci.model.uci dependency
[project/luci.git] / modules / luci-base / luasrc / template.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Licensed to the public under the Apache License 2.0.
3
4 local util = require "luci.util"
5 local config = require "luci.config"
6 local tparser = require "luci.template.parser"
7
8 local tostring, pairs, loadstring = tostring, pairs, loadstring
9 local setmetatable, loadfile = setmetatable, loadfile
10 local getfenv, setfenv, rawget = getfenv, setfenv, rawget
11 local assert, type, error = assert, type, error
12
13 --- LuCI template library.
14 module "luci.template"
15
16 config.template = config.template or {}
17 viewdir = config.template.viewdir or util.libpath() .. "/view"
18
19
20 -- Define the namespace for template modules
21 context = util.threadlocal()
22
23 --- Render a certain template.
24 -- @param name          Template name
25 -- @param scope         Scope to assign to template (optional)
26 function render(name, scope)
27         return Template(name):render(scope or getfenv(2))
28 end
29
30 --- Render a template from a string.
31 -- @param template      Template string
32 -- @param scope         Scope to assign to template (optional)
33 function render_string(template, scope)
34         return Template(nil, template):render(scope or getfenv(2))
35 end
36
37
38 -- Template class
39 Template = util.class()
40
41 -- Shared template cache to store templates in to avoid unnecessary reloading
42 Template.cache = setmetatable({}, {__mode = "v"})
43
44
45 -- Constructor - Reads and compiles the template on-demand
46 function Template.__init__(self, name, template)
47         if name then
48                 self.template = self.cache[name]
49                 self.name = name
50         else
51                 self.name = "[string]"
52         end
53
54         -- Create a new namespace for this template
55         self.viewns = context.viewns
56         
57         -- If we have a cached template, skip compiling and loading
58         if not self.template then
59
60                 -- Compile template
61                 local err
62                 local sourcefile
63
64                 if name then
65                         sourcefile = viewdir .. "/" .. name .. ".htm"
66                         self.template, _, err = tparser.parse(sourcefile)
67                 else
68                         sourcefile = "[string]"
69                         self.template, _, err = tparser.parse_string(template)
70                 end
71
72                 -- If we have no valid template throw error, otherwise cache the template
73                 if not self.template then
74                         error("Failed to load template '" .. name .. "'.\n" ..
75                               "Error while parsing template '" .. sourcefile .. "':\n" ..
76                               (err or "Unknown syntax error"))
77                 elseif name then
78                         self.cache[name] = self.template
79                 end
80         end
81 end
82
83
84 -- Renders a template
85 function Template.render(self, scope)
86         scope = scope or getfenv(2)
87         
88         -- Put our predefined objects in the scope of the template
89         setfenv(self.template, setmetatable({}, {__index =
90                 function(tbl, key)
91                         return rawget(tbl, key) or self.viewns[key] or scope[key]
92                 end}))
93         
94         -- Now finally render the thing
95         local stat, err = util.copcall(self.template)
96         if not stat then
97                 error("Failed to execute template '" .. self.name .. "'.\n" ..
98                       "A runtime error occured: " .. tostring(err or "(nil)"))
99         end
100 end