posix = require 'posix'
json = require 'json'
http = require 'lcurl'
require 'tokyocabinet'
require 'corz'
CACHE = "/srv/tears/embed.db"
cache = tokyocabinet.bdbnew()
cache:open(CACHE, cache.OWRITER + cache.OREADER)
function userurl(basename, basecache, home, fallback)
local path = ("/home/%s/%s"):format(home, basename)
local cache = ("/srv/www/users/%s"):format(basecache)
if not posix.stat(path) then return fallback end
if not posix.stat(cache) then
posix.unlink(cache)
posix.link(path, cache, true)
end
return ("https://intern.radionova.no/users/%s"):format(basecache)
end
function userfile(basename, home, fallback)
local path = ("/home/%s/%s"):format(home, basename)
local ok, data = pcall(function()
return io.open(path):read("*l")
end)
if ok then
return data:gsub("[\r\n]*", "")
else
return fallback
end
end
function oembed(endpoint, id)
local url = endpoint .. id
local cached = cache:get(id)
if cached then
local data = json.decode(cached)
return data.html
end
local body = ""
local result = http.easy{url=url, writefunction=function(s)
body = body .. s
end}:perform()
if result:getinfo(http.INFO_RESPONSE_CODE) == 200 then
cache:put(id, body)
local data = json.decode(body)
return "
" .. data.html .. "
"
else
return ("Klarte ikke embedde %s :("):format(url)
end
end
function twitter(id)
return oembed("https://publish.twitter.com/oembed?url=", id)
end
function youtube(id)
return oembed("http://www.youtube.com/oembed?url=", id)
end
function soundcloud(id)
return oembed("https://soundcloud.com/oembed?format=json&url=", id)
end
function vimeo(id)
return oembed("https://developer.vimeo.com/apis/oembed.json?url=", id)
end
function twitch(id)
return oembed("https://api.twitch.tv/v4/oembed?url=", id)
end
function instagram(id)
return oembed("https://api.instagram.com/oembed/?url=", id)
end
function flickr(id)
return oembed("https://www.flickr.com/services/oembed?url=", id)
end
function facebook(id)
return oembed("https://www.facebook.com/plugins/video/oembed.json?url=", id)
end
local function printf(...) io.write(string.format(...)) end
local function setfenv(fn, env)
local i = 1
while true do
local name = debug.getupvalue(fn, i)
if name == "_ENV" then
debug.upvaluejoin(fn, i, (function()
return env
end), 1)
break
elseif not name then
break
end
i = i + 1
end
return fn
end
local html = {}
function html.print(...)
printf(...)
end
function html.tag(tag, options)
options = options or {}
local text = options.text; options.text = nil
printf("<%s", tag)
for k, v in pairs(options) do
if v and v ~= "" then
printf(" %s='%s'", k, v)
else
printf(" %s", k)
end
end
printf(">")
if text then
printf(text or "")
printf("%s>", tag)
end
printf("\n")
end
function html.dl(facts)
if #facts < 1 then return end
printf("
")
for i,v in ipairs(facts) do
local term, fact = v:match("^(.-)%s*:%s*(.-)$")
html.tag("dt", { text=term })
html.tag("dd", { text=fact })
end
printf("
")
end
function html.label(options)
options.text = options.text or error("no text")
html.tag("label", options)
end
function html.orz(options)
if options.inline then
html.tag("a", { class = 'orz', text = 'orz'})
return
end
local n = options.name or "orz"
local v = options.value or "//:0"
local uri = options.uri or "/orz/"
html.tag('img', { class='orz', ['data-name']=n, src=v, ['data-uri']=uri})
html.tag('input', { name=n, value=v, type='hidden'})
html.tag('a', { class='orz', ['data-name']=n, ['data-uri']=uri, text='orz'})
end
function html.infobox(options)
html.print("
")
options.terms = options.terms or {}
options.terms[''] = ""
-- table.insert(options.terms, "")
local n = options.name or "info[]"
for k, v in ipairs(options.values) do
html.print("
")
local term, fact = v:match("^(.-)%s*:%s*(.-)$")
html.select(n, options.terms, term)
html.tag('input', { name=n, value=fact})
end
html.print("
")
end
function html.date(options)
local n = options.name or "date"
local s = options.size or 10
local v = options.value or ""
html.tag('input', { type='date', name=n, size=s, value=v})
end
function html.text(options)
local n = options.name or "text"
local s = options.size or 18
local v = options.value or ""
html.tag("input", { type='text', name=n, size=s, value=v})
end
function html.hidden(options)
local n = options.name or "hidden"
local v = options.value or ""
html.tag('input', { type='hidden', name=n, value=v})
end
function html.textarea(options)
local n = options.name or "textarea"
local v = (options.value or ""):gsub("[\n]$", "")
local cols = options.cols or 72
local rows = options.rows or 12
html.tag('textarea', { name=n, cols=cols, rows=rows, text=v})
end
function html.select(k, options, selected)
local p = ""
printf("")
end
function html.submit(value)
printf("", value)
end
function html.img(options)
html.tag('img', { src=options.src, alt=options.alt})
end
function html.p(options)
html.tag("p", options)
end
function html.h1(...)
printf("
%s
", ... or '')
end
function html.h2(...)
printf("
%s
", ... or '')
end
function html.h3(...)
printf("
%s
", ... or '')
end
function html.marxup(raw)
corz.marxup(raw, io.output())
end
help = { html = html, os = os, posix = posix, pairs = pairs, ipairs = ipairs }
lupin = {}
lupin.blueprints = {}
lupin.blueprints.default = {
pure = function(body, data)
print(data.title)
if data.type ~= "default" then
printf("# type: %s\n", data.type)
end
data.title = nil
data.type = nil
print(body)
for key, value in pairs(data) do
if key == "info" then
local term
for i,v in ipairs(data.info) do
if i % 2 == 1 then
term = v
else
if #data.info >= i then print(("# info: %s: %s"):format(term, v)) end
end
end
else
print(("# %s: %s"):format(key, value))
end
end
end,
form = function(body, data)
help.form.text('overskrift', data.title)
end,
html = function(body, data)
if data.mode == "title" then
printf("
%s
", data.title)
return
elseif data.mode == "full" then
printf("
%s
", data.title)
end
corz.marxup(body, io.output())
if not next(data) then return end
print("
")
for key, value in pairs(data) do
printf("
%s
%s
", key, value)
end
print("
")
end
}
function help.blueprint(id)
return setmetatable({ id = id }, { __index = lupin.blueprints.default })
end
function lupin.loadblueprints(path)
local blueprints = posix.glob(path .. "/*.lua")
if blueprints then
for i, file in pairs(blueprints) do
local chunk = loadfile(file)
setfenv(chunk, help)
local blueprint = chunk()
lupin.blueprints[blueprint.id] = blueprint
end
end
end
function lupin.transform(path, format, env, mode)
if path ~= "-" then
io.input(path)
end
local raw, data = "", {}
data.info = {}
if env then
data.type = os.getenv("POST_type") or "default"
data.title = os.getenv("POST_title") or "default"
raw = os.getenv("POST_text") or ""
raw = raw:gsub("\r\n", "\n")
raw = raw:gsub("[\n]+$", "")
raw = raw:match("(.-)%s*$")
if lupin.blueprints[data.type] and lupin.blueprints[data.type].env then
lupin.blueprints[data.type].env(data)
end
if os.getenv("POST_info") then
for line in os.getenv("POST_info"):gmatch("[^\r\n]+") do
table.insert(data.info, line)
end
end
else
for line in io.lines() do
if line:match("^#") then
local key, value = line:match("^#%s*(.-):%s*(.+)")
if key and value and key == "info" then table.insert(data.info, value)
elseif key and value then data[key] = value end
else
if not data.title then
data.title = line
else
line = line:gsub("[\r\n]*", "")
raw = raw .. line .. "\n"
end
end
end
data.type = data.type or "default"
end
data.mode = mode
if data.mode == "short" then
raw = data.lead or raw:match("[^\r\n]+")
end
if path ~= "-" then
local stat = posix.stat(path)
local pwd = posix.getpasswd(stat.uid)
data.author = pwd.name
data.authorname = userfile("navn", data.author, data.author)
data.group = posix.getgroup(stat.gid).name
data.groupname = userfile("navn", data.group, data.group)
if data.published then
data.date = os.date("%A %d. %B %Y, kl. %H.%M", data.published)
else
local root = "/srv/www/happiest.place/tears/artikler"
y, m, d = path:match(root .. "/(.*)/(.*)/(.*)/(.*)")
data.date = ("%s/%s/%s"):format(y, m, d)
end
data.portrait = userurl("selfie.jpg", data.author .. ".jpg", data.author)
end
if format == "later" then
data.raw = raw
local info = data.info
data.info = {}
for i,v in ipairs(info) do
local term, fact = v:match("^(.-)%s*:%s*(.-)$")
table.insert(data.info, { term = term, fact = fact })
end
data.text = function()
local f = io.tmpfile()
corz.marxup(data.raw, f)
f:seek("set")
local result = f:read("*a")
result = result:gsub("$%s*twitter:%s*(.-)\n", twitter)
result = result:gsub("$%s*youtube:%s*(.-)\n", youtube)
result = result:gsub("$%s*soundcloud:%s*(.-)\n", soundcloud)
result = result:gsub("$%s*twitch:%s*(.-)\n", twitch)
result = result:gsub("$%s*vimeo:%s*(.-)\n", vimeo)
result = result:gsub("$%s*instagram:%s*(.-)\n", instagram)
result = result:gsub("$%s*flickr:%s*(.-)\n", flickr)
result = result:gsub("$%s*facebook:%s*(.-)\n", facebook)
return result
end
return data
end
lupin.blueprints[data.type][format](raw, data)
end