Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
orbit/src/orbit/routes.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
189 lines (162 sloc)
5.21 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local setmetatable, type, ipairs, table, string = setmetatable, type, ipairs, table, string | |
| local lpeg = require "lpeg" | |
| local re = require "re" | |
| local util = require "wsapi.util" | |
| local _M = {} | |
| local alpha = lpeg.R('AZ', 'az') | |
| local number = lpeg.R('09') | |
| local asterisk = lpeg.P('*') | |
| local question_mark = lpeg.P('?') | |
| local at_sign = lpeg.P('@') | |
| local colon = lpeg.P(':') | |
| local the_dot = lpeg.P('.') | |
| local underscore = lpeg.P('_') | |
| 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 | |
| } | |
| end | |
| 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) | |
| 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 | |
| } | |
| end | |
| 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) | |
| local splat = lpeg.P(lpeg.C(forward_slash + the_dot) * asterisk * #(forward_slash + the_dot + -1)) / | |
| function (prefix) | |
| return { | |
| cap = "*", | |
| tag = "splat", | |
| prefix = prefix | |
| } | |
| end | |
| local rest = lpeg.C((1 - param - opt_param - splat)^1) | |
| local function fold_captures(cap, acc) | |
| if type(cap) == "string" then | |
| return { | |
| cap = lpeg.P(cap) * acc.cap, | |
| 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, | |
| clean = (cap.prefix * (1 - acc.clean)^0)^-1 * acc.clean | |
| } | |
| end | |
| return { | |
| cap = cap.cap * acc.cap, | |
| clean = cap.clean * acc.clean | |
| } | |
| end | |
| local function fold_parts(parts, cap) | |
| if type(cap) == "string" then -- if the capture is a string | |
| parts[#parts+1] = { | |
| tag = "text", | |
| text = cap | |
| } | |
| else -- it must be a table capture | |
| parts[#parts+1] = { | |
| tag = cap.tag, | |
| prefix = cap.prefix, | |
| name = cap.name | |
| } | |
| end | |
| return parts | |
| end | |
| -- the right part (a bottom to top loop) | |
| local function fold_right(t, f, acc) | |
| for i = #t, 1, -1 do | |
| acc = f(t[i], acc) | |
| end | |
| return acc | |
| end | |
| -- the left part (a top to bottom loop) | |
| local function fold_left(t, f, acc) | |
| for i = 1, #t do | |
| acc = f(acc, t[i]) | |
| end | |
| return acc | |
| 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, {}) | |
| end | |
| local function build(parts, params) | |
| local res, i = {}, 1 | |
| params = params or {} | |
| 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) | |
| 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) | |
| 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) | |
| 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 | |
| end | |
| return nil | |
| end, | |
| build = function (t, params) | |
| return build(t.parts, params) | |
| end | |
| } | |
| }) | |
| end | |
| return setmetatable(_M, { | |
| __call = function (_, path) | |
| return _M.R(path) | |
| end | |
| }) |