用C为LUA写一个超迷你的模板引擎.
扫描二维码
随时随地手机看文章
中午在做HTTP服务器,内嵌了LUA引擎作为业务逻辑部分. 但考虑到LUA输出HTML的性能不高,况且 MVC 模式开发网页已经习惯了,何不用C给lua写个最简单的模板引擎呢?说做就做, 项目时间很紧张,所以必须确定目标,想了会儿。定了几个目标:
我要的是编译型的模板,要支持缓存。
不需要强大的模板逻辑方面的指令。将HTML模板文件编译成LUA源代码即可。LUA 还是比较容易扩展的,除了那栈用起来比较变态之外,经过了几个小时的奋斗,C+LUA代码。算是大功告成了。
#include#include#include#include#include#include#include#include "lib/automem.h" #include "lua.h" #include "lauxlib.h" #include "lua-tpl.h" /* LUA 最简单的模板引擎. */ const char LUAFMT_TPL_META[]="LUAFMT_TPL"; void lua_cfmt_createmetatable (lua_State *L); void lua_register_class(lua_State * L,const luaL_Reg * methods,const char * name,lua_CFunction has_index); #if defined(_WIN32) || defined(_WIN64) #define open _open #define close _close #define read _read #define stat( x , y ) _stat( x , y ) #endif static char * file_get_contents(const char * file,int * sz){ char * ret = NULL; struct stat st; int fd; *sz = 0; if ((fd = open(file, O_RDONLY|O_BINARY)) != -1) { if (fstat(fd, &st) != -1) { ret = (char *)malloc(st.st_size); if(NULL != ret){ while( *sz < st.st_size){ *sz += read(fd, ret + *sz, st.st_size - (*sz)); _lseek(fd, *sz, SEEK_SET); } } close(fd); } } return ret; } int file_put_contents(const char* file_name, automem_t* mem) { FILE * fp = fopen(file_name,"wb+"); if(NULL != fp) { fwrite(mem->pdata, mem->size, 1, fp); fclose(fp); return 1; } return 0; } enum{ tpl_state_normal, tpl_state_scode_1, tpl_state_code, tpl_state_ecode_1, tpl_state_escape, }; #define append_end_stringfield(a,b,c) automem_append_voidp( (a) , cmd, lcmd); automem_append_voidp( (a), (b) , (c)); automem_append_voidp( (a) , ecmd, lecmd); automem_append_voidp( (a), ")n", 2); static void lua_tpl_compile_local(lua_State * L, automem_t * mem, const char * buf,int lbuf){ int state = tpl_state_normal, i = 0; const char * sbuf; char c; size_t lcmd = sizeof("request.print([[") -1, lecmd = sizeof("]])") -1, lpre = 0; const char * cmd = "request.print([[", * ecmd = "]])", *pre = NULL; if(lua_isstring(L, 3)) cmd =luaL_checklstring(L, 3, &lcmd); if(lua_isstring(L, 4)) ecmd =luaL_checklstring(L, 4, &lecmd); if(lua_isstring(L, 5)) pre =luaL_checklstring(L, 5, &lpre); if(NULL != pre){ automem_append_voidp(mem, pre, lpre); automem_append_byte(mem,'n'); } sbuf = buf; while(i < lbuf){ c = buf[i]; switch (state) { case tpl_state_normal: switch(c){ case '{': state = tpl_state_scode_1; break; } break; case tpl_state_scode_1: switch(c){ case '#': state =tpl_state_code; append_end_stringfield(mem,sbuf, &buf[i] - sbuf-1); sbuf = &buf[i+1]; break; default: state = tpl_state_normal; break; } break; case tpl_state_code: switch(c){ case '#': state = tpl_state_ecode_1; break; } case tpl_state_ecode_1: switch(c){ case '}': automem_append_voidp(mem,sbuf, &buf[i] - sbuf-1); automem_append_byte(mem,'n'); sbuf = &buf[i+1]; state = tpl_state_normal; break; default: state=tpl_state_code; } default: break; } i++; } if(tpl_state_normal == state){ append_end_stringfield(mem,sbuf, &buf[i] - sbuf); } } /* 对模板文件进行编译.*/ static int lua_tpl_compile(lua_State * L) { int cache = 0,i = 0, lbuf = 0; size_t lfile; const char *cfile = NULL, * file= luaL_checklstring(L, 1,&lfile),* buf; automem_t mem; if(lua_isboolean(L, 2)) cache =lua_toboolean(L, 2); if(0 != cache){ struct stat st1,st2; cfile=(char *)malloc(lfile+5); memcpy((char *)cfile,file,lfile); strcpy((char *)cfile+lfile,".tpl"); if((0 == stat(file,&st1)) && (0 == stat(cfile, &st2))) { if(st1.st_mtime <= st2.st_mtime) { if(NULL != (buf = file_get_contents(cfile, &lbuf))) { free((void *)cfile); lua_pushlstring(L,buf, lbuf); goto lua_tpl_compile_final; } } } } if(NULL != (buf = file_get_contents(file, &lbuf))) { automem_init(&mem,lbuf + 1024); lua_tpl_compile_local(L, &mem, buf, lbuf); free((void*)buf); lua_pushlstring(L,(char *)mem.pdata,mem.size); if(0 != cache && NULL !=cfile) file_put_contents(cfile,&mem); automem_uninit(&mem); } if(NULL != cfile) free((void *)cfile); lua_tpl_compile_final: return 1; } static luaL_Reg fmt_tpl_reg[] = { {"compile",lua_tpl_compile}, {NULL,NULL} }; int luaopen_cfmt_tpl(lua_State * L) { luaL_newlib(L, fmt_tpl_reg); lua_cfmt_createmetatable(L); return 1; }
整个LUA模块扩展就1个函数 compile()。在LUA中的原型如下:
string compile(filePath,cached, write, prefix,suffix,init)
功能: 将html模板编译为lua代码.
参数:
filePath: html源文件的路径.cached: 是否需要缓存.writer: 内容输出函数名.prefix: 文件分界符前缀.init:初始代码,用于做参数展开之类的工作.
当然,光有C的接口还不够,为了使它变得简单易用,还需要用LUA对其包装一下^_^, 包装代码如下:
function util.tpl(writer) local t = require "cfmt.tpl" local tpl = {} local args = {} local _prefix='[=[' local _suffix=']=]' -- 创建的时候指定 writer if nil ~= writer then args['_']=writer end function tpl:assign(name,value) args[name]=value end function tpl:boundary(prefix, suffix) --修改字符串边界符 if nil ~= prefix then _prefix=prefix end if nil ~= suffix then _suffix=suffix end end function tpl:display(tpl,cache,writer) -- tpl 模板文件位置, cache 是否需要缓存 writer 可选,如果创建对象的时候指定了的话. local i=1 if nil~=writer then args['_']=writer end local init = {'local args = ...'} for key,val in pairs(args) do init[#init+1]='local '..key..'=args["'..key..'"]' i=i+1 end init = table.concat(init,'n') local code =t.compile(tpl,cache,'_('.._prefix, _suffix,init) load(code)(args) end return tpl; end
接下来用起来就简单多了,上测试代码.
---外部进来的数据在这里做检测 function login:request(r) local tpl = (require "util").tpl(r.print) local users ={ {['ID']=1,['username']='bywayboy',['age']=31}, {['ID']=1,['username']='liigo',['age']=31}, {['ID']=1,['username']='sunwei',['age']=8}, } local b={['a']=12} tpl:assign('users',users); tpl:assign('title',"测试模板变量.") tpl:assign('name',"某某童鞋") tpl:display("D:\VC\CmdChannel\win32\Debug\test.html",true) end
再来一个模板文件示例:
{#_(title)#}--方121212法ID姓名年龄{# for key,val in pairs(users) do#}{#_(val['ID']) #}{#_(val['username'])#}{#_(val['age'])#}{#end#}{#_(name)#}
该文件最终生成的缓存文件为:
local args = ... local _=args["_"] local name=args["name"] local users=args["users"] local title=args["title"] _([=[]=]) _(title) _([=[--方121212法ID姓名年龄]=]) for key,val in pairs(users) do _([=[]=]) _(val['ID']) _([=[]=]) _(val['username']) _([=[]=]) _(val['age']) _([=[]=]) end _([=[]=]) _(name) _([=[]=])
输出结果如图:
最终生成的HTML代码如下:
测试模板变量.--方121212法ID姓名年龄1bywayboy311liigo311sunwei8某某童鞋