jade-js-source 0.19.0 → 0.27.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/jade_js/jade.js +722 -194
  2. data/lib/jade_js/runtime.js +64 -8
  3. metadata +3 -3
@@ -1,3 +1,4 @@
1
+ (function() {
1
2
 
2
3
  // CommonJS require()
3
4
 
@@ -29,7 +30,7 @@ require.register = function (path, fn){
29
30
 
30
31
  require.relative = function (parent) {
31
32
  return function(p){
32
- if ('.' != p[0]) return require(p);
33
+ if ('.' != p.charAt(0)) return require(p);
33
34
 
34
35
  var path = parent.split('/')
35
36
  , segs = p.split('/');
@@ -62,10 +63,10 @@ var nodes = require('./nodes')
62
63
  , filters = require('./filters')
63
64
  , doctypes = require('./doctypes')
64
65
  , selfClosing = require('./self-closing')
65
- , inlineTags = require('./inline-tags')
66
+ , runtime = require('./runtime')
66
67
  , utils = require('./utils');
67
68
 
68
-
69
+
69
70
  if (!Object.keys) {
70
71
  Object.keys = function(obj){
71
72
  var arr = [];
@@ -75,9 +76,9 @@ var nodes = require('./nodes')
75
76
  }
76
77
  }
77
78
  return arr;
78
- }
79
+ }
79
80
  }
80
-
81
+
81
82
  if (!String.prototype.trimLeft) {
82
83
  String.prototype.trimLeft = function(){
83
84
  return this.replace(/^\s+/, '');
@@ -102,6 +103,7 @@ var Compiler = module.exports = function Compiler(node, options) {
102
103
  this.pp = options.pretty || false;
103
104
  this.debug = false !== options.compileDebug;
104
105
  this.indents = 0;
106
+ this.parentIndents = 0;
105
107
  if (options.doctype) this.setDoctype(options.doctype);
106
108
  };
107
109
 
@@ -110,16 +112,17 @@ var Compiler = module.exports = function Compiler(node, options) {
110
112
  */
111
113
 
112
114
  Compiler.prototype = {
113
-
115
+
114
116
  /**
115
117
  * Compile parse tree to JavaScript.
116
118
  *
117
119
  * @api public
118
120
  */
119
-
121
+
120
122
  compile: function(){
121
123
  this.buf = ['var interp;'];
122
- this.lastBufferedIdx = -1
124
+ if (this.pp) this.buf.push("var __indent = [];");
125
+ this.lastBufferedIdx = -1;
123
126
  this.visit(this.node);
124
127
  return this.buf.join('\n');
125
128
  },
@@ -132,15 +135,14 @@ Compiler.prototype = {
132
135
  * @param {string} name
133
136
  * @api public
134
137
  */
135
-
138
+
136
139
  setDoctype: function(name){
137
- var doctype = doctypes[(name || 'default').toLowerCase()];
138
- doctype = doctype || '<!DOCTYPE ' + name + '>';
139
- this.doctype = doctype;
140
- this.terse = '5' == name || 'html' == name;
140
+ name = (name && name.toLowerCase()) || 'default';
141
+ this.doctype = doctypes[name] || '<!DOCTYPE ' + name + '>';
142
+ this.terse = this.doctype.toLowerCase() == '<!doctype html>';
141
143
  this.xml = 0 == this.doctype.indexOf('<?xml');
142
144
  },
143
-
145
+
144
146
  /**
145
147
  * Buffer the given `str` optionally escaped.
146
148
  *
@@ -148,10 +150,10 @@ Compiler.prototype = {
148
150
  * @param {Boolean} esc
149
151
  * @api public
150
152
  */
151
-
153
+
152
154
  buffer: function(str, esc){
153
155
  if (esc) str = utils.escape(str);
154
-
156
+
155
157
  if (this.lastBufferedIdx == this.buf.length) {
156
158
  this.lastBuffered += str;
157
159
  this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
@@ -159,24 +161,41 @@ Compiler.prototype = {
159
161
  this.buf.push("buf.push('" + str + "');");
160
162
  this.lastBuffered = str;
161
163
  this.lastBufferedIdx = this.buf.length;
162
- }
164
+ }
163
165
  },
164
-
166
+
167
+ /**
168
+ * Buffer an indent based on the current `indent`
169
+ * property and an additional `offset`.
170
+ *
171
+ * @param {Number} offset
172
+ * @param {Boolean} newline
173
+ * @api public
174
+ */
175
+
176
+ prettyIndent: function(offset, newline){
177
+ offset = offset || 0;
178
+ newline = newline ? '\\n' : '';
179
+ this.buffer(newline + Array(this.indents + offset).join(' '));
180
+ if (this.parentIndents)
181
+ this.buf.push("buf.push.apply(buf, __indent);");
182
+ },
183
+
165
184
  /**
166
185
  * Visit `node`.
167
186
  *
168
187
  * @param {Node} node
169
188
  * @api public
170
189
  */
171
-
190
+
172
191
  visit: function(node){
173
192
  var debug = this.debug;
174
193
 
175
194
  if (debug) {
176
- this.buf.push('__.unshift({ lineno: ' + node.line
195
+ this.buf.push('__jade.unshift({ lineno: ' + node.line
177
196
  + ', filename: ' + (node.filename
178
- ? '"' + node.filename + '"'
179
- : '__[0].filename')
197
+ ? JSON.stringify(node.filename)
198
+ : '__jade[0].filename')
180
199
  + ' });');
181
200
  }
182
201
 
@@ -189,16 +208,16 @@ Compiler.prototype = {
189
208
 
190
209
  this.visitNode(node);
191
210
 
192
- if (debug) this.buf.push('__.shift();');
211
+ if (debug) this.buf.push('__jade.shift();');
193
212
  },
194
-
213
+
195
214
  /**
196
215
  * Visit `node`.
197
216
  *
198
217
  * @param {Node} node
199
218
  * @api public
200
219
  */
201
-
220
+
202
221
  visitNode: function(node){
203
222
  var name = node.constructor.name
204
223
  || node.constructor.toString().match(/function ([^(\s]+)()/)[1];
@@ -220,7 +239,7 @@ Compiler.prototype = {
220
239
  this.buf.push('}');
221
240
  this.withinCase = _;
222
241
  },
223
-
242
+
224
243
  /**
225
244
  * Visit when `node`.
226
245
  *
@@ -258,12 +277,34 @@ Compiler.prototype = {
258
277
  */
259
278
 
260
279
  visitBlock: function(block){
261
- var len = block.nodes.length;
280
+ var len = block.nodes.length
281
+ , escape = this.escape
282
+ , pp = this.pp
283
+
284
+ // Block keyword has a special meaning in mixins
285
+ if (this.parentIndents && block.mode) {
286
+ if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
287
+ this.buf.push('block && block();');
288
+ if (pp) this.buf.push("__indent.pop();")
289
+ return;
290
+ }
291
+
292
+ // Pretty print multi-line text
293
+ if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
294
+ this.prettyIndent(1, true);
295
+
262
296
  for (var i = 0; i < len; ++i) {
297
+ // Pretty print text
298
+ if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
299
+ this.prettyIndent(1, false);
300
+
263
301
  this.visit(block.nodes[i]);
302
+ // Multiple text nodes are separated by newlines
303
+ if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
304
+ this.buffer('\\n');
264
305
  }
265
306
  },
266
-
307
+
267
308
  /**
268
309
  * Visit `doctype`. Sets terse mode to `true` when html 5
269
310
  * is used, causing self-closing tags to end with ">" vs "/>",
@@ -272,7 +313,7 @@ Compiler.prototype = {
272
313
  * @param {Doctype} doctype
273
314
  * @api public
274
315
  */
275
-
316
+
276
317
  visitDoctype: function(doctype){
277
318
  if (doctype && (doctype.val || !this.doctype)) {
278
319
  this.setDoctype(doctype.val || 'default');
@@ -292,14 +333,62 @@ Compiler.prototype = {
292
333
 
293
334
  visitMixin: function(mixin){
294
335
  var name = mixin.name.replace(/-/g, '_') + '_mixin'
295
- , args = mixin.args || '';
336
+ , args = mixin.args || ''
337
+ , block = mixin.block
338
+ , attrs = mixin.attrs
339
+ , pp = this.pp;
340
+
341
+ if (mixin.call) {
342
+ if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
343
+ if (block || attrs.length) {
344
+
345
+ this.buf.push(name + '.call({');
346
+
347
+ if (block) {
348
+ this.buf.push('block: function(){');
349
+
350
+ // Render block with no indents, dynamically added when rendered
351
+ this.parentIndents++;
352
+ var _indents = this.indents;
353
+ this.indents = 0;
354
+ this.visit(mixin.block);
355
+ this.indents = _indents;
356
+ this.parentIndents--;
357
+
358
+ if (attrs.length) {
359
+ this.buf.push('},');
360
+ } else {
361
+ this.buf.push('}');
362
+ }
363
+ }
296
364
 
297
- if (mixin.block) {
298
- this.buf.push('var ' + name + ' = function(' + args + '){');
299
- this.visit(mixin.block);
300
- this.buf.push('}');
365
+ if (attrs.length) {
366
+ var val = this.attrs(attrs);
367
+ if (val.inherits) {
368
+ this.buf.push('attributes: merge({' + val.buf
369
+ + '}, attributes), escaped: merge(' + val.escaped + ', escaped, true)');
370
+ } else {
371
+ this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped);
372
+ }
373
+ }
374
+
375
+ if (args) {
376
+ this.buf.push('}, ' + args + ');');
377
+ } else {
378
+ this.buf.push('});');
379
+ }
380
+
381
+ } else {
382
+ this.buf.push(name + '(' + args + ');');
383
+ }
384
+ if (pp) this.buf.push("__indent.pop();")
301
385
  } else {
302
- this.buf.push(name + '(' + args + ');');
386
+ this.buf.push('var ' + name + ' = function(' + args + '){');
387
+ this.buf.push('var block = this.block, attributes = this.attributes || {}, escaped = this.escaped || {};');
388
+ this.parentIndents++;
389
+ this.visit(block);
390
+ this.parentIndents--;
391
+ this.buf.push('};');
303
392
  }
304
393
  },
305
394
 
@@ -310,10 +399,13 @@ Compiler.prototype = {
310
399
  * @param {Tag} tag
311
400
  * @api public
312
401
  */
313
-
402
+
314
403
  visitTag: function(tag){
315
404
  this.indents++;
316
- var name = tag.name;
405
+ var name = tag.name
406
+ , pp = this.pp;
407
+
408
+ if (tag.buffer) name = "' + (" + name + ") + '";
317
409
 
318
410
  if (!this.hasCompiledTag) {
319
411
  if (!this.hasCompiledDoctype && 'html' == name) {
@@ -323,11 +415,10 @@ Compiler.prototype = {
323
415
  }
324
416
 
325
417
  // pretty print
326
- if (this.pp && inlineTags.indexOf(name) == -1) {
327
- this.buffer('\\n' + Array(this.indents).join(' '));
328
- }
418
+ if (pp && !tag.isInline())
419
+ this.prettyIndent(0, true);
329
420
 
330
- if (~selfClosing.indexOf(name) && !this.xml) {
421
+ if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) {
331
422
  this.buffer('<' + name);
332
423
  this.visitAttributes(tag.attrs);
333
424
  this.terse
@@ -343,27 +434,25 @@ Compiler.prototype = {
343
434
  this.buffer('<' + name + '>');
344
435
  }
345
436
  if (tag.code) this.visitCode(tag.code);
346
- if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft()));
347
437
  this.escape = 'pre' == tag.name;
348
438
  this.visit(tag.block);
349
439
 
350
440
  // pretty print
351
- if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) {
352
- this.buffer('\\n' + Array(this.indents).join(' '));
353
- }
441
+ if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
442
+ this.prettyIndent(0, true);
354
443
 
355
444
  this.buffer('</' + name + '>');
356
445
  }
357
446
  this.indents--;
358
447
  },
359
-
448
+
360
449
  /**
361
450
  * Visit `filter`, throwing when the filter does not exist.
362
451
  *
363
452
  * @param {Filter} filter
364
453
  * @api public
365
454
  */
366
-
455
+
367
456
  visitFilter: function(filter){
368
457
  var fn = filters[filter.name];
369
458
 
@@ -375,48 +464,50 @@ Compiler.prototype = {
375
464
  throw new Error('unknown filter ":' + filter.name + '"');
376
465
  }
377
466
  }
467
+
378
468
  if (filter.isASTFilter) {
379
469
  this.buf.push(fn(filter.block, this, filter.attrs));
380
470
  } else {
381
- var text = filter.block.nodes.join('');
471
+ var text = filter.block.nodes.map(function(node){ return node.val }).join('\n');
472
+ filter.attrs = filter.attrs || {};
473
+ filter.attrs.filename = this.options.filename;
382
474
  this.buffer(utils.text(fn(text, filter.attrs)));
383
475
  }
384
476
  },
385
-
477
+
386
478
  /**
387
479
  * Visit `text` node.
388
480
  *
389
481
  * @param {Text} text
390
482
  * @api public
391
483
  */
392
-
484
+
393
485
  visitText: function(text){
394
- text = utils.text(text.nodes.join(''));
486
+ text = utils.text(text.val.replace(/\\/g, '\\\\'));
395
487
  if (this.escape) text = escape(text);
396
488
  this.buffer(text);
397
- this.buffer('\\n');
398
489
  },
399
-
490
+
400
491
  /**
401
492
  * Visit a `comment`, only buffering when the buffer flag is set.
402
493
  *
403
494
  * @param {Comment} comment
404
495
  * @api public
405
496
  */
406
-
497
+
407
498
  visitComment: function(comment){
408
499
  if (!comment.buffer) return;
409
- if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' '));
500
+ if (this.pp) this.prettyIndent(1, true);
410
501
  this.buffer('<!--' + utils.escape(comment.val) + '-->');
411
502
  },
412
-
503
+
413
504
  /**
414
505
  * Visit a `BlockComment`.
415
506
  *
416
507
  * @param {Comment} comment
417
508
  * @api public
418
509
  */
419
-
510
+
420
511
  visitBlockComment: function(comment){
421
512
  if (!comment.buffer) return;
422
513
  if (0 == comment.val.trim().indexOf('if')) {
@@ -429,7 +520,7 @@ Compiler.prototype = {
429
520
  this.buffer('-->');
430
521
  }
431
522
  },
432
-
523
+
433
524
  /**
434
525
  * Visit `code`, respecting buffer / escape flags.
435
526
  * If the code is followed by a block, wrap it in
@@ -438,7 +529,7 @@ Compiler.prototype = {
438
529
  * @param {Code} code
439
530
  * @api public
440
531
  */
441
-
532
+
442
533
  visitCode: function(code){
443
534
  // Wrap code blocks with {}.
444
535
  // we only wrap unbuffered code blocks ATM
@@ -462,26 +553,39 @@ Compiler.prototype = {
462
553
  if (!code.buffer) this.buf.push('}');
463
554
  }
464
555
  },
465
-
556
+
466
557
  /**
467
558
  * Visit `each` block.
468
559
  *
469
560
  * @param {Each} each
470
561
  * @api public
471
562
  */
472
-
563
+
473
564
  visitEach: function(each){
474
565
  this.buf.push(''
475
566
  + '// iterate ' + each.obj + '\n'
476
- + '(function(){\n'
477
- + ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
567
+ + ';(function(){\n'
568
+ + ' if (\'number\' == typeof ' + each.obj + '.length) {\n');
569
+
570
+ if (each.alternative) {
571
+ this.buf.push(' if (' + each.obj + '.length) {');
572
+ }
573
+
574
+ this.buf.push(''
478
575
  + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
479
576
  + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
480
577
 
481
578
  this.visit(each.block);
482
579
 
580
+ this.buf.push(' }\n');
581
+
582
+ if (each.alternative) {
583
+ this.buf.push(' } else {');
584
+ this.visit(each.alternative);
585
+ this.buf.push(' }');
586
+ }
587
+
483
588
  this.buf.push(''
484
- + ' }\n'
485
589
  + ' } else {\n'
486
590
  + ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
487
591
  + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){'
@@ -493,21 +597,43 @@ Compiler.prototype = {
493
597
 
494
598
  this.buf.push(' }\n }\n}).call(this);\n');
495
599
  },
496
-
600
+
497
601
  /**
498
602
  * Visit `attrs`.
499
603
  *
500
604
  * @param {Array} attrs
501
605
  * @api public
502
606
  */
503
-
607
+
504
608
  visitAttributes: function(attrs){
609
+ var val = this.attrs(attrs);
610
+ if (val.inherits) {
611
+ this.buf.push("buf.push(attrs(merge({ " + val.buf +
612
+ " }, attributes), merge(" + val.escaped + ", escaped, true)));");
613
+ } else if (val.constant) {
614
+ eval('var buf={' + val.buf + '};');
615
+ this.buffer(runtime.attrs(buf, JSON.parse(val.escaped)), true);
616
+ } else {
617
+ this.buf.push("buf.push(attrs({ " + val.buf + " }, " + val.escaped + "));");
618
+ }
619
+ },
620
+
621
+ /**
622
+ * Compile attributes.
623
+ */
624
+
625
+ attrs: function(attrs){
505
626
  var buf = []
506
- , classes = [];
627
+ , classes = []
628
+ , escaped = {}
629
+ , constant = attrs.every(function(attr){ return isConstant(attr.val) })
630
+ , inherits = false;
507
631
 
508
632
  if (this.terse) buf.push('terse: true');
509
633
 
510
634
  attrs.forEach(function(attr){
635
+ if (attr.name == 'attributes') return inherits = true;
636
+ escaped[attr.name] = attr.escaped;
511
637
  if (attr.name == 'class') {
512
638
  classes.push('(' + attr.val + ')');
513
639
  } else {
@@ -521,12 +647,40 @@ Compiler.prototype = {
521
647
  buf.push("class: " + classes);
522
648
  }
523
649
 
524
- buf = buf.join(', ').replace('class:', '"class":');
525
-
526
- this.buf.push("buf.push(attrs({ " + buf + " }));");
650
+ return {
651
+ buf: buf.join(', ').replace('class:', '"class":'),
652
+ escaped: JSON.stringify(escaped),
653
+ inherits: inherits,
654
+ constant: constant
655
+ };
527
656
  }
528
657
  };
529
658
 
659
+ /**
660
+ * Check if expression can be evaluated to a constant
661
+ *
662
+ * @param {String} expression
663
+ * @return {Boolean}
664
+ * @api private
665
+ */
666
+
667
+ function isConstant(val){
668
+ // Check strings/literals
669
+ if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val))
670
+ return true;
671
+
672
+ // Check numbers
673
+ if (!isNaN(Number(val)))
674
+ return true;
675
+
676
+ // Check arrays
677
+ var matches;
678
+ if (matches = /^ *\[(.*)\] *$/.exec(val))
679
+ return matches[1].split(',').every(isConstant);
680
+
681
+ return false;
682
+ }
683
+
530
684
  /**
531
685
  * Escape the given string of `html`.
532
686
  *
@@ -555,8 +709,8 @@ require.register("doctypes.js", function(module, exports, require){
555
709
 
556
710
  module.exports = {
557
711
  '5': '<!DOCTYPE html>'
712
+ , 'default': '<!DOCTYPE html>'
558
713
  , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
559
- , 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
560
714
  , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
561
715
  , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
562
716
  , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
@@ -640,7 +794,12 @@ module.exports = {
640
794
  try {
641
795
  md = require('markdown-js');
642
796
  } catch (err) {
643
- throw new Error('Cannot find markdown library, install markdown or discount');
797
+ try {
798
+ md = require('marked');
799
+ } catch (err) {
800
+ throw new
801
+ Error('Cannot find markdown library, install markdown, discount, or marked.');
802
+ }
644
803
  }
645
804
  }
646
805
  }
@@ -655,7 +814,7 @@ module.exports = {
655
814
 
656
815
  coffeescript: function(str){
657
816
  str = str.replace(/\\n/g, '\n');
658
- var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
817
+ var js = require('coffee-script').compile(str).replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
659
818
  return '<script type="text/javascript">\\n' + js + '</script>';
660
819
  }
661
820
  };
@@ -694,7 +853,6 @@ module.exports = [
694
853
  }); // module: inline-tags.js
695
854
 
696
855
  require.register("jade.js", function(module, exports, require){
697
-
698
856
  /*!
699
857
  * Jade
700
858
  * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -714,7 +872,7 @@ var Parser = require('./parser')
714
872
  * Library version.
715
873
  */
716
874
 
717
- exports.version = '0.19.0';
875
+ exports.version = '0.27.6';
718
876
 
719
877
  /**
720
878
  * Expose self closing tags.
@@ -811,11 +969,25 @@ function parse(str, options){
811
969
  }
812
970
  }
813
971
 
972
+ /**
973
+ * Strip any UTF-8 BOM off of the start of `str`, if it exists.
974
+ *
975
+ * @param {String} str
976
+ * @return {String}
977
+ * @api private
978
+ */
979
+
980
+ function stripBOM(str){
981
+ return 0xFEFF == str.charCodeAt(0)
982
+ ? str.substring(1)
983
+ : str;
984
+ }
985
+
814
986
  /**
815
987
  * Compile a `Function` representation of the given jade `str`.
816
988
  *
817
989
  * Options:
818
- *
990
+ *
819
991
  * - `compileDebug` when `false` debugging code is stripped from the compiled template
820
992
  * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
821
993
  * for use with the Jade client-side runtime.js
@@ -834,29 +1006,31 @@ exports.compile = function(str, options){
834
1006
  : 'undefined'
835
1007
  , fn;
836
1008
 
1009
+ str = stripBOM(String(str));
1010
+
837
1011
  if (options.compileDebug !== false) {
838
1012
  fn = [
839
- 'var __ = [{ lineno: 1, filename: ' + filename + ' }];'
1013
+ 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];'
840
1014
  , 'try {'
841
- , parse(String(str), options || {})
1015
+ , parse(str, options)
842
1016
  , '} catch (err) {'
843
- , ' rethrow(err, __[0].filename, __[0].lineno);'
1017
+ , ' rethrow(err, __jade[0].filename, __jade[0].lineno);'
844
1018
  , '}'
845
1019
  ].join('\n');
846
1020
  } else {
847
- fn = parse(String(str), options || {});
1021
+ fn = parse(str, options);
848
1022
  }
849
1023
 
850
1024
  if (client) {
851
- fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn;
1025
+ fn = 'attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;\n' + fn;
852
1026
  }
853
1027
 
854
- fn = new Function('locals, attrs, escape, rethrow', fn);
1028
+ fn = new Function('locals, attrs, escape, rethrow, merge', fn);
855
1029
 
856
1030
  if (client) return fn;
857
1031
 
858
1032
  return function(locals){
859
- return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow);
1033
+ return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow, runtime.merge);
860
1034
  };
861
1035
  };
862
1036
 
@@ -929,16 +1103,18 @@ exports.renderFile = function(path, options, fn){
929
1103
  */
930
1104
 
931
1105
  exports.__express = exports.renderFile;
1106
+
932
1107
  }); // module: jade.js
933
1108
 
934
1109
  require.register("lexer.js", function(module, exports, require){
935
-
936
1110
  /*!
937
1111
  * Jade - Lexer
938
1112
  * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
939
1113
  * MIT Licensed
940
1114
  */
941
1115
 
1116
+ var utils = require('./utils');
1117
+
942
1118
  /**
943
1119
  * Initialize `Lexer` with the given `str`.
944
1120
  *
@@ -1055,9 +1231,9 @@ Lexer.prototype = {
1055
1231
  , nend = 0
1056
1232
  , pos = 0;
1057
1233
  for (var i = 0, len = str.length; i < len; ++i) {
1058
- if (start == str[i]) {
1234
+ if (start == str.charAt(i)) {
1059
1235
  ++nstart;
1060
- } else if (end == str[i]) {
1236
+ } else if (end == str.charAt(i)) {
1061
1237
  if (++nend == nstart) {
1062
1238
  pos = i;
1063
1239
  break;
@@ -1099,6 +1275,19 @@ Lexer.prototype = {
1099
1275
  }
1100
1276
  },
1101
1277
 
1278
+ /**
1279
+ * Blank line.
1280
+ */
1281
+
1282
+ blank: function() {
1283
+ var captures;
1284
+ if (captures = /^\n *\n/.exec(this.input)) {
1285
+ this.consume(captures[0].length - 1);
1286
+ if (this.pipeless) return this.tok('text', '');
1287
+ return this.next();
1288
+ }
1289
+ },
1290
+
1102
1291
  /**
1103
1292
  * Comment.
1104
1293
  */
@@ -1112,14 +1301,26 @@ Lexer.prototype = {
1112
1301
  return tok;
1113
1302
  }
1114
1303
  },
1115
-
1304
+
1305
+ /**
1306
+ * Interpolated tag.
1307
+ */
1308
+
1309
+ interpolation: function() {
1310
+ var captures;
1311
+ if (captures = /^#\{(.*?)\}/.exec(this.input)) {
1312
+ this.consume(captures[0].length);
1313
+ return this.tok('interpolation', captures[1]);
1314
+ }
1315
+ },
1316
+
1116
1317
  /**
1117
1318
  * Tag.
1118
1319
  */
1119
1320
 
1120
1321
  tag: function() {
1121
1322
  var captures;
1122
- if (captures = /^(\w[-:\w]*)/.exec(this.input)) {
1323
+ if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
1123
1324
  this.consume(captures[0].length);
1124
1325
  var tok, name = captures[1];
1125
1326
  if (':' == name[name.length - 1]) {
@@ -1130,6 +1331,7 @@ Lexer.prototype = {
1130
1331
  } else {
1131
1332
  tok = this.tok('tag', name);
1132
1333
  }
1334
+ tok.selfClosing = !! captures[2];
1133
1335
  return tok;
1134
1336
  }
1135
1337
  },
@@ -1171,15 +1373,15 @@ Lexer.prototype = {
1171
1373
  */
1172
1374
 
1173
1375
  text: function() {
1174
- return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
1376
+ return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text');
1175
1377
  },
1176
1378
 
1177
1379
  /**
1178
1380
  * Extends.
1179
1381
  */
1180
1382
 
1181
- extends: function() {
1182
- return this.scan(/^extends +([^\n]+)/, 'extends');
1383
+ "extends": function() {
1384
+ return this.scan(/^extends? +([^\n]+)/, 'extends');
1183
1385
  },
1184
1386
 
1185
1387
  /**
@@ -1220,16 +1422,25 @@ Lexer.prototype = {
1220
1422
 
1221
1423
  block: function() {
1222
1424
  var captures;
1223
- if (captures = /^block +(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) {
1425
+ if (captures = /^block\b *(?:(prepend|append) +)?([^\n]*)/.exec(this.input)) {
1224
1426
  this.consume(captures[0].length);
1225
1427
  var mode = captures[1] || 'replace'
1226
1428
  , name = captures[2]
1227
1429
  , tok = this.tok('block', name);
1430
+
1228
1431
  tok.mode = mode;
1229
1432
  return tok;
1230
1433
  }
1231
1434
  },
1232
1435
 
1436
+ /**
1437
+ * Yield.
1438
+ */
1439
+
1440
+ yield: function() {
1441
+ return this.scan(/^yield */, 'yield');
1442
+ },
1443
+
1233
1444
  /**
1234
1445
  * Include.
1235
1446
  */
@@ -1242,7 +1453,7 @@ Lexer.prototype = {
1242
1453
  * Case.
1243
1454
  */
1244
1455
 
1245
- case: function() {
1456
+ "case": function() {
1246
1457
  return this.scan(/^case +([^\n]+)/, 'case');
1247
1458
  },
1248
1459
 
@@ -1258,7 +1469,7 @@ Lexer.prototype = {
1258
1469
  * Default.
1259
1470
  */
1260
1471
 
1261
- default: function() {
1472
+ "default": function() {
1262
1473
  return this.scan(/^default */, 'default');
1263
1474
  },
1264
1475
 
@@ -1276,13 +1487,35 @@ Lexer.prototype = {
1276
1487
  }
1277
1488
  },
1278
1489
 
1490
+ /**
1491
+ * Call mixin.
1492
+ */
1493
+
1494
+ call: function(){
1495
+ var captures;
1496
+ if (captures = /^\+([-\w]+)/.exec(this.input)) {
1497
+ this.consume(captures[0].length);
1498
+ var tok = this.tok('call', captures[1]);
1499
+
1500
+ // Check for args (not attributes)
1501
+ if (captures = /^ *\((.*?)\)/.exec(this.input)) {
1502
+ if (!/^ *[-\w]+ *=/.test(captures[1])) {
1503
+ this.consume(captures[0].length);
1504
+ tok.args = captures[1];
1505
+ }
1506
+ }
1507
+
1508
+ return tok;
1509
+ }
1510
+ },
1511
+
1279
1512
  /**
1280
1513
  * Mixin.
1281
1514
  */
1282
1515
 
1283
1516
  mixin: function(){
1284
1517
  var captures;
1285
- if (captures = /^mixin +([-\w]+)(?:\((.*)\))?/.exec(this.input)) {
1518
+ if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) {
1286
1519
  this.consume(captures[0].length);
1287
1520
  var tok = this.tok('mixin', captures[1]);
1288
1521
  tok.args = captures[2];
@@ -1316,7 +1549,7 @@ Lexer.prototype = {
1316
1549
  * While.
1317
1550
  */
1318
1551
 
1319
- while: function() {
1552
+ "while": function() {
1320
1553
  var captures;
1321
1554
  if (captures = /^while +([^\n]+)/.exec(this.input)) {
1322
1555
  this.consume(captures[0].length);
@@ -1350,8 +1583,8 @@ Lexer.prototype = {
1350
1583
  var flags = captures[1];
1351
1584
  captures[1] = captures[2];
1352
1585
  var tok = this.tok('code', captures[1]);
1353
- tok.escape = flags[0] === '=';
1354
- tok.buffer = flags[0] === '=' || flags[1] === '=';
1586
+ tok.escape = flags.charAt(0) === '=';
1587
+ tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '=';
1355
1588
  return tok;
1356
1589
  }
1357
1590
  },
@@ -1361,30 +1594,35 @@ Lexer.prototype = {
1361
1594
  */
1362
1595
 
1363
1596
  attrs: function() {
1364
- if ('(' == this.input[0]) {
1597
+ if ('(' == this.input.charAt(0)) {
1365
1598
  var index = this.indexOfDelimiters('(', ')')
1366
1599
  , str = this.input.substr(1, index-1)
1367
1600
  , tok = this.tok('attrs')
1368
1601
  , len = str.length
1369
1602
  , colons = this.colons
1370
1603
  , states = ['key']
1604
+ , escapedAttr
1371
1605
  , key = ''
1372
1606
  , val = ''
1373
1607
  , quote
1374
- , c;
1608
+ , c
1609
+ , p;
1375
1610
 
1376
1611
  function state(){
1377
1612
  return states[states.length - 1];
1378
1613
  }
1379
1614
 
1380
1615
  function interpolate(attr) {
1381
- return attr.replace(/#\{([^}]+)\}/g, function(_, expr){
1382
- return quote + " + (" + expr + ") + " + quote;
1616
+ return attr.replace(/(\\)?#\{([^}]+)\}/g, function(_, escape, expr){
1617
+ return escape
1618
+ ? _
1619
+ : quote + " + (" + expr + ") + " + quote;
1383
1620
  });
1384
1621
  }
1385
1622
 
1386
1623
  this.consume(index + 1);
1387
1624
  tok.attrs = {};
1625
+ tok.escaped = {};
1388
1626
 
1389
1627
  function parse(c) {
1390
1628
  var real = c;
@@ -1405,7 +1643,9 @@ Lexer.prototype = {
1405
1643
  val = val.trim();
1406
1644
  key = key.trim();
1407
1645
  if ('' == key) return;
1408
- tok.attrs[key.replace(/^['"]|['"]$/g, '')] = '' == val
1646
+ key = key.replace(/^['"]|['"]$/g, '').replace('!', '');
1647
+ tok.escaped[key] = escapedAttr;
1648
+ tok.attrs[key] = '' == val
1409
1649
  ? true
1410
1650
  : interpolate(val);
1411
1651
  key = val = '';
@@ -1424,6 +1664,7 @@ Lexer.prototype = {
1424
1664
  val += real;
1425
1665
  break;
1426
1666
  default:
1667
+ escapedAttr = '!' != p;
1427
1668
  states.push('val');
1428
1669
  }
1429
1670
  break;
@@ -1484,14 +1725,20 @@ Lexer.prototype = {
1484
1725
  val += c;
1485
1726
  }
1486
1727
  }
1728
+ p = c;
1487
1729
  }
1488
1730
 
1489
1731
  for (var i = 0; i < len; ++i) {
1490
- parse(str[i]);
1732
+ parse(str.charAt(i));
1491
1733
  }
1492
1734
 
1493
1735
  parse(',');
1494
1736
 
1737
+ if ('/' == this.input.charAt(0)) {
1738
+ this.consume(1);
1739
+ tok.selfClosing = true;
1740
+ }
1741
+
1495
1742
  return tok;
1496
1743
  }
1497
1744
  },
@@ -1602,21 +1849,25 @@ Lexer.prototype = {
1602
1849
 
1603
1850
  next: function() {
1604
1851
  return this.deferred()
1852
+ || this.blank()
1605
1853
  || this.eos()
1606
1854
  || this.pipelessText()
1855
+ || this.yield()
1607
1856
  || this.doctype()
1608
- || this.case()
1857
+ || this.interpolation()
1858
+ || this["case"]()
1609
1859
  || this.when()
1610
- || this.default()
1611
- || this.extends()
1860
+ || this["default"]()
1861
+ || this["extends"]()
1612
1862
  || this.append()
1613
1863
  || this.prepend()
1614
1864
  || this.block()
1615
1865
  || this.include()
1616
1866
  || this.mixin()
1867
+ || this.call()
1617
1868
  || this.conditional()
1618
1869
  || this.each()
1619
- || this.while()
1870
+ || this["while"]()
1620
1871
  || this.assignment()
1621
1872
  || this.tag()
1622
1873
  || this.filter()
@@ -1633,6 +1884,89 @@ Lexer.prototype = {
1633
1884
 
1634
1885
  }); // module: lexer.js
1635
1886
 
1887
+ require.register("nodes/attrs.js", function(module, exports, require){
1888
+
1889
+ /*!
1890
+ * Jade - nodes - Attrs
1891
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1892
+ * MIT Licensed
1893
+ */
1894
+
1895
+ /**
1896
+ * Module dependencies.
1897
+ */
1898
+
1899
+ var Node = require('./node'),
1900
+ Block = require('./block');
1901
+
1902
+ /**
1903
+ * Initialize a `Attrs` node.
1904
+ *
1905
+ * @api public
1906
+ */
1907
+
1908
+ var Attrs = module.exports = function Attrs() {
1909
+ this.attrs = [];
1910
+ };
1911
+
1912
+ /**
1913
+ * Inherit from `Node`.
1914
+ */
1915
+
1916
+ Attrs.prototype = new Node;
1917
+ Attrs.prototype.constructor = Attrs;
1918
+
1919
+
1920
+ /**
1921
+ * Set attribute `name` to `val`, keep in mind these become
1922
+ * part of a raw js object literal, so to quote a value you must
1923
+ * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
1924
+ *
1925
+ * @param {String} name
1926
+ * @param {String} val
1927
+ * @param {Boolean} escaped
1928
+ * @return {Tag} for chaining
1929
+ * @api public
1930
+ */
1931
+
1932
+ Attrs.prototype.setAttribute = function(name, val, escaped){
1933
+ this.attrs.push({ name: name, val: val, escaped: escaped });
1934
+ return this;
1935
+ };
1936
+
1937
+ /**
1938
+ * Remove attribute `name` when present.
1939
+ *
1940
+ * @param {String} name
1941
+ * @api public
1942
+ */
1943
+
1944
+ Attrs.prototype.removeAttribute = function(name){
1945
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
1946
+ if (this.attrs[i] && this.attrs[i].name == name) {
1947
+ delete this.attrs[i];
1948
+ }
1949
+ }
1950
+ };
1951
+
1952
+ /**
1953
+ * Get attribute value by `name`.
1954
+ *
1955
+ * @param {String} name
1956
+ * @return {String}
1957
+ * @api public
1958
+ */
1959
+
1960
+ Attrs.prototype.getAttribute = function(name){
1961
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
1962
+ if (this.attrs[i] && this.attrs[i].name == name) {
1963
+ return this.attrs[i].val;
1964
+ }
1965
+ }
1966
+ };
1967
+
1968
+ }); // module: nodes/attrs.js
1969
+
1636
1970
  require.register("nodes/block-comment.js", function(module, exports, require){
1637
1971
 
1638
1972
  /*!
@@ -1705,6 +2039,12 @@ Block.prototype = new Node;
1705
2039
  Block.prototype.constructor = Block;
1706
2040
 
1707
2041
 
2042
+ /**
2043
+ * Block flag.
2044
+ */
2045
+
2046
+ Block.prototype.isBlock = true;
2047
+
1708
2048
  /**
1709
2049
  * Replace the nodes in `other` with the nodes
1710
2050
  * in `this` block.
@@ -1753,21 +2093,41 @@ Block.prototype.unshift = function(node){
1753
2093
  };
1754
2094
 
1755
2095
  /**
1756
- * Return the "last" block.
2096
+ * Return the "last" block, or the first `yield` node.
1757
2097
  *
1758
2098
  * @return {Block}
1759
2099
  * @api private
1760
2100
  */
1761
2101
 
1762
- Block.prototype.lastBlock = function(){
1763
- var last = this
2102
+ Block.prototype.includeBlock = function(){
2103
+ var ret = this
1764
2104
  , node;
2105
+
1765
2106
  for (var i = 0, len = this.nodes.length; i < len; ++i) {
1766
2107
  node = this.nodes[i];
1767
- if (node.nodes) last = node.lastBlock();
1768
- else if (node.block && !node.block.isEmpty()) last = node.block.lastBlock();
2108
+ if (node.yield) return node;
2109
+ else if (node.textOnly) continue;
2110
+ else if (node.includeBlock) ret = node.includeBlock();
2111
+ else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock();
2112
+ if (ret.yield) return ret;
2113
+ }
2114
+
2115
+ return ret;
2116
+ };
2117
+
2118
+ /**
2119
+ * Return a clone of this block.
2120
+ *
2121
+ * @return {Block}
2122
+ * @api private
2123
+ */
2124
+
2125
+ Block.prototype.clone = function(){
2126
+ var clone = new Block;
2127
+ for (var i = 0, len = this.nodes.length; i < len; ++i) {
2128
+ clone.push(this.nodes[i].clone());
1769
2129
  }
1770
- return last;
2130
+ return clone;
1771
2131
  };
1772
2132
 
1773
2133
 
@@ -2003,7 +2363,7 @@ var Filter = module.exports = function Filter(name, block, attrs) {
2003
2363
  this.name = name;
2004
2364
  this.block = block;
2005
2365
  this.attrs = attrs;
2006
- this.isASTFilter = block instanceof Block;
2366
+ this.isASTFilter = !block.nodes.every(function(node){ return node.isText });
2007
2367
  };
2008
2368
 
2009
2369
  /**
@@ -2062,7 +2422,8 @@ var Node = require('./node');
2062
2422
 
2063
2423
  var Literal = module.exports = function Literal(str) {
2064
2424
  this.str = str
2065
- .replace(/\n/g, "\\n")
2425
+ .replace(/\\/g, "\\\\")
2426
+ .replace(/\n|\r\n/g, "\\n")
2066
2427
  .replace(/'/g, "\\'");
2067
2428
  };
2068
2429
 
@@ -2088,7 +2449,7 @@ require.register("nodes/mixin.js", function(module, exports, require){
2088
2449
  * Module dependencies.
2089
2450
  */
2090
2451
 
2091
- var Node = require('./node');
2452
+ var Attrs = require('./attrs');
2092
2453
 
2093
2454
  /**
2094
2455
  * Initialize a new `Mixin` with `name` and `block`.
@@ -2099,17 +2460,19 @@ var Node = require('./node');
2099
2460
  * @api public
2100
2461
  */
2101
2462
 
2102
- var Mixin = module.exports = function Mixin(name, args, block){
2463
+ var Mixin = module.exports = function Mixin(name, args, block, call){
2103
2464
  this.name = name;
2104
2465
  this.args = args;
2105
2466
  this.block = block;
2467
+ this.attrs = [];
2468
+ this.call = call;
2106
2469
  };
2107
2470
 
2108
2471
  /**
2109
- * Inherit from `Node`.
2472
+ * Inherit from `Attrs`.
2110
2473
  */
2111
2474
 
2112
- Mixin.prototype = new Node;
2475
+ Mixin.prototype = new Attrs;
2113
2476
  Mixin.prototype.constructor = Mixin;
2114
2477
 
2115
2478
 
@@ -2131,6 +2494,18 @@ require.register("nodes/node.js", function(module, exports, require){
2131
2494
  */
2132
2495
 
2133
2496
  var Node = module.exports = function Node(){};
2497
+
2498
+ /**
2499
+ * Clone this node (return itself)
2500
+ *
2501
+ * @return {Node}
2502
+ * @api private
2503
+ */
2504
+
2505
+ Node.prototype.clone = function(){
2506
+ return this;
2507
+ };
2508
+
2134
2509
  }); // module: nodes/node.js
2135
2510
 
2136
2511
  require.register("nodes/tag.js", function(module, exports, require){
@@ -2145,8 +2520,9 @@ require.register("nodes/tag.js", function(module, exports, require){
2145
2520
  * Module dependencies.
2146
2521
  */
2147
2522
 
2148
- var Node = require('./node'),
2149
- Block = require('./block');
2523
+ var Attrs = require('./attrs'),
2524
+ Block = require('./block'),
2525
+ inlineTags = require('../inline-tags');
2150
2526
 
2151
2527
  /**
2152
2528
  * Initialize a `Tag` node with the given tag `name` and optional `block`.
@@ -2163,60 +2539,73 @@ var Tag = module.exports = function Tag(name, block) {
2163
2539
  };
2164
2540
 
2165
2541
  /**
2166
- * Inherit from `Node`.
2542
+ * Inherit from `Attrs`.
2167
2543
  */
2168
2544
 
2169
- Tag.prototype = new Node;
2545
+ Tag.prototype = new Attrs;
2170
2546
  Tag.prototype.constructor = Tag;
2171
2547
 
2172
2548
 
2173
2549
  /**
2174
- * Set attribute `name` to `val`, keep in mind these become
2175
- * part of a raw js object literal, so to quote a value you must
2176
- * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
2550
+ * Clone this tag.
2177
2551
  *
2178
- * @param {String} name
2179
- * @param {String} val
2180
- * @return {Tag} for chaining
2181
- * @api public
2552
+ * @return {Tag}
2553
+ * @api private
2182
2554
  */
2183
2555
 
2184
- Tag.prototype.setAttribute = function(name, val){
2185
- this.attrs.push({ name: name, val: val });
2186
- return this;
2556
+ Tag.prototype.clone = function(){
2557
+ var clone = new Tag(this.name, this.block.clone());
2558
+ clone.line = this.line;
2559
+ clone.attrs = this.attrs;
2560
+ clone.textOnly = this.textOnly;
2561
+ return clone;
2187
2562
  };
2188
2563
 
2189
2564
  /**
2190
- * Remove attribute `name` when present.
2565
+ * Check if this tag is an inline tag.
2191
2566
  *
2192
- * @param {String} name
2193
- * @api public
2567
+ * @return {Boolean}
2568
+ * @api private
2194
2569
  */
2195
2570
 
2196
- Tag.prototype.removeAttribute = function(name){
2197
- for (var i = 0, len = this.attrs.length; i < len; ++i) {
2198
- if (this.attrs[i] && this.attrs[i].name == name) {
2199
- delete this.attrs[i];
2200
- }
2201
- }
2571
+ Tag.prototype.isInline = function(){
2572
+ return ~inlineTags.indexOf(this.name);
2202
2573
  };
2203
2574
 
2204
2575
  /**
2205
- * Get attribute value by `name`.
2576
+ * Check if this tag's contents can be inlined. Used for pretty printing.
2206
2577
  *
2207
- * @param {String} name
2208
- * @return {String}
2209
- * @api public
2578
+ * @return {Boolean}
2579
+ * @api private
2210
2580
  */
2211
2581
 
2212
- Tag.prototype.getAttribute = function(name){
2213
- for (var i = 0, len = this.attrs.length; i < len; ++i) {
2214
- if (this.attrs[i] && this.attrs[i].name == name) {
2215
- return this.attrs[i].val;
2582
+ Tag.prototype.canInline = function(){
2583
+ var nodes = this.block.nodes;
2584
+
2585
+ function isInline(node){
2586
+ // Recurse if the node is a block
2587
+ if (node.isBlock) return node.nodes.every(isInline);
2588
+ return node.isText || (node.isInline && node.isInline());
2589
+ }
2590
+
2591
+ // Empty tag
2592
+ if (!nodes.length) return true;
2593
+
2594
+ // Text-only or inline-only tag
2595
+ if (1 == nodes.length) return isInline(nodes[0]);
2596
+
2597
+ // Multi-line inline-only tag
2598
+ if (this.block.nodes.every(isInline)) {
2599
+ for (var i = 1, len = nodes.length; i < len; ++i) {
2600
+ if (nodes[i-1].isText && nodes[i].isText)
2601
+ return false;
2216
2602
  }
2603
+ return true;
2217
2604
  }
2605
+
2606
+ // Mixed tag
2607
+ return false;
2218
2608
  };
2219
-
2220
2609
  }); // module: nodes/tag.js
2221
2610
 
2222
2611
  require.register("nodes/text.js", function(module, exports, require){
@@ -2241,8 +2630,8 @@ var Node = require('./node');
2241
2630
  */
2242
2631
 
2243
2632
  var Text = module.exports = function Text(line) {
2244
- this.nodes = [];
2245
- if ('string' == typeof line) this.push(line);
2633
+ this.val = '';
2634
+ if ('string' == typeof line) this.val = line;
2246
2635
  };
2247
2636
 
2248
2637
  /**
@@ -2254,17 +2643,10 @@ Text.prototype.constructor = Text;
2254
2643
 
2255
2644
 
2256
2645
  /**
2257
- * Push the given `node.`
2258
- *
2259
- * @param {Node} node
2260
- * @return {Number}
2261
- * @api public
2646
+ * Flag as text.
2262
2647
  */
2263
2648
 
2264
- Text.prototype.push = function(node){
2265
- return this.nodes.push(node);
2266
- };
2267
-
2649
+ Text.prototype.isText = true;
2268
2650
  }); // module: nodes/text.js
2269
2651
 
2270
2652
  require.register("parser.js", function(module, exports, require){
@@ -2280,7 +2662,8 @@ require.register("parser.js", function(module, exports, require){
2280
2662
  */
2281
2663
 
2282
2664
  var Lexer = require('./lexer')
2283
- , nodes = require('./nodes');
2665
+ , nodes = require('./nodes')
2666
+ , utils = require('./utils');
2284
2667
 
2285
2668
  /**
2286
2669
  * Initialize `Parser` with the given input `str` and `filename`.
@@ -2296,6 +2679,7 @@ var Parser = exports = module.exports = function Parser(str, filename, options){
2296
2679
  this.lexer = new Lexer(str, options);
2297
2680
  this.filename = filename;
2298
2681
  this.blocks = {};
2682
+ this.mixins = {};
2299
2683
  this.options = options;
2300
2684
  this.contexts = [this];
2301
2685
  };
@@ -2404,6 +2788,9 @@ Parser.prototype = {
2404
2788
  this.context(parser);
2405
2789
  var ast = parser.parse();
2406
2790
  this.context();
2791
+ // hoist mixins
2792
+ for (var name in this.mixins)
2793
+ ast.unshift(this.mixins[name]);
2407
2794
  return ast;
2408
2795
  }
2409
2796
 
@@ -2448,8 +2835,10 @@ Parser.prototype = {
2448
2835
  * | text
2449
2836
  * | each
2450
2837
  * | code
2838
+ * | yield
2451
2839
  * | id
2452
2840
  * | class
2841
+ * | interpolation
2453
2842
  */
2454
2843
 
2455
2844
  parseExpr: function(){
@@ -2482,6 +2871,15 @@ Parser.prototype = {
2482
2871
  return this.parseEach();
2483
2872
  case 'code':
2484
2873
  return this.parseCode();
2874
+ case 'call':
2875
+ return this.parseCall();
2876
+ case 'interpolation':
2877
+ return this.parseInterpolation();
2878
+ case 'yield':
2879
+ this.advance();
2880
+ var block = new nodes.Block;
2881
+ block.yield = true;
2882
+ return block;
2485
2883
  case 'id':
2486
2884
  case 'class':
2487
2885
  var tok = this.advance();
@@ -2640,6 +3038,10 @@ Parser.prototype = {
2640
3038
  , node = new nodes.Each(tok.code, tok.val, tok.key);
2641
3039
  node.line = this.line();
2642
3040
  node.block = this.block();
3041
+ if (this.peek().type == 'code' && this.peek().val == 'else') {
3042
+ this.advance();
3043
+ node.alternative = this.block();
3044
+ }
2643
3045
  return node;
2644
3046
  },
2645
3047
 
@@ -2657,7 +3059,7 @@ Parser.prototype = {
2657
3059
  if (!this.filename)
2658
3060
  throw new Error('the "filename" option is required to extend templates');
2659
3061
 
2660
- var path = name = this.expect('extends').val.trim()
3062
+ var path = this.expect('extends').val.trim()
2661
3063
  , dir = dirname(this.filename);
2662
3064
 
2663
3065
  var path = join(dir, path + '.jade')
@@ -2715,7 +3117,7 @@ Parser.prototype = {
2715
3117
  , basename = path.basename
2716
3118
  , join = path.join;
2717
3119
 
2718
- var path = name = this.expect('include').val.trim()
3120
+ var path = this.expect('include').val.trim()
2719
3121
  , dir = dirname(this.filename);
2720
3122
 
2721
3123
  if (!this.filename)
@@ -2736,6 +3138,8 @@ Parser.prototype = {
2736
3138
  var path = join(dir, path)
2737
3139
  , str = fs.readFileSync(path, 'utf8')
2738
3140
  , parser = new Parser(str, path, this.options);
3141
+ parser.blocks = utils.merge({}, this.blocks);
3142
+ parser.mixins = this.mixins;
2739
3143
 
2740
3144
  this.context(parser);
2741
3145
  var ast = parser.parse();
@@ -2743,12 +3147,27 @@ Parser.prototype = {
2743
3147
  ast.filename = path;
2744
3148
 
2745
3149
  if ('indent' == this.peek().type) {
2746
- ast.lastBlock().push(this.block());
3150
+ ast.includeBlock().push(this.block());
2747
3151
  }
2748
3152
 
2749
3153
  return ast;
2750
3154
  },
2751
3155
 
3156
+ /**
3157
+ * call ident block
3158
+ */
3159
+
3160
+ parseCall: function(){
3161
+ var tok = this.expect('call')
3162
+ , name = tok.val
3163
+ , args = tok.args
3164
+ , mixin = new nodes.Mixin(name, args, new nodes.Block, true);
3165
+
3166
+ this.tag(mixin);
3167
+ if (mixin.block.isEmpty()) mixin.block = null;
3168
+ return mixin;
3169
+ },
3170
+
2752
3171
  /**
2753
3172
  * mixin block
2754
3173
  */
@@ -2756,11 +3175,18 @@ Parser.prototype = {
2756
3175
  parseMixin: function(){
2757
3176
  var tok = this.expect('mixin')
2758
3177
  , name = tok.val
2759
- , args = tok.args;
2760
- var block = 'indent' == this.peek().type
2761
- ? this.block()
2762
- : null;
2763
- return new nodes.Mixin(name, args, block);
3178
+ , args = tok.args
3179
+ , mixin;
3180
+
3181
+ // definition
3182
+ if ('indent' == this.peek().type) {
3183
+ mixin = new nodes.Mixin(name, args, this.block(), false);
3184
+ this.mixins[name] = mixin;
3185
+ return mixin;
3186
+ // call
3187
+ } else {
3188
+ return new nodes.Mixin(name, args, null, true);
3189
+ }
2764
3190
  },
2765
3191
 
2766
3192
  /**
@@ -2768,32 +3194,31 @@ Parser.prototype = {
2768
3194
  */
2769
3195
 
2770
3196
  parseTextBlock: function(){
2771
- var text = new nodes.Text;
2772
- text.line = this.line();
3197
+ var block = new nodes.Block;
3198
+ block.line = this.line();
2773
3199
  var spaces = this.expect('indent').val;
2774
3200
  if (null == this._spaces) this._spaces = spaces;
2775
3201
  var indent = Array(spaces - this._spaces + 1).join(' ');
2776
3202
  while ('outdent' != this.peek().type) {
2777
3203
  switch (this.peek().type) {
2778
3204
  case 'newline':
2779
- text.push('\\n');
2780
3205
  this.advance();
2781
3206
  break;
2782
3207
  case 'indent':
2783
- text.push('\\n');
2784
3208
  this.parseTextBlock().nodes.forEach(function(node){
2785
- text.push(node);
3209
+ block.push(node);
2786
3210
  });
2787
- text.push('\\n');
2788
3211
  break;
2789
3212
  default:
2790
- text.push(indent + this.advance().val);
3213
+ var text = new nodes.Text(indent + this.advance().val);
3214
+ text.line = this.line();
3215
+ block.push(text);
2791
3216
  }
2792
3217
  }
2793
3218
 
2794
3219
  if (spaces == this._spaces) this._spaces = null;
2795
3220
  this.expect('outdent');
2796
- return text;
3221
+ return block;
2797
3222
  },
2798
3223
 
2799
3224
  /**
@@ -2815,6 +3240,17 @@ Parser.prototype = {
2815
3240
  return block;
2816
3241
  },
2817
3242
 
3243
+ /**
3244
+ * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
3245
+ */
3246
+
3247
+ parseInterpolation: function(){
3248
+ var tok = this.advance();
3249
+ var tag = new nodes.Tag(tok.val);
3250
+ tag.buffer = true;
3251
+ return this.tag(tag);
3252
+ },
3253
+
2818
3254
  /**
2819
3255
  * tag (attrs | class | id)* (text | code | ':')? newline* block?
2820
3256
  */
@@ -2829,9 +3265,20 @@ Parser.prototype = {
2829
3265
  }
2830
3266
  }
2831
3267
 
2832
- var name = this.advance().val
2833
- , tag = new nodes.Tag(name)
2834
- , dot;
3268
+ var tok = this.advance()
3269
+ , tag = new nodes.Tag(tok.val);
3270
+
3271
+ tag.selfClosing = tok.selfClosing;
3272
+
3273
+ return this.tag(tag);
3274
+ },
3275
+
3276
+ /**
3277
+ * Parse tag.
3278
+ */
3279
+
3280
+ tag: function(tag){
3281
+ var dot;
2835
3282
 
2836
3283
  tag.line = this.line();
2837
3284
 
@@ -2845,12 +3292,17 @@ Parser.prototype = {
2845
3292
  tag.setAttribute(tok.type, "'" + tok.val + "'");
2846
3293
  continue;
2847
3294
  case 'attrs':
2848
- var obj = this.advance().attrs
3295
+ var tok = this.advance()
3296
+ , obj = tok.attrs
3297
+ , escaped = tok.escaped
2849
3298
  , names = Object.keys(obj);
3299
+
3300
+ if (tok.selfClosing) tag.selfClosing = true;
3301
+
2850
3302
  for (var i = 0, len = names.length; i < len; ++i) {
2851
3303
  var name = names[i]
2852
3304
  , val = obj[name];
2853
- tag.setAttribute(name, val);
3305
+ tag.setAttribute(name, val, escaped[name]);
2854
3306
  }
2855
3307
  continue;
2856
3308
  default:
@@ -2867,7 +3319,7 @@ Parser.prototype = {
2867
3319
  // (text | code | ':')?
2868
3320
  switch (this.peek().type) {
2869
3321
  case 'text':
2870
- tag.text = this.parseText();
3322
+ tag.block.push(this.parseText());
2871
3323
  break;
2872
3324
  case 'code':
2873
3325
  tag.code = this.parseCode();
@@ -2875,7 +3327,7 @@ Parser.prototype = {
2875
3327
  case ':':
2876
3328
  this.advance();
2877
3329
  tag.block = new nodes.Block;
2878
- tag.block.push(this.parseTag());
3330
+ tag.block.push(this.parseExpr());
2879
3331
  break;
2880
3332
  }
2881
3333
 
@@ -2947,41 +3399,97 @@ if (!Object.keys) {
2947
3399
  }
2948
3400
  }
2949
3401
  return arr;
2950
- }
3402
+ }
3403
+ }
3404
+
3405
+ /**
3406
+ * Merge two attribute objects giving precedence
3407
+ * to values in object `b`. Classes are special-cased
3408
+ * allowing for arrays and merging/joining appropriately
3409
+ * resulting in a string.
3410
+ *
3411
+ * @param {Object} a
3412
+ * @param {Object} b
3413
+ * @return {Object} a
3414
+ * @api private
3415
+ */
3416
+
3417
+ exports.merge = function merge(a, b) {
3418
+ var ac = a['class'];
3419
+ var bc = b['class'];
3420
+
3421
+ if (ac || bc) {
3422
+ ac = ac || [];
3423
+ bc = bc || [];
3424
+ if (!Array.isArray(ac)) ac = [ac];
3425
+ if (!Array.isArray(bc)) bc = [bc];
3426
+ ac = ac.filter(nulls);
3427
+ bc = bc.filter(nulls);
3428
+ a['class'] = ac.concat(bc).join(' ');
3429
+ }
3430
+
3431
+ for (var key in b) {
3432
+ if (key != 'class') {
3433
+ a[key] = b[key];
3434
+ }
3435
+ }
3436
+
3437
+ return a;
3438
+ };
3439
+
3440
+ /**
3441
+ * Filter null `val`s.
3442
+ *
3443
+ * @param {Mixed} val
3444
+ * @return {Mixed}
3445
+ * @api private
3446
+ */
3447
+
3448
+ function nulls(val) {
3449
+ return val != null;
2951
3450
  }
2952
3451
 
2953
3452
  /**
2954
3453
  * Render the given attributes object.
2955
3454
  *
2956
3455
  * @param {Object} obj
3456
+ * @param {Object} escaped
2957
3457
  * @return {String}
2958
3458
  * @api private
2959
3459
  */
2960
3460
 
2961
- exports.attrs = function attrs(obj){
3461
+ exports.attrs = function attrs(obj, escaped){
2962
3462
  var buf = []
2963
3463
  , terse = obj.terse;
3464
+
2964
3465
  delete obj.terse;
2965
3466
  var keys = Object.keys(obj)
2966
3467
  , len = keys.length;
3468
+
2967
3469
  if (len) {
2968
3470
  buf.push('');
2969
3471
  for (var i = 0; i < len; ++i) {
2970
3472
  var key = keys[i]
2971
3473
  , val = obj[key];
3474
+
2972
3475
  if ('boolean' == typeof val || null == val) {
2973
3476
  if (val) {
2974
3477
  terse
2975
3478
  ? buf.push(key)
2976
3479
  : buf.push(key + '="' + key + '"');
2977
3480
  }
3481
+ } else if (0 == key.indexOf('data') && 'string' != typeof val) {
3482
+ buf.push(key + "='" + JSON.stringify(val) + "'");
2978
3483
  } else if ('class' == key && Array.isArray(val)) {
2979
3484
  buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
2980
- } else {
3485
+ } else if (escaped && escaped[key]) {
2981
3486
  buf.push(key + '="' + exports.escape(val) + '"');
3487
+ } else {
3488
+ buf.push(key + '="' + val + '"');
2982
3489
  }
2983
3490
  }
2984
3491
  }
3492
+
2985
3493
  return buf.join(' ');
2986
3494
  };
2987
3495
 
@@ -2995,7 +3503,7 @@ exports.attrs = function attrs(obj){
2995
3503
 
2996
3504
  exports.escape = function escape(html){
2997
3505
  return String(html)
2998
- .replace(/&(?!\w+;)/g, '&amp;')
3506
+ .replace(/&(?!(\w+|\#\d+);)/g, '&amp;')
2999
3507
  .replace(/</g, '&lt;')
3000
3508
  .replace(/>/g, '&gt;')
3001
3509
  .replace(/"/g, '&quot;');
@@ -3018,7 +3526,7 @@ exports.rethrow = function rethrow(err, filename, lineno){
3018
3526
  , str = require('fs').readFileSync(filename, 'utf8')
3019
3527
  , lines = str.split('\n')
3020
3528
  , start = Math.max(lineno - context, 0)
3021
- , end = Math.min(lines.length, lineno + context);
3529
+ , end = Math.min(lines.length, lineno + context);
3022
3530
 
3023
3531
  // Error context
3024
3532
  var context = lines.slice(start, end).map(function(line, i){
@@ -3031,7 +3539,7 @@ exports.rethrow = function rethrow(err, filename, lineno){
3031
3539
 
3032
3540
  // Alter exception message
3033
3541
  err.path = filename;
3034
- err.message = (filename || 'Jade') + ':' + lineno
3542
+ err.message = (filename || 'Jade') + ':' + lineno
3035
3543
  + '\n' + context + '\n\n' + err.message;
3036
3544
  throw err;
3037
3545
  };
@@ -3051,6 +3559,7 @@ module.exports = [
3051
3559
  , 'img'
3052
3560
  , 'link'
3053
3561
  , 'input'
3562
+ , 'source'
3054
3563
  , 'area'
3055
3564
  , 'base'
3056
3565
  , 'col'
@@ -3078,7 +3587,7 @@ require.register("utils.js", function(module, exports, require){
3078
3587
  var interpolate = exports.interpolate = function(str){
3079
3588
  return str.replace(/(\\)?([#!]){(.*?)}/g, function(str, escape, flag, code){
3080
3589
  return escape
3081
- ? str
3590
+ ? str.slice(1)
3082
3591
  : "' + "
3083
3592
  + ('!' == flag ? '' : 'escape')
3084
3593
  + "((interp = " + code.replace(/\\'/g, "'")
@@ -3109,4 +3618,23 @@ var escape = exports.escape = function(str) {
3109
3618
  exports.text = function(str){
3110
3619
  return interpolate(escape(str));
3111
3620
  };
3621
+
3622
+ /**
3623
+ * Merge `b` into `a`.
3624
+ *
3625
+ * @param {Object} a
3626
+ * @param {Object} b
3627
+ * @return {Object}
3628
+ * @api public
3629
+ */
3630
+
3631
+ exports.merge = function(a, b) {
3632
+ for (var key in b) a[key] = b[key];
3633
+ return a;
3634
+ };
3635
+
3636
+
3112
3637
  }); // module: utils.js
3638
+
3639
+ window.jade = require("jade");
3640
+ })();