-- HTML generator DSL for lua 5.3 -- copyright © 2021 citrons -- arbitrary copying, distribution, modification, and use is permitted as long -- as this copyright notice is retained, and you get in the hole immediately. -- go on, get in. local h = {} local esc_sequences = { ["<"] = "<", [">"] = ">", ['"'] = """ } local function escape(x) local escaped = tostring(x) escaped = escaped:gsub("&", "&") for char,esc in pairs(esc_sequences) do escaped = string.gsub(escaped, char, esc) end return escaped end local function render(x) local mt = getmetatable(x) if mt.render then return mt.render(x) else return escape(x) end end local function verify_tag(x) end local meta = {} function meta.render(self) local attr_strings = {} local i = 1 for k,v in pairs(self.attrs) do verify_tag(k) attr_strings[i] = string.format(' %s="%s"', k, escape(v)) i = i + 1 end local attrs = table.concat(attr_strings) local str if self.children then local child_strings = {} for i,c in ipairs(self.children) do child_strings[i] = render(c) end local children = table.concat(child_strings) str = string.format('<%s%s>%s', self.tag, attrs, children, self.tag) else str = string.format('<%s%s />', self.tag, attrs) end if self.tag == "html" then str = "\n" .. str end return str end meta.__tostring = meta.render setmetatable(h, { __index = function(self, tag) verify_tag(tag) return function(...) local attrs, children local args = {...} if #args == 2 then attrs = args[1] if type(args[2]) ~= "table" then children = {args[2]} else children = args[2] end elseif #args == 1 then if type(args[1]) ~= "table" then attrs = {} children = {args[1]} elseif args[1][1] ~= nil then attrs = {} children = args[1] else children = {} attrs = {} -- if args[1] is key/value pairs, use it as attributes for _,_ in pairs(args[1]) do children = nil attrs = args[1] break end end else attrs = {} end if children then for i,v in ipairs(children) do local mt = getmetatable(v) if mt.tohtml then children[i] = mt.tohtml(v) end end end local element = {tag=tag, attrs=attrs, children=children} setmetatable(element, meta) return element end end }) local raw_meta = { render = function(self) return self.str end, __tostring = function(self) return self.str end } function h.raw(str) local raw = {str=str} setmetatable(raw, raw_meta) return raw end return h