-
Notifications
You must be signed in to change notification settings - Fork 13
/
index.js
139 lines (116 loc) · 3.87 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
require.paths.unshift(__dirname + '/lib');
var fs = require("fs"),
util = require("util"),
path = require("path"),
crypto = require("crypto"),
tags = require("tags"),
parser = require("parser"),
widgets = require("widgets"),
filters = require('filters'),
CACHE = {},
DEBUG = false,
ROOT = "./",
fromString, fromFile, createTemplate;
// Call this before using the templates
exports.init = function (root, debug) {
DEBUG = debug;
ROOT = root;
};
createTemplate = function (data, id) {
var template = {
// Allows us to include templates from the compiled code
fromFile: fromFile,
// These are the blocks inside the template
blocks: {},
// Distinguish from other tokens
type: parser.TEMPLATE,
// Allows us to print debug info from the compiled code
util: util,
// The template ID (path relative to tempalte dir)
id: id
},
tokens,
code,
render;
// The template token tree before compiled into javascript
template.tokens = parser.parse.call(template, data, tags);
// The raw template code - can be inserted into other templates
// We don't need this in production
code = parser.compile.call(template);
if (DEBUG) {
template.code = code;
}
// The compiled render function - this is all we need
render = new Function("__context", "__parents", "__filters", "__widgets",
[ '__parents = __parents ? __parents.slice() : [];'
// Prevents circular includes (which will crash node without warning)
, 'for (var i=0, j=__parents.length; i<j; ++i) {'
, ' if (__parents[i] === this.id) {'
, ' return "Circular import of template " + this.id + " in " + __parents[__parents.length-1];'
, ' }'
, '}'
// Add this template as a parent to all includes in its scope
, '__parents.push(this.id);'
, 'var __output = [];'
, 'var __this = this;'
, code
, 'return __output.join("");'].join("\n")
);
template.render = function (context, parents) {
return render.call(this, context, parents, filters, widgets);
};
return template;
};
/*
* Returns a template object from the given filepath.
* The filepath needs to be relative to the template directory.
*/
fromFile = function (filepath) {
if (filepath[0] === '/') {
filepath = filepath.substr(1);
}
if (filepath in CACHE && !DEBUG) {
return CACHE[filepath];
}
var data = fs.readFileSync(ROOT + "/" + filepath, 'utf8');
// TODO: see what error readFileSync returns and warn about it
if (data) {
CACHE[filepath] = createTemplate(data, filepath);
return CACHE[filepath];
}
};
/*
* Returns a template object from the given string.
*/
fromString = function (string) {
var hash = crypto.createHash('md5').update(string).digest('hex');
if (!(hash in CACHE && !DEBUG)) {
CACHE[hash] = createTemplate(string, hash);
}
return CACHE[hash];
};
module.exports = {
init: exports.init,
fromFile: fromFile,
fromString: fromString,
compile: function (source, options, callback) {
var self = this;
if (typeof source === 'string') {
return function (options) {
var tmpl = fromString(source);
options.locals = options.locals || {};
options.partials = options.partials || {};
if (options.body) { // for express.js > v1.0
options.locals.body = options.body;
}
return tmpl.render(options.locals);
};
} else {
return source;
}
},
render: function (template, options) {
template = this.compile(template, options);
return template(options);
}
};