├── LICENSE ├── README.md ├── lib └── resty │ └── tags.lua └── lua-resty-tags-dev-1.rockspec /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Aapo Talvensaari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-tags 2 | 3 | A small DSL for building HTML documents 4 | 5 | ## Synopsis 6 | 7 | ##### Here we define some local functions: 8 | 9 | ```lua 10 | local tags = require "resty.tags" 11 | local html, head, script, body, h1, p, table, tr, th, img, br = tags( 12 | "html", "head", "script", "body", "h1", "p", "table", "tr", "th", "img", "br") 13 | 14 | print( 15 | html { lang = "en" } ( 16 | head ( 17 | script { src = "main.js" } 18 | ), 19 | body ( 20 | h1 { class = 'title "is" bigger than you think', "selected" } "Hello", 21 | h1 "Another Headline", 22 | p ( 23 | " & ", 24 | br, 25 | { Car = "Was Stolen" }, 26 | "Weather" 27 | ), 28 | p "A Dog", 29 | img { src = "logo.png" }, 30 | table( 31 | tr ( 32 | th { class = "selected" } "'Headline'", 33 | th "Headline 2", 34 | th "Headline 3" 35 | ) 36 | ) 37 | ) 38 | ) 39 | ) 40 | ``` 41 | 42 | ##### The above will output HTML similar to: 43 | 44 | ```html 45 | 46 | 47 | 48 | 49 | 50 |

51 | Hello 52 |

53 |

54 | Another Headline 55 |

56 |

57 | <Beautiful> & <Strange> 58 |
59 | table: 0x0004c370Weather 60 |

61 |

62 | A Dog 63 |

64 | 65 | 66 | 67 | 70 | 73 | 76 | 77 |
68 | 'Headline' 69 | 71 | Headline 2 72 | 74 | Headline 3 75 |
78 | 79 | 80 | ``` 81 | 82 | ##### Here we pass in a function: 83 | 84 | ```lua 85 | local tags = require "resty.tags" 86 | local html = tags(function() 87 | return html { lang = "en"} ( 88 | head ( 89 | script { src = "main.js" } 90 | ), 91 | body ( 92 | h1 { class = 'title "is" bigger than you think', "selected" } "Hello", 93 | h1 "Another Headline", 94 | p ( 95 | " & ", 96 | br, 97 | { Car = "Was Stolen" }, 98 | "Weather" 99 | ), 100 | p "A Dog", 101 | img { src = "logo.png" }, 102 | table( 103 | tr ( 104 | th { class = "selected" } "'Headline'", 105 | th "Headline 2", 106 | th "Headline 3" 107 | ) 108 | ) 109 | ) 110 | ) 111 | end) 112 | print(html()) 113 | ``` 114 | 115 | ##### And the output is similar: 116 | 117 | ```html 118 | 119 | 120 | 121 | 122 | 123 |

124 | Hello 125 |

126 |

127 | Another Headline 128 |

129 |

130 | <Beautiful> & <Strange> 131 |
132 | table: 0x00054ce0Weather 133 |

134 |

135 | A Dog 136 |

137 | 138 | 139 | 140 | 143 | 146 | 149 | 150 |
141 | 'Headline' 142 | 144 | Headline 2 145 | 147 | Headline 3 148 |
151 | 152 | 153 | ``` 154 | 155 | ##### In this example we create a table snippet: 156 | 157 | ```lua 158 | local tags = require "resty.tags" 159 | local table = tags(function(rows) 160 | local table = table 161 | for _, row in ipairs(rows) do 162 | local tr = tr 163 | for _, col in ipairs(row) do 164 | tr(td(col)) 165 | end 166 | table(tr) 167 | end 168 | return table 169 | end) 170 | 171 | print(table{ 172 | { "A", 1, 1 }, 173 | { "B", 2, 2 }, 174 | { "C", 3, 3 } 175 | }) 176 | ``` 177 | 178 | ##### And here is the output of it: 179 | 180 | ```html 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 |
A11
B22
C33
198 | ``` 199 | 200 | ##### Some special treatment is done to ` 222 | ``` 223 | 224 | ## License 225 | 226 | `lua-resty-tags` uses two clause BSD license. 227 | 228 | ``` 229 | Copyright (c) 2016, Aapo Talvensaari 230 | All rights reserved. 231 | 232 | Redistribution and use in source and binary forms, with or without modification, 233 | are permitted provided that the following conditions are met: 234 | 235 | * Redistributions of source code must retain the above copyright notice, this 236 | list of conditions and the following disclaimer. 237 | 238 | * Redistributions in binary form must reproduce the above copyright notice, this 239 | list of conditions and the following disclaimer in the documentation and/or 240 | other materials provided with the distribution. 241 | 242 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 243 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 244 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 245 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 246 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES` 247 | -------------------------------------------------------------------------------- /lib/resty/tags.lua: -------------------------------------------------------------------------------- 1 | local type = type 2 | local table = table 3 | local unpack = table.unpack or unpack 4 | local concat = table.concat 5 | local setmetatable = setmetatable 6 | local getmetatable = getmetatable 7 | local tostring = tostring 8 | local setfenv = setfenv 9 | local select = select 10 | local ipairs = ipairs 11 | local pairs = pairs 12 | local gsub = string.gsub 13 | local ngx = ngx 14 | local null = type(ngx) == "table" and ngx.null 15 | local _G = _G 16 | local voids = { 17 | area = true, 18 | base = true, 19 | br = true, 20 | col = true, 21 | command = true, 22 | embed = true, 23 | hr = true, 24 | img = true, 25 | input = true, 26 | keygen = true, 27 | link = true, 28 | meta = true, 29 | param = true, 30 | source = true, 31 | track = true, 32 | wbr = true 33 | } 34 | local elements = { 35 | a = true, 36 | abbr = true, 37 | address = true, 38 | area = true, 39 | article = true, 40 | aside = true, 41 | audio = true, 42 | b = true, 43 | base = true, 44 | bdi = true, 45 | bdo = true, 46 | blockquote = true, 47 | body = true, 48 | br = true, 49 | button = true, 50 | canvas = true, 51 | caption = true, 52 | cite = true, 53 | code = true, 54 | col = true, 55 | colgroup = true, 56 | command = true, 57 | data = true, 58 | datalist = true, 59 | dd = true, 60 | del = true, 61 | details = true, 62 | dfn = true, 63 | div = true, 64 | dl = true, 65 | dt = true, 66 | em = true, 67 | embed = true, 68 | fieldset = true, 69 | figcaption = true, 70 | figure = true, 71 | footer = true, 72 | form = true, 73 | h1 = true, 74 | h2 = true, 75 | h3 = true, 76 | h4 = true, 77 | h5 = true, 78 | h6 = true, 79 | head = true, 80 | header = true, 81 | hgroup = true, 82 | hr = true, 83 | html = true, 84 | i = true, 85 | iframe = true, 86 | img = true, 87 | input = true, 88 | ins = true, 89 | kbd = true, 90 | keygen = true, 91 | label = true, 92 | legend = true, 93 | li = true, 94 | link = true, 95 | main = true, 96 | map = true, 97 | mark = true, 98 | menu = true, 99 | meta = true, 100 | meter = true, 101 | nav = true, 102 | noscript = true, 103 | object = true, 104 | ol = true, 105 | optgroup = true, 106 | option = true, 107 | output = true, 108 | p = true, 109 | param = true, 110 | pre = true, 111 | progress = true, 112 | q = true, 113 | rb = true, 114 | rp = true, 115 | rt = true, 116 | rtc = true, 117 | ruby = true, 118 | s = true, 119 | samp = true, 120 | script = true, 121 | section = true, 122 | select = true, 123 | small = true, 124 | source = true, 125 | span = true, 126 | strong = true, 127 | style = true, 128 | sub = true, 129 | summary = true, 130 | sup = true, 131 | table = true, 132 | tbody = true, 133 | td = true, 134 | template = true, 135 | textarea = true, 136 | tfoot = true, 137 | th = true, 138 | thead = true, 139 | time = true, 140 | title = true, 141 | tr = true, 142 | track = true, 143 | u = true, 144 | ul = true, 145 | var = true, 146 | video = true, 147 | wbr = true 148 | } 149 | local escape = { 150 | ["&"] = "&", 151 | ["<"] = "<", 152 | [">"] = ">", 153 | ['"'] = """, 154 | ["'"] = "'", 155 | ["/"] = "/" 156 | } 157 | local function output(s) 158 | if s == nil or s == null then return "" end 159 | return tostring(s) 160 | end 161 | local function html(s) 162 | if type(s) == "string" then 163 | return gsub(s, "[\">/<'&]", escape) 164 | end 165 | return output(s) 166 | end 167 | local function attr(s) 168 | if type(s) == "string" then 169 | return gsub(s, '["><&]', escape) 170 | end 171 | return output(s) 172 | end 173 | local function none(s) 174 | return s 175 | end 176 | local escapers = { 177 | script = none, 178 | style = none 179 | } 180 | local function copy(s) 181 | local n = #s 182 | local d = {} 183 | for i=1, n do 184 | d[i] = s[i] 185 | end 186 | return d 187 | end 188 | local tag = {} 189 | function tag.new(opts) 190 | return setmetatable(opts, tag) 191 | end 192 | function tag:__tostring() 193 | local n, c, a = self.name, self.childs, self.attributes 194 | if #c == 0 then 195 | return voids[n] and concat{ "<", n, a or "", ">" } or concat{ "<", n, a or "", ">" } 196 | end 197 | return concat{ "<", n, a or "", ">", concat(c), "" } 198 | end 199 | function tag:__call(...) 200 | local n = select("#", ...) 201 | local c, a = self.copy and {} or self.childs, self.attributes 202 | local s = #c 203 | for i=1, n do 204 | local v = select(i, ...) 205 | if type(v) == "table" then 206 | if getmetatable(v) == tag then 207 | c[s+i] = tostring(v) 208 | elseif s == 0 and n == 1 and not a then 209 | local r = {} 210 | local i = 1 211 | for k, v in pairs(v) do 212 | if type(k) ~= "number" then 213 | r[i]=" " 214 | r[i+1] = k 215 | r[i+2] = '="' 216 | r[i+3] = attr(v) 217 | r[i+4] = '"' 218 | i=i+5 219 | end 220 | end 221 | for _, v in ipairs(v) do 222 | r[i]=" " 223 | r[i+1]=attr(v) 224 | i=i+2 225 | end 226 | a = concat(r) 227 | else 228 | c[s+i] = (escapers[self.name] or html)(v) 229 | end 230 | else 231 | c[s+i] = (escapers[self.name] or html)(v) 232 | end 233 | end 234 | if self.copy then 235 | return tag.new{ 236 | name = self.name, 237 | childs = copy(c), 238 | attributes = a 239 | } 240 | end 241 | self.attributes = a 242 | return self 243 | end 244 | local mt = {} 245 | function mt:__index(k) 246 | -- TODO: should we have special handling for table and select (the built-in Lua functions)? 247 | if not elements[k] and _G[k] then 248 | return _G[k] 249 | else 250 | return tag.new{ 251 | name = k, 252 | childs = {} 253 | } 254 | end 255 | end 256 | local context = setmetatable({}, mt) 257 | return function(...) 258 | local argc = select("#", ...) 259 | local r = {} 260 | for i=1, argc do 261 | local v = select(i, ...) 262 | if type(v) == "function" then 263 | r[i] = setfenv(v, context) 264 | else 265 | r[i] = tag.new{ 266 | name = v, 267 | childs = {}, 268 | copy = true 269 | } 270 | end 271 | end 272 | return unpack(r) 273 | end -------------------------------------------------------------------------------- /lua-resty-tags-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-tags" 2 | version = "dev-1" 3 | source = { 4 | url = "git://github.com/bungle/lua-resty-tags.git" 5 | } 6 | description = { 7 | summary = "A small DSL for building HTML documents", 8 | detailed = "lua-resty-tags is a small DSL for building HTML documents using simple Lua structures.", 9 | homepage = "https://github.com/bungle/lua-resty-tags", 10 | maintainer = "Aapo Talvensaari ", 11 | license = "BSD" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["resty.tags"] = "lib/resty/tags.lua" 20 | } 21 | } 22 | --------------------------------------------------------------------------------