478 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			478 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| local setmetatable = setmetatable
 | |
| local loadstring = loadstring
 | |
| local loadchunk
 | |
| local tostring = tostring
 | |
| local setfenv = setfenv
 | |
| local require = require
 | |
| local capture
 | |
| local concat = table.concat
 | |
| local assert = assert
 | |
| local prefix
 | |
| local write = io.write
 | |
| local pcall = pcall
 | |
| local phase
 | |
| local open = io.open
 | |
| local load = load
 | |
| local type = type
 | |
| local dump = string.dump
 | |
| local find = string.find
 | |
| local gsub = string.gsub
 | |
| local byte = string.byte
 | |
| local null
 | |
| local sub = string.sub
 | |
| local ngx = ngx
 | |
| local jit = jit
 | |
| local var
 | |
| 
 | |
| local _VERSION = _VERSION
 | |
| local _ENV = _ENV
 | |
| local _G = _G
 | |
| 
 | |
| local HTML_ENTITIES = {
 | |
|     ["&"] = "&",
 | |
|     ["<"] = "<",
 | |
|     [">"] = ">",
 | |
|     ['"'] = """,
 | |
|     ["'"] = "'",
 | |
|     ["/"] = "/"
 | |
| }
 | |
| 
 | |
| local CODE_ENTITIES = {
 | |
|     ["{"] = "{",
 | |
|     ["}"] = "}",
 | |
|     ["&"] = "&",
 | |
|     ["<"] = "<",
 | |
|     [">"] = ">",
 | |
|     ['"'] = """,
 | |
|     ["'"] = "'",
 | |
|     ["/"] = "/"
 | |
| }
 | |
| 
 | |
| local VAR_PHASES
 | |
| 
 | |
| local ok, newtab = pcall(require, "table.new")
 | |
| if not ok then newtab = function() return {} end end
 | |
| 
 | |
| local caching = true
 | |
| local template = newtab(0, 12)
 | |
| 
 | |
| template._VERSION = "1.9"
 | |
| template.cache    = {}
 | |
| 
 | |
| local function enabled(val)
 | |
|     if val == nil then return true end
 | |
|     return val == true or (val == "1" or val == "true" or val == "on")
 | |
| end
 | |
| 
 | |
| local function trim(s)
 | |
|     return gsub(gsub(s, "^%s+", ""), "%s+$", "")
 | |
| end
 | |
| 
 | |
| local function rpos(view, s)
 | |
|     while s > 0 do
 | |
|         local c = sub(view, s, s)
 | |
|         if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
 | |
|             s = s - 1
 | |
|         else
 | |
|             break
 | |
|         end
 | |
|     end
 | |
|     return s
 | |
| end
 | |
| 
 | |
| local function escaped(view, s)
 | |
|     if s > 1 and sub(view, s - 1, s - 1) == "\\" then
 | |
|         if s > 2 and sub(view, s - 2, s - 2) == "\\" then
 | |
|             return false, 1
 | |
|         else
 | |
|             return true, 1
 | |
|         end
 | |
|     end
 | |
|     return false, 0
 | |
| end
 | |
| 
 | |
| local function readfile(path)
 | |
|     local file = open(path, "rb")
 | |
|     if not file then return nil end
 | |
|     local content = file:read "*a"
 | |
|     file:close()
 | |
|     return content
 | |
| end
 | |
| 
 | |
| local function loadlua(path)
 | |
|     return readfile(path) or path
 | |
| end
 | |
| 
 | |
| local function loadngx(path)
 | |
|     local vars = VAR_PHASES[phase()]
 | |
|     local file, location = path, vars and var.template_location
 | |
|     if sub(file, 1)  == "/" then file = sub(file, 2) end
 | |
|     if location and location ~= "" then
 | |
|         if sub(location, -1) == "/" then location = sub(location, 1, -2) end
 | |
|         local res = capture(concat{ location, '/', file})
 | |
|         if res.status == 200 then return res.body end
 | |
|     end
 | |
|     local root = vars and (var.template_root or var.document_root) or prefix
 | |
|     if sub(root, -1) == "/" then root = sub(root, 1, -2) end
 | |
|     return readfile(concat{ root, "/", file }) or path
 | |
| end
 | |
| 
 | |
| do
 | |
|     if ngx then
 | |
|         VAR_PHASES = {
 | |
|             set           = true,
 | |
|             rewrite       = true,
 | |
|             access        = true,
 | |
|             content       = true,
 | |
|             header_filter = true,
 | |
|             body_filter   = true,
 | |
|             log           = true
 | |
|         }
 | |
|         template.print = ngx.print or write
 | |
|         template.load  = loadngx
 | |
|         prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
 | |
|         if VAR_PHASES[phase()] then
 | |
|             caching = enabled(var.template_cache)
 | |
|         end
 | |
|     else
 | |
|         template.print = write
 | |
|         template.load  = loadlua
 | |
|     end
 | |
|     if _VERSION == "Lua 5.1" then
 | |
|         local context = { __index = function(t, k)
 | |
|             return t.context[k] or t.template[k] or _G[k]
 | |
|         end }
 | |
|         if jit then
 | |
|             loadchunk = function(view)
 | |
|                 return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
 | |
|             end
 | |
|         else
 | |
|             loadchunk = function(view)
 | |
|                 local func = assert(loadstring(view))
 | |
|                 setfenv(func, setmetatable({ template = template }, context))
 | |
|                 return func
 | |
|             end
 | |
|         end
 | |
|     else
 | |
|         local context = { __index = function(t, k)
 | |
|             return t.context[k] or t.template[k] or _ENV[k]
 | |
|         end }
 | |
|         loadchunk = function(view)
 | |
|             return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
 | |
|         end
 | |
|     end
 | |
| end
 | |
| 
 | |
| function template.caching(enable)
 | |
|     if enable ~= nil then caching = enable == true end
 | |
|     return caching
 | |
| end
 | |
| 
 | |
| function template.output(s)
 | |
|     if s == nil or s == null then return "" end
 | |
|     if type(s) == "function" then return template.output(s()) end
 | |
|     return tostring(s)
 | |
| end
 | |
| 
 | |
| function template.escape(s, c)
 | |
|     if type(s) == "string" then
 | |
|         if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
 | |
|         return gsub(s, "[\">/<'&]", HTML_ENTITIES)
 | |
|     end
 | |
|     return template.output(s)
 | |
| end
 | |
| 
 | |
| function template.new(view, layout)
 | |
|     assert(view, "view was not provided for template.new(view, layout).")
 | |
|     local render, compile = template.render, template.compile
 | |
|     if layout then
 | |
|         if type(layout) == "table" then
 | |
|             return setmetatable({ render = function(self, context)
 | |
|                 local context = context or self
 | |
|                 context.blocks = context.blocks or {}
 | |
|                 context.view = compile(view)(context)
 | |
|                 layout.blocks = context.blocks or {}
 | |
|                 layout.view = context.view or ""
 | |
|                 return layout:render()
 | |
|             end }, { __tostring = function(self)
 | |
|                 local context = self
 | |
|                 context.blocks = context.blocks or {}
 | |
|                 context.view = compile(view)(context)
 | |
|                 layout.blocks = context.blocks or {}
 | |
|                 layout.view = context.view
 | |
|                 return tostring(layout)
 | |
|             end })
 | |
|         else
 | |
|             return setmetatable({ render = function(self, context)
 | |
|                 local context = context or self
 | |
|                 context.blocks = context.blocks or {}
 | |
|                 context.view = compile(view)(context)
 | |
|                 return render(layout, context)
 | |
|             end }, { __tostring = function(self)
 | |
|                 local context = self
 | |
|                 context.blocks = context.blocks or {}
 | |
|                 context.view = compile(view)(context)
 | |
|                 return compile(layout)(context)
 | |
|             end })
 | |
|         end
 | |
|     end
 | |
|     return setmetatable({ render = function(self, context)
 | |
|         return render(view, context or self)
 | |
|     end }, { __tostring = function(self)
 | |
|         return compile(view)(self)
 | |
|     end })
 | |
| end
 | |
| 
 | |
| function template.precompile(view, path, strip)
 | |
|     local chunk = dump(template.compile(view), strip ~= false)
 | |
|     if path then
 | |
|         local file = open(path, "wb")
 | |
|         file:write(chunk)
 | |
|         file:close()
 | |
|     end
 | |
|     return chunk
 | |
| end
 | |
| 
 | |
| function template.compile(view, key, plain)
 | |
|     assert(view, "view was not provided for template.compile(view, key, plain).")
 | |
|     if key == "no-cache" then
 | |
|         return loadchunk(template.parse(view, plain)), false
 | |
|     end
 | |
|     key = key or view
 | |
|     local cache = template.cache
 | |
|     if cache[key] then return cache[key], true end
 | |
|     local func = loadchunk(template.parse(view, plain))
 | |
|     if caching then cache[key] = func end
 | |
|     return func, false
 | |
| end
 | |
| 
 | |
| function template.parse(view, plain)
 | |
|     assert(view, "view was not provided for template.parse(view, plain).")
 | |
|     if not plain then
 | |
|         view = template.load(view)
 | |
|         if byte(view, 1, 1) == 27 then return view end
 | |
|     end
 | |
|     local j = 2
 | |
|     local c = {[[
 | |
| context=... or {}
 | |
| local function include(v, c) return template.compile(v)(c or context) end
 | |
| local ___,blocks,layout={},blocks or {}
 | |
| ]] }
 | |
|     local i, s = 1, find(view, "{", 1, true)
 | |
|     while s do
 | |
|         local t, p = sub(view, s + 1, s + 1), s + 2
 | |
|         if t == "{" then
 | |
|             local e = find(view, "}}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if i < s - w then
 | |
|                     c[j] = "___[#___+1]=[=[\n"
 | |
|                     c[j+1] = sub(view, i, s - 1 - w)
 | |
|                     c[j+2] = "]=]\n"
 | |
|                     j=j+3
 | |
|                 end
 | |
|                 if z then
 | |
|                     i = s
 | |
|                 else
 | |
|                     c[j] = "___[#___+1]=template.escape("
 | |
|                     c[j+1] = trim(sub(view, p, e - 1))
 | |
|                     c[j+2] = ")\n"
 | |
|                     j=j+3
 | |
|                     s, i = e + 1, e + 2
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "*" then
 | |
|             local e = find(view, "*}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if i < s - w then
 | |
|                     c[j] = "___[#___+1]=[=[\n"
 | |
|                     c[j+1] = sub(view, i, s - 1 - w)
 | |
|                     c[j+2] = "]=]\n"
 | |
|                     j=j+3
 | |
|                 end
 | |
|                 if z then
 | |
|                     i = s
 | |
|                 else
 | |
|                     c[j] = "___[#___+1]=template.output("
 | |
|                     c[j+1] = trim(sub(view, p, e - 1))
 | |
|                     c[j+2] = ")\n"
 | |
|                     j=j+3
 | |
|                     s, i = e + 1, e + 2
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "%" then
 | |
|             local e = find(view, "%}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if z then
 | |
|                     if i < s - w then
 | |
|                         c[j] = "___[#___+1]=[=[\n"
 | |
|                         c[j+1] = sub(view, i, s - 1 - w)
 | |
|                         c[j+2] = "]=]\n"
 | |
|                         j=j+3
 | |
|                     end
 | |
|                     i = s
 | |
|                 else
 | |
|                     local n = e + 2
 | |
|                     if sub(view, n, n) == "\n" then
 | |
|                         n = n + 1
 | |
|                     end
 | |
|                     local r = rpos(view, s - 1)
 | |
|                     if i <= r then
 | |
|                         c[j] = "___[#___+1]=[=[\n"
 | |
|                         c[j+1] = sub(view, i, r)
 | |
|                         c[j+2] = "]=]\n"
 | |
|                         j=j+3
 | |
|                     end
 | |
|                     c[j] = trim(sub(view, p, e - 1))
 | |
|                     c[j+1] = "\n"
 | |
|                     j=j+2
 | |
|                     s, i = n - 1, n
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "(" then
 | |
|             local e = find(view, ")}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if i < s - w then
 | |
|                     c[j] = "___[#___+1]=[=[\n"
 | |
|                     c[j+1] = sub(view, i, s - 1 - w)
 | |
|                     c[j+2] = "]=]\n"
 | |
|                     j=j+3
 | |
|                 end
 | |
|                 if z then
 | |
|                     i = s
 | |
|                 else
 | |
|                     local f = sub(view, p, e - 1)
 | |
|                     local x = find(f, ",", 2, true)
 | |
|                     if x then
 | |
|                         c[j] = "___[#___+1]=include([=["
 | |
|                         c[j+1] = trim(sub(f, 1, x - 1))
 | |
|                         c[j+2] = "]=],"
 | |
|                         c[j+3] = trim(sub(f, x + 1))
 | |
|                         c[j+4] = ")\n"
 | |
|                         j=j+5
 | |
|                     else
 | |
|                         c[j] = "___[#___+1]=include([=["
 | |
|                         c[j+1] = trim(f)
 | |
|                         c[j+2] = "]=])\n"
 | |
|                         j=j+3
 | |
|                     end
 | |
|                     s, i = e + 1, e + 2
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "[" then
 | |
|             local e = find(view, "]}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if i < s - w then
 | |
|                     c[j] = "___[#___+1]=[=[\n"
 | |
|                     c[j+1] = sub(view, i, s - 1 - w)
 | |
|                     c[j+2] = "]=]\n"
 | |
|                     j=j+3
 | |
|                 end
 | |
|                 if z then
 | |
|                     i = s
 | |
|                 else
 | |
|                     c[j] = "___[#___+1]=include("
 | |
|                     c[j+1] = trim(sub(view, p, e - 1))
 | |
|                     c[j+2] = ")\n"
 | |
|                     j=j+3
 | |
|                     s, i = e + 1, e + 2
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "-" then
 | |
|             local e = find(view, "-}", p, true)
 | |
|             if e then
 | |
|                 local x, y = find(view, sub(view, s, e + 1), e + 2, true)
 | |
|                 if x then
 | |
|                     local z, w = escaped(view, s)
 | |
|                     if z then
 | |
|                         if i < s - w then
 | |
|                             c[j] = "___[#___+1]=[=[\n"
 | |
|                             c[j+1] = sub(view, i, s - 1 - w)
 | |
|                             c[j+2] = "]=]\n"
 | |
|                             j=j+3
 | |
|                         end
 | |
|                         i = s
 | |
|                     else
 | |
|                         y = y + 1
 | |
|                         x = x - 1
 | |
|                         if sub(view, y, y) == "\n" then
 | |
|                             y = y + 1
 | |
|                         end
 | |
|                         local b = trim(sub(view, p, e - 1))
 | |
|                         if b == "verbatim" or b == "raw" then
 | |
|                             if i < s - w then
 | |
|                                 c[j] = "___[#___+1]=[=[\n"
 | |
|                                 c[j+1] = sub(view, i, s - 1 - w)
 | |
|                                 c[j+2] = "]=]\n"
 | |
|                                 j=j+3
 | |
|                             end
 | |
|                             c[j] = "___[#___+1]=[=["
 | |
|                             c[j+1] = sub(view, e + 2, x)
 | |
|                             c[j+2] = "]=]\n"
 | |
|                             j=j+3
 | |
|                         else
 | |
|                             if sub(view, x, x) == "\n" then
 | |
|                                 x = x - 1
 | |
|                             end
 | |
|                             local r = rpos(view, s - 1)
 | |
|                             if i <= r then
 | |
|                                 c[j] = "___[#___+1]=[=[\n"
 | |
|                                 c[j+1] = sub(view, i, r)
 | |
|                                 c[j+2] = "]=]\n"
 | |
|                                 j=j+3
 | |
|                             end
 | |
|                             c[j] = 'blocks["'
 | |
|                             c[j+1] = b
 | |
|                             c[j+2] = '"]=include[=['
 | |
|                             c[j+3] = sub(view, e + 2, x)
 | |
|                             c[j+4] = "]=]\n"
 | |
|                             j=j+5
 | |
|                         end
 | |
|                         s, i = y - 1, y
 | |
|                     end
 | |
|                 end
 | |
|             end
 | |
|         elseif t == "#" then
 | |
|             local e = find(view, "#}", p, true)
 | |
|             if e then
 | |
|                 local z, w = escaped(view, s)
 | |
|                 if i < s - w then
 | |
|                     c[j] = "___[#___+1]=[=[\n"
 | |
|                     c[j+1] = sub(view, i, s - 1 - w)
 | |
|                     c[j+2] = "]=]\n"
 | |
|                     j=j+3
 | |
|                 end
 | |
|                 if z then
 | |
|                     i = s
 | |
|                 else
 | |
|                     e = e + 2
 | |
|                     if sub(view, e, e) == "\n" then
 | |
|                         e = e + 1
 | |
|                     end
 | |
|                     s, i = e - 1, e
 | |
|                 end
 | |
|             end
 | |
|         end
 | |
|         s = find(view, "{", s + 1, true)
 | |
|     end
 | |
|     s = sub(view, i)
 | |
|     if s and s ~= "" then
 | |
|         c[j] = "___[#___+1]=[=[\n"
 | |
|         c[j+1] = s
 | |
|         c[j+2] = "]=]\n"
 | |
|         j=j+3
 | |
|     end
 | |
|     c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
 | |
|     return concat(c)
 | |
| end
 | |
| 
 | |
| function template.render(view, context, key, plain)
 | |
|     assert(view, "view was not provided for template.render(view, context, key, plain).")
 | |
|     return template.print(template.compile(view, key, plain)(context))
 | |
| end
 | |
| 
 | |
| return template
 | 
