UNPKG

12.7 kBJavaScriptView Raw
1var path = require('path'),
2 fs = require('fs'),
3 util = require('util'),
4 moment = require('moment'),
5 domain = require('domain'),
6 vm = require('vm'),
7 atry = require('./atry.js');
8
9var _escape_table = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'":'&apos;' }
10var _escape_reg = new RegExp(/[&<>"']/g);
11var _escape_func = function(m) { return _escape_table[m]; }
12var _escape = function(s) {
13 return typeof(s) != 'string' ? s : s.replace(_escape_reg, _escape_func);
14}
15
16
17function Promise() {
18 this.cbs = [];
19 this.errbs = [];
20 }
21
22
23Promise.prototype = {
24 addCallback: function (cb) {
25 this.cbs.push(cb);
26 },
27 addErrback: function (errb) {
28 this.errbs.push(errb)
29 },
30 emitSuccess: function () {
31 for(var i = 0, len = this.cbs.length; i < len; ++i) {
32 var cb = this.cbs[i];
33 cb.apply(this, arguments);
34 }
35 },
36 // emitError: function (err) {
37 // var called = false;
38 // for(var i = 0, len = this.errbs.length; i < len; ++i) {
39 // var cb = this.errbs[i];
40 // cb.call(this, err);
41 // called = true;
42 // }
43 // if(!called) {
44 // throw(err)
45 // }
46 // }
47}
48
49
50//模板缓存
51//保存内容为
52//{
53// "data": ...
54// "ts":...
55//}
56var templateCache = {};
57// A TemplateEngine holds template factories
58// plus a config for all templates
59var TemplateEngine = function (config) {
60 config = config || {};
61 this.path = config.path || process.cwd();
62 this.includeId = config.includeId || false;
63 this.cacheTime = (config.cacheTime - 0) || 0;
64}
65
66//对外暴露接口config
67exports.Engine = function (config) {
68 return new TemplateEngine(config);
69}
70
71TemplateEngine.prototype = {
72
73 //获取当天的日期字符串
74 getTodayStr:function(){
75 return moment().format("YYYY_MM_DD_HH");
76 },
77 //设置模板解析缓存
78 setCache:function(key, fnList){
79 var todayHourStr = this.getTodayStr()
80 var tmpCache = templateCache[todayHourStr] || {};
81 tmpCache[key] = {
82 "data":fnList,
83 "ts":Date.now(),
84 }
85 //每次自动剔除1小时的模板缓存
86 templateCache = {};
87 templateCache[todayHourStr] = tmpCache;
88 },
89 //获取模板解析缓存
90 getCache:function(key){
91 var todayHourStr = this.getTodayStr();
92 if(!templateCache[todayHourStr] || !templateCache[todayHourStr][key]) return false
93 var now = Date.now();
94 if(now - (templateCache[todayHourStr][key]["ts"]) <= this.cacheTime){
95 return templateCache[todayHourStr][key]["data"]
96 }
97
98 return false;
99 },
100 // template constructor with a callback parameter
101 template: function (templateStrList, cacheKey, cb) {
102 var self = this;
103 //console.log(templateStrList, cacheKey, cb)
104
105 return self.templateAsync(templateStrList, cacheKey, cb);
106
107
108 },
109
110 // Asynchronous template constructor
111 // Returns a promise for a template
112 templateAsync: function (templateStrList, cacheKey, cb) {
113 var self = this;
114 var templatePromise = new Promise();
115 templatePromise.addCallback(cb);
116
117 //缓存
118 var cacheData = this.getCache(cacheKey);
119 if(cacheData) {
120 process.nextTick(function () {
121 templatePromise.emitSuccess(null, cacheData, true);
122 });
123 return templatePromise;
124 }
125
126 load(templateStrList, function (fnList) {
127 var t = function (te, cacheKey, ctx, tpromise) {
128
129 var res = new TemplateResponse(te, cacheKey);
130 res.fnList = fnList;
131 res.totalFn = fnList.length;
132 res.exePos = 0;
133 res.ctx = ctx;
134 res.codeStr = [];
135
136 for(var i=0;i<fnList.length;i++){
137 res.codeStr[i] = fnList[i].codeStr || '';
138 }
139
140 atry(function() {
141 ctx._escape = _escape; //传入escape函数
142 fnList[res.exePos](ctx, res);
143 }).catch(function(err) {
144 if(res.exePos && fnList[res.exePos] && fnList[res.exePos].codeStr){
145 err.codeStr = fnList[res.exePos].codeStr || '';
146 }else{
147 err.codeStr = '';
148 }
149
150 return tpromise.emitSuccess(err);
151 });
152
153 res.exePos++;//表示执行了一个异步
154
155 process.nextTick(function () {
156 res.partFinished();
157 })
158 return res;
159 };
160
161 // 设置缓存
162 self.setCache(cacheKey, t);
163 process.nextTick(function () {
164 templatePromise.emitSuccess(null, t);
165 })
166
167 }, function (e) {
168 templatePromise.emitSuccess(e)
169 });
170
171 return templatePromise;
172 },
173
174 //Should not be public :)
175 // partial: function (filename, res, ctx) {
176 // var partialPromise = new Promise();
177 // var p = this.templateAsync(filename);
178
179 // var partRes;
180
181 // if(res) {
182 // res.schedule(function (res) {
183 // partRes = res;
184 // })
185 // }
186
187 // p.addCallback(function (t) {
188 // var partialResponse = t(ctx);
189
190 // partialResponse.addListener("body", function (chunk) {
191
192 // partRes.print(chunk);
193 // });
194 // partialResponse.addListener("complete", function () {
195
196 // partialResponse.finished = true;
197 // partRes.finish();
198 // res.partFinished();
199 // partialPromise.emitSuccess();
200 // });
201 // })
202 // return partialPromise;
203 // }
204};
205
206function load(templateStrList, cb, errcb) {
207
208
209 var srcList = parseAndGenerate(templateStrList[0]);
210 var fnList = [];
211
212 for(var i=0;i<srcList.length;i++){
213 try{
214 var fn = vm.runInThisContext(srcList[i], {
215 'timeout':3000,
216 });
217 }catch(e){
218 e.codeStr = srcList[i];
219 errcb(e);
220 return;
221 }
222 fn.codeStr = srcList[i];
223 fnList.push(fn);
224 }
225 return cb(fnList);
226}
227
228
229function TemplateResponse(engine, filename) {
230 var self = this;
231 process.EventEmitter.call(this);
232 this.engine = engine;
233 this.filename = filename || "";
234 this.p = [];
235 this.returned = 0;
236 this.finished = false;
237 this.exePos = 0;
238}
239
240util.inherits(TemplateResponse, process.EventEmitter);
241var body = {
242 print: function () {
243 this.p.push.apply(this.p, arguments);
244 },
245
246 toString: function () {
247 return "<a TemplateResponse for "+this.filename+" "+this.finished+">"
248 },
249
250 finish: function () {
251 this.emit("complete");
252 },
253
254 schedule: function (fn) {
255 var part = this._schedule(fn);
256 this.p.push(part);
257 },
258
259 _schedule: function (fn) {
260 var self = this;
261 //var codeStr = self.codeStr[self.exePos] || '';
262
263 var part = new TemplateResponse(this.engine, this.filename+"->async");
264 part.codeStr = self.codeStr[self.exePos] || '';
265 self.lastPos = self.exePos;
266
267
268 part.addListener("complete", function () {
269 part.finished = true;
270 self.partFinished();
271 })
272
273 part.addListener("error", function (err) {
274 part.finished = true;
275 err.codeStr = self.codeStr[self.lastPos] || '';
276 self.partFinished(err);
277 })
278
279 atry(function() {
280 fn.call(part, part);
281 }).catch(function(err) {
282 err.codeStr = self.codeStr[self.lastPos] || '';
283 return part.emit("error",err);
284 });
285
286 return part;
287 },
288
289 // partial: function (filename, ctx) {
290 // return this.engine.partial(filename, this, ctx);
291 // },
292
293 partFinished: function (err) {
294 var self = this;
295
296 if(err){
297 self.emit("error", err);
298 return
299 }
300
301 if(this.returned < this.p.length) {
302 var done = this._partFinished(function (chunk) {
303 self.emit("body", chunk);
304 });
305
306 if(done === this.p.length || this.ctx.__hasFinish__ === true) {
307 //console.log(this.exePos, this.totalFn)
308 //if async fn has been exec,emit complete
309 if(this.exePos == this.totalFn || this.ctx.__hasFinish__ === true){
310 this.emit("complete");
311 }else{
312 try{
313 this.fnList[this.exePos](this.ctx, this);
314 }catch(err){
315
316 err.codeStr = this.fnList[this.exePos]['codeStr'] || '';
317 self.emit("error", err);
318 return
319 }
320 this.exePos++;
321 process.nextTick(function () {
322 self.partFinished();
323 })
324 }
325 }
326 }
327 },
328
329 _partFinished: function (cb, start) {
330 var p = this.p;
331
332 if(start != null) {
333 this.returned = start
334 }
335 for(var i = this.returned, len = p.length; i < len; ++i) {
336 var part = p[i];
337 if(!(part instanceof TemplateResponse)) {
338 cb(part);
339 this.returned++;
340 }
341 else if(part.finished) {
342 part._partFinished(cb, 0)
343 this.returned++;
344 } else {
345 break;
346 }
347 }
348 return i;
349 }
350};
351for(var i in body) {
352 TemplateResponse.prototype[i] = body[i];
353};
354
355function parseAndGenerate(str) {
356 var open = "<%";
357 var close = "%>";
358 var index;
359 var dynPart = [];
360
361 // TODO tweak the second replace so that the first can go away (and the later which reverse it)
362 var staticParts = str.replace(/[\n\r]/g, "<;;;;>").replace(/<%(.*?)%>/g, function (part, code) {
363 dynPart.push(code.replace(/<;;;;>/g, "\n"));
364 return "<%%>"
365 }).replace(/<;;;;>/g, "\n").replace(/'/g, "\\'").replace(/[\n\r]/g, "\\n\\\n").split(/<%%>/);
366
367
368 var staticPartsList = [];
369 var dynPartList = [];
370 var i = 0;
371 staticParts.forEach(function(part, index){
372 if(!staticPartsList[i]){
373 staticPartsList[i] = [];
374 }
375 if(!dynPartList[i]){
376 dynPartList[i] = [];
377 }
378
379 var code = dynPart[index];
380 if(code != null) {
381 if(code.charAt(0) === "-" || code.charAt(0) === "=") {
382 staticPartsList[i].push(part)
383 dynPartList[i].push(code)
384 }
385 else if(code.charAt(0) === "?") {
386 staticPartsList[i].push(part)
387 dynPartList[i].push(code)
388 i++;
389 }
390 else {
391 staticPartsList[i].push(part)
392 dynPartList[i].push(code)
393 }
394 }
395 else{
396 staticPartsList[i].push(part)
397 }
398 })
399
400 var srcList = [];
401 var tempSrc = 'function __template__ (ctx, res) { \r\n'+'\"use strict\"\r\n';
402
403 staticPartsList.forEach(function(staticItemList, j){
404 var src = tempSrc;
405 var dynPart = dynPartList[j];
406
407 src = staticItemList.reduce(function(src, part, index){
408 //console.log(part)
409
410 src = src + "\n res.p.push(\n'" + part + "'\n);\n";
411 var code = dynPart[index];
412 if(code != null) {
413 if(code.charAt(0) === "-") {
414 code = code.substr(1);
415 src = src + "\n res.p.push(\n"+code+"\n);\n";
416 }
417 else if(code.charAt(0) === "="){
418 code = code.substr(1);
419 src = src + "\n res.p.push(ctx._escape(\n"+code+"\n));\n";
420
421 }
422 else if(code.charAt(0) === "?") {
423 code = code.substr(1);
424 src = src + "\n res.p.push(res._schedule(function (res) {var aries = function(){ process.nextTick(function(){res.finish();}) };\n"+code+"\n}));\n"
425 }
426 else {
427 src = src + code
428 }
429 }
430 return src;
431 }, src) + "\n return res}__template__;\n";
432
433 srcList.push(src);
434 })
435
436
437 return srcList;
438}
439
440// function parseJohnResig(str) {
441// // Stolen from underscore.js http://documentcloud.github.com/underscore/underscore.js
442// // JavaScript templating a-la ERB, pilfered from John Resig's
443// // "Secrets of the JavaScript Ninja", page 83.
444// var src =
445// 'function __template__ (ctx, res) {' +
446// 'res.p.push(\'' +
447// str
448// .replace(/[\t]/g, " ")
449// .replace(/[\n\r]/g, "<%\n%>\\n")
450// .split("<%").join("\t")
451// .replace(/((^|%>)[^\t]*)'/g, "$1\n")
452// .replace(/\t\-(.*?)%>/g, "',$1,'")
453// .replace(/\t\?(.*?)%>/g, "',res._schedule(function (res) {$1}),'")
454// .split("\t").join("');")
455// .split("%>").join("res.p.push('")
456// .split("\r").join("\\'")
457// + "');return res}__template__;";
458
459// return src;
460// }