├── Readme.md
├── build.js
├── index.html
├── jade-shim.js
├── jade.js
└── templates.js
/Readme.md:
--------------------------------------------------------------------------------
1 | jade browser
2 | ============
3 |
4 | this is a test project to try to build jade for the browser using brequire.
5 |
6 | running
7 | ------
8 |
9 | open index.html in a browser
10 |
11 | works in Firefox, Chrome
12 |
13 | build
14 | ------
15 |
16 |
node build.js
17 |
18 | will build jade.js
using brequire
19 |
20 | jade-shim.js contains any necessary missing components, currently it consists of:
21 |
22 | * an empty module for 'fs'
23 | * definition for Object.keys
24 |
25 |
26 | potential problems
27 | -----------
28 | * browsers that don't support __proto__
29 |
30 | denendencies
31 | ------------
32 |
33 | npm install brequire jade
34 |
35 | latest npm, brequire
36 |
--------------------------------------------------------------------------------
/build.js:
--------------------------------------------------------------------------------
1 | require("brequire")("jade")
2 | .search("./lib/**", "./index.js")
3 | .include_lib()
4 | .write("./jade.js")
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
--------------------------------------------------------------------------------
/jade-shim.js:
--------------------------------------------------------------------------------
1 | require.module('fs', function(module, exports, require) {
2 | })
3 |
4 | if(!Object.keys) {
5 | Object.keys = function(o) {
6 | var ret = []
7 | for(var i in o) ret.push(i)
8 | return ret
9 | }
10 | }
--------------------------------------------------------------------------------
/jade.js:
--------------------------------------------------------------------------------
1 | // Brequire - CommonJS support for the browser
2 | function require(p) {
3 | var path = require.resolve(p)
4 | var module = require.modules[path];
5 | if(!module) throw("couldn't find module for: " + p);
6 | if(!module.exports) {
7 | module.exports = {};
8 | module.call(module.exports, module, module.exports, require.bind(path));
9 | }
10 | return module.exports;
11 | }
12 |
13 | require.modules = {};
14 |
15 | require.resolve = function(path) {
16 | if(require.modules[path]) return path
17 |
18 | if(!path.match(/\.js$/)) {
19 | if(require.modules[path+".js"]) return path + ".js"
20 | if(require.modules[path+"/index.js"]) return path + "/index.js"
21 | if(require.modules[path+"/index"]) return path + "/index"
22 | }
23 | }
24 |
25 | require.bind = function(path) {
26 | return function(p) {
27 | if(!p.match(/^\./)) return require(p)
28 |
29 | var fullPath = path.split('/');
30 | fullPath.pop();
31 | var parts = p.split('/');
32 | for (var i=0; i < parts.length; i++) {
33 | var part = parts[i];
34 | if (part == '..') fullPath.pop();
35 | else if (part != '.') fullPath.push(part);
36 | }
37 | return require(fullPath.join('/'));
38 | };
39 | };
40 |
41 | require.module = function(path, fn) {
42 | require.modules[path] = fn;
43 | };require.module('jade/lib/compiler.js', function(module, exports, require) {
44 | // start module: jade/lib/compiler.js
45 |
46 |
47 | /*!
48 | * Jade - Compiler
49 | * Copyright(c) 2010 TJ Holowaychuk
50 | * MIT Licensed
51 | */
52 |
53 | /**
54 | * Module dependencies.
55 | */
56 |
57 | var nodes = require('./nodes')
58 | , filters = require('./filters')
59 | , doctypes = require('./doctypes')
60 | , selfClosing = require('./self-closing')
61 | , utils = require('./utils');
62 |
63 | /**
64 | * Initialize `Compiler` with the given `node`.
65 | *
66 | * @param {Node} node
67 | * @param {Object} options
68 | * @api public
69 | */
70 |
71 | var Compiler = module.exports = function Compiler(node, options) {
72 | this.options = options = options || {};
73 | this.node = node;
74 | };
75 |
76 | /**
77 | * Compiler prototype.
78 | */
79 |
80 | Compiler.prototype = {
81 |
82 | /**
83 | * Compile parse tree to JavaScript.
84 | *
85 | * @api public
86 | */
87 |
88 | compile: function(){
89 | this.buf = ['var interp;'];
90 | this.visit(this.node);
91 | return this.buf.join('\n');
92 | },
93 |
94 | /**
95 | * Buffer the given `str` optionally escaped.
96 | *
97 | * @param {String} str
98 | * @param {Boolean} esc
99 | * @api public
100 | */
101 |
102 | buffer: function(str, esc){
103 | if (esc) str = utils.escape(str);
104 | this.buf.push("buf.push('" + str + "');");
105 | },
106 |
107 | /**
108 | * Buffer the given `node`'s lineno.
109 | *
110 | * @param {Node} node
111 | * @api public
112 | */
113 |
114 | line: function(node){
115 | if (node.instrumentLineNumber === false) return;
116 | this.buf.push('__.lineno = ' + node.line + ';');
117 | },
118 |
119 | /**
120 | * Visit `node`.
121 | *
122 | * @param {Node} node
123 | * @api public
124 | */
125 |
126 | visit: function(node){
127 | this.line(node);
128 | return this.visitNode(node);
129 | },
130 |
131 | /**
132 | * Visit `node`.
133 | *
134 | * @param {Node} node
135 | * @api public
136 | */
137 |
138 | visitNode: function(node){
139 | return this['visit' + node.constructor.name](node);
140 | },
141 |
142 | /**
143 | * Visit all nodes in `block`.
144 | *
145 | * @param {Block} block
146 | * @api public
147 | */
148 |
149 | visitBlock: function(block){
150 | for (var i = 0, len = block.length; i < len; ++i) {
151 | this.visit(block[i]);
152 | }
153 | },
154 |
155 | /**
156 | * Visit `doctype`. Sets terse mode to `true` when html 5
157 | * is used, causing self-closing tags to end with ">" vs "/>",
158 | * and boolean attributes are not mirrored.
159 | *
160 | * @param {Doctype} doctype
161 | * @api public
162 | */
163 |
164 | visitDoctype: function(doctype){
165 | var name = doctype.val;
166 | if ('5' == name) this.terse = true;
167 | doctype = doctypes[name || 'default'];
168 | this.xml = 0 == doctype.indexOf('')
189 | : this.buffer('/>');
190 | } else {
191 | // Optimize attributes buffering
192 | if (tag.attrs.length) {
193 | this.buffer('<' + name);
194 | if (tag.attrs.length) this.visitAttributes(tag.attrs);
195 | this.buffer('>');
196 | } else {
197 | this.buffer('<' + name + '>');
198 | }
199 | if (tag.code) this.visitCode(tag.code);
200 | if (tag.text) this.buffer(utils.text(tag.text[0].trimLeft()));
201 | this.escape = 'pre' == tag.name;
202 | this.visit(tag.block);
203 | this.buffer('' + name + '>');
204 | }
205 | },
206 |
207 | /**
208 | * Visit `filter`, throwing when the filter does not exist.
209 | *
210 | * @param {Filter} filter
211 | * @api public
212 | */
213 |
214 | visitFilter: function(filter){
215 | var fn = filters[filter.name];
216 | if (!fn) throw new Error('unknown filter ":' + filter.name + '"');
217 | if (filter.block instanceof nodes.Block) {
218 | this.buf.push(fn(filter.block, this, filter.attrs));
219 | } else {
220 | this.buffer(fn(utils.text(filter.block.join('\\n')), filter.attrs));
221 | }
222 | },
223 |
224 | /**
225 | * Visit `text` node.
226 | *
227 | * @param {Text} text
228 | * @api public
229 | */
230 |
231 | visitText: function(text){
232 | text = utils.text(text.join('\\n'));
233 | if (this.escape) text = escape(text);
234 | this.buffer(text);
235 | this.buffer('\\n');
236 | },
237 |
238 | /**
239 | * Visit a `comment`, only buffering when the buffer flag is set.
240 | *
241 | * @param {Comment} comment
242 | * @api public
243 | */
244 |
245 | visitComment: function(comment){
246 | if (!comment.buffer) return;
247 | this.buffer('');
248 | },
249 |
250 | /**
251 | * Visit a `BlockComment`.
252 | *
253 | * @param {Comment} comment
254 | * @api public
255 | */
256 |
257 | visitBlockComment: function(comment){
258 | if (0 == comment.val.indexOf('if')) {
259 | this.buffer('');
262 | } else {
263 | this.buffer('');
266 | }
267 | },
268 |
269 | /**
270 | * Visit `code`, respecting buffer / escape flags.
271 | * If the code is followed by a block, wrap it in
272 | * a self-calling function.
273 | *
274 | * @param {Code} code
275 | * @api public
276 | */
277 |
278 | visitCode: function(code){
279 | // Wrap code blocks with {}.
280 | // we only wrap unbuffered code blocks ATM
281 | // since they are usually flow control
282 |
283 | // Buffer code
284 | if (code.buffer) {
285 | var val = code.val.trimLeft();
286 | this.buf.push('var __val__ = ' + val);
287 | val = 'null == __val__ ? "" : __val__';
288 | if (code.escape) val = 'escape(' + val + ')';
289 | this.buf.push("buf.push(" + val + ");");
290 | } else {
291 | this.buf.push(code.val);
292 | }
293 |
294 | // Block support
295 | if (code.block) {
296 | if (!code.buffer) this.buf.push('{');
297 | this.visit(code.block);
298 | if (!code.buffer) this.buf.push('}');
299 | }
300 | },
301 |
302 | /**
303 | * Visit `each` block.
304 | *
305 | * @param {Each} each
306 | * @api public
307 | */
308 |
309 | visitEach: function(each){
310 | this.buf.push(''
311 | + '// iterate ' + each.obj + '\n'
312 | + '(function(){\n'
313 | + ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
314 | + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
315 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
316 |
317 | this.visit(each.block);
318 |
319 | this.buf.push(''
320 | + ' }\n'
321 | + ' } else {\n'
322 | + ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
323 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
324 |
325 | this.visit(each.block);
326 |
327 | this.buf.push(' }\n }\n}).call(this);\n');
328 | },
329 |
330 | /**
331 | * Visit `attrs`.
332 | *
333 | * @param {Array} attrs
334 | * @api public
335 | */
336 |
337 | visitAttributes: function(attrs){
338 | var buf = []
339 | , classes = [];
340 | if (this.terse) buf.push('terse: true');
341 | attrs.forEach(function(attr){
342 | if (attr.name == 'class') {
343 | classes.push('(' + attr.val + ')');
344 | } else {
345 | var pair = "'" + attr.name + "':(" + attr.val + ')';
346 | buf.push(pair);
347 | }
348 | });
349 | if (classes.length) {
350 | classes = classes.join(" + ' ' + ");
351 | buf.push("class: " + classes);
352 | }
353 | this.buf.push("buf.push(attrs({ " + buf.join(', ') + " }));");
354 | }
355 | };
356 |
357 | /**
358 | * Escape the given string of `html`.
359 | *
360 | * @param {String} html
361 | * @return {String}
362 | * @api private
363 | */
364 |
365 | function escape(html){
366 | return String(html)
367 | .replace(/&(?!\w+;)/g, '&')
368 | .replace(//g, '>')
370 | .replace(/"/g, '"');
371 | }
372 |
373 |
374 | // end module: jade/lib/compiler.js
375 | });
376 | ;
377 |
378 | require.module('jade/lib/doctypes.js', function(module, exports, require) {
379 | // start module: jade/lib/doctypes.js
380 |
381 |
382 | /*!
383 | * Jade - doctypes
384 | * Copyright(c) 2010 TJ Holowaychuk
385 | * MIT Licensed
386 | */
387 |
388 | module.exports = {
389 | '5': ''
390 | , 'xml': ''
391 | , 'default': ''
392 | , 'transitional': ''
393 | , 'strict': ''
394 | , 'frameset': ''
395 | , '1.1': ''
396 | , 'basic': ''
397 | , 'mobile': ''
398 | };
399 |
400 | // end module: jade/lib/doctypes.js
401 | });
402 | ;
403 |
404 | require.module('jade/lib/filters.js', function(module, exports, require) {
405 | // start module: jade/lib/filters.js
406 |
407 |
408 | /*!
409 | * Jade - filters
410 | * Copyright(c) 2010 TJ Holowaychuk
411 | * MIT Licensed
412 | */
413 |
414 | module.exports = {
415 |
416 | /**
417 | * Wrap text with CDATA block.
418 | */
419 |
420 | cdata: function(str){
421 | return '';
422 | },
423 |
424 | /**
425 | * Transform sass to css, wrapped in style tags.
426 | */
427 |
428 | sass: function(str){
429 | str = str.replace(/\\n/g, '\n');
430 | var sass = require('sass').render(str).replace(/\n/g, '\\n');
431 | return '';
432 | },
433 |
434 | /**
435 | * Transform stylus to css, wrapped in style tags.
436 | */
437 |
438 | stylus: function(str){
439 | var ret;
440 | str = str.replace(/\\n/g, '\n');
441 | var stylus = require('stylus');
442 | stylus(str).render(function(err, css){
443 | if (err) throw err;
444 | ret = css.replace(/\n/g, '\\n');
445 | });
446 | return '';
447 | },
448 |
449 | /**
450 | * Transform sass to css, wrapped in style tags.
451 | */
452 |
453 | less: function(str){
454 | var ret;
455 | str = str.replace(/\\n/g, '\n');
456 | require('less').render(str, function(err, css){
457 | if (err) throw err;
458 | ret = '';
459 | });
460 | return ret;
461 | },
462 |
463 | /**
464 | * Transform markdown to html.
465 | */
466 |
467 | markdown: function(str){
468 | var md;
469 |
470 | // support markdown / discount
471 | try {
472 | md = require('markdown');
473 | } catch (err){
474 | try {
475 | md = require('discount');
476 | } catch (err) {
477 | try {
478 | md = require('markdown-js');
479 | } catch (err) {
480 | throw new Error('Cannot find markdown library, install markdown or discount');
481 | }
482 | }
483 | }
484 |
485 | str = str.replace(/\\n/g, '\n');
486 | return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,''');
487 | },
488 |
489 | /**
490 | * Transform coffeescript to javascript.
491 | */
492 |
493 | coffeescript: function(str){
494 | str = str.replace(/\\n/g, '\n');
495 | var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
496 | return '';
497 | }
498 | };
499 |
500 | // end module: jade/lib/filters.js
501 | });
502 | ;
503 |
504 | require.module('jade/lib/index.js', function(module, exports, require) {
505 | // start module: jade/lib/index.js
506 |
507 |
508 | /*!
509 | * Jade
510 | * Copyright(c) 2010 TJ Holowaychuk
511 | * MIT Licensed
512 | */
513 |
514 | /**
515 | * Module dependencies.
516 | */
517 |
518 | var Parser = require('./parser')
519 | , Compiler = require('./compiler')
520 | , fs = require('fs');
521 |
522 | /**
523 | * Library version.
524 | */
525 |
526 | exports.version = '0.8.8';
527 |
528 | /**
529 | * Intermediate JavaScript cache.
530 | *
531 | * @type Object
532 | */
533 |
534 | var cache = exports.cache = {};
535 |
536 | /**
537 | * Expose self closing tags.
538 | *
539 | * @type Object
540 | */
541 |
542 | exports.selfClosing = require('./self-closing');
543 |
544 | /**
545 | * Default supported doctypes.
546 | *
547 | * @type Object
548 | */
549 |
550 | exports.doctypes = require('./doctypes');
551 |
552 | /**
553 | * Text filters.
554 | *
555 | * @type Object
556 | */
557 |
558 | exports.filters = require('./filters');
559 |
560 | /**
561 | * Utilities.
562 | *
563 | * @type Object
564 | */
565 |
566 | exports.utils = require('./utils');
567 |
568 | /**
569 | * Compiler.
570 | *
571 | * @type Function
572 | */
573 |
574 | exports.Compiler = Compiler;
575 |
576 | /**
577 | * Nodes.
578 | *
579 | * @type Object
580 | */
581 |
582 | exports.nodes = require('./nodes');
583 |
584 | /**
585 | * Render the given attributes object.
586 | *
587 | * @param {Object} obj
588 | * @return {String}
589 | * @api private
590 | */
591 |
592 | function attrs(obj){
593 | var buf = []
594 | , terse = obj.terse;
595 | delete obj.terse;
596 | var keys = Object.keys(obj)
597 | , len = keys.length;
598 | if (len) {
599 | buf.push('');
600 | for (var i = 0; i < len; ++i) {
601 | var key = keys[i]
602 | , val = obj[key];
603 | if (typeof val === 'boolean' || val === '' || val == null) {
604 | if (val) {
605 | terse
606 | ? buf.push(key)
607 | : buf.push(key + '="' + key + '"');
608 | }
609 | } else {
610 | buf.push(key + '="' + escape(val) + '"');
611 | }
612 | }
613 | }
614 | return buf.join(' ');
615 | }
616 |
617 | /**
618 | * Escape the given string of `html`.
619 | *
620 | * @param {String} html
621 | * @return {String}
622 | * @api private
623 | */
624 |
625 | function escape(html){
626 | return String(html)
627 | .replace(/&(?!\w+;)/g, '&')
628 | .replace(//g, '>')
630 | .replace(/"/g, '"');
631 | }
632 |
633 | /**
634 | * Re-throw the given `err` in context to the
635 | * `str` of jade, `filename`, and `lineno`.
636 | *
637 | * @param {Error} err
638 | * @param {String} str
639 | * @param {String} filename
640 | * @param {String} lineno
641 | * @api private
642 | */
643 |
644 | function rethrow(err, str, filename, lineno){
645 | var start = lineno - 3 > 0
646 | ? lineno - 3
647 | : 0;
648 |
649 | // Error context
650 | var context = str.split('\n').slice(start, lineno).map(function(line, i){
651 | return ' '
652 | + (i + start + 1)
653 | + ". '"
654 | + line.replace("'", "\\'")
655 | + "'";
656 | }).join('\n');
657 |
658 | // Alter exception message
659 | err.path = filename;
660 | err.message = (filename || 'Jade') + ':' + lineno
661 | + '\n' + context + '\n\n' + err.message;
662 | throw err;
663 | }
664 |
665 | /**
666 | * Parse the given `str` of jade and return a function body.
667 | *
668 | * @param {String} str
669 | * @param {Object} options
670 | * @return {String}
671 | * @api private
672 | */
673 |
674 | function parse(str, options){
675 | var filename = options.filename;
676 | try {
677 | // Parse
678 | var parser = new Parser(str, filename);
679 | if (options.debug) parser.debug();
680 |
681 | // Compile
682 | var compiler = new (options.compiler || Compiler)(parser.parse(), options)
683 | , js = compiler.compile();
684 |
685 | // Debug compiler
686 | if (options.debug) {
687 | console.log('\n\x1b[1mCompiled Function\x1b[0m:\n\n%s', js.replace(/^/gm, ' '));
688 | }
689 |
690 | try {
691 | return ''
692 | + attrs.toString() + '\n\n'
693 | + escape.toString() + '\n\n'
694 | + 'var buf = [];\n'
695 | + 'with (locals || {}) {' + js + '}'
696 | + 'return buf.join("");';
697 | } catch (err) {
698 | process.compile(js, filename || 'Jade');
699 | return;
700 | }
701 | } catch (err) {
702 | rethrow(err, str, filename, parser.lexer.lineno);
703 | }
704 | }
705 |
706 | /**
707 | * Compile a `Function` representation of the given jade `str`.
708 | *
709 | * @param {String} str
710 | * @param {Options} options
711 | * @return {Function}
712 | * @api public
713 | */
714 |
715 | exports.compile = function(str, options){
716 | var options = options || {}
717 | , input = JSON.stringify(str)
718 | , filename = options.filename
719 | ? JSON.stringify(options.filename)
720 | : 'undefined';
721 |
722 | // Reduce closure madness by injecting some locals
723 | var fn = [
724 | 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
725 | , rethrow.toString()
726 | , 'try {'
727 | , parse(String(str), options || {})
728 | , '} catch (err) {'
729 | , ' rethrow(err, __.input, __.filename, __.lineno);'
730 | , '}'
731 | ].join('\n');
732 |
733 | return new Function('locals', fn);
734 | };
735 |
736 | /**
737 | * Render the given `str` of jade.
738 | *
739 | * Options:
740 | *
741 | * - `scope` Evaluation scope (`this`)
742 | * - `locals` Local variable object
743 | * - `filename` Used in exceptions, and required by `cache`
744 | * - `cache` Cache intermediate JavaScript in memory keyed by `filename`
745 | * - `compiler` Compiler to replade jade's default
746 | *
747 | * @param {String|Buffer} str
748 | * @param {Object} options
749 | * @return {String}
750 | * @api public
751 | */
752 |
753 | exports.render = function(str, options){
754 | var fn
755 | , options = options || {}
756 | , filename = options.filename;
757 |
758 | // Accept Buffers
759 | str = String(str);
760 |
761 | // Cache support
762 | if (options.cache) {
763 | if (filename) {
764 | if (cache[filename]) {
765 | fn = cache[filename];
766 | } else {
767 | fn = cache[filename] = new Function('locals', parse(str, options));
768 | }
769 | } else {
770 | throw new Error('filename is required when using the cache option');
771 | }
772 | } else {
773 | fn = new Function('locals', parse(str, options));
774 | }
775 |
776 | // Render the template
777 | try {
778 | var locals = options.locals || {}
779 | , meta = { lineno: 1 };
780 | locals.__ = meta;
781 | return fn.call(options.scope, locals);
782 | } catch (err) {
783 | rethrow(err, str, filename, meta.lineno);
784 | }
785 | };
786 |
787 | /**
788 | * Render jade template at the given `path`.
789 | *
790 | * @param {String} path
791 | * @param {Object} options
792 | * @param {Function} fn
793 | * @api public
794 | */
795 |
796 | exports.renderFile = function(path, options, fn){
797 | if (typeof options === 'function') {
798 | fn = options;
799 | options = {};
800 | }
801 | options.filename = path;
802 |
803 | // Primed cache
804 | if (options.cache && cache[path]) {
805 | try {
806 | fn(null, exports.render('', options));
807 | } catch (err) {
808 | fn(err);
809 | }
810 | } else {
811 | fs.readFile(path, 'utf8', function(err, str){
812 | if (err) return fn(err);
813 | try {
814 | fn(null, exports.render(str, options));
815 | } catch (err) {
816 | fn(err);
817 | }
818 | });
819 | }
820 | };
821 |
822 | // end module: jade/lib/index.js
823 | });
824 | ;
825 |
826 | require.module('jade/lib/jade.js', function(module, exports, require) {
827 | // start module: jade/lib/jade.js
828 |
829 |
830 | /*!
831 | * Jade
832 | * Copyright(c) 2010 TJ Holowaychuk
833 | * MIT Licensed
834 | */
835 |
836 | /**
837 | * Module dependencies.
838 | */
839 |
840 | var Parser = require('./parser')
841 | , Compiler = require('./compiler')
842 | , fs = require('fs');
843 |
844 | /**
845 | * Library version.
846 | */
847 |
848 | exports.version = '0.8.8';
849 |
850 | /**
851 | * Intermediate JavaScript cache.
852 | *
853 | * @type Object
854 | */
855 |
856 | var cache = exports.cache = {};
857 |
858 | /**
859 | * Expose self closing tags.
860 | *
861 | * @type Object
862 | */
863 |
864 | exports.selfClosing = require('./self-closing');
865 |
866 | /**
867 | * Default supported doctypes.
868 | *
869 | * @type Object
870 | */
871 |
872 | exports.doctypes = require('./doctypes');
873 |
874 | /**
875 | * Text filters.
876 | *
877 | * @type Object
878 | */
879 |
880 | exports.filters = require('./filters');
881 |
882 | /**
883 | * Utilities.
884 | *
885 | * @type Object
886 | */
887 |
888 | exports.utils = require('./utils');
889 |
890 | /**
891 | * Compiler.
892 | *
893 | * @type Function
894 | */
895 |
896 | exports.Compiler = Compiler;
897 |
898 | /**
899 | * Nodes.
900 | *
901 | * @type Object
902 | */
903 |
904 | exports.nodes = require('./nodes');
905 |
906 | /**
907 | * Render the given attributes object.
908 | *
909 | * @param {Object} obj
910 | * @return {String}
911 | * @api private
912 | */
913 |
914 | function attrs(obj){
915 | var buf = []
916 | , terse = obj.terse;
917 | delete obj.terse;
918 | var keys = Object.keys(obj)
919 | , len = keys.length;
920 | if (len) {
921 | buf.push('');
922 | for (var i = 0; i < len; ++i) {
923 | var key = keys[i]
924 | , val = obj[key];
925 | if (typeof val === 'boolean' || val === '' || val == null) {
926 | if (val) {
927 | terse
928 | ? buf.push(key)
929 | : buf.push(key + '="' + key + '"');
930 | }
931 | } else {
932 | buf.push(key + '="' + escape(val) + '"');
933 | }
934 | }
935 | }
936 | return buf.join(' ');
937 | }
938 |
939 | /**
940 | * Escape the given string of `html`.
941 | *
942 | * @param {String} html
943 | * @return {String}
944 | * @api private
945 | */
946 |
947 | function escape(html){
948 | return String(html)
949 | .replace(/&(?!\w+;)/g, '&')
950 | .replace(//g, '>')
952 | .replace(/"/g, '"');
953 | }
954 |
955 | /**
956 | * Re-throw the given `err` in context to the
957 | * `str` of jade, `filename`, and `lineno`.
958 | *
959 | * @param {Error} err
960 | * @param {String} str
961 | * @param {String} filename
962 | * @param {String} lineno
963 | * @api private
964 | */
965 |
966 | function rethrow(err, str, filename, lineno){
967 | var start = lineno - 3 > 0
968 | ? lineno - 3
969 | : 0;
970 |
971 | // Error context
972 | var context = str.split('\n').slice(start, lineno).map(function(line, i){
973 | return ' '
974 | + (i + start + 1)
975 | + ". '"
976 | + line.replace("'", "\\'")
977 | + "'";
978 | }).join('\n');
979 |
980 | // Alter exception message
981 | err.path = filename;
982 | err.message = (filename || 'Jade') + ':' + lineno
983 | + '\n' + context + '\n\n' + err.message;
984 | throw err;
985 | }
986 |
987 | /**
988 | * Parse the given `str` of jade and return a function body.
989 | *
990 | * @param {String} str
991 | * @param {Object} options
992 | * @return {String}
993 | * @api private
994 | */
995 |
996 | function parse(str, options){
997 | var filename = options.filename;
998 | try {
999 | // Parse
1000 | var parser = new Parser(str, filename);
1001 | if (options.debug) parser.debug();
1002 |
1003 | // Compile
1004 | var compiler = new (options.compiler || Compiler)(parser.parse(), options)
1005 | , js = compiler.compile();
1006 |
1007 | // Debug compiler
1008 | if (options.debug) {
1009 | console.log('\n\x1b[1mCompiled Function\x1b[0m:\n\n%s', js.replace(/^/gm, ' '));
1010 | }
1011 |
1012 | try {
1013 | return ''
1014 | + attrs.toString() + '\n\n'
1015 | + escape.toString() + '\n\n'
1016 | + 'var buf = [];\n'
1017 | + 'with (locals || {}) {' + js + '}'
1018 | + 'return buf.join("");';
1019 | } catch (err) {
1020 | process.compile(js, filename || 'Jade');
1021 | return;
1022 | }
1023 | } catch (err) {
1024 | rethrow(err, str, filename, parser.lexer.lineno);
1025 | }
1026 | }
1027 |
1028 | /**
1029 | * Compile a `Function` representation of the given jade `str`.
1030 | *
1031 | * @param {String} str
1032 | * @param {Options} options
1033 | * @return {Function}
1034 | * @api public
1035 | */
1036 |
1037 | exports.compile = function(str, options){
1038 | var options = options || {}
1039 | , input = JSON.stringify(str)
1040 | , filename = options.filename
1041 | ? JSON.stringify(options.filename)
1042 | : 'undefined';
1043 |
1044 | // Reduce closure madness by injecting some locals
1045 | var fn = [
1046 | 'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
1047 | , rethrow.toString()
1048 | , 'try {'
1049 | , parse(String(str), options || {})
1050 | , '} catch (err) {'
1051 | , ' rethrow(err, __.input, __.filename, __.lineno);'
1052 | , '}'
1053 | ].join('\n');
1054 |
1055 | return new Function('locals', fn);
1056 | };
1057 |
1058 | /**
1059 | * Render the given `str` of jade.
1060 | *
1061 | * Options:
1062 | *
1063 | * - `scope` Evaluation scope (`this`)
1064 | * - `locals` Local variable object
1065 | * - `filename` Used in exceptions, and required by `cache`
1066 | * - `cache` Cache intermediate JavaScript in memory keyed by `filename`
1067 | * - `compiler` Compiler to replade jade's default
1068 | *
1069 | * @param {String|Buffer} str
1070 | * @param {Object} options
1071 | * @return {String}
1072 | * @api public
1073 | */
1074 |
1075 | exports.render = function(str, options){
1076 | var fn
1077 | , options = options || {}
1078 | , filename = options.filename;
1079 |
1080 | // Accept Buffers
1081 | str = String(str);
1082 |
1083 | // Cache support
1084 | if (options.cache) {
1085 | if (filename) {
1086 | if (cache[filename]) {
1087 | fn = cache[filename];
1088 | } else {
1089 | fn = cache[filename] = new Function('locals', parse(str, options));
1090 | }
1091 | } else {
1092 | throw new Error('filename is required when using the cache option');
1093 | }
1094 | } else {
1095 | fn = new Function('locals', parse(str, options));
1096 | }
1097 |
1098 | // Render the template
1099 | try {
1100 | var locals = options.locals || {}
1101 | , meta = { lineno: 1 };
1102 | locals.__ = meta;
1103 | return fn.call(options.scope, locals);
1104 | } catch (err) {
1105 | rethrow(err, str, filename, meta.lineno);
1106 | }
1107 | };
1108 |
1109 | /**
1110 | * Render jade template at the given `path`.
1111 | *
1112 | * @param {String} path
1113 | * @param {Object} options
1114 | * @param {Function} fn
1115 | * @api public
1116 | */
1117 |
1118 | exports.renderFile = function(path, options, fn){
1119 | if (typeof options === 'function') {
1120 | fn = options;
1121 | options = {};
1122 | }
1123 | options.filename = path;
1124 |
1125 | // Primed cache
1126 | if (options.cache && cache[path]) {
1127 | try {
1128 | fn(null, exports.render('', options));
1129 | } catch (err) {
1130 | fn(err);
1131 | }
1132 | } else {
1133 | fs.readFile(path, 'utf8', function(err, str){
1134 | if (err) return fn(err);
1135 | try {
1136 | fn(null, exports.render(str, options));
1137 | } catch (err) {
1138 | fn(err);
1139 | }
1140 | });
1141 | }
1142 | };
1143 |
1144 | // end module: jade/lib/jade.js
1145 | });
1146 | ;
1147 |
1148 | require.module('jade/lib/lexer.js', function(module, exports, require) {
1149 | // start module: jade/lib/lexer.js
1150 |
1151 |
1152 | /*!
1153 | * Jade - Lexer
1154 | * Copyright(c) 2010 TJ Holowaychuk
1155 | * MIT Licensed
1156 | */
1157 |
1158 | /**
1159 | * Initialize `Lexer` with the given `str`.
1160 | *
1161 | * @param {String} str
1162 | * @api private
1163 | */
1164 |
1165 | var Lexer = module.exports = function Lexer(str) {
1166 | this.input = str.replace(/\r\n|\r/g, '\n');
1167 | this.deferredTokens = [];
1168 | this.lastIndents = 0;
1169 | this.lineno = 1;
1170 | this.stash = [];
1171 | this.indentStack = [];
1172 | this.indentRe = null;
1173 | this.textPipe = true;
1174 | };
1175 |
1176 | /**
1177 | * Lexer prototype.
1178 | */
1179 |
1180 | Lexer.prototype = {
1181 |
1182 | /**
1183 | * Construct a token with the given `type` and `val`.
1184 | *
1185 | * @param {String} type
1186 | * @param {String} val
1187 | * @return {Object}
1188 | * @api private
1189 | */
1190 |
1191 | tok: function(type, val){
1192 | return {
1193 | type: type
1194 | , line: this.lineno
1195 | , val: val
1196 | }
1197 | },
1198 |
1199 | /**
1200 | * Consume the given `len` of input.
1201 | *
1202 | * @param {Number} len
1203 | * @api private
1204 | */
1205 |
1206 | consume: function(len){
1207 | this.input = this.input.substr(len);
1208 | },
1209 |
1210 | /**
1211 | * Scan for `type` with the given `regexp`.
1212 | *
1213 | * @param {String} type
1214 | * @param {RegExp} regexp
1215 | * @return {Object}
1216 | * @api private
1217 | */
1218 |
1219 | scan: function(regexp, type){
1220 | var captures;
1221 | if (captures = regexp.exec(this.input)) {
1222 | this.consume(captures[0].length);
1223 | return this.tok(type, captures[1]);
1224 | }
1225 | },
1226 |
1227 | /**
1228 | * Defer the given `tok`.
1229 | *
1230 | * @param {Object} tok
1231 | * @api private
1232 | */
1233 |
1234 | defer: function(tok){
1235 | this.deferredTokens.push(tok);
1236 | },
1237 |
1238 | /**
1239 | * Lookahead `n` tokens.
1240 | *
1241 | * @param {Number} n
1242 | * @return {Object}
1243 | * @api private
1244 | */
1245 |
1246 | lookahead: function(n){
1247 | var fetch = n - this.stash.length;
1248 | while (fetch-- > 0) this.stash.push(this.next());
1249 | return this.stash[--n];
1250 | },
1251 |
1252 | /**
1253 | * Return the indexOf `start` / `end` delimiters.
1254 | *
1255 | * @param {String} start
1256 | * @param {String} end
1257 | * @return {Number}
1258 | * @api private
1259 | */
1260 |
1261 | indexOfDelimiters: function(start, end){
1262 | var str = this.input
1263 | , nstart = 0
1264 | , nend = 0
1265 | , pos = 0;
1266 | for (var i = 0, len = str.length; i < len; ++i) {
1267 | if (start == str[i]) {
1268 | ++nstart;
1269 | } else if (end == str[i]) {
1270 | if (++nend == nstart) {
1271 | pos = i;
1272 | break;
1273 | }
1274 | }
1275 | }
1276 | return pos;
1277 | },
1278 |
1279 | /**
1280 | * Stashed token.
1281 | */
1282 |
1283 | stashed: function() {
1284 | return this.stash.length
1285 | && this.stash.shift();
1286 | },
1287 |
1288 | /**
1289 | * Deferred token.
1290 | */
1291 |
1292 | deferred: function() {
1293 | return this.deferredTokens.length
1294 | && this.deferredTokens.shift();
1295 | },
1296 |
1297 | /**
1298 | * end-of-source.
1299 | */
1300 |
1301 | eos: function() {
1302 | if (this.input.length) return;
1303 | if (this.indentStack.length) {
1304 | this.indentStack.shift();
1305 | return this.tok('outdent');
1306 | } else {
1307 | return this.tok('eos');
1308 | }
1309 | },
1310 |
1311 | /**
1312 | * Block comment
1313 | */
1314 |
1315 | blockComment: function() {
1316 | var captures;
1317 | if (captures = /^\/([^\n]+)/.exec(this.input)) {
1318 | this.consume(captures[0].length);
1319 | var tok = this.tok('block-comment', captures[1]);
1320 | return tok;
1321 | }
1322 | },
1323 |
1324 | /**
1325 | * Comment.
1326 | */
1327 |
1328 | comment: function() {
1329 | var captures;
1330 | if (captures = /^ *\/\/(-)?([^\n]+)/.exec(this.input)) {
1331 | this.consume(captures[0].length);
1332 | var tok = this.tok('comment', captures[2]);
1333 | tok.buffer = '-' != captures[1];
1334 | return tok;
1335 | }
1336 | },
1337 |
1338 | /**
1339 | * Tag.
1340 | */
1341 |
1342 | tag: function() {
1343 | var captures;
1344 | if (captures = /^(\w[-:\w]*)/.exec(this.input)) {
1345 | this.consume(captures[0].length);
1346 | var tok, name = captures[1];
1347 | if (':' == name[name.length - 1]) {
1348 | name = name.slice(0, -1);
1349 | tok = this.tok('tag', name);
1350 | this.deferredTokens.push(this.tok(':'));
1351 | while (' ' == this.input[0]) this.input = this.input.substr(1);
1352 | } else {
1353 | tok = this.tok('tag', name);
1354 | }
1355 | return tok;
1356 | }
1357 | },
1358 |
1359 | /**
1360 | * Filter.
1361 | */
1362 |
1363 | filter: function() {
1364 | return this.scan(/^:(\w+)/, 'filter');
1365 | },
1366 |
1367 | /**
1368 | * Doctype.
1369 | */
1370 |
1371 | doctype: function() {
1372 | return this.scan(/^!!! *(\w+)?/, 'doctype');
1373 | },
1374 |
1375 | /**
1376 | * Id.
1377 | */
1378 |
1379 | id: function() {
1380 | return this.scan(/^#([\w-]+)/, 'id');
1381 | },
1382 |
1383 | /**
1384 | * Class.
1385 | */
1386 |
1387 | className: function() {
1388 | return this.scan(/^\.([\w-]+)/, 'class');
1389 | },
1390 |
1391 | /**
1392 | * Text.
1393 | */
1394 |
1395 | text: function() {
1396 | return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
1397 | },
1398 |
1399 | /**
1400 | * Each.
1401 | */
1402 |
1403 | each: function() {
1404 | var captures;
1405 | if (captures = /^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
1406 | this.consume(captures[0].length);
1407 | var tok = this.tok('each', captures[1]);
1408 | tok.key = captures[2] || 'index';
1409 | tok.code = captures[3];
1410 | return tok;
1411 | }
1412 | },
1413 |
1414 | /**
1415 | * Code.
1416 | */
1417 |
1418 | code: function() {
1419 | var captures;
1420 | if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) {
1421 | this.consume(captures[0].length);
1422 | var flags = captures[1];
1423 | captures[1] = captures[2];
1424 | var tok = this.tok('code', captures[1]);
1425 | tok.escape = flags[0] === '=';
1426 | tok.buffer = flags[0] === '=' || flags[1] === '=';
1427 | return tok;
1428 | }
1429 | },
1430 |
1431 | /**
1432 | * Attributes.
1433 | */
1434 |
1435 | attrs: function() {
1436 | if ('(' == this.input[0]) {
1437 | var index = this.indexOfDelimiters('(', ')')
1438 | , str = this.input.substr(1, index-1)
1439 | , tok = this.tok('attrs')
1440 | , len = str.length
1441 | , states = ['key']
1442 | , key = ''
1443 | , val = ''
1444 | , c;
1445 |
1446 | function state(){
1447 | return states[states.length - 1];
1448 | }
1449 |
1450 | this.consume(index + 1);
1451 | tok.attrs = {};
1452 |
1453 | function parse(c) {
1454 | switch (c) {
1455 | case ',':
1456 | case '\n':
1457 | switch (state()) {
1458 | case 'expr':
1459 | case 'array':
1460 | case 'string':
1461 | case 'object':
1462 | val += c;
1463 | break;
1464 | default:
1465 | states.push('key');
1466 | val = val.trim();
1467 | key = key.trim();
1468 | if ('' == key) return;
1469 | tok.attrs[key.replace(/^['"]|['"]$/g, '')] = '' == val
1470 | ? true
1471 | : val;
1472 | key = val = '';
1473 | }
1474 | break;
1475 | case ':':
1476 | case '=':
1477 | switch (state()) {
1478 | case 'val':
1479 | case 'expr':
1480 | case 'array':
1481 | case 'string':
1482 | case 'object':
1483 | val += c;
1484 | break;
1485 | default:
1486 | states.push('val');
1487 | }
1488 | break;
1489 | case '(':
1490 | states.push('expr');
1491 | val += c;
1492 | break;
1493 | case ')':
1494 | states.pop();
1495 | val += c;
1496 | break;
1497 | case '{':
1498 | states.push('object');
1499 | val += c;
1500 | break;
1501 | case '}':
1502 | states.pop();
1503 | val += c;
1504 | break;
1505 | case '[':
1506 | states.push('array');
1507 | val += c;
1508 | break;
1509 | case ']':
1510 | states.pop();
1511 | val += c;
1512 | break;
1513 | case '"':
1514 | case "'":
1515 | if ('key' == state()) break;
1516 | 'string' == state()
1517 | ? states.pop()
1518 | : states.push('string');
1519 | val += c;
1520 | break;
1521 | case '':
1522 | break;
1523 | default:
1524 | if ('key' == state()) {
1525 | key += c;
1526 | } else {
1527 | val += c;
1528 | }
1529 | }
1530 | }
1531 |
1532 | for (var i = 0; i < len; ++i) {
1533 | parse(str[i]);
1534 | }
1535 |
1536 | parse(',');
1537 |
1538 | return tok;
1539 | }
1540 | },
1541 |
1542 | /**
1543 | * Indent.
1544 | */
1545 |
1546 | indent: function() {
1547 | var captures, re;
1548 |
1549 | // established regexp
1550 | if (this.indentRe) {
1551 | captures = this.indentRe.exec(this.input);
1552 | // determine regexp
1553 | } else {
1554 | // tabs
1555 | re = /^\n(\t*) */;
1556 | captures = re.exec(this.input);
1557 |
1558 | // spaces
1559 | if (captures && !captures[1].length) {
1560 | re = /^\n( *)/;
1561 | captures = re.exec(this.input);
1562 | }
1563 |
1564 | // established
1565 | if (captures && captures[1].length) this.indentRe = re;
1566 | }
1567 |
1568 | if (captures) {
1569 | var tok
1570 | , indents = captures[1].length;
1571 |
1572 | ++this.lineno;
1573 | this.consume(indents + 1);
1574 |
1575 | if (' ' == this.input[0] || '\t' == this.input[0]) {
1576 | throw new Error('Invalid indentation, you can use tabs or spaces but not both');
1577 | }
1578 |
1579 | // blank line
1580 | if ('\n' == this.input[0]) return this.advance();
1581 |
1582 | // outdent
1583 | if (this.indentStack.length && indents < this.indentStack[0]) {
1584 | while (this.indentStack.length && this.indentStack[0] > indents) {
1585 | this.stash.push(this.tok('outdent'));
1586 | this.indentStack.shift();
1587 | }
1588 | tok = this.stash.pop();
1589 | // indent
1590 | } else if (indents && indents != this.indentStack[0]) {
1591 | this.indentStack.unshift(indents);
1592 | tok = this.tok('indent');
1593 | // newline
1594 | } else {
1595 | tok = this.tok('newline');
1596 | }
1597 |
1598 | return tok;
1599 | }
1600 | },
1601 |
1602 | /**
1603 | * Pipe-less text consumed only when
1604 | * textPipe is false;
1605 | */
1606 |
1607 | pipelessText: function() {
1608 | if (false === this.textPipe) {
1609 | if ('\n' == this.input[0]) return;
1610 | var i = this.input.indexOf('\n')
1611 | , str = this.input.substr(0, i);
1612 | if (-1 == i) return;
1613 | this.consume(str.length);
1614 | return this.tok('text', str);
1615 | }
1616 | },
1617 |
1618 | /**
1619 | * ':'
1620 | */
1621 |
1622 | colon: function() {
1623 | return this.scan(/^: */, ':');
1624 | },
1625 |
1626 | /**
1627 | * Return the next token object, or those
1628 | * previously stashed by lookahead.
1629 | *
1630 | * @return {Object}
1631 | * @api private
1632 | */
1633 |
1634 | advance: function(){
1635 | return this.stashed()
1636 | || this.next();
1637 | },
1638 |
1639 | /**
1640 | * Return the next token object.
1641 | *
1642 | * @return {Object}
1643 | * @api private
1644 | */
1645 |
1646 | next: function() {
1647 | return this.deferred()
1648 | || this.eos()
1649 | || this.pipelessText()
1650 | || this.tag()
1651 | || this.filter()
1652 | || this.each()
1653 | || this.code()
1654 | || this.doctype()
1655 | || this.id()
1656 | || this.className()
1657 | || this.attrs()
1658 | || this.indent()
1659 | || this.comment()
1660 | || this.blockComment()
1661 | || this.colon()
1662 | || this.text();
1663 | }
1664 | };
1665 |
1666 |
1667 | // end module: jade/lib/lexer.js
1668 | });
1669 | ;
1670 |
1671 | require.module('jade/lib/nodes/block-comment.js', function(module, exports, require) {
1672 | // start module: jade/lib/nodes/block-comment.js
1673 |
1674 |
1675 | /*!
1676 | * Jade - nodes - BlockComment
1677 | * Copyright(c) 2010 TJ Holowaychuk
1678 | * MIT Licensed
1679 | */
1680 |
1681 | /**
1682 | * Module dependencies.
1683 | */
1684 |
1685 | var Node = require('./node');
1686 |
1687 | /**
1688 | * Initialize a `BlockComment` with the given `block`.
1689 | *
1690 | * @param {String} val
1691 | * @param {Block} block
1692 | * @api public
1693 | */
1694 |
1695 | var BlockComment = module.exports = function BlockComment(val, block) {
1696 | this.block = block;
1697 | this.val = val;
1698 | };
1699 |
1700 | /**
1701 | * Inherit from `Node`.
1702 | */
1703 |
1704 | BlockComment.prototype.__proto__ = Node.prototype;
1705 |
1706 | // end module: jade/lib/nodes/block-comment.js
1707 | });
1708 | ;
1709 |
1710 | require.module('jade/lib/nodes/block.js', function(module, exports, require) {
1711 | // start module: jade/lib/nodes/block.js
1712 |
1713 |
1714 | /*!
1715 | * Jade - nodes - Block
1716 | * Copyright(c) 2010 TJ Holowaychuk
1717 | * MIT Licensed
1718 | */
1719 |
1720 | /**
1721 | * Module dependencies.
1722 | */
1723 |
1724 | var Node = require('./node');
1725 |
1726 | /**
1727 | * Initialize a new `Block` with an optional `node`.
1728 | *
1729 | * @param {Node} node
1730 | * @api public
1731 | */
1732 |
1733 | var Block = module.exports = function Block(node){
1734 | if (node) this.push(node);
1735 | };
1736 |
1737 | /**
1738 | * Inherit from `Node`.
1739 | */
1740 |
1741 | Block.prototype.__proto__ = Node.prototype;
1742 |
1743 |
1744 | // end module: jade/lib/nodes/block.js
1745 | });
1746 | ;
1747 |
1748 | require.module('jade/lib/nodes/code.js', function(module, exports, require) {
1749 | // start module: jade/lib/nodes/code.js
1750 |
1751 |
1752 | /*!
1753 | * Jade - nodes - Code
1754 | * Copyright(c) 2010 TJ Holowaychuk
1755 | * MIT Licensed
1756 | */
1757 |
1758 | /**
1759 | * Module dependencies.
1760 | */
1761 |
1762 | var Node = require('./node');
1763 |
1764 | /**
1765 | * Initialize a `Code` node with the given code `val`.
1766 | * Code may also be optionally buffered and escaped.
1767 | *
1768 | * @param {String} val
1769 | * @param {Boolean} buffer
1770 | * @param {Boolean} escape
1771 | * @api public
1772 | */
1773 |
1774 | var Code = module.exports = function Code(val, buffer, escape) {
1775 | this.val = val;
1776 | this.buffer = buffer;
1777 | this.escape = escape;
1778 | if (/^ *else/.test(val)) this.instrumentLineNumber = false;
1779 | };
1780 |
1781 | /**
1782 | * Inherit from `Node`.
1783 | */
1784 |
1785 | Code.prototype.__proto__ = Node.prototype;
1786 |
1787 | // end module: jade/lib/nodes/code.js
1788 | });
1789 | ;
1790 |
1791 | require.module('jade/lib/nodes/comment.js', function(module, exports, require) {
1792 | // start module: jade/lib/nodes/comment.js
1793 |
1794 |
1795 | /*!
1796 | * Jade - nodes - Comment
1797 | * Copyright(c) 2010 TJ Holowaychuk
1798 | * MIT Licensed
1799 | */
1800 |
1801 | /**
1802 | * Module dependencies.
1803 | */
1804 |
1805 | var Node = require('./node');
1806 |
1807 | /**
1808 | * Initialize a `Comment` with the given `val`, optionally `buffer`,
1809 | * otherwise the comment may render in the output.
1810 | *
1811 | * @param {String} val
1812 | * @param {Boolean} buffer
1813 | * @api public
1814 | */
1815 |
1816 | var Comment = module.exports = function Comment(val, buffer) {
1817 | this.val = val;
1818 | this.buffer = buffer;
1819 | };
1820 |
1821 | /**
1822 | * Inherit from `Node`.
1823 | */
1824 |
1825 | Comment.prototype.__proto__ = Node.prototype;
1826 |
1827 | // end module: jade/lib/nodes/comment.js
1828 | });
1829 | ;
1830 |
1831 | require.module('jade/lib/nodes/doctype.js', function(module, exports, require) {
1832 | // start module: jade/lib/nodes/doctype.js
1833 |
1834 |
1835 | /*!
1836 | * Jade - nodes - Doctype
1837 | * Copyright(c) 2010 TJ Holowaychuk
1838 | * MIT Licensed
1839 | */
1840 |
1841 | /**
1842 | * Module dependencies.
1843 | */
1844 |
1845 | var Node = require('./node');
1846 |
1847 | /**
1848 | * Initialize a `Doctype` with the given `val`.
1849 | *
1850 | * @param {String} val
1851 | * @api public
1852 | */
1853 |
1854 | var Doctype = module.exports = function Doctype(val) {
1855 | this.val = val;
1856 | };
1857 |
1858 | /**
1859 | * Inherit from `Node`.
1860 | */
1861 |
1862 | Doctype.prototype.__proto__ = Node.prototype;
1863 |
1864 | // end module: jade/lib/nodes/doctype.js
1865 | });
1866 | ;
1867 |
1868 | require.module('jade/lib/nodes/each.js', function(module, exports, require) {
1869 | // start module: jade/lib/nodes/each.js
1870 |
1871 |
1872 | /*!
1873 | * Jade - nodes - Each
1874 | * Copyright(c) 2010 TJ Holowaychuk
1875 | * MIT Licensed
1876 | */
1877 |
1878 | /**
1879 | * Module dependencies.
1880 | */
1881 |
1882 | var Node = require('./node');
1883 |
1884 | /**
1885 | * Initialize an `Each` node, representing iteration
1886 | *
1887 | * @param {String} obj
1888 | * @param {String} val
1889 | * @param {String} key
1890 | * @param {Block} block
1891 | * @api public
1892 | */
1893 |
1894 | var Each = module.exports = function Each(obj, val, key, block) {
1895 | this.obj = obj;
1896 | this.val = val;
1897 | this.key = key;
1898 | this.block = block;
1899 | };
1900 |
1901 | /**
1902 | * Inherit from `Node`.
1903 | */
1904 |
1905 | Each.prototype.__proto__ = Node.prototype;
1906 |
1907 | // end module: jade/lib/nodes/each.js
1908 | });
1909 | ;
1910 |
1911 | require.module('jade/lib/nodes/filter.js', function(module, exports, require) {
1912 | // start module: jade/lib/nodes/filter.js
1913 |
1914 |
1915 | /*!
1916 | * Jade - nodes - Filter
1917 | * Copyright(c) 2010 TJ Holowaychuk
1918 | * MIT Licensed
1919 | */
1920 |
1921 | /**
1922 | * Module dependencies.
1923 | */
1924 |
1925 | var Node = require('./node');
1926 |
1927 | /**
1928 | * Initialize a `Filter` node with the given
1929 | * filter `name` and `block`.
1930 | *
1931 | * @param {String} name
1932 | * @param {Block|Node} block
1933 | * @api public
1934 | */
1935 |
1936 | var Filter = module.exports = function Filter(name, block, attrs) {
1937 | this.name = name;
1938 | this.block = block;
1939 | this.attrs = attrs;
1940 | };
1941 |
1942 | /**
1943 | * Inherit from `Node`.
1944 | */
1945 |
1946 | Filter.prototype.__proto__ = Node.prototype;
1947 |
1948 | // end module: jade/lib/nodes/filter.js
1949 | });
1950 | ;
1951 |
1952 | require.module('jade/lib/nodes/index.js', function(module, exports, require) {
1953 | // start module: jade/lib/nodes/index.js
1954 |
1955 |
1956 | /*!
1957 | * Jade - nodes
1958 | * Copyright(c) 2010 TJ Holowaychuk
1959 | * MIT Licensed
1960 | */
1961 |
1962 | exports.Node = require('./node');
1963 | exports.Tag = require('./tag');
1964 | exports.Code = require('./code');
1965 | exports.Each = require('./each');
1966 | exports.Text = require('./text');
1967 | exports.Block = require('./block');
1968 | exports.Filter = require('./filter');
1969 | exports.Comment = require('./comment');
1970 | exports.BlockComment = require('./block-comment');
1971 | exports.Doctype = require('./doctype');
1972 |
1973 |
1974 | // end module: jade/lib/nodes/index.js
1975 | });
1976 | ;
1977 |
1978 | require.module('jade/lib/nodes/node.js', function(module, exports, require) {
1979 | // start module: jade/lib/nodes/node.js
1980 |
1981 |
1982 | /*!
1983 | * Jade - nodes - Node
1984 | * Copyright(c) 2010 TJ Holowaychuk
1985 | * MIT Licensed
1986 | */
1987 |
1988 | /**
1989 | * Initialize a `Node`.
1990 | *
1991 | * @api public
1992 | */
1993 |
1994 | var Node = module.exports = function Node(){};
1995 |
1996 | /**
1997 | * Inherit from `Array`.
1998 | */
1999 |
2000 | Node.prototype.__proto__ = Array.prototype;
2001 |
2002 | // end module: jade/lib/nodes/node.js
2003 | });
2004 | ;
2005 |
2006 | require.module('jade/lib/nodes/tag.js', function(module, exports, require) {
2007 | // start module: jade/lib/nodes/tag.js
2008 |
2009 |
2010 | /*!
2011 | * Jade - nodes - Tag
2012 | * Copyright(c) 2010 TJ Holowaychuk
2013 | * MIT Licensed
2014 | */
2015 |
2016 | /**
2017 | * Module dependencies.
2018 | */
2019 |
2020 | var Node = require('./node'),
2021 | Block = require('./block');
2022 |
2023 | /**
2024 | * Initialize a `Tag` node with the given tag `name` and optional `block`.
2025 | *
2026 | * @param {String} name
2027 | * @param {Block} block
2028 | * @api public
2029 | */
2030 |
2031 | var Tag = module.exports = function Tag(name, block) {
2032 | this.name = name;
2033 | this.attrs = [];
2034 | this.block = block || new Block;
2035 | };
2036 |
2037 | /**
2038 | * Set attribute `name` to `val`, keep in mind these become
2039 | * part of a raw js object literal, so to quote a value you must
2040 | * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
2041 | *
2042 | * @param {String} name
2043 | * @param {String} val
2044 | * @return {Tag} for chaining
2045 | * @api public
2046 | */
2047 |
2048 | Tag.prototype.setAttribute = function(name, val){
2049 | this.attrs.push({ name: name, val: val });
2050 | return this;
2051 | };
2052 |
2053 | /**
2054 | * Remove attribute `name` when present.
2055 | *
2056 | * @param {String} name
2057 | * @api public
2058 | */
2059 |
2060 | Tag.prototype.removeAttribute = function(name){
2061 | for (var i = 0, len = this.attrs.length; i < len; ++i) {
2062 | if (this.attrs[i] && this.attrs[i].name == name) {
2063 | delete this.attrs[i];
2064 | }
2065 | }
2066 | };
2067 |
2068 | /**
2069 | * Get attribute value by `name`.
2070 | *
2071 | * @param {String} name
2072 | * @return {String}
2073 | * @api public
2074 | */
2075 |
2076 | Tag.prototype.getAttribute = function(name){
2077 | for (var i = 0, len = this.attrs.length; i < len; ++i) {
2078 | if (this.attrs[i] && this.attrs[i].name == name) {
2079 | return this.attrs[i].val;
2080 | }
2081 | }
2082 | };
2083 |
2084 | /**
2085 | * Inherit from `Node`.
2086 | */
2087 |
2088 | Tag.prototype.__proto__ = Node.prototype;
2089 |
2090 |
2091 | // end module: jade/lib/nodes/tag.js
2092 | });
2093 | ;
2094 |
2095 | require.module('jade/lib/nodes/text.js', function(module, exports, require) {
2096 | // start module: jade/lib/nodes/text.js
2097 |
2098 |
2099 | /*!
2100 | * Jade - nodes - Text
2101 | * Copyright(c) 2010 TJ Holowaychuk
2102 | * MIT Licensed
2103 | */
2104 |
2105 | /**
2106 | * Module dependencies.
2107 | */
2108 |
2109 | var Node = require('./node');
2110 |
2111 | /**
2112 | * Initialize a `Text` node with optional `line`.
2113 | *
2114 | * @param {String} line
2115 | * @api public
2116 | */
2117 |
2118 | var Text = module.exports = function Text(line) {
2119 | if ('string' == typeof line) this.push(line);
2120 | };
2121 |
2122 | /**
2123 | * Inherit from `Node`.
2124 | */
2125 |
2126 | Text.prototype.__proto__ = Node.prototype;
2127 |
2128 | // end module: jade/lib/nodes/text.js
2129 | });
2130 | ;
2131 |
2132 | require.module('jade/lib/parser.js', function(module, exports, require) {
2133 | // start module: jade/lib/parser.js
2134 |
2135 |
2136 | /*!
2137 | * Jade - Parser
2138 | * Copyright(c) 2010 TJ Holowaychuk
2139 | * MIT Licensed
2140 | */
2141 |
2142 | /**
2143 | * Module dependencies.
2144 | */
2145 |
2146 | var Lexer = require('./lexer')
2147 | , nodes = require('./nodes');
2148 |
2149 | /**
2150 | * Initialize `Parser` with the given input `str` and `filename`.
2151 | *
2152 | * @param {String} str
2153 | * @param {String} filename
2154 | * @api public
2155 | */
2156 |
2157 | var Parser = exports = module.exports = function Parser(str, filename){
2158 | this.input = str;
2159 | this.lexer = new Lexer(str);
2160 | this.filename = filename;
2161 | };
2162 |
2163 | /**
2164 | * Tags that may not contain tags.
2165 | */
2166 |
2167 | var textOnly = exports.textOnly = ['pre', 'script', 'textarea', 'style'];
2168 |
2169 | /**
2170 | * Parser prototype.
2171 | */
2172 |
2173 | Parser.prototype = {
2174 |
2175 | /**
2176 | * Output parse tree to stdout.
2177 | *
2178 | * @api public
2179 | */
2180 |
2181 | debug: function(){
2182 | var lexer = new Lexer(this.input)
2183 | , tree = require('sys').inspect(this.parse(), false, 12, true);
2184 | console.log('\n\x1b[1mParse Tree\x1b[0m:\n');
2185 | console.log(tree);
2186 | this.lexer = lexer;
2187 | },
2188 |
2189 | /**
2190 | * Return the next token object.
2191 | *
2192 | * @return {Object}
2193 | * @api private
2194 | */
2195 |
2196 | advance: function(){
2197 | return this.lexer.advance();
2198 | },
2199 |
2200 | /**
2201 | * Single token lookahead.
2202 | *
2203 | * @return {Object}
2204 | * @api private
2205 | */
2206 |
2207 | peek: function() {
2208 | return this.lookahead(1);
2209 | },
2210 |
2211 | /**
2212 | * Return lexer lineno.
2213 | *
2214 | * @return {Number}
2215 | * @api private
2216 | */
2217 |
2218 | line: function() {
2219 | return this.lexer.lineno;
2220 | },
2221 |
2222 | /**
2223 | * `n` token lookahead.
2224 | *
2225 | * @param {Number} n
2226 | * @return {Object}
2227 | * @api private
2228 | */
2229 |
2230 | lookahead: function(n){
2231 | return this.lexer.lookahead(n);
2232 | },
2233 |
2234 | /**
2235 | * Parse input returning a string of js for evaluation.
2236 | *
2237 | * @return {String}
2238 | * @api public
2239 | */
2240 |
2241 | parse: function(){
2242 | var block = new nodes.Block;
2243 | block.line = this.line();
2244 | while (this.peek().type !== 'eos') {
2245 | if (this.peek().type === 'newline') {
2246 | this.advance();
2247 | } else {
2248 | block.push(this.parseExpr());
2249 | }
2250 | }
2251 | return block;
2252 | },
2253 |
2254 | /**
2255 | * Expect the given type, or throw an exception.
2256 | *
2257 | * @param {String} type
2258 | * @api private
2259 | */
2260 |
2261 | expect: function(type){
2262 | if (this.peek().type === type) {
2263 | return this.advance();
2264 | } else {
2265 | throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
2266 | }
2267 | },
2268 |
2269 | /**
2270 | * Accept the given `type`.
2271 | *
2272 | * @param {String} type
2273 | * @api private
2274 | */
2275 |
2276 | accept: function(type){
2277 | if (this.peek().type === type) {
2278 | return this.advance();
2279 | }
2280 | },
2281 |
2282 | /**
2283 | * tag
2284 | * | doctype
2285 | * | filter
2286 | * | comment
2287 | * | text
2288 | * | each
2289 | * | code
2290 | * | id
2291 | * | class
2292 | */
2293 |
2294 | parseExpr: function(){
2295 | switch (this.peek().type) {
2296 | case 'tag':
2297 | return this.parseTag();
2298 | case 'doctype':
2299 | return this.parseDoctype();
2300 | case 'filter':
2301 | return this.parseFilter();
2302 | case 'comment':
2303 | return this.parseComment();
2304 | case 'block-comment':
2305 | return this.parseBlockComment();
2306 | case 'text':
2307 | return this.parseText();
2308 | case 'each':
2309 | return this.parseEach();
2310 | case 'code':
2311 | return this.parseCode();
2312 | case 'id':
2313 | case 'class':
2314 | var tok = this.advance();
2315 | this.lexer.defer(this.lexer.tok('tag', 'div'));
2316 | this.lexer.defer(tok);
2317 | return this.parseExpr();
2318 | default:
2319 | throw new Error('unexpected token "' + this.peek().type + '"');
2320 | }
2321 | },
2322 |
2323 | /**
2324 | * Text
2325 | */
2326 |
2327 | parseText: function(){
2328 | var tok = this.expect('text')
2329 | , node = new nodes.Text(tok.val);
2330 | node.line = this.line();
2331 | return node;
2332 | },
2333 |
2334 | /**
2335 | * code
2336 | */
2337 |
2338 | parseCode: function(){
2339 | var tok = this.expect('code')
2340 | , node = new nodes.Code(tok.val, tok.buffer, tok.escape);
2341 | node.line = this.line();
2342 | if ('indent' == this.peek().type) {
2343 | node.block = this.parseBlock();
2344 | }
2345 | return node;
2346 | },
2347 |
2348 | /**
2349 | * block comment
2350 | */
2351 |
2352 | parseBlockComment: function(){
2353 | var tok = this.expect('block-comment')
2354 | , node = new nodes.BlockComment(tok.val, this.parseBlock());
2355 | node.line = this.line();
2356 | return node;
2357 | },
2358 |
2359 |
2360 | /**
2361 | * comment
2362 | */
2363 |
2364 | parseComment: function(){
2365 | var tok = this.expect('comment')
2366 | , node = new nodes.Comment(tok.val, tok.buffer);
2367 | node.line = this.line();
2368 | return node;
2369 | },
2370 |
2371 | /**
2372 | * doctype
2373 | */
2374 |
2375 | parseDoctype: function(){
2376 | var tok = this.expect('doctype')
2377 | , node = new nodes.Doctype(tok.val);
2378 | node.line = this.line();
2379 | return node;
2380 | },
2381 |
2382 | /**
2383 | * filter attrs? (text | block)
2384 | */
2385 |
2386 | parseFilter: function(){
2387 | var block
2388 | , tok = this.expect('filter')
2389 | , attrs = this.accept('attrs');
2390 |
2391 | if ('text' == tok.val) {
2392 | this.lexer.textPipe = false;
2393 | block = this.parseTextBlock();
2394 | this.lexer.textPipe = true;
2395 | return block;
2396 | } else if ('text' == this.lookahead(2).type) {
2397 | block = this.parseTextBlock();
2398 | } else {
2399 | block = this.parseBlock();
2400 | }
2401 |
2402 | var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
2403 | node.line = this.line();
2404 | return node;
2405 | },
2406 |
2407 | /**
2408 | * each block
2409 | */
2410 |
2411 | parseEach: function(){
2412 | var tok = this.expect('each')
2413 | , node = new nodes.Each(tok.code, tok.val, tok.key, this.parseBlock());
2414 | node.line = this.line();
2415 | return node;
2416 | },
2417 |
2418 | /**
2419 | * indent (text | newline)* outdent
2420 | */
2421 |
2422 | parseTextBlock: function(){
2423 | var text = new nodes.Text
2424 | , pipeless = false === this.lexer.textPipe;
2425 | text.line = this.line();
2426 | this.expect('indent');
2427 | while ('outdent' != this.peek().type) {
2428 | switch (this.peek().type) {
2429 | case 'newline':
2430 | if (pipeless) text.push('\\n');
2431 | this.advance();
2432 | break;
2433 | case 'indent':
2434 | text.push(this.parseTextBlock().map(function(text){
2435 | return ' ' + text;
2436 | }).join('\\n'));
2437 | break;
2438 | default:
2439 | text.push(this.advance().val);
2440 | }
2441 | }
2442 | this.expect('outdent');
2443 | return text;
2444 | },
2445 |
2446 | /**
2447 | * indent expr* outdent
2448 | */
2449 |
2450 | parseBlock: function(){
2451 | var block = new nodes.Block;
2452 | block.line = this.line();
2453 | this.expect('indent');
2454 | while ('outdent' != this.peek().type) {
2455 | if ('newline' == this.peek().type) {
2456 | this.advance();
2457 | } else {
2458 | block.push(this.parseExpr());
2459 | }
2460 | }
2461 | this.expect('outdent');
2462 | return block;
2463 | },
2464 |
2465 | /**
2466 | * tag (attrs | class | id)* (text | code | ':')? newline* block?
2467 | */
2468 |
2469 | parseTag: function(){
2470 | var name = this.advance().val
2471 | , tag = new nodes.Tag(name);
2472 |
2473 | tag.line = this.line();
2474 |
2475 | // (attrs | class | id)*
2476 | out:
2477 | while (true) {
2478 | switch (this.peek().type) {
2479 | case 'id':
2480 | case 'class':
2481 | var tok = this.advance();
2482 | tag.setAttribute(tok.type, "'" + tok.val + "'");
2483 | continue;
2484 | case 'attrs':
2485 | var obj = this.advance().attrs
2486 | , names = Object.keys(obj);
2487 | for (var i = 0, len = names.length; i < len; ++i) {
2488 | var name = names[i]
2489 | , val = obj[name];
2490 | tag.setAttribute(name, val);
2491 | }
2492 | continue;
2493 | default:
2494 | break out;
2495 | }
2496 | }
2497 |
2498 | // check immediate '.'
2499 | if ('.' == this.peek().val) {
2500 | tag.textOnly = true;
2501 | this.advance();
2502 | }
2503 |
2504 | // (text | code | ':')?
2505 | switch (this.peek().type) {
2506 | case 'text':
2507 | tag.text = this.parseText();
2508 | break;
2509 | case 'code':
2510 | tag.code = this.parseCode();
2511 | break;
2512 | case ':':
2513 | this.advance();
2514 | tag.block = new nodes.Block;
2515 | tag.block.push(this.parseTag());
2516 | break;
2517 | }
2518 |
2519 | // newline*
2520 | while (this.peek().type === 'newline') this.advance();
2521 |
2522 | // Assume newline when tag followed by text
2523 | if (this.peek().type === 'text') {
2524 | if (tag.text) tag.text.push('\\n');
2525 | else tag.text = new nodes.Text('\\n');
2526 | }
2527 |
2528 | tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name);
2529 |
2530 | // script special-case
2531 | if ('script' == tag.name) {
2532 | var type = tag.getAttribute('type');
2533 | if (type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
2534 | tag.textOnly = false;
2535 | }
2536 | }
2537 |
2538 | // block?
2539 | if ('indent' == this.peek().type) {
2540 | if (tag.textOnly) {
2541 | this.lexer.textPipe = false;
2542 | tag.block = this.parseTextBlock();
2543 | this.lexer.textPipe = true;
2544 | } else {
2545 | var block = this.parseBlock();
2546 | if (tag.block) {
2547 | for (var i = 0, len = block.length; i < len; ++i) {
2548 | tag.block.push(block[i]);
2549 | }
2550 | } else {
2551 | tag.block = block;
2552 | }
2553 | }
2554 | }
2555 |
2556 | return tag;
2557 | }
2558 | };
2559 |
2560 | // end module: jade/lib/parser.js
2561 | });
2562 | ;
2563 |
2564 | require.module('jade/lib/self-closing.js', function(module, exports, require) {
2565 | // start module: jade/lib/self-closing.js
2566 |
2567 |
2568 | /*!
2569 | * Jade - self closing tags
2570 | * Copyright(c) 2010 TJ Holowaychuk
2571 | * MIT Licensed
2572 | */
2573 |
2574 | module.exports = [
2575 | 'meta'
2576 | , 'img'
2577 | , 'link'
2578 | , 'input'
2579 | , 'area'
2580 | , 'base'
2581 | , 'col'
2582 | , 'br'
2583 | , 'hr'
2584 | ];
2585 |
2586 | // end module: jade/lib/self-closing.js
2587 | });
2588 | ;
2589 |
2590 | require.module('jade/lib/utils.js', function(module, exports, require) {
2591 | // start module: jade/lib/utils.js
2592 |
2593 |
2594 | /*!
2595 | * Jade - utils
2596 | * Copyright(c) 2010 TJ Holowaychuk
2597 | * MIT Licensed
2598 | */
2599 |
2600 | /**
2601 | * Convert interpolation in the given string to JavaScript.
2602 | *
2603 | * @param {String} str
2604 | * @return {String}
2605 | * @api private
2606 | */
2607 |
2608 | var interpolate = exports.interpolate = function(str){
2609 | return str.replace(/(\\)?([#$!]){(.*?)}/g, function(str, escape, flag, code){
2610 | return escape
2611 | ? str
2612 | : "' + "
2613 | + ('!' == flag ? '' : 'escape')
2614 | + "((interp = " + code.replace(/\\'/g, "'")
2615 | + ") == null ? '' : interp) + '";
2616 | });
2617 | };
2618 |
2619 | /**
2620 | * Escape single quotes in `str`.
2621 | *
2622 | * @param {String} str
2623 | * @return {String}
2624 | * @api private
2625 | */
2626 |
2627 | var escape = exports.escape = function(str) {
2628 | return str.replace(/'/g, "\\'");
2629 | };
2630 |
2631 | /**
2632 | * Interpolate, and escape the given `str`.
2633 | *
2634 | * @param {String} str
2635 | * @return {String}
2636 | * @api private
2637 | */
2638 |
2639 | exports.text = function(str){
2640 | return interpolate(escape(str));
2641 | };
2642 |
2643 | // end module: jade/lib/utils.js
2644 | });
2645 | ;
2646 |
2647 | require.module('jade/index.js', function(module, exports, require) {
2648 | // start module: jade/index.js
2649 |
2650 |
2651 | module.exports = require('./lib/jade');
2652 |
2653 | // end module: jade/index.js
2654 | });
2655 | ;
2656 |
2657 |
--------------------------------------------------------------------------------
/templates.js:
--------------------------------------------------------------------------------
1 | var templates = {}
2 | templates.test1 = "h1 heading 1\n\
3 | h2 heading 2\n\
4 | h3 heading 3"
5 |
6 | templates.test2 =
7 | "div.class1\n\
8 | div#id\n\
9 | | inner\n\
10 | div#nav\n\
11 | ul(style='color:red')\n\
12 | li bullets\n\
13 | li bullets\n\
14 | li bullets\n\
15 | li bullets\n\
16 | script\n\
17 | $('body').append('i am from script in jade')"
18 |
19 |
--------------------------------------------------------------------------------