UNPKG

4.51 kBJavaScriptView Raw
1var fs = require('fs'),
2 path = require('path'),
3
4 tags = require('./tags'),
5 parser = require('./parser'),
6 filters = require('./filters'),
7 helpers = require('./helpers'),
8 dateformat = require('./dateformat'),
9
10 _ = require('underscore');
11
12var config = {
13 allowErrors: false,
14 autoescape: true,
15 cache: true,
16 encoding: 'utf8',
17 filters: filters,
18 root: '/',
19 tags: tags,
20 extensions: {},
21 tzOffset: 0
22 },
23 _config = _.extend({}, config),
24 CACHE = {};
25
26// Call this before using the templates
27exports.init = function (options) {
28 CACHE = {};
29 _config = _.extend({}, config, options);
30 _config.filters = _.extend(filters, options.filters);
31 _config.tags = _.extend(tags, options.tags);
32
33 dateformat.defaultTZOffset = _config.tzOffset;
34};
35
36function TemplateError(error) {
37 return { render: function () {
38 return '<pre>' + error.stack + '</pre>';
39 }};
40}
41
42function createRenderFunc(code) {
43 // The compiled render function - this is all we need
44 return new Function('_context', '_parents', '_filters', '_', '_ext', [
45 '_parents = _parents ? _parents.slice() : [];',
46 '_context = _context || {};',
47 // Prevents circular includes (which will crash node without warning)
48 'var j = _parents.length,',
49 ' _output = "",',
50 ' _this = this;',
51 // Note: this loop averages much faster than indexOf across all cases
52 'while (j--) {',
53 ' if (_parents[j] === this.id) {',
54 ' return "Circular import of template " + this.id + " in " + _parents[_parents.length-1];',
55 ' }',
56 '}',
57 // Add this template as a parent to all includes in its scope
58 '_parents.push(this.id);',
59 code,
60 'return _output;'
61 ].join(''));
62}
63
64function createTemplate(data, id) {
65 var template = {
66 // Allows us to include templates from the compiled code
67 compileFile: exports.compileFile,
68 // These are the blocks inside the template
69 blocks: {},
70 // Distinguish from other tokens
71 type: parser.TEMPLATE,
72 // The template ID (path relative to template dir)
73 id: id
74 },
75 tokens,
76 code,
77 render;
78
79 // The template token tree before compiled into javascript
80 if (_config.allowErrors) {
81 tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
82 } else {
83 try {
84 tokens = parser.parse.call(template, data, _config.tags, _config.autoescape);
85 } catch (e) {
86 return new TemplateError(e);
87 }
88 }
89
90 template.tokens = tokens;
91
92 // The raw template code
93 code = parser.compile.call(template);
94
95 if (code !== false) {
96 render = createRenderFunc(code);
97 } else {
98 render = function (_context, _parents, _filters, _, _ext) {
99 template.tokens = tokens;
100 code = parser.compile.call(template, '', _context);
101 var fn = createRenderFunc(code);
102 return fn.call(this, _context, _parents, _filters, _, _ext);
103 };
104 }
105
106 template.render = function (context, parents) {
107 if (_config.allowErrors) {
108 return render.call(this, context, parents, _config.filters, _, _config.extensions);
109 }
110 try {
111 return render.call(this, context, parents, _config.filters, _, _config.extensions);
112 } catch (e) {
113 return new TemplateError(e);
114 }
115 };
116
117 return template;
118}
119
120function getTemplate(source, options) {
121 var key = options.filename || source;
122 if (_config.cache || options.cache) {
123 if (!CACHE.hasOwnProperty(key)) {
124 CACHE[key] = createTemplate(source, key);
125 }
126
127 return CACHE[key];
128 }
129
130 return createTemplate(source, key);
131}
132
133exports.compileFile = function (filepath, forceAllowErrors) {
134 var tpl, get;
135
136 if (_config.cache && CACHE.hasOwnProperty(filepath)) {
137 return CACHE[filepath];
138 }
139
140 if (typeof window !== 'undefined') {
141 throw new TemplateError({ stack: 'You must pre-compile all templates in-browser. Use `swig.compile(template);`.' });
142 }
143
144 get = function () {
145 var file = ((/^\//).test(filepath) || (/^.:/).test(filepath)) ? filepath : _config.root + '/' + filepath,
146 data = fs.readFileSync(file, config.encoding);
147 tpl = getTemplate(data, { filename: filepath });
148 };
149
150 if (_config.allowErrors || forceAllowErrors) {
151 get();
152 } else {
153 try {
154 get();
155 } catch (error) {
156 tpl = new TemplateError(error);
157 }
158 }
159 return tpl;
160};
161
162exports.compile = function (source, options) {
163 options = options || {};
164 var tmpl = getTemplate(source, options || {});
165
166 return function (source, options) {
167 return tmpl.render(source, options);
168 };
169};