diff --git a/samples/blog/blog.lua b/samples/blog/blog.lua index 44c491a..ab020fe 100644 --- a/samples/blog/blog.lua +++ b/samples/blog/blog.lua @@ -4,12 +4,13 @@ local orbit = require "orbit" local orcache = require "orbit.cache" local markdown = require "markdown" local wsutil = require "wsapi.util" +local unpack = unpack or table.unpack -- -- Declares that this is module is an Orbit app -- local blog = setmetatable(orbit.new(), { __index = _G }) -if _VERSION == "Lua 5.2" then +if _VERSION >= "Lua 5.2" then _ENV = blog else setfenv(1, blog) diff --git a/samples/hello/hello.lua b/samples/hello/hello.lua index d84cdf7..050d88a 100644 --- a/samples/hello/hello.lua +++ b/samples/hello/hello.lua @@ -5,7 +5,12 @@ local orbit = require "orbit" -- Orbit applications are usually modules, -- orbit.new does the necessary initialization -module("hello", package.seeall, orbit.new) +local hello = orbit.new() +if _VERSION >= "Lua 5.2" then + _ENV = hello +else + setfenv(1, hello) +end -- These are the controllers, each receives a web object -- that is the request/response, plus any extra captures from the diff --git a/samples/toycms/toycms.lua b/samples/toycms/toycms.lua index 5cae4cd..b07aac3 100644 --- a/samples/toycms/toycms.lua +++ b/samples/toycms/toycms.lua @@ -5,9 +5,12 @@ local markdown = require "markdown" local orcache = require "orbit.cache" local cosmo = require "cosmo" local wsutil = require "wsapi.util" +local unpack = unpack or table.unpack -local toycms = setmetatable(orbit.new(), { __index = _G }) -if _VERSION == "Lua 5.2" then +local toycms = setmetatable(orbit.new("toycms"), { __index = _G }) +toycms.cosmo = cosmo + +if _VERSION >= "Lua 5.2" then _ENV = toycms else setfenv(1, toycms) @@ -25,10 +28,10 @@ mapper.conn = env:connect(unpack(database.conn_data)) mapper.driver = database.driver models = { - post = toycms:model "toycms_post", - comment = toycms:model "toycms_comment", - section = toycms:model "toycms_section", - user = toycms:model "toycms_user" + post = toycms:model "post", + comment = toycms:model "comment", + section = toycms:model "section", + user = toycms:model "user" } cache = orcache.new(toycms, cache_path) diff --git a/src/orbit.lua b/src/orbit.lua index f52584b..d1a27f2 100644 --- a/src/orbit.lua +++ b/src/orbit.lua @@ -3,9 +3,14 @@ local wsreq = require "wsapi.request" local wsres = require "wsapi.response" local wsutil = require "wsapi.util" +local unpack = unpack or table.unpack +local setfenv = setfenv or require("orbit.envfunc").setfenv +local getfenv = getfenv or require("orbit.envfunc").getfenv + local orm local orpages + local _M = _M or {} _M._NAME = "orbit" @@ -182,15 +187,15 @@ _M.web_methods = {} local web_methods = _M.web_methods local function flatten(t) - local res = {} - for _, item in ipairs(t) do - if type(item) == "table" then - res[#res + 1] = flatten(item) - else - res[#res + 1] = item - end - end - return table.concat(res) + local res = {} + for _, item in ipairs(t) do + if type(item) == "table" then + res[#res + 1] = flatten(item) + else + res[#res + 1] = item + end + end + return table.concat(res) end local function make_tag(name, data, class) @@ -215,185 +220,210 @@ local function make_tag(name, data, class) end function _M.new(app_module) - if type(app_module) == "string" then - app_module = { _NAME = app_module } - else - app_module = app_module or {} - end - for k, v in pairs(app_module_methods) do - app_module[k] = v - end - app_module.run = function (wsapi_env) - return _M.run(app_module, wsapi_env) - end - app_module.real_path = wsapi.app_path or "." - app_module.mapper = { default = true } - app_module.not_found = function (web) - web.status = "404 Not Found" - return [[ - Not Found -

Not found!

]] - end - app_module.server_error = function (web, msg) - web.status = "500 Server Error" - return [[ - Server Error -
]] .. msg .. [[
]] - end - app_module.reparse = REPARSE - app_module.dispatch_table = { get = {}, post = {}, put = {}, delete = {}, options = {} } - return app_module + if type(app_module) == "string" then + app_module = { _NAME = app_module } + else + app_module = app_module or {} + end + for k, v in pairs(app_module_methods) do + app_module[k] = v + end + app_module.run = function (wsapi_env) + return _M.run(app_module, wsapi_env) + end + app_module.real_path = wsapi.app_path or "." + app_module.mapper = { default = true } + app_module.not_found = function (web) + web.status = "404 Not Found" + return [[ + Not Found +

Not found!

]] + end + app_module.server_error = function (web, msg) + web.status = "500 Server Error" + return [[ + Server Error +
]] .. msg .. [[
]] + end + app_module.reparse = REPARSE + app_module.dispatch_table = { + get = {}, + post = {}, + put = {}, + delete = {}, + options = {} + } + return app_module end local function serve_file(app_module) - return function (web) - local filename = web.real_path .. web.path_info - return app_module:serve_static(web, filename) - end + return function (web) + local filename = web.real_path .. web.path_info + return app_module:serve_static(web, filename) + end end function app_module_methods.dispatch_get(app_module, func, ...) - for _, pat in ipairs{ ... } do - table.insert(app_module.dispatch_table.get, { pattern = pat, - handler = func }) - end + for _, pat in ipairs{ ... } do + table.insert(app_module.dispatch_table.get, { + pattern = pat, + handler = func + }) + end end function app_module_methods.dispatch_post(app_module, func, ...) - for _, pat in ipairs{ ... } do - table.insert(app_module.dispatch_table.post, { pattern = pat, - handler = func }) - end + for _, pat in ipairs{ ... } do + table.insert(app_module.dispatch_table.post, { + pattern = pat, + handler = func + }) + end end function app_module_methods.dispatch_put(app_module, func, ...) - for _, pat in ipairs{ ... } do - table.insert(app_module.dispatch_table.put, { pattern = pat, - handler = func }) - end + for _, pat in ipairs{ ... } do + table.insert(app_module.dispatch_table.put, { + pattern = pat, + handler = func + }) + end end function app_module_methods.dispatch_delete(app_module, func, ...) - for _, pat in ipairs{ ... } do - table.insert(app_module.dispatch_table.delete, { pattern = pat, - handler = func }) - end + for _, pat in ipairs{ ... } do + table.insert(app_module.dispatch_table.delete, { + pattern = pat, + handler = func + }) + end end function app_module_methods.dispatch_options(app_module, func, ...) - for _, pat in ipairs{ ... } do - table.insert(app_module.dispatch_table.options, { pattern = pat, - handler = func }) - end + for _, pat in ipairs{ ... } do + table.insert(app_module.dispatch_table.options, { + pattern = pat, + handler = func + }) + end end function app_module_methods.dispatch_wsapi(app_module, func, ...) for _, pat in ipairs{ ... } do for _, tab in pairs(app_module.dispatch_table) do - table.insert(tab, { pattern = pat, handler = func, wsapi = true }) + table.insert(tab, { + pattern = pat, + handler = func, + wsapi = true + }) end end end function app_module_methods.dispatch_static(app_module, ...) - app_module:dispatch_get(serve_file(app_module), ...) + app_module:dispatch_get(serve_file(app_module), ...) end function app_module_methods.serve_static(app_module, web, filename) - local ext = string.match(filename, "%.([^%.]+)$") - if app_module.use_xsendfile then + local ext = string.match(filename, "%.([^%.]+)$") + if app_module.use_xsendfile then + web.headers["Content-Type"] = _M.mime_types[ext] or + "application/octet-stream" + web.headers["X-Sendfile"] = filename + return "xsendfile" + else + local file = io.open(filename, "rb") + if not file then + return app_module.not_found(web) + else web.headers["Content-Type"] = _M.mime_types[ext] or - "application/octet-stream" - web.headers["X-Sendfile"] = filename - return "xsendfile" - else - local file = io.open(filename, "rb") - if not file then - return app_module.not_found(web) - else - web.headers["Content-Type"] = _M.mime_types[ext] or - "application/octet-stream" - local contents = file:read("*a") - file:close() - return contents - end - end + "application/octet-stream" + local contents = file:read("*a") + file:close() + return contents + end + end end local function newtag(name) local tag = {} setmetatable(tag, { - __call = function (_, data) - return make_tag(name, data) - end, - __index = function(_, class) - return function (data) - return make_tag(name, data, class) - end - end - }) + __call = function (_, data) + return make_tag(name, data) + end, + __index = function(_, class) + return function (data) + return make_tag(name, data, class) + end + end + }) return tag end local function htmlify_func(func) local tags = {} - local env = { H = function (name) - local tag = tags[name] - if not tag then - tag = newtag(name) - tags[name] = tag - end - return tag - end - } + local env = { + H = function (name) + local tag = tags[name] + if not tag then + tag = newtag(name) + tags[name] = tag + end + return tag + end + } local old_env = getfenv(func) - setmetatable(env, { __index = function (env, name) - if old_env[name] then - return old_env[name] - else - local tag = newtag(name) - rawset(env, name, tag) - return tag - end - end }) + setmetatable(env, { + __index = function (env, name) + if old_env[name] then + return old_env[name] + else + local tag = newtag(name) + rawset(env, name, tag) + return tag + end + end + }) setfenv(func, env) end function _M.htmlify(app_module, ...) - if type(app_module) == "function" then - htmlify_func(app_module) - for _, func in ipairs{...} do - htmlify_func(func) - end - else - local patterns = { ... } - for _, patt in ipairs(patterns) do - if type(patt) == "function" then - htmlify_func(patt) - else - for name, func in pairs(app_module) do - if string.match(name, "^" .. patt .. "$") and - type(func) == "function" then - htmlify_func(func) - end - end - end + if type(app_module) == "function" then + htmlify_func(app_module) + for _, func in ipairs{...} do + htmlify_func(func) + end + else + local patterns = { ... } + for _, patt in ipairs(patterns) do + if type(patt) == "function" then + htmlify_func(patt) + else + for name, func in pairs(app_module) do + if string.match(name, "^" .. patt .. "$") + and type(func) == "function" + then + htmlify_func(func) + end + end end - end + end + end end app_module_methods.htmlify = _M.htmlify function app_module_methods.model(app_module, ...) - if app_module.mapper.default then - local table_prefix = (app_module._NAME and app_module._NAME .. "_") or "" - if not orm then - orm = require "orbit.model" - end - app_module.mapper = orm.new(app_module.mapper.table_prefix or table_prefix, - app_module.mapper.conn, app_module.mapper.driver, app_module.mapper.logging) - end - return app_module.mapper:new(...) + if app_module.mapper.default then + local table_prefix = (app_module._NAME and app_module._NAME .. "_") or "" + if not orm then + orm = require "orbit.model" + end + app_module.mapper = orm.new(app_module.mapper.table_prefix or table_prefix, + app_module.mapper.conn, app_module.mapper.driver, + app_module.mapper.logging) + end + return app_module.mapper:new(...) end function web_methods:redirect(url) @@ -465,8 +495,8 @@ end for name, func in pairs(wsutil) do web_methods[name] = function (self, ...) - return func(...) - end + return func(...) + end end local function dispatcher(app_module, method, path, index) @@ -478,26 +508,28 @@ local function dispatcher(app_module, method, path, index) local item = app_module.dispatch_table[method][index] local captures if type(item.pattern) == "string" then - captures = { string.match(path, "^" .. item.pattern .. "$") } + captures = { string.match(path, "^" .. item.pattern .. "$") } else - captures = { item.pattern:match(path) } + captures = { item.pattern:match(path) } end if #captures > 0 then - for i = 1, #captures do - if type(captures[i]) == "string" then - captures[i] = wsutil.url_decode(captures[i]) - end - end - return item.handler, captures, item.wsapi, index + for i = 1, #captures do + if type(captures[i]) == "string" then + captures[i] = wsutil.url_decode(captures[i]) + end + end + return item.handler, captures, item.wsapi, index end end end end local function make_web_object(app_module, wsapi_env) - local web = { status = "200 Ok", response = "", - headers = { ["Content-Type"]= "text/html" }, - cookies = {} } + local web = { + status = "200 Ok", response = "", + headers = { ["Content-Type"]= "text/html" }, + cookies = {} + } setmetatable(web, { __index = web_methods }) web.vars = wsapi_env web.prefix = app_module.prefix or wsapi_env.SCRIPT_NAME @@ -510,15 +542,20 @@ local function make_web_object(app_module, wsapi_env) web.doc_root = wsapi_env.DOCUMENT_ROOT local req = wsreq.new(wsapi_env) local res = wsres.new(web.status, web.headers) + web.set_cookie = function (_, name, value) - res:set_cookie(name, value) - end + res:set_cookie(name, value) + end + web.delete_cookie = function (_, name, path) - res:delete_cookie(name, path) - end + res:delete_cookie(name, path) + end + web.path_info = req.path_info web.path_translated = wsapi_env.PATH_TRANSLATED - if web.path_translated == "" then web.path_translated = wsapi_env.SCRIPT_FILENAME end + if web.path_translated == "" then + web.path_translated = wsapi_env.SCRIPT_FILENAME + end web.script_name = wsapi_env.SCRIPT_NAME web.method = string.lower(req.method) web.input, web.cookies = req.params, req.cookies @@ -528,14 +565,13 @@ end function _M.run(app_module, wsapi_env) local handler, captures, wsapi_handler, index = dispatcher(app_module, - string.lower(wsapi_env.REQUEST_METHOD), - wsapi_env.PATH_INFO) + string.lower(wsapi_env.REQUEST_METHOD), wsapi_env.PATH_INFO) handler = handler or app_module.not_found captures = captures or {} if wsapi_handler then local ok, status, headers, res = xpcall(function () - return handler(wsapi_env, unpack(captures)) - end, debug.traceback) + return handler(wsapi_env, unpack(captures)) + end, debug.traceback) if ok then return status, headers, res else @@ -545,30 +581,31 @@ function _M.run(app_module, wsapi_env) local web, res = make_web_object(app_module, wsapi_env) repeat local reparse = false - local ok, response = xpcall(function () - return handler(web, unpack(captures)) - end, function(msg) return debug.traceback(msg) end) + local ok, response = xpcall( + function () + return handler(web, unpack(captures)) + end, + function(msg) return debug.traceback(msg) end + ) if not ok then res.status = "500 Internal Server Error" res:write(app_module.server_error(web, response)) else if response == REPARSE then - reparse = true - handler, captures, wsapi_handler, index = dispatcher(app_module, - string.lower(wsapi_env.REQUEST_METHOD), - wsapi_env.PATH_INFO, index) - handler, captures = handler or app_module.not_found, captures or {} - if wsapi_handler then - error("cannot reparse to WSAPI handler") - end + reparse = true + handler, captures, wsapi_handler, index = dispatcher(app_module, + string.lower(wsapi_env.REQUEST_METHOD), wsapi_env.PATH_INFO, index) + handler, captures = handler or app_module.not_found, captures or {} + if wsapi_handler then + error("cannot reparse to WSAPI handler") + end else - res.status = web.status - res:write(response) + res.status = web.status + res:write(response) end end until not reparse return res:finish() end -return _M - +return _M \ No newline at end of file diff --git a/src/orbit/cache.lua b/src/orbit/cache.lua index 83017c2..2b1165b 100644 --- a/src/orbit/cache.lua +++ b/src/orbit/cache.lua @@ -1,118 +1,122 @@ local lfs = require "lfs" -module("orbit.cache", package.seeall) +local _M = {} local function pathinfo_to_file(path_info) - local atom = path_info:find("/xml$") - if atom then - path_info = path_info:sub(2, atom - 1) - else - path_info = path_info:sub(2, #path_info) - end - path_info = string.gsub(path_info, "/", "-") - if path_info == "" then path_info = "index" end - if atom then - return path_info .. '.xml' - else - return path_info .. '.html' - end + local atom = path_info:find("/xml$") + if atom then + path_info = path_info:sub(2, atom - 1) + else + path_info = path_info:sub(2, #path_info) + end + path_info = string.gsub(path_info, "/", "-") + if path_info == "" then path_info = "index" end + if atom then + return path_info .. '.xml' + else + return path_info .. '.html' + end end -function get(cache, key) +function _M.get(cache, key) if not cache.base_path then - local headers = {} - if key:find("/xml$") then - headers["Content-Type"] = "text/xml" - else - headers["Content-Type"] = "text/html" - end - return cache.values[key], headers + local headers = {} + if key:find("/xml$") then + headers["Content-Type"] = "text/xml" + else + headers["Content-Type"] = "text/html" + end + return cache.values[key], headers else - local filename = cache.base_path .. "/" .. pathinfo_to_file(key) - local web = { headers = {} } - if lfs.attributes(filename, "mode") == "file" then - local response = cache.app:serve_static(web, filename) - return response, web.headers - end + local filename = cache.base_path .. "/" .. pathinfo_to_file(key) + local web = { headers = {} } + if lfs.attributes(filename, "mode") == "file" then + local response = cache.app:serve_static(web, filename) + return response, web.headers + end end end local function writefile(filename, contents) local file = assert(io.open(filename, "wb")) if lfs.lock(file, "w") then - file:write(contents) - lfs.unlock(file) - file:close() + file:write(contents) + lfs.unlock(file) + file:close() else - file:close() + file:close() end end -function set(cache, key, value) +function _M.set(cache, key, value) if not cache.base_path then - cache.values[key] = value + cache.values[key] = value else - local filename = cache.base_path .. "/" .. pathinfo_to_file(key) - writefile(filename, value) + local filename = cache.base_path .. "/" .. pathinfo_to_file(key) + writefile(filename, value) end end local function cached(cache, f) return function (web, ...) - local body, headers = cache:get(web.path_info) - if body then - for k, v in pairs(headers) do - web.headers[k] = v - end - return body - else - local key = web.path_info - local body = f(web, ...) - cache:set(key, body) - return body - end - end + local body, headers = cache:get(web.path_info) + if body then + for k, v in pairs(headers) do + web.headers[k] = v + end + return body + else + local key = web.path_info + local body = f(web, ...) + cache:set(key, body) + return body + end + end end -function invalidate(cache, ...) - for _, key in ipairs{...} do - if not cache.base_path then - cache.values[key] = nil - else - local filename = cache.base_path .. "/" .. pathinfo_to_file(key) - os.remove(filename) - end - end +function _M.invalidate(cache, ...) + for _, key in ipairs{...} do + if not cache.base_path then + cache.values[key] = nil + else + local filename = cache.base_path .. "/" .. pathinfo_to_file(key) + os.remove(filename) + end + end end -function nuke(cache) - if not cache.base_path then - cache.values = {} - else - for file in lfs.dir(cache.base_path) do - if file ~= "." and file ~= ".." then - os.remove(cache.base_path .. "/" .. file) - end +function _M.nuke(cache) + if not cache.base_path then + cache.values = {} + else + for file in lfs.dir(cache.base_path) do + if file ~= "." and file ~= ".." then + os.remove(cache.base_path .. "/" .. file) end - end + end + end end -function new(app, base_path) - local values - if not base_path then - values = {} - else - local dir = lfs.attributes(base_path, "mode") - if not dir then - assert(lfs.mkdir(base_path)) - elseif dir ~= "directory" then - error("base path of cache " .. base_path .. " not a directory") - end - end - local cache = { app = app, values = values, - base_path = base_path } - setmetatable(cache, { __index = _M, __call = function (tab, f) - return cached(tab, f) - end }) - return cache +function _M.new(app, base_path) + local values + if not base_path then + values = {} + else + local dir = lfs.attributes(base_path, "mode") + if not dir then + assert(lfs.mkdir(base_path)) + elseif dir ~= "directory" then + error("base path of cache " .. base_path .. " not a directory") + end + end + local cache = { app = app, values = values, base_path = base_path } + setmetatable(cache, { + __index = _M, + __call = function (tab, f) + return cached(tab, f) + end + }) + return cache end + +return _M \ No newline at end of file diff --git a/src/orbit/envfunc.lua b/src/orbit/envfunc.lua new file mode 100644 index 0000000..e7370a3 --- /dev/null +++ b/src/orbit/envfunc.lua @@ -0,0 +1,33 @@ +local _M = {} + +function _M.setfenv(func, env) + local i = 1 + while true do + local name = debug.getupvalue(func, i) + if name == "_ENV" then + debug.upvaluejoin(func, i, function() + return env + end, 1) + break + elseif not name then + break + end + i = i + 1 + end + return func +end + +function _M.getfenv(func) + local i = 1 + while true do + local name, val = debug.getupvalue(func, i) + if name == "_ENV" then + return val + elseif not name then + break + end + i = i + 1 + end +end + +return _M \ No newline at end of file diff --git a/src/orbit/model.lua b/src/orbit/model.lua index 18dbaa4..fa6b44c 100755 --- a/src/orbit/model.lua +++ b/src/orbit/model.lua @@ -1,11 +1,12 @@ local lpeg = require "lpeg" local re = require "re" -module("orbit.model", package.seeall) +local unpack = unpack or table.unpack -model_methods = {} +local _M = {} -dao_methods = {} +_M.model_methods = {} +_M.dao_methods = {} local type_names = {} @@ -90,9 +91,10 @@ end function convert.datetime(v) local year, month, day, hour, min, sec = string.match(v, "(%d+)%-(%d+)%-(%d+) (%d+):(%d+):(%d+)") - return os.time({ year = tonumber(year), month = tonumber(month), - day = tonumber(day), hour = tonumber(hour), - min = tonumber(min), sec = tonumber(sec) }) + return os.time({ + year = tonumber(year), month = tonumber(month), day = tonumber(day), + hour = tonumber(hour), min = tonumber(min), sec = tonumber(sec) + }) end convert.timestamp = convert.datetime @@ -154,9 +156,13 @@ escape.timestamp = escape.datetime function escape.boolean(v, driver) if v then - if driver == "sqlite3" or driver == "postgres" then return "'t'" else return tostring(v) end + if driver == "sqlite3" or driver == "postgres" then + return "'t'" else return tostring(v) + end else - if driver == "sqlite3" or driver == "postgres" then return "'f'" else return tostring(v) end + if driver == "sqlite3" or driver == "postgres" then + return "'f'" else return tostring(v) + end end end @@ -172,9 +178,9 @@ local function escape_values(row) else local esc = escape[m.type] if esc then - row_escaped[m.name] = esc(row[m.name], row.driver, row.model.conn) + row_escaped[m.name] = esc(row[m.name], row.driver, row.model.conn) else - error("no escape function for type " .. m.type) + error("no escape function for type " .. m.type) end end end @@ -227,7 +233,8 @@ local function parse_condition(dao, condition, args) elseif type(args[j]) == "table" then local values = {} for _, value in ipairs(args[j]) do - values[#values + 1] = escape[dao.meta[part].type](value, dao.driver, dao.model.conn) + values[#values + 1] = escape[dao.meta[part].type](value, dao.driver, + dao.model.conn) end parts[i] = part .. " IN (" .. table.concat(values,", ") .. ")" else @@ -242,44 +249,56 @@ end local function build_inject(project, inject, dao) local fields = {} if project then - for i, field in ipairs(project) do - fields[i] = dao.table_name .. "." .. field .. " as " .. field - end + for i, field in ipairs(project) do + fields[i] = dao.table_name .. "." .. field .. " as " .. field + end else - for i, field in ipairs(dao.meta) do - fields[i] = dao.table_name .. "." .. field.name .. " as " .. field.name - end + for i, field in ipairs(dao.meta) do + fields[i] = dao.table_name .. "." .. field.name .. " as " .. field.name + end end local inject_fields = {} local model = inject.model for _, field in ipairs(inject.fields) do - inject_fields[model.name .. "_" .. field] = - model.meta[field] + inject_fields[model.name .. "_" .. field] = model.meta[field] fields[#fields + 1] = model.table_name .. "." .. field .. " as " .. model.name .. "_" .. field end setmetatable(dao.meta, { __index = inject_fields }) return table.concat(fields, ", "), dao.table_name .. ", " .. - model.table_name, model.name .. "_id = " .. model.table_name .. ".id" + model.table_name, dao.table_name .. "." .. model.name .. "_id = " .. + model.table_name .. ".id" end local function build_query_by(dao, condition, args) local parts = parse_condition(dao, condition, args) local order = "" local field_list, table_list, select, limit, offset - if args.distinct then select = "select distinct " else select = "select " end - if tonumber(args.count) then limit = " limit " .. tonumber(args.count) else limit = "" end - if tonumber(args.offset) then offset = " offset " .. tonumber(args.offset) else offset = "" end + if args.distinct then + select = "select distinct " + else + select = "select " + end + if tonumber(args.count) then + limit = " limit " .. tonumber(args.count) + else + limit = "" + end + if tonumber(args.offset) then + offset = " offset " .. tonumber(args.offset) + else + offset = "" + end if args.order then order = " order by " .. args.order end if args.inject then if #parts > 0 then parts[#parts + 1] = "and" end - field_list, table_list, parts[#parts + 1] = build_inject(args.fields, args.inject, - dao) + field_list, table_list, parts[#parts + 1] = build_inject(args.fields, + args.inject, dao) else if args.fields then - field_list = table.concat(args.fields, ", ") + field_list = table.concat(args.fields, ", ") else - field_list = "*" + field_list = "*" end table_list = dao.table_name end @@ -298,7 +317,7 @@ local function find_all_by(dao, condition, args) end local function dao_index(dao, name) - local m = dao_methods[name] + local m = _M.dao_methods[name] if m then return m else @@ -314,7 +333,7 @@ local function dao_index(dao, name) end end -function model_methods:new(name, dao) +function _M.model_methods:new(name, dao) dao = dao or {} dao.model, dao.name, dao.table_name, dao.meta, dao.driver = self, name, self.table_prefix .. name, {}, self.driver @@ -326,59 +345,72 @@ function model_methods:new(name, dao) local names, types = cursor:getcolnames(), cursor:getcoltypes() cursor:close() for i = 1, #names do - local colinfo = { name = names[i], - type = type_names[self.driver](types[i]) } + local colinfo = { + name = names[i], + type = type_names[self.driver](types[i]) + } dao.meta[i] = colinfo dao.meta[colinfo.name] = colinfo end return dao end -function recycle(fresh_conn, timeout) +function _M.recycle(fresh_conn, timeout) local created_at = os.time() local conn = fresh_conn() timeout = timeout or 20000 - return setmetatable({}, { __index = function (tab, meth) - tab[meth] = function (tab, ...) - if created_at + timeout < os.time() then - created_at = os.time() - pcall(conn.close, conn) - conn = fresh_conn() - end - return conn[meth](conn, ...) - end - return tab[meth] - end - }) -end - -function new(table_prefix, conn, driver, logging) + return setmetatable({}, { + __index = function (tab, meth) + tab[meth] = function (tab, ...) + if created_at + timeout < os.time() then + created_at = os.time() + pcall(conn.close, conn) + conn = fresh_conn() + end + return conn[meth](conn, ...) + end + return tab[meth] + end + }) +end + +function _M.new(table_prefix, conn, driver, logging) driver = driver or "sqlite3" - local app_model = { table_prefix = table_prefix or "", conn = conn, driver = driver or "sqlite3", logging = logging, models = {} } - setmetatable(app_model, { __index = model_methods }) + local app_model = { + table_prefix = table_prefix or "", + conn = conn, + driver = driver or "sqlite3", + logging = logging, + models = {} + } + setmetatable(app_model, { __index = _M.model_methods }) return app_model end -function dao_methods.find(dao, id, inject) +function _M.dao_methods.find(dao, id, inject) if not type(id) == "number" then error("find error: id must be a number") end - local sql = "select * from " .. dao.table_name .. - " where id=" .. id + local sql = "select * from " .. dao.table_name .. " where id=" .. id if dao.logging then log_query(sql) end return fetch_row(dao, sql) end -condition_parser = re.compile([[ - top <- {~ * ~} - s <- %s+ -> ' ' / '' - condition <- ( '(' ')' / ) ( )* - simple <- (%func '?') -> apply / / - - field <- ! {[_%w]+('.'[_%w]+)?} - op <- {~ [!<>=~]+ / ((%s+ -> ' ') ! %w+)+ ~} - conective <- [aA][nN][dD] / [oO][rR] - ]], { func = lpeg.Carg(1) , apply = function (f, field, op) return f(field, op) end }) +condition_parser = re.compile( + [[ + top <- {~ * ~} + s <- %s+ -> ' ' / '' + condition <- ( '(' ')' / ) ( )* + simple <- (%func '?') -> apply / / + + field <- ! {[_%w]+('.'[_%w]+)?} + op <- {~ [!<>=~]+ / ((%s+ -> ' ') ! %w+)+ ~} + conective <- [aA][nN][dD] / [oO][rR] + ]], + { + func = lpeg.Carg(1), + apply = function (f, field, op) return f(field, op) end + }) local function build_query(dao, condition, args) local i = 0 @@ -390,36 +422,47 @@ local function build_query(dao, condition, args) end if condition ~= "" then condition = " where " .. - condition_parser:match(condition, 1, - function (field, op) - i = i + 1 - if not args[i] then - return "id=id" - elseif type(args[i]) == "table" and args[i].type == "query" then - return field .. " " .. op .. " (" .. args[i][1] .. ")" - elseif type(args[i]) == "table" then - local values = {} - for j, value in ipairs(args[i]) do - values[#values + 1] = field .. " " .. op .. " " .. - escape[dao.meta[field].type](value, dao.driver, dao.model.conn) - end - return "(" .. table.concat(values, " or ") .. ")" - else - return field .. " " .. op .. " " .. - escape[dao.meta[field].type](args[i], dao.driver, dao.model.conn) - end - end) + condition_parser:match(condition, 1, function (field, op) + i = i + 1 + if not args[i] then + return "id=id" + elseif type(args[i]) == "table" and args[i].type == "query" then + return field .. " " .. op .. " (" .. args[i][1] .. ")" + elseif type(args[i]) == "table" then + local values = {} + for j, value in ipairs(args[i]) do + values[#values + 1] = field .. " " .. op .. " " .. + escape[dao.meta[field].type](value, dao.driver, dao.model.conn) + end + return "(" .. table.concat(values, " or ") .. ")" + else + return field .. " " .. op .. " " .. + escape[dao.meta[field].type](args[i], dao.driver, dao.model.conn) + end + end) end local order = "" if args.order then order = " order by " .. args.order end local field_list, table_list, select, limit, offset - if args.distinct then select = "select distinct " else select = "select " end - if tonumber(args.count) then limit = " limit " .. tonumber(args.count) else limit = "" end - if tonumber(args.offset) then offset = " offset " .. tonumber(args.offset) else offset = "" end + if args.distinct then + select = "select distinct " + else + select = "select " + end + if tonumber(args.count) then + limit = " limit " .. tonumber(args.count) + else + limit = "" + end + if tonumber(args.offset) then + offset = " offset " .. tonumber(args.offset) + else + offset = "" + end if args.inject then local inject_condition - field_list, table_list, inject_condition = build_inject(args.fields, args.inject, - dao) + field_list, table_list, inject_condition = build_inject(args.fields, + args.inject, dao) if condition == "" then condition = " where " .. inject_condition else @@ -431,7 +474,10 @@ local function build_query(dao, condition, args) else field_list = "*" end - table_list = table.concat({ dao.table_name, unpack(args.from or {}) }, ", ") + table_list = table.concat({ + dao.table_name, + unpack(args.from or {}) + }, ", ") end local sql = select .. field_list .. " from " .. table_list .. condition .. order .. limit .. offset @@ -439,16 +485,16 @@ local function build_query(dao, condition, args) return sql end -function dao_methods.find_first(dao, condition, args) +function _M.dao_methods.find_first(dao, condition, args) return fetch_row(dao, build_query(dao, condition, args)) end -function dao_methods.find_all(dao, condition, args) +function _M.dao_methods.find_all(dao, condition, args) return fetch_rows(dao, build_query(dao, condition, args), - (args and args.count) or (condition and condition.count)) + (args and args.count) or (condition and condition.count)) end -function dao_methods.new(dao, row) +function _M.dao_methods.new(dao, row) row = row or {} setmetatable(row, { __index = dao }) return row @@ -508,7 +554,7 @@ local function insert(row) end end -function dao_methods.save(row, force_insert) +function _M.dao_methods.save(row, force_insert) if row.id and (not force_insert) then update(row) else @@ -516,7 +562,7 @@ function dao_methods.save(row, force_insert) end end -function dao_methods.delete(row) +function _M.dao_methods.delete(row) if row.id then local sql = "delete from " .. row.table_name .. " where id = " .. row.id if row.model.logging then log_query(sql) end @@ -524,3 +570,5 @@ function dao_methods.delete(row) if ok then row.id = nil else error(err) end end end + +return _M \ No newline at end of file diff --git a/src/orbit/ophandler.lua b/src/orbit/ophandler.lua index 8ebc2ec..6a7e8c3 100644 --- a/src/orbit/ophandler.lua +++ b/src/orbit/ophandler.lua @@ -13,9 +13,9 @@ local wscom = require "wsapi.common" -- Returns the Orbit Pages handler ------------------------------------------------------------------------------- local function makeHandler (diskpath, params) - params = setmetatable({ modname = params.modname or "orbit.pages" }, { __index = params or {} }) - local op_loader = wscom.make_isolated_launcher(params) - return wsxav.makeHandler(op_loader, nil, diskpath) + params = setmetatable({ modname = params.modname or "orbit.pages" }, { __index = params or {} }) + local op_loader = wscom.make_isolated_launcher(params) + return wsxav.makeHandler(op_loader, nil, diskpath) end -return { makeHandler = makeHandler } +return { makeHandler = makeHandler } \ No newline at end of file diff --git a/src/orbit/pages.lua b/src/orbit/pages.lua index dfc1740..36da082 100644 --- a/src/orbit/pages.lua +++ b/src/orbit/pages.lua @@ -1,17 +1,17 @@ - local orbit = require "orbit" local model = require "orbit.model" local cosmo = require "cosmo" local io, string = io, string -local setmetatable, loadstring, setfenv = setmetatable, loadstring, setfenv +local setmetatable, loadstring = setmetatable, loadstring or load +local setfenv = setfenv or require("orbit.envfunc").setfenv local type, error, tostring = type, error, tostring local print, pcall, xpcall, traceback = print, pcall, xpcall, debug.traceback -local select, unpack = select, unpack +local select, unpack = select, unpack or table.unpack local _G = _G -module("orbit.pages", orbit.new) +local _M = {} local template_cache = {} @@ -26,21 +26,21 @@ local function splitpath(filename) return path, file end -function load(filename, contents) +function _M.load(filename, contents) filename = filename or contents local template = template_cache[filename] if not template then - if not contents then - local file = io.open(filename) - if not file then - return nil - end - contents = file:read("*a") - file:close() - if contents:sub(1,3) == BOM then contents = contents:sub(4) end - end - template = cosmo.compile(remove_shebang(contents)) - template_cache[filename] = template + if not contents then + local file = io.open(filename) + if not file then + return nil + end + contents = file:read("*a") + file:close() + if contents:sub(1,3) == BOM then contents = contents:sub(4) end + end + template = cosmo.compile(remove_shebang(contents)) + template_cache[filename] = template end return template end @@ -48,15 +48,14 @@ end local function env_index(env, key) local val = _G[key] if not val and type(key) == "string" then - local template = - load(env.web.real_path .. "/" .. key .. ".op") + local template = load(env.web.real_path .. "/" .. key .. ".op") if not template then return nil end return function (arg) - arg = arg or {} - if arg[1] then arg.it = arg[1] end - local subt_env = setmetatable(arg, { __index = env }) - return template(subt_env) - end + arg = arg or {} + if arg[1] then arg.it = arg[1] end + local subt_env = setmetatable(arg, { __index = env }) + return template(subt_env) + end end return val end @@ -85,13 +84,15 @@ local function make_env(web, initial) end end env["if"] = function (arg) - if type(arg[1]) == "function" then arg[1] = arg[1](select(2, unpack(arg))) end - if arg[1] then - cosmo.yield{ it = arg[1], _template = 1 } - else - cosmo.yield{ _template = 2 } - end - end + if type(arg[1]) == "function" then + arg[1] = arg[1](select(2, unpack(arg))) + end + if arg[1] then + cosmo.yield{ it = arg[1], _template = 1 } + else + cosmo.yield{ _template = 2 } + end + end function env.redirect(target) if type(target) == "table" then target = target[1] end web:redirect(target) @@ -143,16 +144,20 @@ local function make_env(web, initial) return env end -function fill(web, template, env) +function _M.fill(web, template, env) if template then - local ok, res = xpcall(function () return template(make_env(web, env)) end, - function (msg) - if type(msg) == "table" and msg[1] == abort then - return msg - else - return traceback(msg) - end - end) + local ok, res = xpcall( + function () + return template(make_env(web, env)) + end, + function (msg) + if type(msg) == "table" and msg[1] == abort then + return msg + else + return traceback(msg) + end + end + ) if not ok and (type(res) ~= "table" or res[1] ~= abort) then error(res) elseif ok then @@ -163,20 +168,20 @@ function fill(web, template, env) end end -function handle_get(web) +function _M.handle_get(web) local filename = web.path_translated web.real_path = splitpath(filename) local res = fill(web, load(filename)) if res then return res else - web.status = 404 - return [[ - Not Found -

Not found!

]] + web.status = 404 + return [[ + Not Found +

Not found!

]] end end -handle_post = handle_get +_M.handle_post = _M.handle_get -return _M +return _M \ No newline at end of file diff --git a/src/orbit/routes.lua b/src/orbit/routes.lua index f0e597d..db3ae16 100644 --- a/src/orbit/routes.lua +++ b/src/orbit/routes.lua @@ -1,4 +1,5 @@ -local setmetatable, type, ipairs, table, string = setmetatable, type, ipairs, table, string +local setmetatable, type, ipairs, table, string = setmetatable, type, ipairs, + table, string local lpeg = require "lpeg" local re = require "re" @@ -18,41 +19,53 @@ local forward_slash = lpeg.P('/') local slash_or_dot = forward_slash + the_dot local function cap_param(prefix, name, dot) - local inner = (1 - lpeg.S('/' .. (dot or '')))^1 - local close = lpeg.P'/' + (dot or -1) + -1 - return { - cap = lpeg.Carg(1) * slash_or_dot * lpeg.C(inner^1) * #close / function (params, item, delim) params[name] = util.url_decode(item) end, - clean = slash_or_dot * inner^1 * #close, - tag = "param", - name = name, - prefix = prefix - } + local inner = (1 - lpeg.S('/' .. (dot or '')))^1 + local close = lpeg.P'/' + (dot or -1) + -1 + return { + cap = lpeg.Carg(1) * slash_or_dot * lpeg.C(inner^1) * + #close / function (params, item, delim) + params[name] = util.url_decode(item) + end, + clean = slash_or_dot * inner^1 * #close, + tag = "param", + name = name, + prefix = prefix + } end -local param_pre = lpeg.C(slash_or_dot) * colon * lpeg.C((alpha + number + underscore)^1) +local param_pre = lpeg.C(slash_or_dot) * colon * + lpeg.C((alpha + number + underscore)^1) local param = (param_pre * #(forward_slash + -1) / cap_param) + - (param_pre * #the_dot / function (prefix, name) return cap_param(prefix, name, ".") end) + (param_pre * #the_dot / function (prefix, name) + return cap_param(prefix, name, ".") + end) local function cap_opt_param(prefix, name, dot) - local inner = (1 - lpeg.S('/' .. (dot or '')))^1 - local close = lpeg.P('/') + lpeg.P(dot or -1) + -1 - return { - cap = (lpeg.Carg(1) * slash_or_dot * lpeg.C(inner) * #close / function (params, item, delim) params[name] = util.url_decode(item) end)^-1, - clean = (slash_or_dot * inner * #lpeg.C(close))^-1, - tag = "opt", - name = name, - prefix = prefix - } + local inner = (1 - lpeg.S('/' .. (dot or '')))^1 + local close = lpeg.P('/') + lpeg.P(dot or -1) + -1 + return { + cap = (lpeg.Carg(1) * slash_or_dot * lpeg.C(inner) * + #close / function (params, item, delim) + params[name] = util.url_decode(item) + end)^-1, + clean = (slash_or_dot * inner * #lpeg.C(close))^-1, + tag = "opt", + name = name, + prefix = prefix + } end -local opt_param_pre = lpeg.C(slash_or_dot) * question_mark * colon * lpeg.C((alpha + number + underscore)^1) * question_mark +local opt_param_pre = lpeg.C(slash_or_dot) * question_mark * colon * + lpeg.C((alpha + number + underscore)^1) * question_mark local opt_param = (opt_param_pre * #(forward_slash + -1) / cap_opt_param) + - (opt_param_pre * #the_dot / function (prefix, name) return cap_opt_param(prefix, name, ".") end) + (opt_param_pre * #the_dot / function (prefix, name) + return cap_opt_param(prefix, name, ".") + end) -local splat = lpeg.P(lpeg.C(forward_slash + the_dot) * asterisk * #(forward_slash + the_dot + -1)) / - function (prefix) +local splat = lpeg.P(lpeg.C(forward_slash + the_dot) * asterisk * + #(forward_slash + the_dot + -1)) / function (prefix) return { cap = "*", tag = "splat", @@ -69,21 +82,19 @@ local function fold_captures(cap, acc) clean = lpeg.P(cap) * acc.clean } end - -- if we have a star match (match everything) if cap.cap == "*" then return { - cap = (lpeg.Carg(1) * (cap.prefix * lpeg.C((1 - acc.clean)^0))^-1 / - function (params, splat) - params.splat = params.splat or {} - if splat and splat ~= "" then - params.splat[#params.splat+1] = util.url_decode(splat) - end - end) * acc.cap, + cap = (lpeg.Carg(1) * (cap.prefix * + lpeg.C((1 - acc.clean)^0))^-1 / function (params, splat) + params.splat = params.splat or {} + if splat and splat ~= "" then + params.splat[#params.splat+1] = util.url_decode(splat) + end + end) * acc.cap, clean = (cap.prefix * (1 - acc.clean)^0)^-1 * acc.clean } end - return { cap = cap.cap * acc.cap, clean = cap.clean * acc.clean @@ -91,7 +102,6 @@ local function fold_captures(cap, acc) end local function fold_parts(parts, cap) - if type(cap) == "string" then -- if the capture is a string parts[#parts+1] = { tag = "text", @@ -104,7 +114,6 @@ local function fold_parts(parts, cap) name = cap.name } end - return parts end @@ -126,7 +135,10 @@ end local route = lpeg.Ct((param + opt_param + splat + rest)^0 * -1) / function (caps) local forward_slash_at_end = lpeg.P('/')^-1 * -1 - return fold_right(caps, fold_captures, { cap = forward_slash_at_end, clean = forward_slash_at_end }), fold_left(caps, fold_parts, {}) + return fold_right(caps, fold_captures, { + cap = forward_slash_at_end, + clean = forward_slash_at_end + }), fold_left(caps, fold_parts, {}) end local function build(parts, params) @@ -135,51 +147,58 @@ local function build(parts, params) params.splat = params.splat or {} for _, part in ipairs(parts) do if part.tag == 'param' then - if not params[part.name] then error('route parameter ' .. part.name .. ' does not exist') end - local s = string.gsub (params[part.name], '([^%.@]+)', function (s) return util.url_encode(s) end) + if not params[part.name] then + error('route parameter ' .. part.name .. ' does not exist') + end + local s = string.gsub (params[part.name], '([^%.@]+)', function (s) + return util.url_encode(s) + end) res[#res+1] = part.prefix .. s elseif part.tag == "splat" then - local s = string.gsub (params.splat[i] or '', '([^/%.@]+)', function (s) return util.url_encode(s) end) + local s = string.gsub (params.splat[i] or '', '([^/%.@]+)', function (s) + return util.url_encode(s) + end) res[#res+1] = part.prefix .. s i = i + 1 elseif part.tag == "opt" then if params and params[part.name] then - local s = string.gsub (params[part.name], '([^%.@]+)', function (s) return util.url_encode(s) end) + local s = string.gsub (params[part.name], '([^%.@]+)', function (s) + return util.url_encode(s) + end) res[#res+1] = part.prefix .. s end else res[#res+1] = part.text end end - if #res > 0 then return table.concat(res) end - return '/' end function _M.R(path) local p, b = route:match(path) - - return setmetatable({ - parser = p.cap, - parts = b - }, { - __index = { - match = function (t, s) - local params = {} - if t.parser:match(s, 1, params) then - return params + return setmetatable( + { + parser = p.cap, + parts = b + }, + { + __index = { + match = function (t, s) + local params = {} + if t.parser:match(s, 1, params) then + return params + end + return nil + end, + build = function (t, params) + return build(t.parts, params) end - return nil - end, - build = function (t, params) - return build(t.parts, params) - end + } } - }) - + ) end return setmetatable(_M, {