aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthe lemons <citrons@mondecitronne.com>2021-08-31 23:22:36 -0500
committerthe lemons <citrons@mondecitronne.com>2021-08-31 23:22:36 -0500
commit5f1744c5b0f9761db402dfbca9cc85e1542ddb5f (patch)
tree6b6a8441024c11eeef2c853f4143b4068ecaca9f
create the
-rw-r--r--README.md60
-rw-r--r--example.lua105
-rw-r--r--html.lua124
3 files changed, 289 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8700e8b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+# simple HTML DSL for lua 5.3
+
+this library returns a table which, when indexed with the name of an HTML tag, provides a function which generates a data structure representing an HTML element. when this data structure is converted to a string, the corresponding HTML is generated.
+
+ local h = require "html"
+
+calling a tag's function with no arguments produces a self-closing tag.
+
+### source
+ print(h.hr())
+### output
+ <hr />
+
+calling a tag's function with a single array or value argument produces a tag with the children provided in the array. non-table values may appear in the list of children or in its stead. they are converted to a string an escaped.
+
+### source
+ print(h.section{h.h1"foo",h.p"bar"})
+### output
+ <section><h1>foo</h1><p>bar</p></section>
+
+calling a tag's function with a single set of key/value pairs as the argument produces a self-closing tag with attributes corresponding to the key/value pairs
+
+### source
+ print(h.meta{charset = "utf-8"})
+### output
+ <meta charset="utf-8" />
+
+calling a tag's function with a set of key/value pairs and then an array or value as the arguments produces a tag with attributes corresponding to the key/value pairs.
+
+### source
+ print(h.a({href = "https://example.com"}, "foobar"))
+### output
+ <a href="https://example.com">foobar</a>
+
+a table used in a list of children must either be generated from the HTML functions or have a `tohtml` function or a `render` function, returning HTML structures or HTML source respectively. a table that is not a set of attributes or an array of element children may not be used as arguments to an HTML function.
+
+### source
+ local db_record = {name = "foobar", id = 256}
+ setmetatable(db_record, {
+ tohtml = function(x)
+ return h.a(
+ {href = "https://example.com/" .. x.id}, x.name)
+ end
+ })
+
+ print(h.p{"search result: ", db_record})
+### output
+ <p>search result: <a href="https://example.com/256">foobar</a></p>
+
+the `raw` function will insert text verbatim into the HTML, unescaped.
+
+### source
+ local html_source = "<marquee>hello</marquee>"
+ print(h.p{html_source})
+ print(h.p{h.raw(html_source)})
+### output
+ <p>&lt;marquee&gt;hello&lt;/marquee&gt;</p>
+ <p><marquee>hello</marquee></p>
+
+included with this documentation is `example.lua` which produces this documenation in HTML via the DSL.
diff --git a/example.lua b/example.lua
new file mode 100644
index 0000000..042425e
--- /dev/null
+++ b/example.lua
@@ -0,0 +1,105 @@
+local h = require "html"
+
+function code_example(source, output)
+ return h.div {
+ h.h3 "source",
+ h.code {h.pre{source}},
+ h.h3 "output",
+ h.code {h.pre{output}}
+ }
+end
+
+
+print(h.html {
+ h.head {
+ h.meta{charset = "utf-8"},
+ h.style{h.raw[[
+ body {
+ max-width: 65ch; font-family: sans-serif; margin:auto;
+ }
+ ]]}
+ },
+ h.body {
+ h.h1[[simple HTML DSL for lua 5.3]],
+ h.p[[
+ this library returns a table which, when indexed with the name of
+ an HTML tag, provides a function which generates a data structure
+ representing an HTML element. when this data structure is converted
+ to a string, the corresponding HTML is generated.
+ ]],
+ h.code[[local h = require "html"]],
+ h.p[[
+ calling a tag's function with no arguments produces a self-closing
+ tag.
+ ]],
+ code_example([[print(h.hr())]], [[<hr />]]),
+ h.p[[
+ calling a tag's function with a single array or value argument
+ produces a tag with the children provided in the array. non-table
+ values may appear in the list of children or in its stead. they are
+ converted to a string an escaped.
+ ]],
+ code_example(
+ [[print(h.section{h.h1"foo",h.p"bar"})]],
+ [[<section><h1>foo</h1><p>bar</p></section>]]
+ ),
+ h.p[[
+ calling a tag's function with a single set of key/value pairs as
+ the argument produces a self-closing tag with attributes
+ corresponding to the key/value pairs
+ ]],
+ code_example(
+ [[print(h.meta{charset = "utf-8"})]],
+ [[<meta charset="utf-8" />]]
+ ),
+ h.p[[
+ calling a tag's function with a set of key/value pairs and then an
+ array or value as the arguments produces a tag with attributes
+ corresponding to the key/value pairs.
+ ]],
+ code_example(
+ [[print(h.a({href = "https://example.com"}, "foobar"))]],
+ [[<a href="https://example.com">foobar</a>]]
+ ),
+ h.p[[
+ a table used in a list of children must either be generated from
+ the HTML functions or have a "tohtml" function or a "render"
+ function, returning HTML structures or HTML source respectively. a
+ table that is not a set of attributes or an array of element
+ children may not be used as arguments to an HTML function.
+ ]],
+ code_example(
+ [[
+local db_record = {name = "foobar", id = 256}
+setmetatable(db_record, {
+ tohtml = function(x)
+ return h.a(
+ {href = "https://example.com/" .. x.id}, x.name)
+ end
+})
+
+print(h.p{"search result: ", db_record})
+ ]],
+ '<p>search result: <a href="https://example.com/256">foobar</a></p>'
+ ),
+ h.p[[
+ the `raw` function will insert text verbatim into the HTML,
+ unescaped.
+ ]],
+ code_example(
+ [[
+local html_source = "<marquee>hello</marquee>"
+print(h.p{html_source})
+print(h.p{h.raw(html_source)})
+ ]],
+ [[
+<p>&lt;marquee&gt;hello&lt;/marquee&gt;</p>
+<p><marquee>hello</marquee></p>
+ ]]
+ ),
+ h.p[[
+ included with this documentation is "example.lua" which produces
+ this documenation in HTML via the DSL.
+ ]]
+ }
+})
diff --git a/html.lua b/html.lua
new file mode 100644
index 0000000..454eae8
--- /dev/null
+++ b/html.lua
@@ -0,0 +1,124 @@
+-- HTML generator DSL for lua 5.3
+
+-- copyright © 2021 citrons <citrons@mondecitronne.com>
+-- 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 = {
+ ["<"] = "&lt;",
+ [">"] = "&gt;",
+ ['"'] = "&quot;"
+}
+local function escape(x)
+ local escaped = tostring(x)
+ escaped = escaped:gsub("&", "&amp;")
+
+ 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</%s>',
+ self.tag, attrs, children, self.tag)
+ else
+ str = string.format('<%s%s />', self.tag, attrs)
+ end
+
+ if self.tag == "html" then
+ str = "<!DOCTYPE html>\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
+