UNPKG

11.5 kBJavaScriptView Raw
1var Code, call_bound_func, coffee, coffeecup, parser, skeleton, uglify, _ref,
2 __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
3
4coffee = require('coffee-script');
5
6_ref = require('uglify-js'), uglify = _ref.uglify, parser = _ref.parser;
7
8coffeecup = null;
9
10exports.setup = function(cc) {
11 return coffeecup = cc;
12};
13
14skeleton = 'var __cc = {\n buffer: \'\'\n};\nvar text = function(txt) {\n if (typeof txt === \'string\' || txt instanceof String) {\n __cc.buffer += txt;\n } else if (typeof txt === \'number\' || txt instanceof Number) {\n __cc.buffer += txt.toString();\n }\n};\nvar h = function(txt) {\n var escaped;\n if (typeof txt === \'string\' || txt instanceof String) {\n escaped = txt.replace(/&/g, \'&amp;\')\n .replace(/</g, \'&lt;\')\n .replace(/>/g, \'&gt;\')\n .replace(/"/g, \'&quot;\');\n } else {\n escaped = txt;\n }\n return escaped;\n};\nvar yield = function(f) {\n var temp_buffer = \'\';\n var old_buffer = __cc.buffer;\n __cc.buffer = temp_buffer;\n f();\n temp_buffer = __cc.buffer;\n __cc.buffer = old_buffer;\n return temp_buffer;\n};\n';
15
16call_bound_func = function(func) {
17 return ['call', ['dot', func, 'call'], [['name', 'data']]];
18};
19
20Code = (function() {
21
22 function Code(parent) {
23 this.parent = parent;
24 this.nodes = [];
25 this.line = '';
26 }
27
28 Code.prototype.call = function(arg) {
29 return ['stat', ['call', ['name', 'text'], [arg]]];
30 };
31
32 Code.prototype.append = function(str) {
33 if (this.block != null) {
34 return this.block.append(str);
35 } else {
36 return this.line += str;
37 }
38 };
39
40 Code.prototype.flush = function() {
41 if (this.block != null) {
42 return this.block.flush();
43 } else {
44 this.merge_text(['string', this.line]);
45 return this.line = '';
46 }
47 };
48
49 Code.prototype.open_if = function(condition) {
50 this.flush();
51 if (this.block != null) {
52 return this.block.open_if(condition);
53 } else {
54 this.block = new Code();
55 return this.block.condition = condition;
56 }
57 };
58
59 Code.prototype.close_if = function() {
60 this.flush();
61 if (this.block.block != null) {
62 return this.block.close_if();
63 } else {
64 this.nodes.push(['if', this.block.condition, ['block', this.block.nodes]]);
65 return delete this.block;
66 }
67 };
68
69 Code.prototype.push = function(node) {
70 this.flush();
71 if (this.block != null) {
72 return this.block.push(node);
73 } else {
74 return this.merge_text(node);
75 }
76 };
77
78 Code.prototype.merge_text = function(arg) {
79 var l, ok, oldArg, prev, _ref2, _ref3;
80 if (arg[0] === 'binary' && arg[1] === '+') {
81 this.merge_text(arg[2]);
82 arg = arg[3];
83 }
84 if (l = this.nodes.length) {
85 prev = this.nodes[l - 1];
86 if (prev[0] === 'stat' && prev[1][0] === 'call' && prev[1][1][0] === 'name' && prev[1][1][1] === 'text') {
87 oldArg = prev[1][2][0];
88 ok = ['string', 'num'];
89 if ((_ref2 = oldArg[0], __indexOf.call(ok, _ref2) >= 0) && (_ref3 = arg[0], __indexOf.call(ok, _ref3) >= 0)) {
90 prev[1][2][0] = ['string', oldArg[1] + arg[1]];
91 return;
92 }
93 }
94 }
95 return this.nodes.push(this.call(arg));
96 };
97
98 Code.prototype.get_nodes = function() {
99 this.flush();
100 if (this.parent[0] === 'stat') return ['splice', this.nodes];
101 return call_bound_func(['function', null, [], this.nodes]);
102 };
103
104 return Code;
105
106})();
107
108exports.compile = function(source, hardcoded_locals, options) {
109 var ast, code, compiled, escape, w;
110 escape = function(node) {
111 if (options.autoescape) return ['call', ['name', 'h'], [node]];
112 return node;
113 };
114 ast = parser.parse(hardcoded_locals + ("(" + source + ").call(data);"));
115 w = uglify.ast_walker();
116 ast = w.with_walkers({
117 call: function(expr, args) {
118 var arg, classes, code, comment, condition, contents, doctype, escape_all, func, i, id, idx, name, node, render_attrs, _i, _j, _k, _len, _len2, _len3, _len4, _ref2, _ref3, _ref4;
119 name = expr[1];
120 if (name === 'doctype') {
121 code = new Code(w.parent());
122 if (args.length > 0) {
123 doctype = args[0][1].toString();
124 if (doctype in coffeecup.doctypes) {
125 code.append(coffeecup.doctypes[doctype]);
126 } else {
127 throw new Error('Invalid doctype');
128 }
129 } else {
130 code.append(coffeecup.doctypes["default"]);
131 }
132 return code.get_nodes();
133 } else if (name === 'comment') {
134 comment = args[0];
135 code = new Code(w.parent());
136 if (comment[0] === 'string') {
137 code.append("<!--" + comment[1] + "-->");
138 } else {
139 code.append('<!--');
140 code.push(escape(comment));
141 code.append('-->');
142 }
143 return code.get_nodes();
144 } else if (name === 'ie') {
145 condition = args[0], contents = args[1];
146 code = new Code(w.parent());
147 if (condition[0] === 'string') {
148 code.append("<!--[if " + condition[1] + "]>");
149 } else {
150 code.append('<!--[if ');
151 code.push(escape(condition));
152 code.append(']>');
153 }
154 code.push(call_bound_func(w.walk(contents)));
155 code.append('<![endif]-->');
156 return code.get_nodes();
157 } else if (__indexOf.call(coffeecup.tags, name) >= 0 || (name === 'tag' || name === 'coffeescript')) {
158 if (name === 'tag') name = args.shift()[1];
159 if (name === 'coffeescript') {
160 name = 'script';
161 for (_i = 0, _len = args.length; _i < _len; _i++) {
162 arg = args[_i];
163 if ((_ref2 = arg[0]) !== 'string' && _ref2 !== 'object' && _ref2 !== 'function') {
164 throw new Error('Invalid argument to coffeescript function');
165 }
166 if (arg[0] === 'string' && (args.length === 1 || arg !== args[0])) {
167 arg[1] = coffee.compile(arg[1], {
168 bare: true
169 });
170 }
171 }
172 }
173 code = new Code(w.parent());
174 code.append("<" + name);
175 for (_j = 0, _len2 = args.length; _j < _len2; _j++) {
176 arg = args[_j];
177 switch (arg[0]) {
178 case 'function':
179 if (name === 'script') {
180 func = uglify.gen_code(arg, {
181 beautify: true,
182 indent_level: 2
183 });
184 contents = ['string', "" + func + ".call(this);"];
185 } else {
186 func = w.walk(arg);
187 _ref3 = func[3];
188 for (idx = 0, _len3 = _ref3.length; idx < _len3; idx++) {
189 node = _ref3[idx];
190 if (node[0] === 'return' && (node[1] != null) && node[1][0] !== 'string') {
191 func[3][idx][1] = escape(node[1]);
192 }
193 }
194 contents = call_bound_func(func);
195 }
196 break;
197 case 'object':
198 render_attrs = function(obj, prefix) {
199 var attr, key, value, varname, _k, _len4, _ref4, _ref5, _results;
200 if (prefix == null) prefix = '';
201 _results = [];
202 for (_k = 0, _len4 = obj.length; _k < _len4; _k++) {
203 attr = obj[_k];
204 key = attr[0];
205 value = attr[1];
206 if (value[0] === 'name' && value[1] === 'true') {
207 _results.push(code.append(" " + key + "=\"" + key + "\""));
208 } else if (value[0] === 'name' && ((_ref4 = value[1]) === 'undefined' || _ref4 === 'null' || _ref4 === 'false')) {
209 continue;
210 } else if ((_ref5 = value[0]) === 'name' || _ref5 === 'dot') {
211 varname = uglify.gen_code(value);
212 condition = "typeof " + varname + " !== 'undefined' && " + varname + " !== null && " + varname + " !== false";
213 code.open_if(parser.parse(condition)[1][0][1]);
214 code.append(" " + (prefix + key) + "=\"");
215 code.push(escape(value));
216 code.append('"');
217 _results.push(code.close_if());
218 } else if (value[0] === 'string') {
219 _results.push(code.append(" " + (prefix + key) + "=\"" + value[1] + "\""));
220 } else if (value[0] === 'function') {
221 func = uglify.gen_code(value).replace(/"/g, '&quot;');
222 _results.push(code.append(" " + (prefix + key) + "=\"" + func + ".call(this);\""));
223 } else if (value[0] === 'object') {
224 _results.push(render_attrs(value[1], prefix + key + '-'));
225 } else {
226 code.append(" " + (prefix + key) + "=\"");
227 code.push(escape(value));
228 _results.push(code.append('"'));
229 }
230 }
231 return _results;
232 };
233 render_attrs(arg[1]);
234 break;
235 case 'string':
236 if (args.length > 1 && arg === args[0]) {
237 classes = [];
238 _ref4 = arg[1].split('.');
239 for (_k = 0, _len4 = _ref4.length; _k < _len4; _k++) {
240 i = _ref4[_k];
241 if (__indexOf.call(i, '#') >= 0) {
242 id = i.replace('#', '');
243 } else {
244 if (i !== '') classes.push(i);
245 }
246 }
247 if (id) code.append(" id=\"" + id + "\"");
248 if (classes.length > 0) {
249 code.append(" class=\"" + (classes.join(' ')) + "\"");
250 }
251 } else {
252 arg[1] = arg[1].replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
253 contents = arg;
254 }
255 break;
256 case 'binary':
257 escape_all = function(node) {
258 switch (node[0]) {
259 case 'binary':
260 node[2] = escape_all(node[2]);
261 node[3] = escape_all(node[3]);
262 return node;
263 case 'string':
264 return node;
265 case 'call':
266 if (node[1][0] === 'name' && node[1][1] === 'yield') {
267 return node;
268 }
269 return escape(node);
270 default:
271 return escape(node);
272 }
273 };
274 contents = escape_all(w.walk(arg));
275 break;
276 default:
277 contents = escape(w.walk(arg));
278 }
279 }
280 if (__indexOf.call(coffeecup.self_closing, name) >= 0) {
281 code.append(' />');
282 } else {
283 code.append('>');
284 }
285 if (contents != null) code.push(contents);
286 if (!(__indexOf.call(coffeecup.self_closing, name) >= 0)) {
287 code.append("</" + name + ">");
288 }
289 return code.get_nodes();
290 }
291 return null;
292 }
293 }, function() {
294 return w.walk(ast);
295 });
296 compiled = uglify.gen_code(ast, {
297 beautify: true,
298 indent_level: 2
299 });
300 if (options.locals) compiled = "with(data.locals){" + compiled + "}";
301 code = skeleton + compiled + "return __cc.buffer;";
302 return new Function('data', code);
303};