smr/src/lua/util.lua

225 lines
5.4 KiB
Lua

local sql = require("lsqlite3")
local config = require("config")
local types = require("types")
local db = require("db")
local queries = require("queries")
local util = {}
local stmnt_comments
local oldconfigure = configure
function configure(...)
stmnt_comments = assert(db.conn:prepare(queries.select_comments))
return oldconfigure(...)
end
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
--no underscore because we use that for our operative pages
local url_characters =
[[abcdefghijklmnopqrstuvwxyz]]..
[[ABCDEFGHIJKLMNOPQRSTUVWXYZ]]..
[[0123456789]]
local url_characters_legacy =
url_characters ..
[[$-+!*'(),]]
local url_characters_rev = {}
for i = 1,string.len(url_characters) do
url_characters_rev[string.sub(url_characters,i,i)] = i
end
local url_characters_rev_legacy = {}
for i = 1,string.len(url_characters_legacy) do
url_characters_rev_legacy[string.sub(url_characters_legacy,i,i)] = i
end
--[[
Encode a number to a shorter HTML-safe url path
]]
function util.encode_id(number)
local result = {}
local charlen = string.len(url_characters)
repeat
local pos = (number % charlen) + 1
number = math.floor(number / charlen)
table.insert(result,string.sub(url_characters,pos,pos))
until number == 0
return table.concat(result)
end
--[[
Legacy code, try to encode with invalid characters in the url first
]]
local new_encode = util.encode_id
function util.encode_id(number)
if number >= config.legacy_url_cutoff then
return new_encode(number)
else
local result = {}
local charlen = string.len(url_characters_legacy)
repeat
local pos = (number % charlen) + 1
number = math.floor(number / charlen)
table.insert(result,string.sub(url_characters_legacy,pos,pos))
until number == 0
return table.concat(result)
end
end
--[[
Given a short HTML-safe url path, convert it to a storyid
]]
function util.decode_id(s)
local res, id = pcall(function()
local n = 0
local charlen = string.len(url_characters)
for i = 1,string.len(s) do
local char = string.sub(s,i,i)
local pos = url_characters_rev[char] - 1
n = n + (pos*math.pow(charlen,i-1))
end
return n
end)
if res then
return id
else
return false,"Failed to decode id:" .. s
end
end
--[[
Legacy code, try to decode with invalid characters in the url first
]]
local new_decode = util.decode_id
function util.decode_id(s)
local res, id = pcall(function()
local n = 0
local charlen = string.len(url_characters_legacy)
for i = 1,string.len(s) do
local char = string.sub(s,i,i)
local pos = url_characters_rev_legacy[char] - 1
n = n + (pos * math.pow(charlen,i-1))
end
return n
end)
if res then
if id > config.legacy_url_cutoff then
return new_decode(s)
else
return id
end
else
return false,"Failed to decode id:" .. s
end
end
--arbitary data to hex encoded string
function util.encode_unlisted(str)
assert(type(str) == "string","Tried to encode something not a string:" .. type(Str))
local safe = {}
for i = 1,#str do
local byte = str:byte(i)
table.insert(safe,string.format("%02x",byte))
end
return table.concat(safe)
end
--hex encoded string to arbitrary data
function util.decode_unlisted(str)
local output = {}
for byte in str:gmatch("%x%x") do
table.insert(output, string.char(tonumber(byte,16)))
end
return table.concat(output)
end
--[[
Parses a semicolon seperated string into it's parts:
1. seperates by semicolon
2. trims whitespace
3. lowercases
4. capitalizes the first letter.
Returns an array of zero or more strings.
There is no blank tag, parsing "one;two;;three" will yield
{"one","two","three"}
]]
function util.parse_tags(str)
local tags = {}
for tag in string.gmatch(str,"([^;]+)") do
assert(tag, "Found a nil or false tag in:" .. str)
local tag_trimmed = string.match(tag,"%s*(.*)%s*")
local tag_lower = string.lower(tag_trimmed)
local tag_capitalized = string.gsub(tag_lower,"^.",string.upper)
assert(tag_capitalized, "After processing tag:" .. tag .. " it was falsey.")
if string.len(tag_capitalized) > 0 then
table.insert(tags, tag_capitalized)
end
end
return tags
end
--[[
Get the comments for a story
Comments are a table with the structure:
comment :: table {
author :: string - The author's text name
isanon :: boolean - True if the author is anon (author string will be "Anonymous")
text :: string - The text of the comment
}
]]
function util.get_comments(sid)
stmnt_comments:bind_names{
id = sid
}
local comments = {}
for com_author, com_isanon, com_text in db.sql_rows(stmnt_comments) do
table.insert(comments,{
author = com_author,
isanon = com_isanon == 1, --int to boolean
text = com_text
})
end
return comments
end
if config.debugging then
function util.checktypes(...)
local args = {...}
if #args == 1 then
args = table.unpack(args)
end
assert(
#args % 3 == 0,
"Arguments to checktypes() must be triplets of " ..
"<variable>, <lua type>, <type check function> "
)
for i = 1,#args,3 do
local var, ltype, veri_f = args[i+0], args[i+1], args[i+2]
assert(
type(var) == ltype,
string.format(
"Expected argument %d (%q) to be type %s, but was %s",
i/3
)
)
if veri_f then
assert(veri_f(var))
end
end
end
else
function util.checktypes(...)
end
end
local function decodeentity(capture)
return string.char(tonumber(capture,16)) --Decode base 16 and conver to character
end
function util.decodeentities(str)
return string.gsub(str,"%%(%x%x)",decodeentity)
end
return util