diff --git a/src/.init.lua b/src/.init.lua index b6424b5..052a882 100644 --- a/src/.init.lua +++ b/src/.init.lua @@ -6,8 +6,6 @@ package.path = package.path .. ";plugin/?.lua" util = require "util" json = require "json" -liluat = require "liluat" -template = require "template" -- special script called by main redbean process at startup HidePath('/usr/share/zoneinfo/') @@ -71,7 +69,10 @@ print = function(...) Log(kLogInfo, ... or "''") end -- nice redbean logging fro -- init URL router app.url['^/data'] = '/data.lua' -- setup custom file endpoint -app.url['^/$'] = '/page/index.lua' +app.get('^/$', function(req,res,next) + res.template = 'page/index.html' + next() +end) app.url['^/admin[/]?$'] = '/page/admin/index.lua' app.get('^/design[/]?$', util.pagerender('page/design.html')) diff --git a/src/.lua/liluat.lua b/src/.lua/liluat.lua deleted file mode 100644 index 83c72ff..0000000 --- a/src/.lua/liluat.lua +++ /dev/null @@ -1,535 +0,0 @@ ---[[ --- liluat - Lightweight Lua Template engine --- --- Project page: https://github.com/FSMaxB/liluat --- --- liluat is based on slt2 by henix, see https://github.com/henix/slt2 --- --- Copyright © 2016 Max Bruckner --- Copyright © 2011-2016 henix --- --- Permission is hereby granted, free of charge, to any person obtaining a copy --- of this software and associated documentation files (the "Software"), to deal --- in the Software without restriction, including without limitation the rights --- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --- copies of the Software, and to permit persons to whom the Software is furnished --- to do so, subject to the following conditions: --- --- The above copyright notice and this permission notice shall be included in --- all copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, --- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR --- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---]] - -local liluat = { - private = {} --used to expose private functions for testing -} - --- print the current version -liluat.version = function () - return "1.2.0" -end - --- returns a string containing the fist line until the last line -local function string_lines(lines, first, last) - -- allow negative line numbers - first = (first >= 1) and first or 1 - - local start_position - local current_position = 1 - local line_counter = 1 - repeat - if line_counter == first then - start_position = current_position - end - current_position = lines:find('\n', current_position + 1, true) - line_counter = line_counter + 1 - until (line_counter == (last + 1)) or (not current_position) - - return lines:sub(start_position, current_position) -end -liluat.private.string_lines = string_lines - --- escape a string for use in lua patterns --- (this simply prepends all non alphanumeric characters with '%' -local function escape_pattern(text) - return text:gsub("([^%w])", "%%%1" --[[function (match) return "%"..match end--]]) -end -liluat.private.escape_pattern = escape_pattern - --- recursively copy a table -local function clone_table(table) - local clone = {} - - for key, value in pairs(table) do - if type(value) == "table" then - clone[key] = clone_table(value) - else - clone[key] = value - end - end - - return clone -end -liluat.private.clone_table = clone_table - --- recursively merge two tables, the second one has precedence --- if 'shallow' is set, the second table isn't copied recursively, --- its content is only referenced instead -local function merge_tables(a, b, shallow) - a = a or {} - b = b or {} - - local merged = clone_table(a) - - for key, value in pairs(b) do - if (type(value) == "table") and (not shallow) then - if a[key] then - merged[key] = merge_tables(a[key], value) - else - merged[key] = clone_table(value) - end - else - merged[key] = value - end - end - - return merged -end -liluat.private.merge_tables = merge_tables - -local default_options = { - start_tag = "{{", - end_tag = "}}", - trim_right = "code", - trim_left = "code" -} - --- initialise table of options (use the provided, default otherwise) -local function initialise_options(options) - return merge_tables(default_options, options) -end - --- creates an iterator that iterates over all chunks in the given template --- a chunk is either a template delimited by start_tag and end_tag or a normal text --- the iterator also returns the type of the chunk as second return value -local function all_chunks(template, options) - options = initialise_options(options) - - -- pattern to match a template chunk - local template_pattern = escape_pattern(options.start_tag) .. "([+-]?)(.-)([+-]?)" .. escape_pattern(options.end_tag) - local include_pattern = "^"..escape_pattern(options.start_tag) .. "[+-]?include:(.-)[+-]?" .. escape_pattern(options.end_tag) - local expression_pattern = "^"..escape_pattern(options.start_tag) .. "[+-]?=(.-)[+-]?" .. escape_pattern(options.end_tag) - local position = 1 - - return function () - if not position then - return nil - end - - local template_start, template_end, trim_left, template_capture, trim_right = template:find(template_pattern, position) - - local chunk = {} - if template_start == position then -- next chunk is a template chunk - if trim_left == "+" then - chunk.trim_left = false - elseif trim_left == "-" then - chunk.trim_left = true - end - if trim_right == "+" then - chunk.trim_right = false - elseif trim_right == "-" then - chunk.trim_right = true - end - - local include_start, include_end, include_capture = template:find(include_pattern, position) - local expression_start, expression_end, expression_capture - if not include_start then - expression_start, expression_end, expression_capture = template:find(expression_pattern, position) - end - - if include_start then - chunk.type = "include" - chunk.text = include_capture - elseif expression_start then - chunk.type = "expression" - chunk.text = expression_capture - else - chunk.type = "code" - chunk.text = template_capture - end - - position = template_end + 1 - return chunk - elseif template_start then -- next chunk is a text chunk - chunk.type = "text" - chunk.text = template:sub(position, template_start - 1) - position = template_start - return chunk - else -- no template chunk found --> either text chunk until end of file or no chunk at all - chunk.text = template:sub(position) - chunk.type = "text" - position = nil - return (#chunk.text > 0) and chunk or nil - end - end -end -liluat.private.all_chunks = all_chunks - -local function read_entire_file(path) - assert(path) - local file = assert(io.open(path)) - local file_content = file:read('*a') - file:close() - return file_content -end -liluat.private.read_entire_file = read_entire_file - --- a whitelist of allowed functions -local sandbox_whitelist = { - ipairs = ipairs, - next = next, - pairs = pairs, - rawequal = rawequal, - rawget = rawget, - rawset = rawset, - select = select, - tonumber = tonumber, - tostring = tostring, - type = type, - unpack = unpack, - string = string, - table = table, - math = math, - os = { - date = os.date, - difftime = os.difftime, - time = os.time, - }, - coroutine = coroutine -} - --- puts line numbers in front of a string and optionally highlights a single line -local function prepend_line_numbers(lines, first, highlight) - first = (first and (first >= 1)) and first or 1 - lines = lines:gsub("\n$", "") -- make sure the last line isn't empty - lines = lines:gsub("^\n", "") -- make sure the first line isn't empty - - local current_line = first + 1 - return string.format("%3d: ", first) .. lines:gsub('\n', function () - local highlight_char = ' ' - if current_line == tonumber(highlight) then - highlight_char = '> ' - end - - local replacement = string.format("\n%3d:%s", current_line, highlight_char) - current_line = current_line + 1 - - return replacement - end) -end -liluat.private.prepend_line_numbers = prepend_line_numbers - --- creates a function in a sandbox from a given code, --- name of the execution context and an environment --- that will be available inside the sandbox, --- optionally overwrite the whitelist -local function sandbox(code, name, environment, whitelist, reference) - whitelist = whitelist or sandbox_whitelist - name = name or 'unknown' - - -- prepare the environment - environment = merge_tables(whitelist, environment, reference) - - local func - local error_message - if setfenv then --Lua 5.1 and compatible - if code:byte(1) == 27 then - error("Lua bytecode not permitted.", 2) - end - func, error_message = loadstring(code) - if func then - setfenv(func, environment) - end - else -- Lua 5.2 and later - func, error_message = load(code, name, 't', environment) - end - - -- handle compile error and print pretty error message - if not func then - local line_number, message = error_message:match(":(%d+):(.*)") - -- lines before and after the error - local lines = string_lines(code, line_number - 3, line_number + 3) - error( - 'Syntax error in sandboxed code "' .. name .. '" in line ' .. line_number .. ':\n' - .. message .. '\n\n' - .. prepend_line_numbers(lines, line_number - 3, line_number), - 3 - ) - end - - return func -end -liluat.private.sandbox = sandbox - -local function parse_string_literal(string_literal) - return sandbox('return' .. string_literal, nil, nil, {})() -end -liluat.private.parse_string_literal = parse_string_literal - --- add an include to the include_list and throw an error if --- an inclusion cycle is detected -local function add_include_and_detect_cycles(include_list, path) - local parent = include_list[0] - while parent do -- while the root hasn't been reached - if parent[path] then - error("Cyclic inclusion detected") - end - - parent = parent[0] - end - - include_list[path] = { - [0] = include_list - } -end -liluat.private.add_include_and_detect_cycles = add_include_and_detect_cycles - --- extract the name of a directory from a path -local function dirname(path) - return path:match("^(.*/).-$") or "" -end -liluat.private.dirname = dirname - --- splits a template into chunks --- chunks are either a template delimited by start_tag and end_tag --- or a text chunk (everything else) --- @return table -local function parse(template, options, output, include_list, current_path) - options = initialise_options(options) - current_path = current_path or "." -- current include path - - include_list = include_list or {} -- a list of files that were included - local output = output or {} - - for chunk in all_chunks(template, options) do - -- handle includes - if chunk.type == "include" then -- include chunk - local include_path_literal = chunk.text - local path = parse_string_literal(include_path_literal) - - -- build complete path - if path:find("^/") then - --absolute path, don't modify - elseif options.base_path then - path = options.base_path .. "/" .. path - else - path = dirname(current_path) .. path - end - - add_include_and_detect_cycles(include_list, path) - - local included_template = read_entire_file(path) - parse(included_template, options, output, include_list[path], path) - elseif (chunk.type == "text") and output[#output] and (output[#output].type == "text") then - -- ensure that no two text chunks follow each other - output[#output].text = output[#output].text .. chunk.text - else -- other chunk - table.insert(output, chunk) - end - - end - - return output -end -liluat.private.parse = parse - --- inline included template files --- @return string -function liluat.inline(template, options, start_path) - options = initialise_options(options) - - local output = {} - for _,chunk in ipairs(parse(template, options, nil, nil, start_path)) do - if chunk.type == "expression" then - table.insert(output, options.start_tag .. "=" .. chunk.text .. options.end_tag) - elseif chunk.type == "code" then - table.insert(output, options.start_tag .. chunk.text .. options.end_tag) - else - table.insert(output, chunk.text) - end - end - - return table.concat(output) -end - --- @return { string } -function liluat.get_dependencies(template, options, start_path) - options = initialise_options(options) - - local include_list = {} - parse(template, options, nil, include_list, start_path) - - local dependencies = {} - local have_seen = {} -- list of includes that were already added - local function recursive_traversal(list) - for key, value in pairs(list) do - if (type(key) == "string") and (not have_seen[key]) then - have_seen[key] = true - table.insert(dependencies, key) - recursive_traversal(value) - end - end - end - - recursive_traversal(include_list) - return dependencies -end - --- compile a template into lua code --- @return { name = string, code = string / function} -function liluat.compile(template, options, template_name, start_path) - options = initialise_options(options) - template_name = template_name or 'liluat.compile' - - local output_function = "__liluat_output_function" - - -- split the template string into chunks - local lexed_template = parse(template, options, nil, nil, start_path) - - -- table of code fragments the template is compiled into - local lua_code = {} - - for i, chunk in ipairs(lexed_template) do - -- check if the chunk is a template (either code or expression) - if chunk.type == "expression" then - table.insert(lua_code, output_function..'('..chunk.text..')') - elseif chunk.type == "code" then - table.insert(lua_code, chunk.text) - else --text chunk - -- determine if this block needs to be trimmed right - -- (strip newline) - local trim_right = false - if lexed_template[i - 1] and (lexed_template[i - 1].trim_right == true) then - trim_right = true - elseif lexed_template[i - 1] and (lexed_template[i - 1].trim_right == false) then - trim_right = false - elseif options.trim_right == "all" then - trim_right = true - elseif options.trim_right == "code" then - trim_right = lexed_template[i - 1] and (lexed_template[i - 1].type == "code") - elseif options.trim_right == "expression" then - trim_right = lexed_template[i - 1] and (lexed_template[i - 1].type == "expression") - end - - -- determine if this block needs to be trimmed left - -- (strip whitespaces in front) - local trim_left = false - if lexed_template[i + 1] and (lexed_template[i + 1].trim_left == true) then - trim_left = true - elseif lexed_template[i + 1] and (lexed_template[i + 1].trim_left == false) then - trim_left = false - elseif options.trim_left == "all" then - trim_left = true - elseif options.trim_left == "code" then - trim_left = lexed_template[i + 1] and (lexed_template[i + 1].type == "code") - elseif options.trim_left == "expression" then - trim_left = lexed_template[i + 1] and (lexed_template[i + 1].type == "expression") - end - - if trim_right and trim_left then - -- both at once - if i == 1 then - if chunk.text:find("^.*\n") then - chunk.text = chunk.text:match("^(.*\n)%s-$") - elseif chunk.text:find("^%s-$") then - chunk.text = "" - end - elseif chunk.text:find("^\n") then --have to trim a newline - if chunk.text:find("^\n.*\n") then --at least two newlines - chunk.text = chunk.text:match("^\n(.*\n)%s-$") or chunk.text:match("^\n(.*)$") - elseif chunk.text:find("^\n%s-$") then - chunk.text = "" - else - chunk.text = chunk.text:gsub("^\n", "") - end - else - chunk.text = chunk.text:match("^(.*\n)%s-$") or chunk.text - end - elseif trim_left then - if i == 1 and chunk.text:find("^%s-$") then - chunk.text = "" - else - chunk.text = chunk.text:match("^(.*\n)%s-$") or chunk.text - end - elseif trim_right then - chunk.text = chunk.text:gsub("^\n", "") - end - if not (chunk.text == "") then - table.insert(lua_code, output_function..'('..string.format("%q", chunk.text)..')') - end - end - end - - return { - name = template_name, - code = table.concat(lua_code, '\n') - } -end - --- compile a file --- @return { name = string, code = string / function } -function liluat.compile_file(filename, options) - return liluat.compile(read_entire_file(filename), options, filename, filename) -end - --- @return a coroutine function -function liluat.render_coroutine(template, environment, options) - options = initialise_options(options) - environment = merge_tables({__liluat_output_function = coroutine.yield}, environment, options.reference) - - return sandbox(template.code, template.name, environment, nil, options.reference) -end - --- @return string -function liluat.render(t, env, options) - options = initialise_options(options) - - local result = {} - - -- add closure that renders the text into the result table - env = merge_tables({ - __liluat_output_function = function (text) - table.insert(result, text) end - }, - env, - options.reference - ) - - -- compile and run the lua code - local render_function = sandbox(t.code, t.name, env, nil, options.reference) - local status, error_message = pcall(render_function) - if not status then - local line_number, message = error_message:match(":(%d+):(.*)") - message = message or '' - line_number = line_number or 0 - -- lines before and after the error - local lines = string_lines(t.code, line_number - 3, line_number + 3) - error( - 'Runtime error in sandboxed code "' .. t.name .. '" in line ' .. line_number .. ':\n' - .. message .. '\n\n' - .. prepend_line_numbers(lines, line_number - 3, line_number), - 2 - ) - end - - return table.concat(result) -end - -return liluat - diff --git a/src/.lua/template.lua b/src/.lua/template.lua deleted file mode 100644 index 0a0496e..0000000 --- a/src/.lua/template.lua +++ /dev/null @@ -1,130 +0,0 @@ --- local compile = require "tmpl" --- --- local s = [[ --- --- ---
--- {! this is a comment !} --- ---