template.lua 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. local util = require "luci.util"
  4. local config = require "luci.config"
  5. local tparser = require "luci.template.parser"
  6. local tostring, pairs, loadstring = tostring, pairs, loadstring
  7. local setmetatable, loadfile = setmetatable, loadfile
  8. local getfenv, setfenv, rawget = getfenv, setfenv, rawget
  9. local assert, type, error = assert, type, error
  10. local table, string, unpack = table, string, unpack
  11. ---
  12. --- bootstrap
  13. ---
  14. local _G = _G
  15. local L = _G.L
  16. local http = _G.L.http
  17. local disp = require "luci.dispatcher"
  18. local i18n = require "luci.i18n"
  19. local xml = require "luci.xml"
  20. local fs = require "nixio.fs"
  21. --- LuCI template library.
  22. module "luci.template"
  23. config.template = config.template or {}
  24. viewdir = config.template.viewdir or util.libpath() .. "/view"
  25. -- Define the namespace for template modules
  26. context = {} --util.threadlocal()
  27. --- Render a certain template.
  28. -- @param name Template name
  29. -- @param scope Scope to assign to template (optional)
  30. function render(name, scope)
  31. return Template(name):render(scope or getfenv(2))
  32. end
  33. --- Render a template from a string.
  34. -- @param template Template string
  35. -- @param scope Scope to assign to template (optional)
  36. function render_string(template, scope)
  37. return Template(nil, template):render(scope or getfenv(2))
  38. end
  39. -- Template class
  40. Template = util.class()
  41. -- Shared template cache to store templates in to avoid unnecessary reloading
  42. Template.cache = setmetatable({}, {__mode = "v"})
  43. local function _ifattr(cond, key, val, noescape)
  44. if cond then
  45. local env = getfenv(3)
  46. local scope = (type(env.self) == "table") and env.self
  47. if type(val) == "table" then
  48. if not next(val) then
  49. return ''
  50. else
  51. val = util.serialize_json(val)
  52. end
  53. end
  54. val = tostring(val or
  55. (type(env[key]) ~= "function" and env[key]) or
  56. (scope and type(scope[key]) ~= "function" and scope[key]) or "")
  57. if noescape ~= true then
  58. val = xml.pcdata(val)
  59. end
  60. return string.format(' %s="%s"', tostring(key), val)
  61. else
  62. return ''
  63. end
  64. end
  65. context.viewns = setmetatable({
  66. include = function(name)
  67. if fs.access(viewdir .. "/" .. name .. ".htm") then
  68. Template(name):render(getfenv(2))
  69. else
  70. L.include(name, getfenv(2))
  71. end
  72. end;
  73. translate = i18n.translate;
  74. translatef = i18n.translatef;
  75. export = function(k, v) if context.viewns[k] == nil then context.viewns[k] = v end end;
  76. striptags = xml.striptags;
  77. pcdata = xml.pcdata;
  78. ifattr = function(...) return _ifattr(...) end;
  79. attr = function(...) return _ifattr(true, ...) end;
  80. url = disp.build_url;
  81. }, {__index=function(tbl, key)
  82. if key == "controller" then
  83. return disp.build_url()
  84. elseif key == "REQUEST_URI" then
  85. return disp.build_url(unpack(disp.context.requestpath))
  86. elseif key == "FULL_REQUEST_URI" then
  87. local url = { http:getenv("SCRIPT_NAME") or "", http:getenv("PATH_INFO") }
  88. local query = http:getenv("QUERY_STRING")
  89. if query and #query > 0 then
  90. url[#url+1] = "?"
  91. url[#url+1] = query
  92. end
  93. return table.concat(url, "")
  94. elseif key == "token" then
  95. return disp.context.authtoken
  96. elseif key == "theme" then
  97. return L.media and fs.basename(L.media) or tostring(L)
  98. elseif key == "resource" then
  99. return L.config.main.resourcebase
  100. else
  101. return rawget(tbl, key) or _G[key] or L[key]
  102. end
  103. end})
  104. -- Constructor - Reads and compiles the template on-demand
  105. function Template.__init__(self, name, template)
  106. if name then
  107. self.template = self.cache[name]
  108. self.name = name
  109. else
  110. self.name = "[string]"
  111. end
  112. -- Create a new namespace for this template
  113. self.viewns = context.viewns
  114. -- If we have a cached template, skip compiling and loading
  115. if not self.template then
  116. -- Compile template
  117. local err
  118. local sourcefile
  119. if name then
  120. sourcefile = viewdir .. "/" .. name .. ".htm"
  121. self.template, _, err = tparser.parse(sourcefile)
  122. else
  123. sourcefile = "[string]"
  124. self.template, _, err = tparser.parse_string(template)
  125. end
  126. -- If we have no valid template throw error, otherwise cache the template
  127. if not self.template then
  128. error("Failed to load template '" .. self.name .. "'.\n" ..
  129. "Error while parsing template '" .. sourcefile .. "':\n" ..
  130. (err or "Unknown syntax error"))
  131. elseif name then
  132. self.cache[name] = self.template
  133. end
  134. end
  135. end
  136. -- Renders a template
  137. function Template.render(self, scope)
  138. scope = scope or getfenv(2)
  139. -- Put our predefined objects in the scope of the template
  140. setfenv(self.template, setmetatable({}, {__index =
  141. function(tbl, key)
  142. return rawget(tbl, key) or self.viewns[key] or scope[key]
  143. end}))
  144. -- Now finally render the thing
  145. local stat, err = util.copcall(self.template)
  146. if not stat then
  147. error("Failed to execute template '" .. self.name .. "'.\n" ..
  148. "A runtime error occurred: " .. tostring(err or "(nil)"))
  149. end
  150. end