├── LICENSE ├── README ├── TODO ├── bin └── mercury ├── lp.lua ├── mercury.lua ├── rockspecs └── mercury-scm-0.rockspec └── samples ├── fibonacci_codegen.lua ├── fibonacci_haml.lua ├── fibonacci_luapages.lua ├── greetings.lua └── no_pollution.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Daniele Alessandri 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Mercury 2 | ... because Sinatra is not the only one performing on the stage. 3 | ================================================================================ 4 | 5 | -- Introduction ---------------------------------------------------------------- 6 | 7 | Mercury aims to be a Sinatra-like web framework (or DSL, if you like) for 8 | creating web applications in Lua, quickly and painlessly. 9 | 10 | require 'mercury' 11 | 12 | module('hello', package.seeall, mercury.application) 13 | 14 | get('/', function() 15 | return "Hello world!" 16 | end) 17 | 18 | Mercury is currently in its very early stages of development, it started like 19 | an hack and right now it still is nothing more than that. Having said that, I 20 | thought that things might get interesting and so I decided to release the 21 | actual source code and start working on it to get it properly done. 22 | 23 | -- About the name -------------------------------------------------------------- 24 | 25 | "Mercury" because: 26 | * like Sinatra, he is a legend of the music history. That is, Freddie Mercury. 27 | * most of the cool web projects coded in Lua have astronomical references: 28 | - the Kepler Project (web development platform) 29 | - Orbit (MVC framework) 30 | - Cosmo (templating system) 31 | - Sputnik (wiki engine) 32 | * it is the smallest planet in our solar system and the nearest to the Sun... 33 | * ... and Mercury is the smallest web framework and the nearest to the "core". 34 | 35 | -- How to execute the examples ------------------------------------------------- 36 | 37 | If you have installed Mercury using LuaRocks you should have the 'mercury' 38 | executable file available in your PATH, you just need to enter in the samples 39 | directory and launch one of the applications like this: 40 | 41 | mercury greetings 42 | 43 | If, instead, you have simply cloned the repository, you can launch one of the 44 | example applications from the root directory of the project like this: 45 | 46 | LUA_PATH="samples/?.lua;;" ./bin/mercury greetings 47 | 48 | -- Contributors ---------------------------------------------------------------- 49 | 50 | * Mauricio Henrique Bomfim (http://github.com/mauriciobomfim) 51 | Support for Lua Pages as a templating engine for Mercury. 52 | 53 | * Norman Clarke (http://github.com/norman) 54 | Rockspec file for LuaRocks and various fixes. 55 | 56 | * Francois Perrad (http://github.com/fperrad) 57 | Support for CodeGen as a templating engine for Mercury. 58 | 59 | -- Notes ----------------------------------------------------------------------- 60 | 61 | Want to help or contribute? Drop me a mail to suppakilla{at}gmail.com 62 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Change the directory layout of the repository. 2 | 3 | * Write docs, more samples and a test suite. 4 | 5 | * Implement before/after hooks (using coroutines). 6 | 7 | * Implement halt and the not_found and error handlers. 8 | 9 | * Make streamable responses more user-friendly. 10 | 11 | * The rest that is currently missing... :-) 12 | -------------------------------------------------------------------------------- /bin/mercury: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | --[[ 3 | Mercury: http://github.com/nrk/mercury/ 4 | Xavante: http://www.keplerproject.org/xavante/ 5 | 6 | Usage: mercury APPNAME 7 | ]] 8 | 9 | pcall(require, 'luarocks.require') 10 | require 'xavante' 11 | require 'wsapi.xavante' 12 | 13 | if not arg[1] then 14 | print([[ 15 | Please specify a mercury application to boot with Xavante, e.g.: 16 | 17 | mercury greetings 18 | ]]) 19 | os.exit(1) 20 | end 21 | 22 | -- TODO: checks and more options 23 | if arg[1] == nil then error("please specify a mercury application") end 24 | local application = arg[1]:gsub('^(.-)%.lua$', '%1') 25 | 26 | xavante.HTTP{ 27 | server = {host = "127.0.0.1", port = 7654}, 28 | 29 | defaultHost = { 30 | rules = { 31 | { 32 | match = { "^/(.-)$" }, 33 | with = wsapi.xavante.makeHandler(application) 34 | } 35 | } 36 | } 37 | } 38 | 39 | xavante.start() 40 | -------------------------------------------------------------------------------- /lp.lua: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | -- Lua Pages Template Engine. 3 | -- 4 | -- Based on: http://www.lua.inf.puc-rio.br/~mascarenhas/template 5 | -- From: Fabio Mascarenhas 6 | --------------------------------------------------------------------------- 7 | 8 | local find, format, gsub, strsub = 9 | string.find, string.format, string.gsub, string.sub 10 | local concat, tinsert = table.concat, table.insert 11 | local getfenv, setfenv, setmetatable = getfenv, setfenv, setmetatable 12 | local assert, loadstring, tostring = assert, loadstring, tostring 13 | local base = _G 14 | 15 | module(...) 16 | 17 | -- 18 | -- Builds a piece of Lua code which outputs the (part of the) given string. 19 | -- @param s String. 20 | -- @param i Number with the initial position in the string. 21 | -- @param f Number with the final position in the string (default == -1). 22 | -- @return String with the correspondent Lua code which outputs the part of the string. 23 | -- 24 | local function out(s, i, f) 25 | s = strsub(s, i, f or -1) 26 | if s == "" then return s end 27 | -- we could use `%q' here, but this way we have better control 28 | s = gsub(s, "([\\\n\'])", "\\%1") 29 | -- substitute '\r' by '\'+'r' and let `loadstring' reconstruct it 30 | s = gsub(s, "\r", "\\r") 31 | return format(" __outfunc('%s')", s) 32 | end 33 | 34 | 35 | ---------------------------------------------------------------------------- 36 | -- Translate the template to Lua code. 37 | -- @param s String to translate. 38 | -- @return String with translated code. 39 | ---------------------------------------------------------------------------- 40 | function translate(s) 41 | s = gsub(s, "<%%(.-)%%>", "") 42 | local res = {} 43 | local start = 1 -- start of print("XXXX") untranslated part in `s' 44 | while true do 45 | local ip, fp, target, exp, code = find(s, "<%?(%w*)[ \t]*(=?)(.-)%?>", start) 46 | if not ip then break end 47 | tinsert(res, out(s, start, ip-1)) 48 | if target ~= "" and target ~= "lua" then 49 | -- not for Lua; pass whole instruction to the output 50 | tinsert(res, out(s, ip, fp)) 51 | else 52 | if exp == "=" then -- expression? 53 | tinsert(res, format(" __outfunc(%s)", code)) 54 | else -- command 55 | tinsert(res, format(" %s", code)) 56 | end 57 | end 58 | start = fp + 1 59 | end 60 | tinsert(res, out(s, start)) 61 | return [[ 62 | local __env, __outfunc = ... 63 | local __restorenv 64 | local __result = {} 65 | if not __outfunc then 66 | local insert = tinsert 67 | local tostring = tostring 68 | __outfunc = function (s) 69 | if s then 70 | insert(__result, tostring(s)) 71 | end 72 | end 73 | end 74 | do 75 | local env = getfenv(1) 76 | local setfenv = setfenv 77 | setfenv(1, __env) 78 | __restoreenv = function () 79 | setfenv(2, env) 80 | end 81 | end 82 | ]] .. concat(res, "\n") .. [[ 83 | __restoreenv() 84 | return concat(__result) 85 | ]] 86 | end 87 | 88 | ---------------------------------------------------------------------------- 89 | -- Internal compilation cache. 90 | 91 | local cache = {} 92 | setmetatable(cache, { __index = function (tab, key) 93 | local new = {} 94 | tab[key] = new 95 | return new 96 | end }) 97 | 98 | ---------------------------------------------------------------------------- 99 | -- Translates a template into a Lua function. 100 | -- Does NOT execute the resulting function. 101 | -- Uses a cache of templates. 102 | -- @param string String with the template to be translated. 103 | -- @param chunkname String with the name of the chunk, for debugging purposes. 104 | -- @return Function with the resulting translation. 105 | 106 | function compile(string, chunkname) 107 | chunkname = chunkname or string 108 | local f, err = cache[string][chunkname] 109 | if f then return f end 110 | f = assert(loadstring(translate(string), chunkname)) 111 | setfenv(f, { tinsert = tinsert, concat = concat, 112 | getfenv = getfenv, setfenv = setfenv, 113 | tostring = tostring}) 114 | cache[string][chunkname] = f 115 | return f 116 | end 117 | 118 | ---------------------------------------------------------------------------- 119 | -- "Fills" the template using the environment env, and returns the result 120 | -- @param template String with template 121 | -- @param env Global environment for template 122 | -- @return String with result of template application 123 | 124 | function fill(template, env) 125 | local env = env or {} 126 | local prog = compile(template) 127 | local out = {} 128 | local outfunc = function (s) 129 | tinsert(out, s) 130 | end 131 | setmetatable(env, { __index = base }) -- combine with global environment 132 | prog(env, outfunc) 133 | return concat(out) 134 | end 135 | 136 | -------------------------------------------------------------------------------- /mercury.lua: -------------------------------------------------------------------------------- 1 | require 'wsapi.request' 2 | require 'wsapi.response' 3 | require 'wsapi.util' 4 | 5 | module('mercury', package.seeall) 6 | 7 | local mercury_env = getfenv() 8 | local route_env = setmetatable({ }, { __index = _G }) 9 | local route_table = { GET = {}, POST = {}, PUT = {}, DELETE = {} } 10 | 11 | local application_methods = { 12 | get = function(path, method, options) add_route('GET', path, method) end, 13 | post = function(path, method, options) add_route('POST', path, method) end, 14 | put = function(path, method, options) add_route('PUT', path, method) end, 15 | delete = function(path, method, options) add_route('DELETE', path, method) end, 16 | helper = function(name, method) set_helper(route_env, name, method) end, 17 | helpers = function(helpers) 18 | if type(helpers) == 'table' then 19 | set_helpers(route_env, helpers) 20 | elseif type(helpers) == 'function' then 21 | local temporary_env = setmetatable({}, { 22 | __newindex = function(e,k,v) 23 | set_helper(route_env, k, v) 24 | end, 25 | }) 26 | setfenv(helpers, temporary_env)() 27 | else 28 | -- TODO: raise an error? 29 | end 30 | end, 31 | } 32 | 33 | function set_helpers(environment, methods) 34 | for name, method in pairs(methods) do 35 | set_helper(environment, name, method) 36 | end 37 | end 38 | 39 | function set_helper(environment, name, method) 40 | if type(method) ~= 'function' then 41 | error('"' .. name .. '" is an invalid helper, only functions are allowed.') 42 | end 43 | environment[name] = setfenv(method, environment) 44 | end 45 | 46 | -- 47 | -- *** ext functions *** -- 48 | -- 49 | 50 | function merge_tables(...) 51 | local numargs, out = select('#', ...), {} 52 | for i = 1, numargs do 53 | local t = select(i, ...) 54 | if type(t) == "table" then 55 | for k, v in pairs(t) do out[k] = v end 56 | end 57 | end 58 | return out 59 | end 60 | 61 | -- 62 | -- *** route environment *** -- 63 | -- 64 | 65 | (function() setfenv(1, route_env) 66 | -- This is a glorious trick to setup a different environment for routes since 67 | -- Lua functions inherits the environment in which they are *created*! This 68 | -- will not be compatible with Lua 5.2, for which the new _ENV should provide 69 | -- a much more clean way to achieve a similar result. 70 | 71 | -- TODO: Create a function like setup_route_environment(fun) 72 | local templating_engines = { 73 | haml = function(template, options, locals) 74 | local haml = haml.new(options) 75 | return function(env) 76 | return haml:render(template, mercury_env.merge_tables(env, locals)) 77 | end 78 | end, 79 | cosmo = function(template, values) 80 | return function(env) 81 | return cosmo.fill(template, values) 82 | end 83 | end, 84 | string = function(template, ...) 85 | return function(env) 86 | return string.format(template, unpack(arg)) 87 | end 88 | end, 89 | lp = function(template, values) 90 | return function(env) 91 | local lp = require 'lp' 92 | return lp.fill(template, mercury_env.merge_tables(env, values)) 93 | end 94 | end, 95 | codegen = function(template, top, values) 96 | local CodeGen = require 'CodeGen' 97 | return function(env) 98 | local tmpl = CodeGen(template, values, env) 99 | return tmpl(top) 100 | end 101 | end, 102 | } 103 | 104 | local route_methods = { 105 | pass = function() 106 | coroutine.yield({ pass = true }) 107 | end, 108 | -- Use a table to group template-related methods to prevent name clashes. 109 | t = setmetatable({ }, { 110 | __index = function(env, name) 111 | local engine = templating_engines[name] 112 | 113 | if type(engine) == nil then 114 | error('cannot find template renderer "'.. name ..'"') 115 | end 116 | 117 | return function(...) 118 | coroutine.yield({ template = engine(...) }) 119 | end 120 | end 121 | }), 122 | } 123 | 124 | for k, v in pairs(route_methods) do route_env[k] = v end 125 | 126 | setfenv(1, mercury_env) end)() 127 | 128 | -- 129 | -- *** application *** -- 130 | -- 131 | 132 | function application(application, fun) 133 | if type(application) == 'string' then 134 | application = { _NAME = application } 135 | else 136 | application = application or {} 137 | end 138 | 139 | for k, v in pairs(application_methods) do 140 | application[k] = v 141 | end 142 | 143 | application.run = function(wsapi_env) 144 | return run(application, wsapi_env) 145 | end 146 | 147 | local mt = { __index = _G } 148 | 149 | if fun then 150 | setfenv(fun, setmetatable(application, mt))() 151 | else 152 | setmetatable(application, mt) 153 | end 154 | 155 | return application 156 | end 157 | 158 | function add_route(verb, path, handler, options) 159 | table.insert(route_table[verb], { 160 | pattern = compile_url_pattern(path), 161 | handler = setfenv(handler, route_env), 162 | options = options, 163 | }) 164 | end 165 | 166 | function error_500(response, output) 167 | response.status = 500 168 | response.headers = { ['Content-type'] = 'text/html' } 169 | response:write( 170 | '
An error has occurred while serving this page.\n\n' ..
171 |         'Error details:\n' .. output:gsub("\n", "
") .. 172 | '
' 173 | ) 174 | return response:finish() 175 | end 176 | 177 | function compile_url_pattern(pattern) 178 | local compiled_pattern = { 179 | original = pattern, 180 | params = { }, 181 | } 182 | 183 | -- Lua pattern matching is blazing fast compared to regular expressions, 184 | -- but at the same time it is tricky when you need to mimic some of 185 | -- their behaviors. 186 | pattern = pattern:gsub("[%(%)%.%%%+%-%%?%[%^%$%*]", function(char) 187 | if char == '*' then return ':*' else return '%' .. char end 188 | end) 189 | 190 | pattern = pattern:gsub(':([%w%*]+)(/?)', function(param, slash) 191 | if param == '*' then 192 | table.insert(compiled_pattern.params, 'splat') 193 | return '(.-)' .. slash 194 | else 195 | table.insert(compiled_pattern.params, param) 196 | return '([^/?&#]+)' .. slash 197 | end 198 | 199 | end) 200 | 201 | if pattern:sub(-1) ~= '/' then pattern = pattern .. '/' end 202 | compiled_pattern.pattern = '^' .. pattern .. '?$' 203 | 204 | return compiled_pattern 205 | end 206 | 207 | function extract_parameters(pattern, matches) 208 | local params = { } 209 | for i,k in ipairs(pattern.params) do 210 | if (k == 'splat') then 211 | if not params.splat then params.splat = {} end 212 | table.insert(params.splat, wsapi.util.url_decode(matches[i])) 213 | else 214 | params[k] = wsapi.util.url_decode(matches[i]) 215 | end 216 | end 217 | return params 218 | end 219 | 220 | function extract_post_parameters(request, params) 221 | for k,v in pairs(request.POST) do 222 | if not params[k] then params[k] = v end 223 | end 224 | end 225 | 226 | function url_match(pattern, path) 227 | local matches = { string.match(path, pattern.pattern) } 228 | if #matches > 0 then 229 | return true, extract_parameters(pattern, matches) 230 | else 231 | return false, nil 232 | end 233 | end 234 | 235 | function prepare_route(route, request, response, params) 236 | route_env.params = params 237 | route_env.request = request 238 | route_env.response = response 239 | return route.handler 240 | end 241 | 242 | function router(application, state, request, response) 243 | local verb, path = state.vars.REQUEST_METHOD, state.vars.PATH_INFO 244 | 245 | return coroutine.wrap(function() 246 | local routes = verb == "HEAD" and route_table["GET"] or route_table[verb] 247 | for _, route in ipairs(routes) do 248 | local match, params = url_match(route.pattern, path) 249 | if match then 250 | if verb == 'POST' then extract_post_parameters(request, params) end 251 | coroutine.yield(prepare_route(route, request, response, params)) 252 | end 253 | end 254 | end) 255 | end 256 | 257 | function initialize(application, wsapi_env) 258 | -- TODO: Taken from Orbit! It will change soon to adapt request 259 | -- and response to a more suitable model. 260 | local web = { 261 | status = 200, 262 | headers = { ["Content-Type"]= "text/html" }, 263 | cookies = {} 264 | } 265 | 266 | web.vars = wsapi_env 267 | web.prefix = application.prefix or wsapi_env.SCRIPT_NAME 268 | web.suffix = application.suffix 269 | web.doc_root = wsapi_env.DOCUMENT_ROOT 270 | 271 | if wsapi_env.APP_PATH == '' then 272 | web.real_path = application.real_path or '.' 273 | else 274 | web.real_path = wsapi_env.APP_PATH 275 | end 276 | 277 | local wsapi_req = wsapi.request.new(wsapi_env) 278 | local wsapi_res = wsapi.response.new(web.status, web.headers) 279 | 280 | web.set_cookie = function(_, name, value) 281 | wsapi_res:set_cookie(name, value) 282 | end 283 | 284 | web.delete_cookie = function(_, name, path) 285 | wsapi_res:delete_cookie(name, path) 286 | end 287 | 288 | web.path_info = wsapi_req.path_info 289 | 290 | if not wsapi_env.PATH_TRANSLATED == '' then 291 | web.path_translated = wsapi_env.PATH_TRANSLATED 292 | else 293 | web.path_translated = wsapi_env.SCRIPT_FILENAME 294 | end 295 | 296 | web.script_name = wsapi_env.SCRIPT_NAME 297 | web.method = string.lower(wsapi_req.method) 298 | web.input = wsapi_req.params 299 | web.cookies = wsapi_req.cookies 300 | 301 | return web, wsapi_req, wsapi_res 302 | end 303 | 304 | function run(application, wsapi_env) 305 | local state, request, response = initialize(application, wsapi_env) 306 | 307 | for route in router(application, state, request, response) do 308 | local coroute = coroutine.create(route) 309 | local success, output = coroutine.resume(coroute) 310 | 311 | if not success then 312 | return error_500(response, output) 313 | end 314 | 315 | if not output then 316 | -- render an empty body 317 | return response:finish() 318 | end 319 | 320 | local output_type = type(output) 321 | if output_type == 'function' then 322 | -- First attempt at streaming responses using coroutines. 323 | return response.status, response.headers, coroutine.wrap(output) 324 | elseif output_type == 'string' then 325 | response:write(output) 326 | return response:finish() 327 | elseif output.template then 328 | response:write(output.template(getfenv(route)) or 'template rendered an empty body') 329 | return response:finish() 330 | else 331 | if not output.pass then 332 | return error_500(response, output) 333 | end 334 | end 335 | end 336 | 337 | local function emit_no_routes_matched() 338 | coroutine.yield('ERROR') 339 | coroutine.yield('Sorry, no route found to match ' .. request.path_info .. '

') 340 | if application.debug_mode then 341 | coroutine.yield('REQUEST DATA:
' .. tostring(request) .. '

') 342 | coroutine.yield('RESPONSE DATA:
' .. tostring(response) .. '

') 343 | end 344 | coroutine.yield('') 345 | end 346 | 347 | return 404, { ['Content-type'] = 'text/html' }, coroutine.wrap(emit_no_routes_matched) 348 | end 349 | -------------------------------------------------------------------------------- /rockspecs/mercury-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "mercury" 2 | version = "scm-0" 3 | source = { 4 | url = "git://github.com/nrk/mercury.git" 5 | } 6 | description = { 7 | summary = "A small framework for creating web apps in Lua", 8 | detailed = [[ 9 | Mercury aims to be a Sinatra-like web framework (or DSL, if you like) for 10 | creating web applications in Lua, quickly and painlessly. 11 | ]], 12 | license = "MIT/X11", 13 | homepage = "http://github.com/nrk/mercury" 14 | } 15 | dependencies = { 16 | "lua >= 5.1", 17 | "wsapi", 18 | "xavante", 19 | "wsapi-xavante" 20 | } 21 | 22 | build = { 23 | type = "none", 24 | install = { 25 | lua = { 26 | "mercury.lua", 27 | ["mercury.lp"] = "lp.lua" 28 | }, 29 | bin = { 30 | ["mercury"] = "bin/mercury" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/fibonacci_codegen.lua: -------------------------------------------------------------------------------- 1 | require 'luarocks.require' 2 | require 'mercury' 3 | 4 | --[[ 5 | lua-CodeGen is a "safe" template engine. 6 | see http://fperrad.github.com/lua-CodeGen/ 7 | ]] 8 | 9 | module('fibonacci_codegen', package.seeall, mercury.application) 10 | 11 | local templates = { 12 | index = [[ 13 | 14 | 15 | 16 | Fibonacci numbers generator 17 | 20 | 30 | 31 | 32 |

Welcome to the Fibonacci numbers generator!!!

33 | 34 |

You can try one of the following samples:

35 | 38 | 39 |

40 |

41 | 44 | 45 | 46 |
47 |

48 | 49 | 50 | 51 | 52 | ]], 53 | _sample_link = [[ 54 |
  • 55 | Generate numbers up to ${it} 56 |
  • 57 | ]], 58 | 59 | fibonacci = [[ 60 | 61 | 62 | Fibonacci numbers generator 63 | 64 | 65 |

    66 | Generating Fibonacci numbers up to ${params.limit}!!! 67 |

    68 | 71 | 72 | 73 | ]], 74 | _value_item = [[ 75 |
  • ${it}
  • 76 | ]], 77 | } 78 | 79 | local function get_samples(how_many) 80 | local samples = {} 81 | for i = 1, how_many do 82 | table.insert(samples, math.random(1, 1000000)) 83 | end 84 | return samples 85 | end 86 | 87 | local function fibonacci(maxn) 88 | return coroutine.wrap(function() 89 | local x, y = 0, 1 90 | while x <= maxn do 91 | coroutine.yield(x) 92 | x, y = y, x + y 93 | end 94 | end) 95 | end 96 | 97 | get('/', function() 98 | t.codegen(templates, 'index', { samples = get_samples(4) }) 99 | end) 100 | 101 | get('/fibonacci/:limit', function() 102 | local numbers = {} 103 | for val in fibonacci(tonumber(params.limit)) do 104 | numbers[#numbers+1] = val 105 | end 106 | t.codegen(templates, 'fibonacci', { numbers = numbers }) 107 | end) 108 | 109 | post('/fibonacci/', function() 110 | local numbers = {} 111 | for val in fibonacci(tonumber(params.limit)) do 112 | numbers[#numbers+1] = val 113 | end 114 | t.codegen(templates, 'fibonacci', { numbers = numbers }) 115 | end) 116 | -------------------------------------------------------------------------------- /samples/fibonacci_haml.lua: -------------------------------------------------------------------------------- 1 | require 'luarocks.require' 2 | require 'mercury' 3 | require 'haml' 4 | 5 | module('fibonacci_haml', package.seeall, mercury.application) 6 | 7 | local templates = { 8 | index = [[ 9 | %html 10 | %head 11 | %title Fibonacci numbers generator 12 | %style(type="text/css") 13 | :plain 14 | #fibonateUpTo { width: 50px; border: 1px solid #000; } 15 | :javascript 16 | function check_fibonacci() { 17 | var your_number = parseInt(document.getElementById('your_number').value); 18 | if (isNaN(your_number)) { 19 | alert("You must specify a valid number"); 20 | return false; 21 | } 22 | return true; 23 | } 24 | %body 25 | %p Welcome to the Fibonacci numbers generator! 26 | %p You can try one of the following samples: 27 | %ul 28 | - for _, maxn in pairs(samples) do 29 | - local link = "/fibonacci/" .. maxn 30 | %li 31 | %a(href=link)= "Generate numbers up to " .. maxn 32 | %p 33 | %form(method="post" action="./fibonacci/" onsubmit="return check_fibonacci()") 34 | %label(for="your_number") 35 | If you prefer, you can generate Fibonacci numbers up to 36 | %input(id="your_number" name="limit" type="text") 37 | %input(type="submit" value="Try it yourself!") 38 | ]], 39 | 40 | fibonacci = [[ 41 | %html 42 | %head 43 | %title Fibonacci numbers generator 44 | %body 45 | %p 46 | :plain 47 | Generating Fibonacci numbers up to #{params.limit}! 48 | %ul 49 | - for number in fibonacci(tonumber(params.limit)) do 50 | %li= number 51 | ]], 52 | } 53 | 54 | helpers(function() 55 | function get_samples(how_many) 56 | local samples = {} 57 | for i = 1, how_many do 58 | table.insert(samples, math.random(1, 1000000)) 59 | end 60 | return samples 61 | end 62 | 63 | function fibonacci(maxn) 64 | return coroutine.wrap(function() 65 | local x, y = 0, 1 66 | while x <= maxn do 67 | coroutine.yield(x) 68 | x, y = y, x + y 69 | end 70 | end) 71 | end 72 | end) 73 | 74 | get('/', function() 75 | t.haml(templates.index, nil, { samples = get_samples(4) }) 76 | end) 77 | 78 | get('/fibonacci/:limit', function() 79 | t.haml(templates.fibonacci) 80 | end) 81 | 82 | post('/fibonacci/', function() 83 | t.haml(templates.fibonacci) 84 | end) 85 | -------------------------------------------------------------------------------- /samples/fibonacci_luapages.lua: -------------------------------------------------------------------------------- 1 | require 'luarocks.require' 2 | require 'mercury' 3 | 4 | module('fibonacci_luapages', package.seeall, mercury.application) 5 | 6 | local templates = { 7 | index = [[ 8 | 9 | 10 | 11 | Fibonacci numbers generator 12 | 15 | 25 | 26 | 27 |

    Welcome to the Fibonacci numbers generator!

    28 | 29 |

    You can try one of the following samples:

    30 | 39 | 40 |

    41 |

    42 | 45 | 46 | 47 |
    48 |

    49 | 50 | 51 | 52 | 53 | ]], 54 | 55 | fibonacci = [[ 56 | 57 | 58 | Fibonacci numbers generator 59 | 60 | 61 |

    62 | Generating Fibonacci numbers up to <%= params.limit %>! 63 |

    64 | 69 | 70 | 71 | ]], 72 | } 73 | 74 | helpers(function() 75 | function get_samples(how_many) 76 | local samples = {} 77 | for i = 1, how_many do 78 | table.insert(samples, math.random(1, 1000000)) 79 | end 80 | return samples 81 | end 82 | 83 | function fibonacci(maxn) 84 | return coroutine.wrap(function() 85 | local x, y = 0, 1 86 | while x <= maxn do 87 | coroutine.yield(x) 88 | x, y = y, x + y 89 | end 90 | end) 91 | end 92 | end) 93 | 94 | get('/', function() 95 | t.lp(templates.index, { samples = get_samples(4) }) 96 | end) 97 | 98 | get('/fibonacci/:limit', function() 99 | t.lp(templates.fibonacci) 100 | end) 101 | 102 | post('/fibonacci/', function() 103 | t.lp(templates.fibonacci) 104 | end) 105 | -------------------------------------------------------------------------------- /samples/greetings.lua: -------------------------------------------------------------------------------- 1 | require 'mercury' 2 | 3 | module('greetings', package.seeall, mercury.application) 4 | 5 | local languages = { 6 | en = 'Hi %s, how are you?', 7 | it = 'Ciao %s, come stai?', 8 | ja = '今日は%sさん、お元気ですか。 ' 9 | } 10 | 11 | local response_body = [[ 12 | 13 | 14 | Sinatra in Lua? Sure, it is called "Mercury"! 15 | 16 | 17 | 18 | %s 19 | 20 | 21 | ]] 22 | 23 | local function localized_message(language) 24 | return languages[language] or languages.en 25 | end 26 | 27 | get('/', function() 28 | return response_body:format([[ 29 | Welcome to the first Mercury application ever built!

    30 |
    31 |
    32 | Please fill in your name and choose your preferred language 33 |
    34 | English
    35 | Italian
    36 | Japanese
    37 |
    38 | 39 |
    40 | ]]) 41 | end) 42 | 43 | post('/say_hi/', function() 44 | if params.name == '' or not params.name then 45 | return response_body:format([[ 46 | Sorry but I do not believe you, you can not have no name ;-) 47 | Please try again. 48 | ]]) 49 | end 50 | 51 | local message = localized_message(params.lang) 52 | 53 | return response_body:format( 54 | message:format(params.name) .. 55 | '

    If you do not like POST-based greetings, then ' .. 56 | string.format('you can try this!', 57 | params.lang or 'en', 58 | params.name 59 | ) 60 | ) 61 | end) 62 | 63 | get('/say_hi/:lang/:name/', function() 64 | local message = localized_message(params.lang) 65 | return response_body:format(message:format(params.name)) 66 | end) -------------------------------------------------------------------------------- /samples/no_pollution.lua: -------------------------------------------------------------------------------- 1 | -- You can also define your application in a dedicated environment without 2 | -- polluting Lua's global scope, you can think of it as a sort of Sinatra::Base 3 | 4 | require 'mercury' 5 | 6 | module(..., package.seeall) 7 | 8 | local myapp = mercury.application('no_pollution', function() 9 | local app_name = _NAME 10 | 11 | get('/', function() 12 | return string.format('

    Welcome to %s!

    ', app_name) 13 | end) 14 | 15 | get('/hello/:name', function() 16 | return string.format('Hello %s!', params.name) 17 | end) 18 | end) 19 | 20 | run = myapp.run 21 | --------------------------------------------------------------------------------