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!
]] - end - app_module.server_error = function (web, msg) - web.status = "500 Server Error" - return [[ -]] .. 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!
]] + end + app_module.server_error = function (web, msg) + web.status = "500 Server Error" + return [[ +]] .. 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 <- {~
Not found!
]] + web.status = 404 + return [[ +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, {