用C為LUA寫一個超迷你的模板引擎.
中午在做HTTP服務器,內嵌了LUA引擎作為業(yè)務邏輯部分. 但考慮到LUA輸出HTML的性能不高,況且 MVC 模式開發(fā)網頁已經習慣了,何不用C給lua寫個最簡單的模板引擎呢?說做就做, 項目時間很緊張,所以必須確定目標,想了會兒。定了幾個目標:
我要的是編譯型的模板,要支持緩存。
不需要強大的模板邏輯方面的指令。將HTML模板文件編譯成LUA源代碼即可。LUA 還是比較容易擴展的,除了那棧用起來比較變態(tài)之外,經過了幾個小時的奮斗,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=']=]'
--?創(chuàng)建的時候指定?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?可選,如果創(chuàng)建對象的時候指定了的話.
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某某童鞋





