puglite 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/angular-webpack/index.js +27 -0
- package/angular-webpack/loader.js +37 -0
- package/builders/browser/index.js +49 -0
- package/builders/browser/schema.json +138 -0
- package/builders/dev-server/index.js +48 -0
- package/builders/dev-server/schema.json +98 -0
- package/builders/karma/README.md +82 -0
- package/builders/karma/index.js +40 -0
- package/builders/karma/schema.json +260 -0
- package/builders/vitest/README.md +218 -0
- package/builders/vitest/index.js +98 -0
- package/builders/vitest/schema.json +8 -0
- package/builders.json +25 -0
- package/lib/attrs.js +150 -0
- package/lib/build.js +27 -0
- package/lib/code-gen.js +769 -0
- package/lib/error.js +56 -0
- package/lib/index.js +416 -0
- package/lib/lexer.d.ts +366 -0
- package/lib/lexer.js +1713 -0
- package/lib/parser-lib/inline-tags.js +23 -0
- package/lib/parser.js +1299 -0
- package/lib/prepublish.js +70 -0
- package/lib/runtime-build.js +27 -0
- package/lib/runtime-lib/dependencies.js +34 -0
- package/lib/runtime-lib/internals.js +8 -0
- package/lib/runtime-lib/sources.js +13 -0
- package/lib/runtime-wrap.js +10 -0
- package/lib/runtime.js +287 -0
- package/lib/strip-comments.js +77 -0
- package/lib/wrap.js +10 -0
- package/package.json +72 -0
package/lib/code-gen.js
ADDED
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var doctypes = require('doctypes');
|
|
4
|
+
var makeError = require('./error');
|
|
5
|
+
var buildRuntime = require('./runtime-build');
|
|
6
|
+
var runtime = require('./runtime');
|
|
7
|
+
var compileAttrs = require('./attrs');
|
|
8
|
+
var selfClosing = require('void-elements');
|
|
9
|
+
var constantinople = require('constantinople');
|
|
10
|
+
var stringify = require('js-stringify');
|
|
11
|
+
var addWith = require('with');
|
|
12
|
+
|
|
13
|
+
// This is used to prevent pretty printing inside certain tags
|
|
14
|
+
var WHITE_SPACE_SENSITIVE_TAGS = {
|
|
15
|
+
pre: true,
|
|
16
|
+
textarea: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
var INTERNAL_VARIABLES = [
|
|
20
|
+
'pug',
|
|
21
|
+
'pug_interp',
|
|
22
|
+
'pug_debug_filename',
|
|
23
|
+
'pug_debug_line',
|
|
24
|
+
'pug_debug_sources',
|
|
25
|
+
'pug_html',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
module.exports = generateCode;
|
|
29
|
+
module.exports.CodeGenerator = Compiler;
|
|
30
|
+
function generateCode(ast, options) {
|
|
31
|
+
return new Compiler(ast, options).compile();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isConstant(src) {
|
|
35
|
+
return constantinople(src, {pug: runtime, pug_interp: undefined});
|
|
36
|
+
}
|
|
37
|
+
function toConstant(src) {
|
|
38
|
+
return constantinople.toConstant(src, {pug: runtime, pug_interp: undefined});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isIdentifier(name) {
|
|
42
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initialize `Compiler` with the given `node`.
|
|
47
|
+
*
|
|
48
|
+
* @param {Node} node
|
|
49
|
+
* @param {Object} options
|
|
50
|
+
* @api public
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
function Compiler(node, options) {
|
|
54
|
+
this.options = options = options || {};
|
|
55
|
+
this.node = node;
|
|
56
|
+
this.bufferedConcatenationCount = 0;
|
|
57
|
+
this.hasCompiledDoctype = false;
|
|
58
|
+
this.hasCompiledTag = false;
|
|
59
|
+
this.pp = options.pretty || false;
|
|
60
|
+
if (this.pp && typeof this.pp !== 'string') {
|
|
61
|
+
this.pp = ' ';
|
|
62
|
+
}
|
|
63
|
+
if (this.pp && !/^\s+$/.test(this.pp)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'The pretty parameter should either be a boolean or whitespace only string'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
if (this.options.templateName && !isIdentifier(this.options.templateName)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'The templateName parameter must be a valid JavaScript identifier if specified.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (
|
|
74
|
+
this.doctype &&
|
|
75
|
+
(this.doctype.includes('<') || this.doctype.includes('>'))
|
|
76
|
+
) {
|
|
77
|
+
throw new Error('Doctype can not contain "<" or ">"');
|
|
78
|
+
}
|
|
79
|
+
if (this.options.globals && !this.options.globals.every(isIdentifier)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
'The globals option must be an array of valid JavaScript identifiers if specified.'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.debug = false !== options.compileDebug;
|
|
86
|
+
this.indents = 0;
|
|
87
|
+
this.parentIndents = 0;
|
|
88
|
+
this.terse = false;
|
|
89
|
+
this.eachCount = 0;
|
|
90
|
+
if (options.doctype) this.setDoctype(options.doctype);
|
|
91
|
+
this.runtimeFunctionsUsed = [];
|
|
92
|
+
this.inlineRuntimeFunctions = options.inlineRuntimeFunctions || false;
|
|
93
|
+
if (this.debug && this.inlineRuntimeFunctions) {
|
|
94
|
+
this.runtimeFunctionsUsed.push('rethrow');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compiler prototype.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
Compiler.prototype = {
|
|
103
|
+
runtime: function(name) {
|
|
104
|
+
if (this.inlineRuntimeFunctions) {
|
|
105
|
+
this.runtimeFunctionsUsed.push(name);
|
|
106
|
+
return 'pug_' + name;
|
|
107
|
+
} else {
|
|
108
|
+
return 'pug.' + name;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
error: function(message, code, node) {
|
|
113
|
+
var err = makeError(code, message, {
|
|
114
|
+
line: node.line,
|
|
115
|
+
column: node.column,
|
|
116
|
+
filename: node.filename,
|
|
117
|
+
});
|
|
118
|
+
throw err;
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compile parse tree to JavaScript.
|
|
123
|
+
*
|
|
124
|
+
* @api public
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
compile: function() {
|
|
128
|
+
this.buf = [];
|
|
129
|
+
if (this.pp) this.buf.push('var pug_indent = [];');
|
|
130
|
+
this.lastBufferedIdx = -1;
|
|
131
|
+
this.visit(this.node);
|
|
132
|
+
var js = this.buf.join('\n');
|
|
133
|
+
var globals = this.options.globals
|
|
134
|
+
? this.options.globals.concat(INTERNAL_VARIABLES)
|
|
135
|
+
: INTERNAL_VARIABLES;
|
|
136
|
+
if (this.options.self) {
|
|
137
|
+
js = 'var self = locals || {};' + js;
|
|
138
|
+
} else {
|
|
139
|
+
js = addWith(
|
|
140
|
+
'locals || {}',
|
|
141
|
+
js,
|
|
142
|
+
globals.concat(
|
|
143
|
+
this.runtimeFunctionsUsed.map(function(name) {
|
|
144
|
+
return 'pug_' + name;
|
|
145
|
+
})
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (this.debug) {
|
|
150
|
+
if (this.options.includeSources) {
|
|
151
|
+
js =
|
|
152
|
+
'var pug_debug_sources = ' +
|
|
153
|
+
stringify(this.options.includeSources) +
|
|
154
|
+
';\n' +
|
|
155
|
+
js;
|
|
156
|
+
}
|
|
157
|
+
js =
|
|
158
|
+
'var pug_debug_filename, pug_debug_line;' +
|
|
159
|
+
'try {' +
|
|
160
|
+
js +
|
|
161
|
+
'} catch (err) {' +
|
|
162
|
+
(this.inlineRuntimeFunctions ? 'pug_rethrow' : 'pug.rethrow') +
|
|
163
|
+
'(err, pug_debug_filename, pug_debug_line' +
|
|
164
|
+
(this.options.includeSources
|
|
165
|
+
? ', pug_debug_sources[pug_debug_filename]'
|
|
166
|
+
: '') +
|
|
167
|
+
');' +
|
|
168
|
+
'}';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
buildRuntime(this.runtimeFunctionsUsed) +
|
|
173
|
+
'function ' +
|
|
174
|
+
(this.options.templateName || 'template') +
|
|
175
|
+
'(locals) {var pug_html = "", pug_interp;' +
|
|
176
|
+
js +
|
|
177
|
+
';return pug_html;}'
|
|
178
|
+
);
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Sets the default doctype `name`. Sets terse mode to `true` when
|
|
183
|
+
* html 5 is used, causing self-closing tags to end with ">" vs "/>",
|
|
184
|
+
* and boolean attributes are not mirrored.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} name
|
|
187
|
+
* @api public
|
|
188
|
+
*/
|
|
189
|
+
|
|
190
|
+
setDoctype: function(name) {
|
|
191
|
+
this.doctype = doctypes[name.toLowerCase()] || '<!DOCTYPE ' + name + '>';
|
|
192
|
+
this.terse = this.doctype.toLowerCase() == '<!doctype html>';
|
|
193
|
+
this.xml = 0 == this.doctype.indexOf('<?xml');
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Buffer the given `str` exactly as is or with interpolation
|
|
198
|
+
*
|
|
199
|
+
* @param {String} str
|
|
200
|
+
* @param {Boolean} interpolate
|
|
201
|
+
* @api public
|
|
202
|
+
*/
|
|
203
|
+
|
|
204
|
+
buffer: function(str) {
|
|
205
|
+
var self = this;
|
|
206
|
+
|
|
207
|
+
str = stringify(str);
|
|
208
|
+
str = str.substr(1, str.length - 2);
|
|
209
|
+
|
|
210
|
+
if (
|
|
211
|
+
this.lastBufferedIdx == this.buf.length &&
|
|
212
|
+
this.bufferedConcatenationCount < 100
|
|
213
|
+
) {
|
|
214
|
+
if (this.lastBufferedType === 'code') {
|
|
215
|
+
this.lastBuffered += ' + "';
|
|
216
|
+
this.bufferedConcatenationCount++;
|
|
217
|
+
}
|
|
218
|
+
this.lastBufferedType = 'text';
|
|
219
|
+
this.lastBuffered += str;
|
|
220
|
+
this.buf[this.lastBufferedIdx - 1] =
|
|
221
|
+
'pug_html = pug_html + ' +
|
|
222
|
+
this.bufferStartChar +
|
|
223
|
+
this.lastBuffered +
|
|
224
|
+
'";';
|
|
225
|
+
} else {
|
|
226
|
+
this.bufferedConcatenationCount = 0;
|
|
227
|
+
this.buf.push('pug_html = pug_html + "' + str + '";');
|
|
228
|
+
this.lastBufferedType = 'text';
|
|
229
|
+
this.bufferStartChar = '"';
|
|
230
|
+
this.lastBuffered = str;
|
|
231
|
+
this.lastBufferedIdx = this.buf.length;
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Buffer the given `src` so it is evaluated at run time
|
|
237
|
+
*
|
|
238
|
+
* @param {String} src
|
|
239
|
+
* @api public
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
bufferExpression: function(src) {
|
|
243
|
+
if (isConstant(src)) {
|
|
244
|
+
return this.buffer(toConstant(src) + '');
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
this.lastBufferedIdx == this.buf.length &&
|
|
248
|
+
this.bufferedConcatenationCount < 100
|
|
249
|
+
) {
|
|
250
|
+
this.bufferedConcatenationCount++;
|
|
251
|
+
if (this.lastBufferedType === 'text') this.lastBuffered += '"';
|
|
252
|
+
this.lastBufferedType = 'code';
|
|
253
|
+
this.lastBuffered += ' + (' + src + ')';
|
|
254
|
+
this.buf[this.lastBufferedIdx - 1] =
|
|
255
|
+
'pug_html = pug_html + (' +
|
|
256
|
+
this.bufferStartChar +
|
|
257
|
+
this.lastBuffered +
|
|
258
|
+
');';
|
|
259
|
+
} else {
|
|
260
|
+
this.bufferedConcatenationCount = 0;
|
|
261
|
+
this.buf.push('pug_html = pug_html + (' + src + ');');
|
|
262
|
+
this.lastBufferedType = 'code';
|
|
263
|
+
this.bufferStartChar = '';
|
|
264
|
+
this.lastBuffered = '(' + src + ')';
|
|
265
|
+
this.lastBufferedIdx = this.buf.length;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Buffer an indent based on the current `indent`
|
|
271
|
+
* property and an additional `offset`.
|
|
272
|
+
*
|
|
273
|
+
* @param {Number} offset
|
|
274
|
+
* @param {Boolean} newline
|
|
275
|
+
* @api public
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
prettyIndent: function(offset, newline) {
|
|
279
|
+
offset = offset || 0;
|
|
280
|
+
newline = newline ? '\n' : '';
|
|
281
|
+
this.buffer(newline + Array(this.indents + offset).join(this.pp));
|
|
282
|
+
if (this.parentIndents)
|
|
283
|
+
this.buf.push('pug_html = pug_html + pug_indent.join("");');
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Visit `node`.
|
|
288
|
+
*
|
|
289
|
+
* @param {Node} node
|
|
290
|
+
* @api public
|
|
291
|
+
*/
|
|
292
|
+
|
|
293
|
+
visit: function(node, parent) {
|
|
294
|
+
var debug = this.debug;
|
|
295
|
+
|
|
296
|
+
if (!node) {
|
|
297
|
+
var msg;
|
|
298
|
+
if (parent) {
|
|
299
|
+
msg =
|
|
300
|
+
'A child of ' +
|
|
301
|
+
parent.type +
|
|
302
|
+
' (' +
|
|
303
|
+
(parent.filename || 'Pug') +
|
|
304
|
+
':' +
|
|
305
|
+
parent.line +
|
|
306
|
+
')';
|
|
307
|
+
} else {
|
|
308
|
+
msg = 'A top-level node';
|
|
309
|
+
}
|
|
310
|
+
msg += ' is ' + node + ', expected a Pug AST Node.';
|
|
311
|
+
throw new TypeError(msg);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (debug && node.debug !== false && node.type !== 'Block') {
|
|
315
|
+
if (node.line) {
|
|
316
|
+
var js = ';pug_debug_line = ' + node.line;
|
|
317
|
+
if (node.filename)
|
|
318
|
+
js += ';pug_debug_filename = ' + stringify(node.filename);
|
|
319
|
+
this.buf.push(js + ';');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!this['visit' + node.type]) {
|
|
324
|
+
var msg;
|
|
325
|
+
if (parent) {
|
|
326
|
+
msg = 'A child of ' + parent.type;
|
|
327
|
+
} else {
|
|
328
|
+
msg = 'A top-level node';
|
|
329
|
+
}
|
|
330
|
+
msg +=
|
|
331
|
+
' (' +
|
|
332
|
+
(node.filename || 'Pug') +
|
|
333
|
+
':' +
|
|
334
|
+
node.line +
|
|
335
|
+
')' +
|
|
336
|
+
' is of type ' +
|
|
337
|
+
node.type +
|
|
338
|
+
',' +
|
|
339
|
+
' which is not supported by pug-code-gen.';
|
|
340
|
+
switch (node.type) {
|
|
341
|
+
case 'Filter':
|
|
342
|
+
msg += ' Filters are not supported in puglite.';
|
|
343
|
+
break;
|
|
344
|
+
case 'Extends':
|
|
345
|
+
case 'Include':
|
|
346
|
+
case 'NamedBlock':
|
|
347
|
+
case 'FileReference': // unlikely but for the sake of completeness
|
|
348
|
+
msg += ' Extends and includes are not supported in puglite.';
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
throw new TypeError(msg);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.visitNode(node);
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Visit `node`.
|
|
359
|
+
*
|
|
360
|
+
* @param {Node} node
|
|
361
|
+
* @api public
|
|
362
|
+
*/
|
|
363
|
+
|
|
364
|
+
visitNode: function(node) {
|
|
365
|
+
return this['visit' + node.type](node);
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Visit case `node`.
|
|
370
|
+
*
|
|
371
|
+
* @param {Literal} node
|
|
372
|
+
* @api public
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
visitCase: function(node) {
|
|
376
|
+
throw new Error('Case statements are not supported in puglite. Use your framework for logic.');
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Visit when `node`.
|
|
381
|
+
*
|
|
382
|
+
* @param {Literal} node
|
|
383
|
+
* @api public
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
visitWhen: function(node) {
|
|
387
|
+
throw new Error('Case/when statements are not supported in puglite. Use your framework for logic.');
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Visit literal `node`.
|
|
392
|
+
*
|
|
393
|
+
* @param {Literal} node
|
|
394
|
+
* @api public
|
|
395
|
+
*/
|
|
396
|
+
|
|
397
|
+
visitLiteral: function(node) {
|
|
398
|
+
this.buffer(node.str);
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
visitNamedBlock: function(block) {
|
|
402
|
+
return this.visitBlock(block);
|
|
403
|
+
},
|
|
404
|
+
/**
|
|
405
|
+
* Visit all nodes in `block`.
|
|
406
|
+
*
|
|
407
|
+
* @param {Block} block
|
|
408
|
+
* @api public
|
|
409
|
+
*/
|
|
410
|
+
|
|
411
|
+
visitBlock: function(block) {
|
|
412
|
+
var escapePrettyMode = this.escapePrettyMode;
|
|
413
|
+
var pp = this.pp;
|
|
414
|
+
|
|
415
|
+
// Pretty print multi-line text
|
|
416
|
+
if (
|
|
417
|
+
pp &&
|
|
418
|
+
block.nodes.length > 1 &&
|
|
419
|
+
!escapePrettyMode &&
|
|
420
|
+
block.nodes[0].type === 'Text' &&
|
|
421
|
+
block.nodes[1].type === 'Text'
|
|
422
|
+
) {
|
|
423
|
+
this.prettyIndent(1, true);
|
|
424
|
+
}
|
|
425
|
+
for (var i = 0; i < block.nodes.length; ++i) {
|
|
426
|
+
// Pretty print text
|
|
427
|
+
if (
|
|
428
|
+
pp &&
|
|
429
|
+
i > 0 &&
|
|
430
|
+
!escapePrettyMode &&
|
|
431
|
+
block.nodes[i].type === 'Text' &&
|
|
432
|
+
block.nodes[i - 1].type === 'Text' &&
|
|
433
|
+
/\n$/.test(block.nodes[i - 1].val)
|
|
434
|
+
) {
|
|
435
|
+
this.prettyIndent(1, false);
|
|
436
|
+
}
|
|
437
|
+
this.visit(block.nodes[i], block);
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Visit a mixin's `block` keyword.
|
|
443
|
+
*
|
|
444
|
+
* @param {MixinBlock} block
|
|
445
|
+
* @api public
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
visitMixinBlock: function(block) {
|
|
449
|
+
throw new Error('Mixins are not supported in puglite. Please remove mixin usage.');
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Visit `doctype`. Sets terse mode to `true` when html 5
|
|
454
|
+
* is used, causing self-closing tags to end with ">" vs "/>",
|
|
455
|
+
* and boolean attributes are not mirrored.
|
|
456
|
+
*
|
|
457
|
+
* @param {Doctype} doctype
|
|
458
|
+
* @api public
|
|
459
|
+
*/
|
|
460
|
+
|
|
461
|
+
visitDoctype: function(doctype) {
|
|
462
|
+
if (doctype && (doctype.val || !this.doctype)) {
|
|
463
|
+
this.setDoctype(doctype.val || 'html');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (this.doctype) this.buffer(this.doctype);
|
|
467
|
+
this.hasCompiledDoctype = true;
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Visit `mixin`, generating a function that
|
|
472
|
+
* may be called within the template.
|
|
473
|
+
*
|
|
474
|
+
* @param {Mixin} mixin
|
|
475
|
+
* @api public
|
|
476
|
+
*/
|
|
477
|
+
|
|
478
|
+
visitMixin: function(mixin) {
|
|
479
|
+
throw new Error('Mixins are not supported in puglite. Please remove mixin usage.');
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Visit `tag` buffering tag markup, generating
|
|
484
|
+
* attributes, visiting the `tag`'s code and block.
|
|
485
|
+
*
|
|
486
|
+
* @param {Tag} tag
|
|
487
|
+
* @param {boolean} interpolated
|
|
488
|
+
* @api public
|
|
489
|
+
*/
|
|
490
|
+
|
|
491
|
+
visitTag: function(tag, interpolated) {
|
|
492
|
+
this.indents++;
|
|
493
|
+
var name = tag.name,
|
|
494
|
+
pp = this.pp,
|
|
495
|
+
self = this;
|
|
496
|
+
|
|
497
|
+
function bufferName() {
|
|
498
|
+
if (interpolated) self.bufferExpression(tag.expr);
|
|
499
|
+
else self.buffer(name);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true)
|
|
503
|
+
this.escapePrettyMode = true;
|
|
504
|
+
|
|
505
|
+
if (!this.hasCompiledTag) {
|
|
506
|
+
if (!this.hasCompiledDoctype && 'html' == name) {
|
|
507
|
+
this.visitDoctype();
|
|
508
|
+
}
|
|
509
|
+
this.hasCompiledTag = true;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// pretty print
|
|
513
|
+
if (pp && !tag.isInline) this.prettyIndent(0, true);
|
|
514
|
+
if (tag.selfClosing || (!this.xml && selfClosing[tag.name])) {
|
|
515
|
+
this.buffer('<');
|
|
516
|
+
bufferName();
|
|
517
|
+
this.visitAttributes(
|
|
518
|
+
tag.attrs,
|
|
519
|
+
this.attributeBlocks(tag.attributeBlocks)
|
|
520
|
+
);
|
|
521
|
+
if (this.terse && !tag.selfClosing) {
|
|
522
|
+
this.buffer('>');
|
|
523
|
+
} else {
|
|
524
|
+
this.buffer('/>');
|
|
525
|
+
}
|
|
526
|
+
// if it is non-empty throw an error
|
|
527
|
+
if (
|
|
528
|
+
tag.code ||
|
|
529
|
+
(tag.block &&
|
|
530
|
+
!(tag.block.type === 'Block' && tag.block.nodes.length === 0) &&
|
|
531
|
+
tag.block.nodes.some(function(tag) {
|
|
532
|
+
return tag.type !== 'Text' || !/^\s*$/.test(tag.val);
|
|
533
|
+
}))
|
|
534
|
+
) {
|
|
535
|
+
this.error(
|
|
536
|
+
name +
|
|
537
|
+
' is a self closing element: <' +
|
|
538
|
+
name +
|
|
539
|
+
'/> but contains nested content.',
|
|
540
|
+
'SELF_CLOSING_CONTENT',
|
|
541
|
+
tag
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
// Optimize attributes buffering
|
|
546
|
+
this.buffer('<');
|
|
547
|
+
bufferName();
|
|
548
|
+
this.visitAttributes(
|
|
549
|
+
tag.attrs,
|
|
550
|
+
this.attributeBlocks(tag.attributeBlocks)
|
|
551
|
+
);
|
|
552
|
+
this.buffer('>');
|
|
553
|
+
if (tag.code) this.visitCode(tag.code);
|
|
554
|
+
this.visit(tag.block, tag);
|
|
555
|
+
|
|
556
|
+
// pretty print
|
|
557
|
+
if (
|
|
558
|
+
pp &&
|
|
559
|
+
!tag.isInline &&
|
|
560
|
+
WHITE_SPACE_SENSITIVE_TAGS[tag.name] !== true &&
|
|
561
|
+
!tagCanInline(tag)
|
|
562
|
+
)
|
|
563
|
+
this.prettyIndent(0, true);
|
|
564
|
+
|
|
565
|
+
this.buffer('</');
|
|
566
|
+
bufferName();
|
|
567
|
+
this.buffer('>');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (WHITE_SPACE_SENSITIVE_TAGS[tag.name] === true)
|
|
571
|
+
this.escapePrettyMode = false;
|
|
572
|
+
|
|
573
|
+
this.indents--;
|
|
574
|
+
},
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Visit InterpolatedTag.
|
|
578
|
+
*
|
|
579
|
+
* @param {InterpolatedTag} tag
|
|
580
|
+
* @api public
|
|
581
|
+
*/
|
|
582
|
+
|
|
583
|
+
visitInterpolatedTag: function(tag) {
|
|
584
|
+
throw new Error('Interpolation (#{var}) is not supported in puglite. Use your framework for data binding.');
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Visit `text` node.
|
|
589
|
+
*
|
|
590
|
+
* @param {Text} text
|
|
591
|
+
* @api public
|
|
592
|
+
*/
|
|
593
|
+
|
|
594
|
+
visitText: function(text) {
|
|
595
|
+
this.buffer(text.val);
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Visit a `comment`, only buffering when the buffer flag is set.
|
|
600
|
+
*
|
|
601
|
+
* @param {Comment} comment
|
|
602
|
+
* @api public
|
|
603
|
+
*/
|
|
604
|
+
|
|
605
|
+
visitComment: function(comment) {
|
|
606
|
+
if (!comment.buffer) return;
|
|
607
|
+
if (this.pp) this.prettyIndent(1, true);
|
|
608
|
+
this.buffer('<!--' + comment.val + '-->');
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Visit a `YieldBlock`.
|
|
613
|
+
*
|
|
614
|
+
* This is necessary since we allow compiling a file with `yield`.
|
|
615
|
+
*
|
|
616
|
+
* @param {YieldBlock} block
|
|
617
|
+
* @api public
|
|
618
|
+
*/
|
|
619
|
+
|
|
620
|
+
visitYieldBlock: function(block) {},
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Visit a `BlockComment`.
|
|
624
|
+
*
|
|
625
|
+
* @param {Comment} comment
|
|
626
|
+
* @api public
|
|
627
|
+
*/
|
|
628
|
+
|
|
629
|
+
visitBlockComment: function(comment) {
|
|
630
|
+
if (!comment.buffer) return;
|
|
631
|
+
if (this.pp) this.prettyIndent(1, true);
|
|
632
|
+
this.buffer('<!--' + (comment.val || ''));
|
|
633
|
+
this.visit(comment.block, comment);
|
|
634
|
+
if (this.pp) this.prettyIndent(1, true);
|
|
635
|
+
this.buffer('-->');
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Visit `code`, respecting buffer / escape flags.
|
|
640
|
+
* If the code is followed by a block, wrap it in
|
|
641
|
+
* a self-calling function.
|
|
642
|
+
*
|
|
643
|
+
* @param {Code} code
|
|
644
|
+
* @api public
|
|
645
|
+
*/
|
|
646
|
+
|
|
647
|
+
visitCode: function(code) {
|
|
648
|
+
throw new Error('Code blocks (= and -) are not supported in puglite. Use your framework for logic and data binding.');
|
|
649
|
+
},
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Visit `Conditional`.
|
|
653
|
+
*
|
|
654
|
+
* @param {Conditional} cond
|
|
655
|
+
* @api public
|
|
656
|
+
*/
|
|
657
|
+
|
|
658
|
+
visitConditional: function(cond) {
|
|
659
|
+
throw new Error('Conditionals (if/else/unless) are not supported in puglite. Use your framework for logic.');
|
|
660
|
+
},
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Visit `While`.
|
|
664
|
+
*
|
|
665
|
+
* @param {While} loop
|
|
666
|
+
* @api public
|
|
667
|
+
*/
|
|
668
|
+
|
|
669
|
+
visitWhile: function(loop) {
|
|
670
|
+
throw new Error('While loops are not supported in puglite. Use your framework for logic.');
|
|
671
|
+
},
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Visit `each` block.
|
|
675
|
+
*
|
|
676
|
+
* @param {Each} each
|
|
677
|
+
* @api public
|
|
678
|
+
*/
|
|
679
|
+
|
|
680
|
+
visitEach: function(each) {
|
|
681
|
+
throw new Error('Each loops are not supported in puglite. Use your framework for iteration.');
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
visitEachOf: function(each) {
|
|
685
|
+
throw new Error('Each loops are not supported in puglite. Use your framework for iteration.');
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Visit `attrs`.
|
|
690
|
+
*
|
|
691
|
+
* @param {Array} attrs
|
|
692
|
+
* @api public
|
|
693
|
+
*/
|
|
694
|
+
|
|
695
|
+
visitAttributes: function(attrs, attributeBlocks) {
|
|
696
|
+
if (attributeBlocks.length) {
|
|
697
|
+
if (attrs.length) {
|
|
698
|
+
var val = this.attrs(attrs);
|
|
699
|
+
attributeBlocks.unshift(val);
|
|
700
|
+
}
|
|
701
|
+
if (attributeBlocks.length > 1) {
|
|
702
|
+
this.bufferExpression(
|
|
703
|
+
this.runtime('attrs') +
|
|
704
|
+
'(' +
|
|
705
|
+
this.runtime('merge') +
|
|
706
|
+
'([' +
|
|
707
|
+
attributeBlocks.join(',') +
|
|
708
|
+
']), ' +
|
|
709
|
+
stringify(this.terse) +
|
|
710
|
+
')'
|
|
711
|
+
);
|
|
712
|
+
} else {
|
|
713
|
+
this.bufferExpression(
|
|
714
|
+
this.runtime('attrs') +
|
|
715
|
+
'(' +
|
|
716
|
+
attributeBlocks[0] +
|
|
717
|
+
', ' +
|
|
718
|
+
stringify(this.terse) +
|
|
719
|
+
')'
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
} else if (attrs.length) {
|
|
723
|
+
this.attrs(attrs, true);
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Compile attributes.
|
|
729
|
+
*/
|
|
730
|
+
|
|
731
|
+
attrs: function(attrs, buffer) {
|
|
732
|
+
var res = compileAttrs(attrs, {
|
|
733
|
+
terse: this.terse,
|
|
734
|
+
format: buffer ? 'html' : 'object',
|
|
735
|
+
runtime: this.runtime.bind(this),
|
|
736
|
+
});
|
|
737
|
+
if (buffer) {
|
|
738
|
+
this.bufferExpression(res);
|
|
739
|
+
}
|
|
740
|
+
return res;
|
|
741
|
+
},
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Compile attribute blocks.
|
|
745
|
+
*/
|
|
746
|
+
|
|
747
|
+
attributeBlocks: function(attributeBlocks) {
|
|
748
|
+
return (
|
|
749
|
+
attributeBlocks &&
|
|
750
|
+
attributeBlocks.slice().map(function(attrBlock) {
|
|
751
|
+
return attrBlock.val;
|
|
752
|
+
})
|
|
753
|
+
);
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
function tagCanInline(tag) {
|
|
758
|
+
function isInline(node) {
|
|
759
|
+
// Recurse if the node is a block
|
|
760
|
+
if (node.type === 'Block') return node.nodes.every(isInline);
|
|
761
|
+
// When there is a YieldBlock here, it is an indication that the file is
|
|
762
|
+
// expected to be included but is not. If this is the case, the block
|
|
763
|
+
// must be empty.
|
|
764
|
+
if (node.type === 'YieldBlock') return true;
|
|
765
|
+
return (node.type === 'Text' && !/\n/.test(node.val)) || node.isInline;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return tag.block.nodes.every(isInline);
|
|
769
|
+
}
|