local sgmatch = string.gmatch
local utils = require 'bin.scaffold.utils'
local gitignore = [[
# lor
client_body_temp
fastcgi_temp
logs
proxy_temp
tmp
uwsgi_temp
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
]]
local mime_types = [[
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
image/svg+xml svg svgz;
image/webp webp;
application/font-woff woff;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.wap.wmlc wmlc;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
]]
local index_view_tpl = [[
]]
local user_info_view_tpl = [[
{{desc}}
{{id}}
{{name}}
]]
local main_tpl = [[
local app = require("app.server")
app:run()
]]
local server_tpl = [[
local string_find = string.find
local lor = require("lor.index")
local router = require("app.router")
local app = lor()
-- 模板配置
app:conf("view enable", true)
app:conf("view engine", "tmpl")
app:conf("view ext", "html")
app:conf("view layout", "")
app:conf("views", "./app/views")
-- session和cookie支持,如果不需要可注释以下配置
local mw_cookie = require("lor.lib.middleware.cookie")
local mw_session = require("lor.lib.middleware.session")
app:use(mw_cookie())
app:use(mw_session({
session_key = "__app__", -- the key injected in cookie
session_aes_key = "aes_key_for_session", -- should set by yourself
timeout = 3600 -- default session timeout is 3600 seconds
}))
-- 自定义中间件1: 注入一些全局变量供模板渲染使用
local mw_inject_version = require("app.middleware.inject_app_info")
app:use(mw_inject_version())
-- 自定义中间件2: 设置响应头
app:use(function(req, res, next)
res:set_header("X-Powered-By", "Lor framework")
next()
end)
router(app) -- 业务路由处理
-- 错误处理插件,可根据需要定义多个
app:erroruse(function(err, req, res, next)
ngx.log(ngx.ERR, err)
if req:is_found() ~= true then
if string_find(req.headers["Accept"], "application/json") then
res:status(404):json({
success = false,
msg = "404! sorry, not found."
})
else
res:status(404):send("404! sorry, not found. " .. (req.path or ""))
end
else
if string_find(req.headers["Accept"], "application/json") then
res:status(500):json({
success = false,
msg = "500! internal error, please check the log."
})
else
res:status(500):send("internal error, please check the log.")
end
end
end)
return app
]]
local router_tpl = [[
-- 业务路由管理
local userRouter = require("app.routes.user")
return function(app)
-- simple router: hello world!
app:get("/hello", function(req, res, next)
res:send("hi! welcome to lor framework.")
end)
-- simple router: render html, visit "/" or "/?name=foo&desc=bar
app:get("/", function(req, res, next)
local data = {
name = req.query.name or "lor",
desc = req.query.desc or 'a framework of lua based on OpenResty'
}
res:render("index", data)
end)
-- group router: 对以`/user`开始的请求做过滤处理
app:use("/user", userRouter())
end
]]
local user_router_tpl = [[
local lor = require("lor.index")
local userRouter = lor:Router() -- 生成一个group router对象
-- 按id查找用户
-- e.g. /query/123
userRouter:get("/query/:id", function(req, res, next)
local query_id = tonumber(req.params.id) -- 从req.params取参数
if not query_id then
return res:render("user/info", {
desc = "Error to find user, path variable `id` should be a number. e.g. /user/query/123"
})
end
-- 渲染页面
res:render("user/info", {
id = query_id,
name = "user" .. query_id,
desc = "User Information"
})
end)
-- 删除用户
-- e.g. /delete?id=123
userRouter:delete("/delete", function(req, res, next)
local id = req.query.id -- 从req.query取参数
if not id then
return res:html("Error: query param id is required.
")
end
-- 返回html
res:html("succeed to delete user
user id is:" .. id .. "")
end)
-- 修改用户
-- e.g. /put/123?name=sumory
userRouter:put("/put/:id", function(req, res, next)
local id = req.params.id -- 从req.params取参数
local name = req.query.name -- 从req.query取参数
if not id or not name then
return res:send("error params: id and name are required.")
end
-- 返回文本格式的响应结果
res:send("succeed to modify user[" .. id .. "] with new name:" .. name)
end)
-- 创建用户
userRouter:post("/post", function(req, res, next)
local content_type = req.headers['Content-Type']
-- 如果请求类型为form表单或json请求体
if string.find(content_type, "application/x-www-form-urlencoded",1, true) or
string.find(content_type, "application/json",1, true) then
local id = req.body.id -- 从请求体取参数
local name = req.body.name -- 从请求体取参数
if not id or not name then
return res:json({
success = false,
msg = "error params: id and name are required."
})
end
res:json({-- 返回json格式的响应体
success = true,
data = {
id = id,
name = name,
desc = "succeed to create new user" .. id
}
})
else -- 不支持其他请求体
res:status(500):send("not supported request Content-Type[" .. content_type .. "]")
end
end)
return userRouter
]]
local middleware_tpl = [[
### 自定义插件目录(define your own middleware)
You are recommended to define your own middlewares and keep them in one place to manage.
建议用户将自定义插件存放在此目录下统一管理,然后在其他地方引用,插件的格式如下:
```
local middleware = function(params)
return function(req, res, next)
-- do something with req/res
next()
end
end
return middleware
```
]]
local middleware_example_tpl = [[
--- 中间件示例: 为每个请求注入一些通用的变量
local lor = require("lor.index")
return function()
return function(req, res, next)
-- res.locals是一个table, 可以在这里注入一些“全局”变量
-- 这个示例里注入app的名称和版本号, 在渲染页面时即可使用
res.locals.app_name = "lor application"
res.locals.app_version = lor.version or ""
next()
end
end
]]
local static_tpl = [[
### 静态文件目录(static files directory)
nginx对应配置为
```
location /static {
alias app/static;
}
```
]]
local ngx_conf_directory = [[
### nginx configuration directory
]]
local ngx_config = require 'bin.scaffold.nginx.config'
local ngx_conf_template = require 'bin.scaffold.nginx.conf_template'
local function nginx_conf_content()
-- read nginx.conf file
local nginx_conf_template = ngx_conf_template.get_ngx_conf_template()
-- append notice
nginx_conf_template = [[
#generated by `lor framework`
]] .. nginx_conf_template
local match = {}
local tmp = 1
for v in sgmatch(nginx_conf_template , '{{(.-)}}') do
match[tmp] = v
tmp = tmp + 1
end
for _, directive in ipairs(match) do
if ngx_config[directive] ~= nil then
nginx_conf_template = string.gsub(nginx_conf_template, '{{' .. directive .. '}}', ngx_config[directive])
else
nginx_conf_template = string.gsub(nginx_conf_template, '{{' .. directive .. '}}', '#' .. directive)
end
end
return nginx_conf_template
end
local ngx_conf_tpl = nginx_conf_content()
local start_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh start.sh -- start application @dev
# sh start.sh ${env} -- start application @${env}
# examples:
# sh start.sh prod -- use conf/nginx-prod.conf to start OpenResty
# sh start.sh -- use conf/nginx-dev.conf to start OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "start lor application with profile: "${PROFILE}
nginx -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local stop_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh stop.sh -- stop application @dev
# sh stop.sh ${env} -- stop application @${env}
# examples:
# sh stop.sh prod -- use conf/nginx-prod.conf to stop OpenResty
# sh stop.sh -- use conf/nginx-dev.conf to stop OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "stop lor application with profile: "${PROFILE}
nginx -s stop -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local reload_sh = [[
#!/bin/sh
#####################################################################
# usage:
# sh reload.sh -- reload application @dev
# sh reload.sh ${env} -- reload application @${env}
# examples:
# sh reload.sh prod -- use conf/nginx-prod.conf to reload OpenResty
# sh reload.sh -- use conf/nginx-dev.conf to reload OpenResty
#####################################################################
if [ -n "$1" ];then
PROFILE="$1"
else
PROFILE=dev
fi
mkdir -p logs & mkdir -p tmp
echo "reload lor application with profile: "${PROFILE}
nginx -s reload -p `pwd`/ -c conf/nginx-${PROFILE}.conf
]]
local Generator = {}
Generator.files = {
['.gitignore'] = gitignore,
['app/main.lua'] = main_tpl,
['app/server.lua'] = server_tpl,
['app/router.lua'] = router_tpl,
['app/routes/user.lua'] = user_router_tpl,
['app/views/index.html'] = index_view_tpl,
['app/views/user/info.html'] = user_info_view_tpl,
['app/middleware/README.md'] = middleware_tpl,
['app/middleware/inject_app_info.lua'] = middleware_example_tpl,
['app/static/README.md'] = static_tpl, -- static files directory,e.g. js/css/img
['conf/README.md'] = ngx_conf_directory, -- nginx config directory
['conf/nginx-dev.conf'] = ngx_conf_tpl, -- nginx config file
['conf/mime.types'] = mime_types, -- nginx mime
['start.sh'] = start_sh,
['stop.sh'] = stop_sh,
['reload.sh'] = reload_sh
}
function Generator.new(name)
print('Creating app: ' .. name .. '...')
Generator.create_files(name)
end
function Generator.create_files(parent)
for file_path, file_content in pairs(Generator.files) do
local full_file_path = parent .. '/' .. file_path
local full_file_dirname = utils.dirname(full_file_path)
os.execute('mkdir -p ' .. full_file_dirname .. ' > /dev/null')
local fw = io.open(full_file_path, 'w')
fw:write(file_content)
fw:close()
print(' created file ' .. full_file_path)
end
end
return Generator