jade-js-source 0.19.0 → 0.27.6

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.
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
+ })();