jader 0.0.3

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 (69) hide show
  1. data/.gitignore +8 -0
  2. data/Gemfile +11 -0
  3. data/LICENSE +22 -0
  4. data/README.md +356 -0
  5. data/Rakefile +4 -0
  6. data/jader.gemspec +28 -0
  7. data/lib/jader.rb +10 -0
  8. data/lib/jader/compiler.rb +70 -0
  9. data/lib/jader/configuration.rb +19 -0
  10. data/lib/jader/engine.rb +11 -0
  11. data/lib/jader/renderer.rb +30 -0
  12. data/lib/jader/serialize.rb +82 -0
  13. data/lib/jader/source.rb +19 -0
  14. data/lib/jader/template.rb +29 -0
  15. data/lib/jader/version.rb +3 -0
  16. data/spec/compiler_spec.rb +32 -0
  17. data/spec/dummy/README.rdoc +261 -0
  18. data/spec/dummy/Rakefile +7 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  20. data/spec/dummy/app/assets/javascripts/includes/util.js +8 -0
  21. data/spec/dummy/app/assets/javascripts/mixins/application_mixins.jade +3 -0
  22. data/spec/dummy/app/assets/javascripts/mixins/users_mixins.jade +3 -0
  23. data/spec/dummy/app/assets/javascripts/sample.jst.jade +5 -0
  24. data/spec/dummy/app/assets/javascripts/views/users/index.jst.jade +2 -0
  25. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  27. data/spec/dummy/app/controllers/users_controller.rb +10 -0
  28. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  29. data/spec/dummy/app/mailers/.gitkeep +0 -0
  30. data/spec/dummy/app/models/.gitkeep +0 -0
  31. data/spec/dummy/app/models/user.rb +4 -0
  32. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/spec/dummy/app/views/users/index.jade +4 -0
  34. data/spec/dummy/app/views/users/show.jade +5 -0
  35. data/spec/dummy/config.ru +4 -0
  36. data/spec/dummy/config/application.rb +65 -0
  37. data/spec/dummy/config/boot.rb +10 -0
  38. data/spec/dummy/config/database.yml +7 -0
  39. data/spec/dummy/config/environment.rb +5 -0
  40. data/spec/dummy/config/environments/development.rb +31 -0
  41. data/spec/dummy/config/environments/production.rb +64 -0
  42. data/spec/dummy/config/environments/test.rb +35 -0
  43. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/dummy/config/initializers/inflections.rb +15 -0
  45. data/spec/dummy/config/initializers/jader.rb +4 -0
  46. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  47. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  48. data/spec/dummy/config/initializers/session_store.rb +8 -0
  49. data/spec/dummy/config/initializers/wrap_parameters.rb +10 -0
  50. data/spec/dummy/config/locales/en.yml +5 -0
  51. data/spec/dummy/config/routes.rb +60 -0
  52. data/spec/dummy/db/migrate/20120816043248_user.rb +11 -0
  53. data/spec/dummy/db/schema.rb +21 -0
  54. data/spec/dummy/lib/assets/.gitkeep +0 -0
  55. data/spec/dummy/log/.gitkeep +0 -0
  56. data/spec/dummy/public/404.html +26 -0
  57. data/spec/dummy/public/422.html +26 -0
  58. data/spec/dummy/public/500.html +25 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/dummy/script/rails +6 -0
  61. data/spec/integration/renderer_spec.rb +59 -0
  62. data/spec/source_spec.rb +19 -0
  63. data/spec/spec_helper.rb +22 -0
  64. data/spec/support/global.rb +7 -0
  65. data/spec/support/matchers.rb +17 -0
  66. data/spec/template_spec.rb +40 -0
  67. data/vendor/assets/javascripts/jade/jade.js +3168 -0
  68. data/vendor/assets/javascripts/jade/runtime.js +128 -0
  69. metadata +278 -0
@@ -0,0 +1,11 @@
1
+ class User < ActiveRecord::Migration
2
+ def up
3
+ create_table :users do |t|
4
+ t.string :name
5
+ t.string :email
6
+ end
7
+ end
8
+
9
+ def down
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20120816043248) do
15
+
16
+ create_table "users", :force => true do |t|
17
+ t.string "name"
18
+ t.string "email"
19
+ end
20
+
21
+ end
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Jader::Renderer do
4
+
5
+ context 'users/index' do
6
+
7
+ before do
8
+ User.all.each do |user|
9
+ user.destroy
10
+ end
11
+ @joe = User.create(:name => 'Joe', :email => 'joe@gmail.com')
12
+ @mike = User.create(:name => 'Mike', :email => 'mike@gmail.com')
13
+ end
14
+
15
+ before :each do
16
+ get '/users'
17
+ end
18
+
19
+ it 'render users index page' do
20
+ response.should render_template(:index)
21
+ end
22
+
23
+ it 'renders the template HTML' do
24
+ response.body.should include '<h1 id="topHeading">Hello All Users</h1>'
25
+ end
26
+
27
+ it 'renders users' do
28
+ response.body.should include @mike.name
29
+ response.body.should include @joe.name
30
+ end
31
+
32
+ it 'uses a function available from server-side includes' do
33
+ response.body.should =~ /\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/
34
+ end
35
+ end
36
+
37
+ context 'users/show' do
38
+
39
+ before :each do
40
+ @user = stub_model(User, :name => 'Sam', :email => 'sam@gmail.com')
41
+ User.should_receive(:find).and_return(@user)
42
+ end
43
+
44
+ it 'renders instance variables' do
45
+ get user_path(@user)
46
+ response.body.should include 'My name is %s' % @user.name
47
+ end
48
+
49
+ it 'renders HTML generated by application mixin' do
50
+ get user_path(@user)
51
+ response.body.should include @user.name.downcase
52
+ end
53
+
54
+ it 'renders HTML generated by users mixin' do
55
+ get user_path(@user)
56
+ response.body.should include @user.email.upcase
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Jader::Source do
4
+ it "should contain jade asset" do
5
+ File.exist?(Jader::Source::jade_path).should be_true
6
+ end
7
+
8
+ it "should contain runtime asset" do
9
+ File.exist?(Jader::Source::runtime_path).should be_true
10
+ end
11
+
12
+ it "should be able to read jade source" do
13
+ IO.read(Jader::Source::jade_path).should_not be_empty
14
+ end
15
+
16
+ it "should be able to read runtime source" do
17
+ IO.read(Jader::Source::runtime_path).should_not be_empty
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+ require 'pry'
6
+
7
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
8
+ require "rails/test_help"
9
+ require "rspec/rails"
10
+
11
+ require 'jader'
12
+
13
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
14
+
15
+ Rails.backtrace_cleaner.remove_silencers!
16
+
17
+ RSpec.configure do |config|
18
+ require 'rspec/mocks'
19
+ require 'rspec/expectations'
20
+ config.include RSpec::Matchers
21
+ config.mock_with :rspec
22
+ end
@@ -0,0 +1,7 @@
1
+ def assets
2
+ Rails.application.assets
3
+ end
4
+
5
+ def asset_for(name)
6
+ assets[name]
7
+ end
@@ -0,0 +1,17 @@
1
+ RSpec::Matchers.define :serve do |asset_name|
2
+ match do |sprockets|
3
+ !!sprockets[asset_name]
4
+ end
5
+
6
+ failure_message_for_should do |sprockets|
7
+ "expected #{asset_name} to be served, but it wasn't"
8
+ end
9
+
10
+ failure_message_for_should_not do |sprockets|
11
+ "expected #{asset_name} NOT to be served, but it was"
12
+ end
13
+
14
+ description do
15
+ "serve #{asset_name}"
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+ describe Jader::Compiler do
4
+
5
+ before :all do
6
+ d = Rails.root.join('tmp','cache','assets')
7
+ FileUtils.rm_r d if Dir.exists? d
8
+ end
9
+
10
+ def template(source, file)
11
+ Jader::Template.new(file){source}
12
+ end
13
+
14
+ it 'should have default mime type' do
15
+ Jader::Template.default_mime_type.should == 'application/javascript'
16
+ end
17
+
18
+ it 'should be served' do
19
+ assets.should serve 'sample.js'
20
+ asset_for('sample.js').body.should include "Yap, it works"
21
+ end
22
+
23
+ it 'should work fine with JST' do
24
+ context = ExecJS.compile %{
25
+ #{asset_for('application.js').to_s}
26
+ html = JST['sample']({name: 'Yorik'})
27
+ }
28
+ context.eval('html').should == "<!DOCTYPE html><head><title>Hello, Yorik :)</title></head><body>Yap, it works\n</body>"
29
+ end
30
+
31
+ it 'should use mixins in JST' do
32
+ phrase = 'Hi There'
33
+ context = ExecJS.compile %{
34
+ #{asset_for('application.js').to_s}
35
+ html = JST['views/users/index']({phrase: '#{phrase}'})
36
+ }
37
+ context.eval('html').should include(phrase.upcase)
38
+ end
39
+
40
+ end
@@ -0,0 +1,3168 @@
1
+ (function() {
2
+
3
+ // CommonJS require()
4
+
5
+ function require(p){
6
+ var path = require.resolve(p)
7
+ , mod = require.modules[path];
8
+ if (!mod) throw new Error('failed to require "' + p + '"');
9
+ if (!mod.exports) {
10
+ mod.exports = {};
11
+ mod.call(mod.exports, mod, mod.exports, require.relative(path));
12
+ }
13
+ return mod.exports;
14
+ }
15
+
16
+ require.modules = {};
17
+
18
+ require.resolve = function (path){
19
+ var orig = path
20
+ , reg = path + '.js'
21
+ , index = path + '/index.js';
22
+ return require.modules[reg] && reg
23
+ || require.modules[index] && index
24
+ || orig;
25
+ };
26
+
27
+ require.register = function (path, fn){
28
+ require.modules[path] = fn;
29
+ };
30
+
31
+ require.relative = function (parent) {
32
+ return function(p){
33
+ if ('.' != p[0]) return require(p);
34
+
35
+ var path = parent.split('/')
36
+ , segs = p.split('/');
37
+ path.pop();
38
+
39
+ for (var i = 0; i < segs.length; i++) {
40
+ var seg = segs[i];
41
+ if ('..' == seg) path.pop();
42
+ else if ('.' != seg) path.push(seg);
43
+ }
44
+
45
+ return require(path.join('/'));
46
+ };
47
+ };
48
+
49
+
50
+ require.register("compiler.js", function(module, exports, require){
51
+
52
+ /*!
53
+ * Jade - Compiler
54
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
55
+ * MIT Licensed
56
+ */
57
+
58
+ /**
59
+ * Module dependencies.
60
+ */
61
+
62
+ var nodes = require('./nodes')
63
+ , filters = require('./filters')
64
+ , doctypes = require('./doctypes')
65
+ , selfClosing = require('./self-closing')
66
+ , inlineTags = require('./inline-tags')
67
+ , utils = require('./utils');
68
+
69
+
70
+ if (!Object.keys) {
71
+ Object.keys = function(obj){
72
+ var arr = [];
73
+ for (var key in obj) {
74
+ if (obj.hasOwnProperty(key)) {
75
+ arr.push(key);
76
+ }
77
+ }
78
+ return arr;
79
+ }
80
+ }
81
+
82
+ if (!String.prototype.trimLeft) {
83
+ String.prototype.trimLeft = function(){
84
+ return this.replace(/^\s+/, '');
85
+ }
86
+ }
87
+
88
+
89
+
90
+ /**
91
+ * Initialize `Compiler` with the given `node`.
92
+ *
93
+ * @param {Node} node
94
+ * @param {Object} options
95
+ * @api public
96
+ */
97
+
98
+ var Compiler = module.exports = function Compiler(node, options) {
99
+ this.options = options = options || {};
100
+ this.node = node;
101
+ this.hasCompiledDoctype = false;
102
+ this.hasCompiledTag = false;
103
+ this.pp = options.pretty || false;
104
+ this.debug = false !== options.compileDebug;
105
+ this.indents = 0;
106
+ if (options.doctype) this.setDoctype(options.doctype);
107
+ };
108
+
109
+ /**
110
+ * Compiler prototype.
111
+ */
112
+
113
+ Compiler.prototype = {
114
+
115
+ /**
116
+ * Compile parse tree to JavaScript.
117
+ *
118
+ * @api public
119
+ */
120
+
121
+ compile: function(){
122
+ this.buf = ['var interp;'];
123
+ this.lastBufferedIdx = -1
124
+ this.visit(this.node);
125
+ return this.buf.join('\n');
126
+ },
127
+
128
+ /**
129
+ * Sets the default doctype `name`. Sets terse mode to `true` when
130
+ * html 5 is used, causing self-closing tags to end with ">" vs "/>",
131
+ * and boolean attributes are not mirrored.
132
+ *
133
+ * @param {string} name
134
+ * @api public
135
+ */
136
+
137
+ setDoctype: function(name){
138
+ var doctype = doctypes[(name || 'default').toLowerCase()];
139
+ doctype = doctype || '<!DOCTYPE ' + name + '>';
140
+ this.doctype = doctype;
141
+ this.terse = '5' == name || 'html' == name;
142
+ this.xml = 0 == this.doctype.indexOf('<?xml');
143
+ },
144
+
145
+ /**
146
+ * Buffer the given `str` optionally escaped.
147
+ *
148
+ * @param {String} str
149
+ * @param {Boolean} esc
150
+ * @api public
151
+ */
152
+
153
+ buffer: function(str, esc){
154
+ if (esc) str = utils.escape(str);
155
+
156
+ if (this.lastBufferedIdx == this.buf.length) {
157
+ this.lastBuffered += str;
158
+ this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
159
+ } else {
160
+ this.buf.push("buf.push('" + str + "');");
161
+ this.lastBuffered = str;
162
+ this.lastBufferedIdx = this.buf.length;
163
+ }
164
+ },
165
+
166
+ /**
167
+ * Visit `node`.
168
+ *
169
+ * @param {Node} node
170
+ * @api public
171
+ */
172
+
173
+ visit: function(node){
174
+ var debug = this.debug;
175
+
176
+ if (debug) {
177
+ this.buf.push('__jade.unshift({ lineno: ' + node.line
178
+ + ', filename: ' + (node.filename
179
+ ? '"' + node.filename + '"'
180
+ : '__jade[0].filename')
181
+ + ' });');
182
+ }
183
+
184
+ // Massive hack to fix our context
185
+ // stack for - else[ if] etc
186
+ if (false === node.debug && this.debug) {
187
+ this.buf.pop();
188
+ this.buf.pop();
189
+ }
190
+
191
+ this.visitNode(node);
192
+
193
+ if (debug) this.buf.push('__jade.shift();');
194
+ },
195
+
196
+ /**
197
+ * Visit `node`.
198
+ *
199
+ * @param {Node} node
200
+ * @api public
201
+ */
202
+
203
+ visitNode: function(node){
204
+ var name = node.constructor.name
205
+ || node.constructor.toString().match(/function ([^(\s]+)()/)[1];
206
+ return this['visit' + name](node);
207
+ },
208
+
209
+ /**
210
+ * Visit case `node`.
211
+ *
212
+ * @param {Literal} node
213
+ * @api public
214
+ */
215
+
216
+ visitCase: function(node){
217
+ var _ = this.withinCase;
218
+ this.withinCase = true;
219
+ this.buf.push('switch (' + node.expr + '){');
220
+ this.visit(node.block);
221
+ this.buf.push('}');
222
+ this.withinCase = _;
223
+ },
224
+
225
+ /**
226
+ * Visit when `node`.
227
+ *
228
+ * @param {Literal} node
229
+ * @api public
230
+ */
231
+
232
+ visitWhen: function(node){
233
+ if ('default' == node.expr) {
234
+ this.buf.push('default:');
235
+ } else {
236
+ this.buf.push('case ' + node.expr + ':');
237
+ }
238
+ this.visit(node.block);
239
+ this.buf.push(' break;');
240
+ },
241
+
242
+ /**
243
+ * Visit literal `node`.
244
+ *
245
+ * @param {Literal} node
246
+ * @api public
247
+ */
248
+
249
+ visitLiteral: function(node){
250
+ var str = node.str.replace(/\n/g, '\\\\n');
251
+ this.buffer(str);
252
+ },
253
+
254
+ /**
255
+ * Visit all nodes in `block`.
256
+ *
257
+ * @param {Block} block
258
+ * @api public
259
+ */
260
+
261
+ visitBlock: function(block){
262
+ var len = block.nodes.length;
263
+ for (var i = 0; i < len; ++i) {
264
+ this.visit(block.nodes[i]);
265
+ }
266
+ },
267
+
268
+ /**
269
+ * Visit `doctype`. Sets terse mode to `true` when html 5
270
+ * is used, causing self-closing tags to end with ">" vs "/>",
271
+ * and boolean attributes are not mirrored.
272
+ *
273
+ * @param {Doctype} doctype
274
+ * @api public
275
+ */
276
+
277
+ visitDoctype: function(doctype){
278
+ if (doctype && (doctype.val || !this.doctype)) {
279
+ this.setDoctype(doctype.val || 'default');
280
+ }
281
+
282
+ if (this.doctype) this.buffer(this.doctype);
283
+ this.hasCompiledDoctype = true;
284
+ },
285
+
286
+ /**
287
+ * Visit `mixin`, generating a function that
288
+ * may be called within the template.
289
+ *
290
+ * @param {Mixin} mixin
291
+ * @api public
292
+ */
293
+
294
+ visitMixin: function(mixin){
295
+ var name = mixin.name.replace(/-/g, '_') + '_mixin'
296
+ , args = mixin.args || '';
297
+
298
+ if (mixin.block) {
299
+ this.buf.push('var ' + name + ' = function(' + args + '){');
300
+ this.visit(mixin.block);
301
+ this.buf.push('}');
302
+ } else {
303
+ this.buf.push(name + '(' + args + ');');
304
+ }
305
+ },
306
+
307
+ /**
308
+ * Visit `tag` buffering tag markup, generating
309
+ * attributes, visiting the `tag`'s code and block.
310
+ *
311
+ * @param {Tag} tag
312
+ * @api public
313
+ */
314
+
315
+ visitTag: function(tag){
316
+ this.indents++;
317
+ var name = tag.name;
318
+
319
+ if (!this.hasCompiledTag) {
320
+ if (!this.hasCompiledDoctype && 'html' == name) {
321
+ this.visitDoctype();
322
+ }
323
+ this.hasCompiledTag = true;
324
+ }
325
+
326
+ // pretty print
327
+ if (this.pp && inlineTags.indexOf(name) == -1) {
328
+ this.buffer('\\n' + Array(this.indents).join(' '));
329
+ }
330
+
331
+ if (~selfClosing.indexOf(name) && !this.xml) {
332
+ this.buffer('<' + name);
333
+ this.visitAttributes(tag.attrs);
334
+ this.terse
335
+ ? this.buffer('>')
336
+ : this.buffer('/>');
337
+ } else {
338
+ // Optimize attributes buffering
339
+ if (tag.attrs.length) {
340
+ this.buffer('<' + name);
341
+ if (tag.attrs.length) this.visitAttributes(tag.attrs);
342
+ this.buffer('>');
343
+ } else {
344
+ this.buffer('<' + name + '>');
345
+ }
346
+ if (tag.code) this.visitCode(tag.code);
347
+ if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft()));
348
+ this.escape = 'pre' == tag.name;
349
+ this.visit(tag.block);
350
+
351
+ // pretty print
352
+ if (this.pp && !~inlineTags.indexOf(name) && !tag.textOnly) {
353
+ this.buffer('\\n' + Array(this.indents).join(' '));
354
+ }
355
+
356
+ this.buffer('</' + name + '>');
357
+ }
358
+ this.indents--;
359
+ },
360
+
361
+ /**
362
+ * Visit `filter`, throwing when the filter does not exist.
363
+ *
364
+ * @param {Filter} filter
365
+ * @api public
366
+ */
367
+
368
+ visitFilter: function(filter){
369
+ var fn = filters[filter.name];
370
+
371
+ // unknown filter
372
+ if (!fn) {
373
+ if (filter.isASTFilter) {
374
+ throw new Error('unknown ast filter "' + filter.name + ':"');
375
+ } else {
376
+ throw new Error('unknown filter ":' + filter.name + '"');
377
+ }
378
+ }
379
+ if (filter.isASTFilter) {
380
+ this.buf.push(fn(filter.block, this, filter.attrs));
381
+ } else {
382
+ var text = filter.block.nodes.join('');
383
+ this.buffer(utils.text(fn(text, filter.attrs)));
384
+ }
385
+ },
386
+
387
+ /**
388
+ * Visit `text` node.
389
+ *
390
+ * @param {Text} text
391
+ * @api public
392
+ */
393
+
394
+ visitText: function(text){
395
+ text = utils.text(text.nodes.join(''));
396
+ if (this.escape) text = escape(text);
397
+ this.buffer(text);
398
+ this.buffer('\\n');
399
+ },
400
+
401
+ /**
402
+ * Visit a `comment`, only buffering when the buffer flag is set.
403
+ *
404
+ * @param {Comment} comment
405
+ * @api public
406
+ */
407
+
408
+ visitComment: function(comment){
409
+ if (!comment.buffer) return;
410
+ if (this.pp) this.buffer('\\n' + Array(this.indents + 1).join(' '));
411
+ this.buffer('<!--' + utils.escape(comment.val) + '-->');
412
+ },
413
+
414
+ /**
415
+ * Visit a `BlockComment`.
416
+ *
417
+ * @param {Comment} comment
418
+ * @api public
419
+ */
420
+
421
+ visitBlockComment: function(comment){
422
+ if (!comment.buffer) return;
423
+ if (0 == comment.val.trim().indexOf('if')) {
424
+ this.buffer('<!--[' + comment.val.trim() + ']>');
425
+ this.visit(comment.block);
426
+ this.buffer('<![endif]-->');
427
+ } else {
428
+ this.buffer('<!--' + comment.val);
429
+ this.visit(comment.block);
430
+ this.buffer('-->');
431
+ }
432
+ },
433
+
434
+ /**
435
+ * Visit `code`, respecting buffer / escape flags.
436
+ * If the code is followed by a block, wrap it in
437
+ * a self-calling function.
438
+ *
439
+ * @param {Code} code
440
+ * @api public
441
+ */
442
+
443
+ visitCode: function(code){
444
+ // Wrap code blocks with {}.
445
+ // we only wrap unbuffered code blocks ATM
446
+ // since they are usually flow control
447
+
448
+ // Buffer code
449
+ if (code.buffer) {
450
+ var val = code.val.trimLeft();
451
+ this.buf.push('var __val__ = ' + val);
452
+ val = 'null == __val__ ? "" : __val__';
453
+ if (code.escape) val = 'escape(' + val + ')';
454
+ this.buf.push("buf.push(" + val + ");");
455
+ } else {
456
+ this.buf.push(code.val);
457
+ }
458
+
459
+ // Block support
460
+ if (code.block) {
461
+ if (!code.buffer) this.buf.push('{');
462
+ this.visit(code.block);
463
+ if (!code.buffer) this.buf.push('}');
464
+ }
465
+ },
466
+
467
+ /**
468
+ * Visit `each` block.
469
+ *
470
+ * @param {Each} each
471
+ * @api public
472
+ */
473
+
474
+ visitEach: function(each){
475
+ this.buf.push(''
476
+ + '// iterate ' + each.obj + '\n'
477
+ + '(function(){\n'
478
+ + ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
479
+ + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
480
+ + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
481
+
482
+ this.visit(each.block);
483
+
484
+ this.buf.push(''
485
+ + ' }\n'
486
+ + ' } else {\n'
487
+ + ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
488
+ + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){'
489
+ + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
490
+
491
+ this.visit(each.block);
492
+
493
+ this.buf.push(' }\n');
494
+
495
+ this.buf.push(' }\n }\n}).call(this);\n');
496
+ },
497
+
498
+ /**
499
+ * Visit `attrs`.
500
+ *
501
+ * @param {Array} attrs
502
+ * @api public
503
+ */
504
+
505
+ visitAttributes: function(attrs){
506
+ var buf = []
507
+ , classes = [];
508
+
509
+ if (this.terse) buf.push('terse: true');
510
+
511
+ attrs.forEach(function(attr){
512
+ if (attr.name == 'class') {
513
+ classes.push('(' + attr.val + ')');
514
+ } else {
515
+ var pair = "'" + attr.name + "':(" + attr.val + ')';
516
+ buf.push(pair);
517
+ }
518
+ });
519
+
520
+ if (classes.length) {
521
+ classes = classes.join(" + ' ' + ");
522
+ buf.push("class: " + classes);
523
+ }
524
+
525
+ buf = buf.join(', ').replace('class:', '"class":');
526
+
527
+ this.buf.push("buf.push(attrs({ " + buf + " }));");
528
+ }
529
+ };
530
+
531
+ /**
532
+ * Escape the given string of `html`.
533
+ *
534
+ * @param {String} html
535
+ * @return {String}
536
+ * @api private
537
+ */
538
+
539
+ function escape(html){
540
+ return String(html)
541
+ .replace(/&(?!\w+;)/g, '&amp;')
542
+ .replace(/</g, '&lt;')
543
+ .replace(/>/g, '&gt;')
544
+ .replace(/"/g, '&quot;');
545
+ }
546
+
547
+ }); // module: compiler.js
548
+
549
+ require.register("doctypes.js", function(module, exports, require){
550
+
551
+ /*!
552
+ * Jade - doctypes
553
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
554
+ * MIT Licensed
555
+ */
556
+
557
+ module.exports = {
558
+ '5': '<!DOCTYPE html>'
559
+ , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
560
+ , 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
561
+ , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
562
+ , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
563
+ , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
564
+ , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
565
+ , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
566
+ , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
567
+ };
568
+ }); // module: doctypes.js
569
+
570
+ require.register("filters.js", function(module, exports, require){
571
+
572
+ /*!
573
+ * Jade - filters
574
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
575
+ * MIT Licensed
576
+ */
577
+
578
+ module.exports = {
579
+
580
+ /**
581
+ * Wrap text with CDATA block.
582
+ */
583
+
584
+ cdata: function(str){
585
+ return '<![CDATA[\\n' + str + '\\n]]>';
586
+ },
587
+
588
+ /**
589
+ * Transform sass to css, wrapped in style tags.
590
+ */
591
+
592
+ sass: function(str){
593
+ str = str.replace(/\\n/g, '\n');
594
+ var sass = require('sass').render(str).replace(/\n/g, '\\n');
595
+ return '<style type="text/css">' + sass + '</style>';
596
+ },
597
+
598
+ /**
599
+ * Transform stylus to css, wrapped in style tags.
600
+ */
601
+
602
+ stylus: function(str, options){
603
+ var ret;
604
+ str = str.replace(/\\n/g, '\n');
605
+ var stylus = require('stylus');
606
+ stylus(str, options).render(function(err, css){
607
+ if (err) throw err;
608
+ ret = css.replace(/\n/g, '\\n');
609
+ });
610
+ return '<style type="text/css">' + ret + '</style>';
611
+ },
612
+
613
+ /**
614
+ * Transform less to css, wrapped in style tags.
615
+ */
616
+
617
+ less: function(str){
618
+ var ret;
619
+ str = str.replace(/\\n/g, '\n');
620
+ require('less').render(str, function(err, css){
621
+ if (err) throw err;
622
+ ret = '<style type="text/css">' + css.replace(/\n/g, '\\n') + '</style>';
623
+ });
624
+ return ret;
625
+ },
626
+
627
+ /**
628
+ * Transform markdown to html.
629
+ */
630
+
631
+ markdown: function(str){
632
+ var md;
633
+
634
+ // support markdown / discount
635
+ try {
636
+ md = require('markdown');
637
+ } catch (err){
638
+ try {
639
+ md = require('discount');
640
+ } catch (err) {
641
+ try {
642
+ md = require('markdown-js');
643
+ } catch (err) {
644
+ try {
645
+ md = require('marked');
646
+ } catch (err) {
647
+ throw new
648
+ Error('Cannot find markdown library, install markdown, discount, or marked.');
649
+ }
650
+ }
651
+ }
652
+ }
653
+
654
+ str = str.replace(/\\n/g, '\n');
655
+ return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'&#39;');
656
+ },
657
+
658
+ /**
659
+ * Transform coffeescript to javascript.
660
+ */
661
+
662
+ coffeescript: function(str){
663
+ str = str.replace(/\\n/g, '\n');
664
+ var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
665
+ return '<script type="text/javascript">\\n' + js + '</script>';
666
+ }
667
+ };
668
+
669
+ }); // module: filters.js
670
+
671
+ require.register("inline-tags.js", function(module, exports, require){
672
+
673
+ /*!
674
+ * Jade - inline tags
675
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
676
+ * MIT Licensed
677
+ */
678
+
679
+ module.exports = [
680
+ 'a'
681
+ , 'abbr'
682
+ , 'acronym'
683
+ , 'b'
684
+ , 'br'
685
+ , 'code'
686
+ , 'em'
687
+ , 'font'
688
+ , 'i'
689
+ , 'img'
690
+ , 'ins'
691
+ , 'kbd'
692
+ , 'map'
693
+ , 'samp'
694
+ , 'small'
695
+ , 'span'
696
+ , 'strong'
697
+ , 'sub'
698
+ , 'sup'
699
+ ];
700
+ }); // module: inline-tags.js
701
+
702
+ require.register("jade.js", function(module, exports, require){
703
+ /*!
704
+ * Jade
705
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
706
+ * MIT Licensed
707
+ */
708
+
709
+ /**
710
+ * Module dependencies.
711
+ */
712
+
713
+ var Parser = require('./parser')
714
+ , Lexer = require('./lexer')
715
+ , Compiler = require('./compiler')
716
+ , runtime = require('./runtime')
717
+
718
+ /**
719
+ * Library version.
720
+ */
721
+
722
+ exports.version = '0.20.0';
723
+
724
+ /**
725
+ * Expose self closing tags.
726
+ */
727
+
728
+ exports.selfClosing = require('./self-closing');
729
+
730
+ /**
731
+ * Default supported doctypes.
732
+ */
733
+
734
+ exports.doctypes = require('./doctypes');
735
+
736
+ /**
737
+ * Text filters.
738
+ */
739
+
740
+ exports.filters = require('./filters');
741
+
742
+ /**
743
+ * Utilities.
744
+ */
745
+
746
+ exports.utils = require('./utils');
747
+
748
+ /**
749
+ * Expose `Compiler`.
750
+ */
751
+
752
+ exports.Compiler = Compiler;
753
+
754
+ /**
755
+ * Expose `Parser`.
756
+ */
757
+
758
+ exports.Parser = Parser;
759
+
760
+ /**
761
+ * Expose `Lexer`.
762
+ */
763
+
764
+ exports.Lexer = Lexer;
765
+
766
+ /**
767
+ * Nodes.
768
+ */
769
+
770
+ exports.nodes = require('./nodes');
771
+
772
+ /**
773
+ * Jade runtime mixins.
774
+ */
775
+
776
+ exports.runtime = runtime;
777
+
778
+ /**
779
+ * Template function cache.
780
+ */
781
+
782
+ exports.cache = {};
783
+
784
+ /**
785
+ * Parse the given `str` of jade and return a function body.
786
+ *
787
+ * @param {String} str
788
+ * @param {Object} options
789
+ * @return {String}
790
+ * @api private
791
+ */
792
+
793
+ function parse(str, options){
794
+ try {
795
+ // Parse
796
+ var parser = new Parser(str, options.filename, options);
797
+
798
+ // Compile
799
+ var compiler = new (options.compiler || Compiler)(parser.parse(), options)
800
+ , js = compiler.compile();
801
+
802
+ // Debug compiler
803
+ if (options.debug) {
804
+ console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' '));
805
+ }
806
+
807
+ return ''
808
+ + 'var buf = [];\n'
809
+ + (options.self
810
+ ? 'var self = locals || {};\n' + js
811
+ : 'with (locals || {}) {\n' + js + '\n}\n')
812
+ + 'return buf.join("");';
813
+ } catch (err) {
814
+ parser = parser.context();
815
+ runtime.rethrow(err, parser.filename, parser.lexer.lineno);
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Precompile a string representation of the given jade `str`.
821
+ *
822
+ * Options:
823
+ *
824
+ * - `compileDebug` when `false` debugging code is stripped from the compiled template
825
+ * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
826
+ * for use with the Jade client-side runtime.js
827
+ *
828
+ * @param {String} str
829
+ * @param {Options} options
830
+ * @return {Function}
831
+ * @api public
832
+ */
833
+
834
+ exports.precompile = function(str, options){
835
+ var options = options || {}
836
+ , client = options.client
837
+ , filename = options.filename
838
+ ? JSON.stringify(options.filename)
839
+ : 'undefined'
840
+ , fn;
841
+
842
+ if (options.compileDebug !== false) {
843
+ fn = [
844
+ 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];'
845
+ , 'try {'
846
+ , parse(String(str), options)
847
+ , '} catch (err) {'
848
+ , ' rethrow(err, __jade[0].filename, __jade[0].lineno);'
849
+ , '}'
850
+ ].join('\n');
851
+ } else {
852
+ fn = parse(String(str), options);
853
+ }
854
+
855
+ if (client) {
856
+ fn = 'var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;\n' + fn;
857
+ }
858
+
859
+ return fn;
860
+ }
861
+
862
+ /**
863
+ * Compile a `Function` representation of the given jade `str`.
864
+ *
865
+ * Options:
866
+ *
867
+ * - `compileDebug` when `false` debugging code is stripped from the compiled template
868
+ * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
869
+ * for use with the Jade client-side runtime.js
870
+ *
871
+ * @param {String} str
872
+ * @param {Options} options
873
+ * @return {Function}
874
+ * @api public
875
+ */
876
+
877
+ exports.compile = function(str, options){
878
+ var options = options || {}
879
+ , client = options.client,
880
+ fn;
881
+
882
+ fn = new Function('locals, attrs, escape, rethrow', exports.precompile(str, options));
883
+
884
+ if (client) return fn;
885
+
886
+ return function(locals){
887
+ return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow);
888
+ };
889
+ };
890
+
891
+ /**
892
+ * Render the given `str` of jade and invoke
893
+ * the callback `fn(err, str)`.
894
+ *
895
+ * Options:
896
+ *
897
+ * - `cache` enable template caching
898
+ * - `filename` filename required for `include` / `extends` and caching
899
+ *
900
+ * @param {String} str
901
+ * @param {Object|Function} options or fn
902
+ * @param {Function} fn
903
+ * @api public
904
+ */
905
+
906
+ exports.render = function(str, options, fn){
907
+ // swap args
908
+ if ('function' == typeof options) {
909
+ fn = options, options = {};
910
+ }
911
+
912
+ // cache requires .filename
913
+ if (options.cache && !options.filename) {
914
+ return fn(new Error('the "filename" option is required for caching'));
915
+ }
916
+
917
+ try {
918
+ var path = options.filename;
919
+ var tmpl = options.cache
920
+ ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
921
+ : exports.compile(str, options);
922
+ fn(null, tmpl(options));
923
+ } catch (err) {
924
+ fn(err);
925
+ }
926
+ };
927
+
928
+ /**
929
+ * Render a Jade file at the given `path` and callback `fn(err, str)`.
930
+ *
931
+ * @param {String} path
932
+ * @param {Object|Function} options or callback
933
+ * @param {Function} fn
934
+ * @api public
935
+ */
936
+
937
+ exports.renderFile = function(path, options, fn){
938
+ var key = path + ':string';
939
+
940
+ if ('function' == typeof options) {
941
+ fn = options, options = {};
942
+ }
943
+
944
+ try {
945
+ options.filename = path;
946
+ var str = options.cache
947
+ ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
948
+ : fs.readFileSync(path, 'utf8');
949
+ exports.render(str, options, fn);
950
+ } catch (err) {
951
+ fn(err);
952
+ }
953
+ };
954
+
955
+ /**
956
+ * Express support.
957
+ */
958
+
959
+ exports.__express = exports.renderFile;
960
+
961
+ }); // module: jade.js
962
+
963
+ require.register("lexer.js", function(module, exports, require){
964
+
965
+ /*!
966
+ * Jade - Lexer
967
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
968
+ * MIT Licensed
969
+ */
970
+
971
+ /**
972
+ * Initialize `Lexer` with the given `str`.
973
+ *
974
+ * Options:
975
+ *
976
+ * - `colons` allow colons for attr delimiters
977
+ *
978
+ * @param {String} str
979
+ * @param {Object} options
980
+ * @api private
981
+ */
982
+
983
+ var Lexer = module.exports = function Lexer(str, options) {
984
+ options = options || {};
985
+ this.input = str.replace(/\r\n|\r/g, '\n');
986
+ this.colons = options.colons;
987
+ this.deferredTokens = [];
988
+ this.lastIndents = 0;
989
+ this.lineno = 1;
990
+ this.stash = [];
991
+ this.indentStack = [];
992
+ this.indentRe = null;
993
+ this.pipeless = false;
994
+ };
995
+
996
+ /**
997
+ * Lexer prototype.
998
+ */
999
+
1000
+ Lexer.prototype = {
1001
+
1002
+ /**
1003
+ * Construct a token with the given `type` and `val`.
1004
+ *
1005
+ * @param {String} type
1006
+ * @param {String} val
1007
+ * @return {Object}
1008
+ * @api private
1009
+ */
1010
+
1011
+ tok: function(type, val){
1012
+ return {
1013
+ type: type
1014
+ , line: this.lineno
1015
+ , val: val
1016
+ }
1017
+ },
1018
+
1019
+ /**
1020
+ * Consume the given `len` of input.
1021
+ *
1022
+ * @param {Number} len
1023
+ * @api private
1024
+ */
1025
+
1026
+ consume: function(len){
1027
+ this.input = this.input.substr(len);
1028
+ },
1029
+
1030
+ /**
1031
+ * Scan for `type` with the given `regexp`.
1032
+ *
1033
+ * @param {String} type
1034
+ * @param {RegExp} regexp
1035
+ * @return {Object}
1036
+ * @api private
1037
+ */
1038
+
1039
+ scan: function(regexp, type){
1040
+ var captures;
1041
+ if (captures = regexp.exec(this.input)) {
1042
+ this.consume(captures[0].length);
1043
+ return this.tok(type, captures[1]);
1044
+ }
1045
+ },
1046
+
1047
+ /**
1048
+ * Defer the given `tok`.
1049
+ *
1050
+ * @param {Object} tok
1051
+ * @api private
1052
+ */
1053
+
1054
+ defer: function(tok){
1055
+ this.deferredTokens.push(tok);
1056
+ },
1057
+
1058
+ /**
1059
+ * Lookahead `n` tokens.
1060
+ *
1061
+ * @param {Number} n
1062
+ * @return {Object}
1063
+ * @api private
1064
+ */
1065
+
1066
+ lookahead: function(n){
1067
+ var fetch = n - this.stash.length;
1068
+ while (fetch-- > 0) this.stash.push(this.next());
1069
+ return this.stash[--n];
1070
+ },
1071
+
1072
+ /**
1073
+ * Return the indexOf `start` / `end` delimiters.
1074
+ *
1075
+ * @param {String} start
1076
+ * @param {String} end
1077
+ * @return {Number}
1078
+ * @api private
1079
+ */
1080
+
1081
+ indexOfDelimiters: function(start, end){
1082
+ var str = this.input
1083
+ , nstart = 0
1084
+ , nend = 0
1085
+ , pos = 0;
1086
+ for (var i = 0, len = str.length; i < len; ++i) {
1087
+ if (start == str[i]) {
1088
+ ++nstart;
1089
+ } else if (end == str[i]) {
1090
+ if (++nend == nstart) {
1091
+ pos = i;
1092
+ break;
1093
+ }
1094
+ }
1095
+ }
1096
+ return pos;
1097
+ },
1098
+
1099
+ /**
1100
+ * Stashed token.
1101
+ */
1102
+
1103
+ stashed: function() {
1104
+ return this.stash.length
1105
+ && this.stash.shift();
1106
+ },
1107
+
1108
+ /**
1109
+ * Deferred token.
1110
+ */
1111
+
1112
+ deferred: function() {
1113
+ return this.deferredTokens.length
1114
+ && this.deferredTokens.shift();
1115
+ },
1116
+
1117
+ /**
1118
+ * end-of-source.
1119
+ */
1120
+
1121
+ eos: function() {
1122
+ if (this.input.length) return;
1123
+ if (this.indentStack.length) {
1124
+ this.indentStack.shift();
1125
+ return this.tok('outdent');
1126
+ } else {
1127
+ return this.tok('eos');
1128
+ }
1129
+ },
1130
+
1131
+ /**
1132
+ * Comment.
1133
+ */
1134
+
1135
+ comment: function() {
1136
+ var captures;
1137
+ if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) {
1138
+ this.consume(captures[0].length);
1139
+ var tok = this.tok('comment', captures[2]);
1140
+ tok.buffer = '-' != captures[1];
1141
+ return tok;
1142
+ }
1143
+ },
1144
+
1145
+ /**
1146
+ * Tag.
1147
+ */
1148
+
1149
+ tag: function() {
1150
+ var captures;
1151
+ if (captures = /^(\w[-:\w]*)/.exec(this.input)) {
1152
+ this.consume(captures[0].length);
1153
+ var tok, name = captures[1];
1154
+ if (':' == name[name.length - 1]) {
1155
+ name = name.slice(0, -1);
1156
+ tok = this.tok('tag', name);
1157
+ this.defer(this.tok(':'));
1158
+ while (' ' == this.input[0]) this.input = this.input.substr(1);
1159
+ } else {
1160
+ tok = this.tok('tag', name);
1161
+ }
1162
+ return tok;
1163
+ }
1164
+ },
1165
+
1166
+ /**
1167
+ * Filter.
1168
+ */
1169
+
1170
+ filter: function() {
1171
+ return this.scan(/^:(\w+)/, 'filter');
1172
+ },
1173
+
1174
+ /**
1175
+ * Doctype.
1176
+ */
1177
+
1178
+ doctype: function() {
1179
+ return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype');
1180
+ },
1181
+
1182
+ /**
1183
+ * Id.
1184
+ */
1185
+
1186
+ id: function() {
1187
+ return this.scan(/^#([\w-]+)/, 'id');
1188
+ },
1189
+
1190
+ /**
1191
+ * Class.
1192
+ */
1193
+
1194
+ className: function() {
1195
+ return this.scan(/^\.([\w-]+)/, 'class');
1196
+ },
1197
+
1198
+ /**
1199
+ * Text.
1200
+ */
1201
+
1202
+ text: function() {
1203
+ return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
1204
+ },
1205
+
1206
+ /**
1207
+ * Extends.
1208
+ */
1209
+
1210
+ extends: function() {
1211
+ return this.scan(/^extends +([^\n]+)/, 'extends');
1212
+ },
1213
+
1214
+ /**
1215
+ * Block prepend.
1216
+ */
1217
+
1218
+ prepend: function() {
1219
+ var captures;
1220
+ if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
1221
+ this.consume(captures[0].length);
1222
+ var mode = 'prepend'
1223
+ , name = captures[1]
1224
+ , tok = this.tok('block', name);
1225
+ tok.mode = mode;
1226
+ return tok;
1227
+ }
1228
+ },
1229
+
1230
+ /**
1231
+ * Block append.
1232
+ */
1233
+
1234
+ append: function() {
1235
+ var captures;
1236
+ if (captures = /^append +([^\n]+)/.exec(this.input)) {
1237
+ this.consume(captures[0].length);
1238
+ var mode = 'append'
1239
+ , name = captures[1]
1240
+ , tok = this.tok('block', name);
1241
+ tok.mode = mode;
1242
+ return tok;
1243
+ }
1244
+ },
1245
+
1246
+ /**
1247
+ * Block.
1248
+ */
1249
+
1250
+ block: function() {
1251
+ var captures;
1252
+ if (captures = /^block +(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) {
1253
+ this.consume(captures[0].length);
1254
+ var mode = captures[1] || 'replace'
1255
+ , name = captures[2]
1256
+ , tok = this.tok('block', name);
1257
+ tok.mode = mode;
1258
+ return tok;
1259
+ }
1260
+ },
1261
+
1262
+ /**
1263
+ * Yield.
1264
+ */
1265
+
1266
+ yield: function() {
1267
+ return this.scan(/^yield */, 'yield');
1268
+ },
1269
+
1270
+ /**
1271
+ * Include.
1272
+ */
1273
+
1274
+ include: function() {
1275
+ return this.scan(/^include +([^\n]+)/, 'include');
1276
+ },
1277
+
1278
+ /**
1279
+ * Case.
1280
+ */
1281
+
1282
+ case: function() {
1283
+ return this.scan(/^case +([^\n]+)/, 'case');
1284
+ },
1285
+
1286
+ /**
1287
+ * When.
1288
+ */
1289
+
1290
+ when: function() {
1291
+ return this.scan(/^when +([^:\n]+)/, 'when');
1292
+ },
1293
+
1294
+ /**
1295
+ * Default.
1296
+ */
1297
+
1298
+ default: function() {
1299
+ return this.scan(/^default */, 'default');
1300
+ },
1301
+
1302
+ /**
1303
+ * Assignment.
1304
+ */
1305
+
1306
+ assignment: function() {
1307
+ var captures;
1308
+ if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) {
1309
+ this.consume(captures[0].length);
1310
+ var name = captures[1]
1311
+ , val = captures[2];
1312
+ return this.tok('code', 'var ' + name + ' = (' + val + ');');
1313
+ }
1314
+ },
1315
+
1316
+ /**
1317
+ * Mixin.
1318
+ */
1319
+
1320
+ mixin: function(){
1321
+ var captures;
1322
+ if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) {
1323
+ this.consume(captures[0].length);
1324
+ var tok = this.tok('mixin', captures[1]);
1325
+ tok.args = captures[2];
1326
+ return tok;
1327
+ }
1328
+ },
1329
+
1330
+ /**
1331
+ * Conditional.
1332
+ */
1333
+
1334
+ conditional: function() {
1335
+ var captures;
1336
+ if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
1337
+ this.consume(captures[0].length);
1338
+ var type = captures[1]
1339
+ , js = captures[2];
1340
+
1341
+ switch (type) {
1342
+ case 'if': js = 'if (' + js + ')'; break;
1343
+ case 'unless': js = 'if (!(' + js + '))'; break;
1344
+ case 'else if': js = 'else if (' + js + ')'; break;
1345
+ case 'else': js = 'else'; break;
1346
+ }
1347
+
1348
+ return this.tok('code', js);
1349
+ }
1350
+ },
1351
+
1352
+ /**
1353
+ * While.
1354
+ */
1355
+
1356
+ while: function() {
1357
+ var captures;
1358
+ if (captures = /^while +([^\n]+)/.exec(this.input)) {
1359
+ this.consume(captures[0].length);
1360
+ return this.tok('code', 'while (' + captures[1] + ')');
1361
+ }
1362
+ },
1363
+
1364
+ /**
1365
+ * Each.
1366
+ */
1367
+
1368
+ each: function() {
1369
+ var captures;
1370
+ if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
1371
+ this.consume(captures[0].length);
1372
+ var tok = this.tok('each', captures[1]);
1373
+ tok.key = captures[2] || '$index';
1374
+ tok.code = captures[3];
1375
+ return tok;
1376
+ }
1377
+ },
1378
+
1379
+ /**
1380
+ * Code.
1381
+ */
1382
+
1383
+ code: function() {
1384
+ var captures;
1385
+ if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) {
1386
+ this.consume(captures[0].length);
1387
+ var flags = captures[1];
1388
+ captures[1] = captures[2];
1389
+ var tok = this.tok('code', captures[1]);
1390
+ tok.escape = flags[0] === '=';
1391
+ tok.buffer = flags[0] === '=' || flags[1] === '=';
1392
+ return tok;
1393
+ }
1394
+ },
1395
+
1396
+ /**
1397
+ * Attributes.
1398
+ */
1399
+
1400
+ attrs: function() {
1401
+ if ('(' == this.input[0]) {
1402
+ var index = this.indexOfDelimiters('(', ')')
1403
+ , str = this.input.substr(1, index-1)
1404
+ , tok = this.tok('attrs')
1405
+ , len = str.length
1406
+ , colons = this.colons
1407
+ , states = ['key']
1408
+ , key = ''
1409
+ , val = ''
1410
+ , quote
1411
+ , c;
1412
+
1413
+ function state(){
1414
+ return states[states.length - 1];
1415
+ }
1416
+
1417
+ function interpolate(attr) {
1418
+ return attr.replace(/#\{([^}]+)\}/g, function(_, expr){
1419
+ return quote + " + (" + expr + ") + " + quote;
1420
+ });
1421
+ }
1422
+
1423
+ this.consume(index + 1);
1424
+ tok.attrs = {};
1425
+
1426
+ function parse(c) {
1427
+ var real = c;
1428
+ // TODO: remove when people fix ":"
1429
+ if (colons && ':' == c) c = '=';
1430
+ switch (c) {
1431
+ case ',':
1432
+ case '\n':
1433
+ switch (state()) {
1434
+ case 'expr':
1435
+ case 'array':
1436
+ case 'string':
1437
+ case 'object':
1438
+ val += c;
1439
+ break;
1440
+ default:
1441
+ states.push('key');
1442
+ val = val.trim();
1443
+ key = key.trim();
1444
+ if ('' == key) return;
1445
+ tok.attrs[key.replace(/^['"]|['"]$/g, '')] = '' == val
1446
+ ? true
1447
+ : interpolate(val);
1448
+ key = val = '';
1449
+ }
1450
+ break;
1451
+ case '=':
1452
+ switch (state()) {
1453
+ case 'key char':
1454
+ key += real;
1455
+ break;
1456
+ case 'val':
1457
+ case 'expr':
1458
+ case 'array':
1459
+ case 'string':
1460
+ case 'object':
1461
+ val += real;
1462
+ break;
1463
+ default:
1464
+ states.push('val');
1465
+ }
1466
+ break;
1467
+ case '(':
1468
+ if ('val' == state()
1469
+ || 'expr' == state()) states.push('expr');
1470
+ val += c;
1471
+ break;
1472
+ case ')':
1473
+ if ('expr' == state()
1474
+ || 'val' == state()) states.pop();
1475
+ val += c;
1476
+ break;
1477
+ case '{':
1478
+ if ('val' == state()) states.push('object');
1479
+ val += c;
1480
+ break;
1481
+ case '}':
1482
+ if ('object' == state()) states.pop();
1483
+ val += c;
1484
+ break;
1485
+ case '[':
1486
+ if ('val' == state()) states.push('array');
1487
+ val += c;
1488
+ break;
1489
+ case ']':
1490
+ if ('array' == state()) states.pop();
1491
+ val += c;
1492
+ break;
1493
+ case '"':
1494
+ case "'":
1495
+ switch (state()) {
1496
+ case 'key':
1497
+ states.push('key char');
1498
+ break;
1499
+ case 'key char':
1500
+ states.pop();
1501
+ break;
1502
+ case 'string':
1503
+ if (c == quote) states.pop();
1504
+ val += c;
1505
+ break;
1506
+ default:
1507
+ states.push('string');
1508
+ val += c;
1509
+ quote = c;
1510
+ }
1511
+ break;
1512
+ case '':
1513
+ break;
1514
+ default:
1515
+ switch (state()) {
1516
+ case 'key':
1517
+ case 'key char':
1518
+ key += c;
1519
+ break;
1520
+ default:
1521
+ val += c;
1522
+ }
1523
+ }
1524
+ }
1525
+
1526
+ for (var i = 0; i < len; ++i) {
1527
+ parse(str[i]);
1528
+ }
1529
+
1530
+ parse(',');
1531
+
1532
+ return tok;
1533
+ }
1534
+ },
1535
+
1536
+ /**
1537
+ * Indent | Outdent | Newline.
1538
+ */
1539
+
1540
+ indent: function() {
1541
+ var captures, re;
1542
+
1543
+ // established regexp
1544
+ if (this.indentRe) {
1545
+ captures = this.indentRe.exec(this.input);
1546
+ // determine regexp
1547
+ } else {
1548
+ // tabs
1549
+ re = /^\n(\t*) */;
1550
+ captures = re.exec(this.input);
1551
+
1552
+ // spaces
1553
+ if (captures && !captures[1].length) {
1554
+ re = /^\n( *)/;
1555
+ captures = re.exec(this.input);
1556
+ }
1557
+
1558
+ // established
1559
+ if (captures && captures[1].length) this.indentRe = re;
1560
+ }
1561
+
1562
+ if (captures) {
1563
+ var tok
1564
+ , indents = captures[1].length;
1565
+
1566
+ ++this.lineno;
1567
+ this.consume(indents + 1);
1568
+
1569
+ if (' ' == this.input[0] || '\t' == this.input[0]) {
1570
+ throw new Error('Invalid indentation, you can use tabs or spaces but not both');
1571
+ }
1572
+
1573
+ // blank line
1574
+ if ('\n' == this.input[0]) return this.tok('newline');
1575
+
1576
+ // outdent
1577
+ if (this.indentStack.length && indents < this.indentStack[0]) {
1578
+ while (this.indentStack.length && this.indentStack[0] > indents) {
1579
+ this.stash.push(this.tok('outdent'));
1580
+ this.indentStack.shift();
1581
+ }
1582
+ tok = this.stash.pop();
1583
+ // indent
1584
+ } else if (indents && indents != this.indentStack[0]) {
1585
+ this.indentStack.unshift(indents);
1586
+ tok = this.tok('indent', indents);
1587
+ // newline
1588
+ } else {
1589
+ tok = this.tok('newline');
1590
+ }
1591
+
1592
+ return tok;
1593
+ }
1594
+ },
1595
+
1596
+ /**
1597
+ * Pipe-less text consumed only when
1598
+ * pipeless is true;
1599
+ */
1600
+
1601
+ pipelessText: function() {
1602
+ if (this.pipeless) {
1603
+ if ('\n' == this.input[0]) return;
1604
+ var i = this.input.indexOf('\n');
1605
+ if (-1 == i) i = this.input.length;
1606
+ var str = this.input.substr(0, i);
1607
+ this.consume(str.length);
1608
+ return this.tok('text', str);
1609
+ }
1610
+ },
1611
+
1612
+ /**
1613
+ * ':'
1614
+ */
1615
+
1616
+ colon: function() {
1617
+ return this.scan(/^: */, ':');
1618
+ },
1619
+
1620
+ /**
1621
+ * Return the next token object, or those
1622
+ * previously stashed by lookahead.
1623
+ *
1624
+ * @return {Object}
1625
+ * @api private
1626
+ */
1627
+
1628
+ advance: function(){
1629
+ return this.stashed()
1630
+ || this.next();
1631
+ },
1632
+
1633
+ /**
1634
+ * Return the next token object.
1635
+ *
1636
+ * @return {Object}
1637
+ * @api private
1638
+ */
1639
+
1640
+ next: function() {
1641
+ return this.deferred()
1642
+ || this.eos()
1643
+ || this.pipelessText()
1644
+ || this.yield()
1645
+ || this.doctype()
1646
+ || this.case()
1647
+ || this.when()
1648
+ || this.default()
1649
+ || this.extends()
1650
+ || this.append()
1651
+ || this.prepend()
1652
+ || this.block()
1653
+ || this.include()
1654
+ || this.mixin()
1655
+ || this.conditional()
1656
+ || this.each()
1657
+ || this.while()
1658
+ || this.assignment()
1659
+ || this.tag()
1660
+ || this.filter()
1661
+ || this.code()
1662
+ || this.id()
1663
+ || this.className()
1664
+ || this.attrs()
1665
+ || this.indent()
1666
+ || this.comment()
1667
+ || this.colon()
1668
+ || this.text();
1669
+ }
1670
+ };
1671
+
1672
+ }); // module: lexer.js
1673
+
1674
+ require.register("nodes/block-comment.js", function(module, exports, require){
1675
+
1676
+ /*!
1677
+ * Jade - nodes - BlockComment
1678
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1679
+ * MIT Licensed
1680
+ */
1681
+
1682
+ /**
1683
+ * Module dependencies.
1684
+ */
1685
+
1686
+ var Node = require('./node');
1687
+
1688
+ /**
1689
+ * Initialize a `BlockComment` with the given `block`.
1690
+ *
1691
+ * @param {String} val
1692
+ * @param {Block} block
1693
+ * @param {Boolean} buffer
1694
+ * @api public
1695
+ */
1696
+
1697
+ var BlockComment = module.exports = function BlockComment(val, block, buffer) {
1698
+ this.block = block;
1699
+ this.val = val;
1700
+ this.buffer = buffer;
1701
+ };
1702
+
1703
+ /**
1704
+ * Inherit from `Node`.
1705
+ */
1706
+
1707
+ BlockComment.prototype = new Node;
1708
+ BlockComment.prototype.constructor = BlockComment;
1709
+
1710
+ }); // module: nodes/block-comment.js
1711
+
1712
+ require.register("nodes/block.js", function(module, exports, require){
1713
+
1714
+ /*!
1715
+ * Jade - nodes - Block
1716
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
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
+ this.nodes = [];
1735
+ if (node) this.push(node);
1736
+ };
1737
+
1738
+ /**
1739
+ * Inherit from `Node`.
1740
+ */
1741
+
1742
+ Block.prototype = new Node;
1743
+ Block.prototype.constructor = Block;
1744
+
1745
+
1746
+ /**
1747
+ * Replace the nodes in `other` with the nodes
1748
+ * in `this` block.
1749
+ *
1750
+ * @param {Block} other
1751
+ * @api private
1752
+ */
1753
+
1754
+ Block.prototype.replace = function(other){
1755
+ other.nodes = this.nodes;
1756
+ };
1757
+
1758
+ /**
1759
+ * Pust the given `node`.
1760
+ *
1761
+ * @param {Node} node
1762
+ * @return {Number}
1763
+ * @api public
1764
+ */
1765
+
1766
+ Block.prototype.push = function(node){
1767
+ return this.nodes.push(node);
1768
+ };
1769
+
1770
+ /**
1771
+ * Check if this block is empty.
1772
+ *
1773
+ * @return {Boolean}
1774
+ * @api public
1775
+ */
1776
+
1777
+ Block.prototype.isEmpty = function(){
1778
+ return 0 == this.nodes.length;
1779
+ };
1780
+
1781
+ /**
1782
+ * Unshift the given `node`.
1783
+ *
1784
+ * @param {Node} node
1785
+ * @return {Number}
1786
+ * @api public
1787
+ */
1788
+
1789
+ Block.prototype.unshift = function(node){
1790
+ return this.nodes.unshift(node);
1791
+ };
1792
+
1793
+ /**
1794
+ * Return the "last" block, or the first `yield` node.
1795
+ *
1796
+ * @return {Block}
1797
+ * @api private
1798
+ */
1799
+
1800
+ Block.prototype.includeBlock = function(){
1801
+ var ret = this
1802
+ , node;
1803
+
1804
+ for (var i = 0, len = this.nodes.length; i < len; ++i) {
1805
+ node = this.nodes[i];
1806
+ if (node.yield) return node;
1807
+ else if (node.textOnly) continue;
1808
+ else if (node.includeBlock) ret = node.includeBlock();
1809
+ else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock();
1810
+ }
1811
+
1812
+ return ret;
1813
+ };
1814
+
1815
+
1816
+ }); // module: nodes/block.js
1817
+
1818
+ require.register("nodes/case.js", function(module, exports, require){
1819
+
1820
+ /*!
1821
+ * Jade - nodes - Case
1822
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1823
+ * MIT Licensed
1824
+ */
1825
+
1826
+ /**
1827
+ * Module dependencies.
1828
+ */
1829
+
1830
+ var Node = require('./node');
1831
+
1832
+ /**
1833
+ * Initialize a new `Case` with `expr`.
1834
+ *
1835
+ * @param {String} expr
1836
+ * @api public
1837
+ */
1838
+
1839
+ var Case = exports = module.exports = function Case(expr, block){
1840
+ this.expr = expr;
1841
+ this.block = block;
1842
+ };
1843
+
1844
+ /**
1845
+ * Inherit from `Node`.
1846
+ */
1847
+
1848
+ Case.prototype = new Node;
1849
+ Case.prototype.constructor = Case;
1850
+
1851
+
1852
+ var When = exports.When = function When(expr, block){
1853
+ this.expr = expr;
1854
+ this.block = block;
1855
+ this.debug = false;
1856
+ };
1857
+
1858
+ /**
1859
+ * Inherit from `Node`.
1860
+ */
1861
+
1862
+ When.prototype = new Node;
1863
+ When.prototype.constructor = When;
1864
+
1865
+
1866
+
1867
+ }); // module: nodes/case.js
1868
+
1869
+ require.register("nodes/code.js", function(module, exports, require){
1870
+
1871
+ /*!
1872
+ * Jade - nodes - Code
1873
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1874
+ * MIT Licensed
1875
+ */
1876
+
1877
+ /**
1878
+ * Module dependencies.
1879
+ */
1880
+
1881
+ var Node = require('./node');
1882
+
1883
+ /**
1884
+ * Initialize a `Code` node with the given code `val`.
1885
+ * Code may also be optionally buffered and escaped.
1886
+ *
1887
+ * @param {String} val
1888
+ * @param {Boolean} buffer
1889
+ * @param {Boolean} escape
1890
+ * @api public
1891
+ */
1892
+
1893
+ var Code = module.exports = function Code(val, buffer, escape) {
1894
+ this.val = val;
1895
+ this.buffer = buffer;
1896
+ this.escape = escape;
1897
+ if (val.match(/^ *else/)) this.debug = false;
1898
+ };
1899
+
1900
+ /**
1901
+ * Inherit from `Node`.
1902
+ */
1903
+
1904
+ Code.prototype = new Node;
1905
+ Code.prototype.constructor = Code;
1906
+
1907
+ }); // module: nodes/code.js
1908
+
1909
+ require.register("nodes/comment.js", function(module, exports, require){
1910
+
1911
+ /*!
1912
+ * Jade - nodes - Comment
1913
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1914
+ * MIT Licensed
1915
+ */
1916
+
1917
+ /**
1918
+ * Module dependencies.
1919
+ */
1920
+
1921
+ var Node = require('./node');
1922
+
1923
+ /**
1924
+ * Initialize a `Comment` with the given `val`, optionally `buffer`,
1925
+ * otherwise the comment may render in the output.
1926
+ *
1927
+ * @param {String} val
1928
+ * @param {Boolean} buffer
1929
+ * @api public
1930
+ */
1931
+
1932
+ var Comment = module.exports = function Comment(val, buffer) {
1933
+ this.val = val;
1934
+ this.buffer = buffer;
1935
+ };
1936
+
1937
+ /**
1938
+ * Inherit from `Node`.
1939
+ */
1940
+
1941
+ Comment.prototype = new Node;
1942
+ Comment.prototype.constructor = Comment;
1943
+
1944
+ }); // module: nodes/comment.js
1945
+
1946
+ require.register("nodes/doctype.js", function(module, exports, require){
1947
+
1948
+ /*!
1949
+ * Jade - nodes - Doctype
1950
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1951
+ * MIT Licensed
1952
+ */
1953
+
1954
+ /**
1955
+ * Module dependencies.
1956
+ */
1957
+
1958
+ var Node = require('./node');
1959
+
1960
+ /**
1961
+ * Initialize a `Doctype` with the given `val`.
1962
+ *
1963
+ * @param {String} val
1964
+ * @api public
1965
+ */
1966
+
1967
+ var Doctype = module.exports = function Doctype(val) {
1968
+ this.val = val;
1969
+ };
1970
+
1971
+ /**
1972
+ * Inherit from `Node`.
1973
+ */
1974
+
1975
+ Doctype.prototype = new Node;
1976
+ Doctype.prototype.constructor = Doctype;
1977
+
1978
+ }); // module: nodes/doctype.js
1979
+
1980
+ require.register("nodes/each.js", function(module, exports, require){
1981
+
1982
+ /*!
1983
+ * Jade - nodes - Each
1984
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
1985
+ * MIT Licensed
1986
+ */
1987
+
1988
+ /**
1989
+ * Module dependencies.
1990
+ */
1991
+
1992
+ var Node = require('./node');
1993
+
1994
+ /**
1995
+ * Initialize an `Each` node, representing iteration
1996
+ *
1997
+ * @param {String} obj
1998
+ * @param {String} val
1999
+ * @param {String} key
2000
+ * @param {Block} block
2001
+ * @api public
2002
+ */
2003
+
2004
+ var Each = module.exports = function Each(obj, val, key, block) {
2005
+ this.obj = obj;
2006
+ this.val = val;
2007
+ this.key = key;
2008
+ this.block = block;
2009
+ };
2010
+
2011
+ /**
2012
+ * Inherit from `Node`.
2013
+ */
2014
+
2015
+ Each.prototype = new Node;
2016
+ Each.prototype.constructor = Each;
2017
+
2018
+ }); // module: nodes/each.js
2019
+
2020
+ require.register("nodes/filter.js", function(module, exports, require){
2021
+
2022
+ /*!
2023
+ * Jade - nodes - Filter
2024
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2025
+ * MIT Licensed
2026
+ */
2027
+
2028
+ /**
2029
+ * Module dependencies.
2030
+ */
2031
+
2032
+ var Node = require('./node')
2033
+ , Block = require('./block');
2034
+
2035
+ /**
2036
+ * Initialize a `Filter` node with the given
2037
+ * filter `name` and `block`.
2038
+ *
2039
+ * @param {String} name
2040
+ * @param {Block|Node} block
2041
+ * @api public
2042
+ */
2043
+
2044
+ var Filter = module.exports = function Filter(name, block, attrs) {
2045
+ this.name = name;
2046
+ this.block = block;
2047
+ this.attrs = attrs;
2048
+ this.isASTFilter = block instanceof Block;
2049
+ };
2050
+
2051
+ /**
2052
+ * Inherit from `Node`.
2053
+ */
2054
+
2055
+ Filter.prototype = new Node;
2056
+ Filter.prototype.constructor = Filter;
2057
+
2058
+ }); // module: nodes/filter.js
2059
+
2060
+ require.register("nodes/index.js", function(module, exports, require){
2061
+
2062
+ /*!
2063
+ * Jade - nodes
2064
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2065
+ * MIT Licensed
2066
+ */
2067
+
2068
+ exports.Node = require('./node');
2069
+ exports.Tag = require('./tag');
2070
+ exports.Code = require('./code');
2071
+ exports.Each = require('./each');
2072
+ exports.Case = require('./case');
2073
+ exports.Text = require('./text');
2074
+ exports.Block = require('./block');
2075
+ exports.Mixin = require('./mixin');
2076
+ exports.Filter = require('./filter');
2077
+ exports.Comment = require('./comment');
2078
+ exports.Literal = require('./literal');
2079
+ exports.BlockComment = require('./block-comment');
2080
+ exports.Doctype = require('./doctype');
2081
+
2082
+ }); // module: nodes/index.js
2083
+
2084
+ require.register("nodes/literal.js", function(module, exports, require){
2085
+
2086
+ /*!
2087
+ * Jade - nodes - Literal
2088
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2089
+ * MIT Licensed
2090
+ */
2091
+
2092
+ /**
2093
+ * Module dependencies.
2094
+ */
2095
+
2096
+ var Node = require('./node');
2097
+
2098
+ /**
2099
+ * Initialize a `Literal` node with the given `str.
2100
+ *
2101
+ * @param {String} str
2102
+ * @api public
2103
+ */
2104
+
2105
+ var Literal = module.exports = function Literal(str) {
2106
+ this.str = str
2107
+ .replace(/\n/g, "\\n")
2108
+ .replace(/'/g, "\\'");
2109
+ };
2110
+
2111
+ /**
2112
+ * Inherit from `Node`.
2113
+ */
2114
+
2115
+ Literal.prototype = new Node;
2116
+ Literal.prototype.constructor = Literal;
2117
+
2118
+
2119
+ }); // module: nodes/literal.js
2120
+
2121
+ require.register("nodes/mixin.js", function(module, exports, require){
2122
+
2123
+ /*!
2124
+ * Jade - nodes - Mixin
2125
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2126
+ * MIT Licensed
2127
+ */
2128
+
2129
+ /**
2130
+ * Module dependencies.
2131
+ */
2132
+
2133
+ var Node = require('./node');
2134
+
2135
+ /**
2136
+ * Initialize a new `Mixin` with `name` and `block`.
2137
+ *
2138
+ * @param {String} name
2139
+ * @param {String} args
2140
+ * @param {Block} block
2141
+ * @api public
2142
+ */
2143
+
2144
+ var Mixin = module.exports = function Mixin(name, args, block){
2145
+ this.name = name;
2146
+ this.args = args;
2147
+ this.block = block;
2148
+ };
2149
+
2150
+ /**
2151
+ * Inherit from `Node`.
2152
+ */
2153
+
2154
+ Mixin.prototype = new Node;
2155
+ Mixin.prototype.constructor = Mixin;
2156
+
2157
+
2158
+
2159
+ }); // module: nodes/mixin.js
2160
+
2161
+ require.register("nodes/node.js", function(module, exports, require){
2162
+
2163
+ /*!
2164
+ * Jade - nodes - Node
2165
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2166
+ * MIT Licensed
2167
+ */
2168
+
2169
+ /**
2170
+ * Initialize a `Node`.
2171
+ *
2172
+ * @api public
2173
+ */
2174
+
2175
+ var Node = module.exports = function Node(){};
2176
+ }); // module: nodes/node.js
2177
+
2178
+ require.register("nodes/tag.js", function(module, exports, require){
2179
+
2180
+ /*!
2181
+ * Jade - nodes - Tag
2182
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2183
+ * MIT Licensed
2184
+ */
2185
+
2186
+ /**
2187
+ * Module dependencies.
2188
+ */
2189
+
2190
+ var Node = require('./node'),
2191
+ Block = require('./block');
2192
+
2193
+ /**
2194
+ * Initialize a `Tag` node with the given tag `name` and optional `block`.
2195
+ *
2196
+ * @param {String} name
2197
+ * @param {Block} block
2198
+ * @api public
2199
+ */
2200
+
2201
+ var Tag = module.exports = function Tag(name, block) {
2202
+ this.name = name;
2203
+ this.attrs = [];
2204
+ this.block = block || new Block;
2205
+ };
2206
+
2207
+ /**
2208
+ * Inherit from `Node`.
2209
+ */
2210
+
2211
+ Tag.prototype = new Node;
2212
+ Tag.prototype.constructor = Tag;
2213
+
2214
+
2215
+ /**
2216
+ * Set attribute `name` to `val`, keep in mind these become
2217
+ * part of a raw js object literal, so to quote a value you must
2218
+ * '"quote me"', otherwise or example 'user.name' is literal JavaScript.
2219
+ *
2220
+ * @param {String} name
2221
+ * @param {String} val
2222
+ * @return {Tag} for chaining
2223
+ * @api public
2224
+ */
2225
+
2226
+ Tag.prototype.setAttribute = function(name, val){
2227
+ this.attrs.push({ name: name, val: val });
2228
+ return this;
2229
+ };
2230
+
2231
+ /**
2232
+ * Remove attribute `name` when present.
2233
+ *
2234
+ * @param {String} name
2235
+ * @api public
2236
+ */
2237
+
2238
+ Tag.prototype.removeAttribute = function(name){
2239
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
2240
+ if (this.attrs[i] && this.attrs[i].name == name) {
2241
+ delete this.attrs[i];
2242
+ }
2243
+ }
2244
+ };
2245
+
2246
+ /**
2247
+ * Get attribute value by `name`.
2248
+ *
2249
+ * @param {String} name
2250
+ * @return {String}
2251
+ * @api public
2252
+ */
2253
+
2254
+ Tag.prototype.getAttribute = function(name){
2255
+ for (var i = 0, len = this.attrs.length; i < len; ++i) {
2256
+ if (this.attrs[i] && this.attrs[i].name == name) {
2257
+ return this.attrs[i].val;
2258
+ }
2259
+ }
2260
+ };
2261
+
2262
+ }); // module: nodes/tag.js
2263
+
2264
+ require.register("nodes/text.js", function(module, exports, require){
2265
+
2266
+ /*!
2267
+ * Jade - nodes - Text
2268
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2269
+ * MIT Licensed
2270
+ */
2271
+
2272
+ /**
2273
+ * Module dependencies.
2274
+ */
2275
+
2276
+ var Node = require('./node');
2277
+
2278
+ /**
2279
+ * Initialize a `Text` node with optional `line`.
2280
+ *
2281
+ * @param {String} line
2282
+ * @api public
2283
+ */
2284
+
2285
+ var Text = module.exports = function Text(line) {
2286
+ this.nodes = [];
2287
+ if ('string' == typeof line) this.push(line);
2288
+ };
2289
+
2290
+ /**
2291
+ * Inherit from `Node`.
2292
+ */
2293
+
2294
+ Text.prototype = new Node;
2295
+ Text.prototype.constructor = Text;
2296
+
2297
+
2298
+ /**
2299
+ * Push the given `node.`
2300
+ *
2301
+ * @param {Node} node
2302
+ * @return {Number}
2303
+ * @api public
2304
+ */
2305
+
2306
+ Text.prototype.push = function(node){
2307
+ return this.nodes.push(node);
2308
+ };
2309
+
2310
+ }); // module: nodes/text.js
2311
+
2312
+ require.register("parser.js", function(module, exports, require){
2313
+
2314
+ /*!
2315
+ * Jade - Parser
2316
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2317
+ * MIT Licensed
2318
+ */
2319
+
2320
+ /**
2321
+ * Module dependencies.
2322
+ */
2323
+
2324
+ var Lexer = require('./lexer')
2325
+ , nodes = require('./nodes');
2326
+
2327
+ /**
2328
+ * Initialize `Parser` with the given input `str` and `filename`.
2329
+ *
2330
+ * @param {String} str
2331
+ * @param {String} filename
2332
+ * @param {Object} options
2333
+ * @api public
2334
+ */
2335
+
2336
+ var Parser = exports = module.exports = function Parser(str, filename, options){
2337
+ this.input = str;
2338
+ this.lexer = new Lexer(str, options);
2339
+ this.filename = filename;
2340
+ this.blocks = {};
2341
+ this.options = options;
2342
+ this.contexts = [this];
2343
+ };
2344
+
2345
+ /**
2346
+ * Tags that may not contain tags.
2347
+ */
2348
+
2349
+ var textOnly = exports.textOnly = ['script', 'style'];
2350
+
2351
+ /**
2352
+ * Parser prototype.
2353
+ */
2354
+
2355
+ Parser.prototype = {
2356
+
2357
+ /**
2358
+ * Push `parser` onto the context stack,
2359
+ * or pop and return a `Parser`.
2360
+ */
2361
+
2362
+ context: function(parser){
2363
+ if (parser) {
2364
+ this.contexts.push(parser);
2365
+ } else {
2366
+ return this.contexts.pop();
2367
+ }
2368
+ },
2369
+
2370
+ /**
2371
+ * Return the next token object.
2372
+ *
2373
+ * @return {Object}
2374
+ * @api private
2375
+ */
2376
+
2377
+ advance: function(){
2378
+ return this.lexer.advance();
2379
+ },
2380
+
2381
+ /**
2382
+ * Skip `n` tokens.
2383
+ *
2384
+ * @param {Number} n
2385
+ * @api private
2386
+ */
2387
+
2388
+ skip: function(n){
2389
+ while (n--) this.advance();
2390
+ },
2391
+
2392
+ /**
2393
+ * Single token lookahead.
2394
+ *
2395
+ * @return {Object}
2396
+ * @api private
2397
+ */
2398
+
2399
+ peek: function() {
2400
+ return this.lookahead(1);
2401
+ },
2402
+
2403
+ /**
2404
+ * Return lexer lineno.
2405
+ *
2406
+ * @return {Number}
2407
+ * @api private
2408
+ */
2409
+
2410
+ line: function() {
2411
+ return this.lexer.lineno;
2412
+ },
2413
+
2414
+ /**
2415
+ * `n` token lookahead.
2416
+ *
2417
+ * @param {Number} n
2418
+ * @return {Object}
2419
+ * @api private
2420
+ */
2421
+
2422
+ lookahead: function(n){
2423
+ return this.lexer.lookahead(n);
2424
+ },
2425
+
2426
+ /**
2427
+ * Parse input returning a string of js for evaluation.
2428
+ *
2429
+ * @return {String}
2430
+ * @api public
2431
+ */
2432
+
2433
+ parse: function(){
2434
+ var block = new nodes.Block, parser;
2435
+ block.line = this.line();
2436
+
2437
+ while ('eos' != this.peek().type) {
2438
+ if ('newline' == this.peek().type) {
2439
+ this.advance();
2440
+ } else {
2441
+ block.push(this.parseExpr());
2442
+ }
2443
+ }
2444
+
2445
+ if (parser = this.extending) {
2446
+ this.context(parser);
2447
+ var ast = parser.parse();
2448
+ this.context();
2449
+ return ast;
2450
+ }
2451
+
2452
+ return block;
2453
+ },
2454
+
2455
+ /**
2456
+ * Expect the given type, or throw an exception.
2457
+ *
2458
+ * @param {String} type
2459
+ * @api private
2460
+ */
2461
+
2462
+ expect: function(type){
2463
+ if (this.peek().type === type) {
2464
+ return this.advance();
2465
+ } else {
2466
+ throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
2467
+ }
2468
+ },
2469
+
2470
+ /**
2471
+ * Accept the given `type`.
2472
+ *
2473
+ * @param {String} type
2474
+ * @api private
2475
+ */
2476
+
2477
+ accept: function(type){
2478
+ if (this.peek().type === type) {
2479
+ return this.advance();
2480
+ }
2481
+ },
2482
+
2483
+ /**
2484
+ * tag
2485
+ * | doctype
2486
+ * | mixin
2487
+ * | include
2488
+ * | filter
2489
+ * | comment
2490
+ * | text
2491
+ * | each
2492
+ * | code
2493
+ * | yield
2494
+ * | id
2495
+ * | class
2496
+ */
2497
+
2498
+ parseExpr: function(){
2499
+ switch (this.peek().type) {
2500
+ case 'tag':
2501
+ return this.parseTag();
2502
+ case 'mixin':
2503
+ return this.parseMixin();
2504
+ case 'block':
2505
+ return this.parseBlock();
2506
+ case 'case':
2507
+ return this.parseCase();
2508
+ case 'when':
2509
+ return this.parseWhen();
2510
+ case 'default':
2511
+ return this.parseDefault();
2512
+ case 'extends':
2513
+ return this.parseExtends();
2514
+ case 'include':
2515
+ return this.parseInclude();
2516
+ case 'doctype':
2517
+ return this.parseDoctype();
2518
+ case 'filter':
2519
+ return this.parseFilter();
2520
+ case 'comment':
2521
+ return this.parseComment();
2522
+ case 'text':
2523
+ return this.parseText();
2524
+ case 'each':
2525
+ return this.parseEach();
2526
+ case 'code':
2527
+ return this.parseCode();
2528
+ case 'yield':
2529
+ this.advance();
2530
+ var block = new nodes.Block;
2531
+ block.yield = true;
2532
+ return block;
2533
+ case 'id':
2534
+ case 'class':
2535
+ var tok = this.advance();
2536
+ this.lexer.defer(this.lexer.tok('tag', 'div'));
2537
+ this.lexer.defer(tok);
2538
+ return this.parseExpr();
2539
+ default:
2540
+ throw new Error('unexpected token "' + this.peek().type + '"');
2541
+ }
2542
+ },
2543
+
2544
+ /**
2545
+ * Text
2546
+ */
2547
+
2548
+ parseText: function(){
2549
+ var tok = this.expect('text')
2550
+ , node = new nodes.Text(tok.val);
2551
+ node.line = this.line();
2552
+ return node;
2553
+ },
2554
+
2555
+ /**
2556
+ * ':' expr
2557
+ * | block
2558
+ */
2559
+
2560
+ parseBlockExpansion: function(){
2561
+ if (':' == this.peek().type) {
2562
+ this.advance();
2563
+ return new nodes.Block(this.parseExpr());
2564
+ } else {
2565
+ return this.block();
2566
+ }
2567
+ },
2568
+
2569
+ /**
2570
+ * case
2571
+ */
2572
+
2573
+ parseCase: function(){
2574
+ var val = this.expect('case').val
2575
+ , node = new nodes.Case(val);
2576
+ node.line = this.line();
2577
+ node.block = this.block();
2578
+ return node;
2579
+ },
2580
+
2581
+ /**
2582
+ * when
2583
+ */
2584
+
2585
+ parseWhen: function(){
2586
+ var val = this.expect('when').val
2587
+ return new nodes.Case.When(val, this.parseBlockExpansion());
2588
+ },
2589
+
2590
+ /**
2591
+ * default
2592
+ */
2593
+
2594
+ parseDefault: function(){
2595
+ this.expect('default');
2596
+ return new nodes.Case.When('default', this.parseBlockExpansion());
2597
+ },
2598
+
2599
+ /**
2600
+ * code
2601
+ */
2602
+
2603
+ parseCode: function(){
2604
+ var tok = this.expect('code')
2605
+ , node = new nodes.Code(tok.val, tok.buffer, tok.escape)
2606
+ , block
2607
+ , i = 1;
2608
+ node.line = this.line();
2609
+ while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i;
2610
+ block = 'indent' == this.lookahead(i).type;
2611
+ if (block) {
2612
+ this.skip(i-1);
2613
+ node.block = this.block();
2614
+ }
2615
+ return node;
2616
+ },
2617
+
2618
+ /**
2619
+ * comment
2620
+ */
2621
+
2622
+ parseComment: function(){
2623
+ var tok = this.expect('comment')
2624
+ , node;
2625
+
2626
+ if ('indent' == this.peek().type) {
2627
+ node = new nodes.BlockComment(tok.val, this.block(), tok.buffer);
2628
+ } else {
2629
+ node = new nodes.Comment(tok.val, tok.buffer);
2630
+ }
2631
+
2632
+ node.line = this.line();
2633
+ return node;
2634
+ },
2635
+
2636
+ /**
2637
+ * doctype
2638
+ */
2639
+
2640
+ parseDoctype: function(){
2641
+ var tok = this.expect('doctype')
2642
+ , node = new nodes.Doctype(tok.val);
2643
+ node.line = this.line();
2644
+ return node;
2645
+ },
2646
+
2647
+ /**
2648
+ * filter attrs? text-block
2649
+ */
2650
+
2651
+ parseFilter: function(){
2652
+ var block
2653
+ , tok = this.expect('filter')
2654
+ , attrs = this.accept('attrs');
2655
+
2656
+ this.lexer.pipeless = true;
2657
+ block = this.parseTextBlock();
2658
+ this.lexer.pipeless = false;
2659
+
2660
+ var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
2661
+ node.line = this.line();
2662
+ return node;
2663
+ },
2664
+
2665
+ /**
2666
+ * tag ':' attrs? block
2667
+ */
2668
+
2669
+ parseASTFilter: function(){
2670
+ var block
2671
+ , tok = this.expect('tag')
2672
+ , attrs = this.accept('attrs');
2673
+
2674
+ this.expect(':');
2675
+ block = this.block();
2676
+
2677
+ var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
2678
+ node.line = this.line();
2679
+ return node;
2680
+ },
2681
+
2682
+ /**
2683
+ * each block
2684
+ */
2685
+
2686
+ parseEach: function(){
2687
+ var tok = this.expect('each')
2688
+ , node = new nodes.Each(tok.code, tok.val, tok.key);
2689
+ node.line = this.line();
2690
+ node.block = this.block();
2691
+ return node;
2692
+ },
2693
+
2694
+ /**
2695
+ * 'extends' name
2696
+ */
2697
+
2698
+ parseExtends: function(){
2699
+ var path = require('path')
2700
+ , fs = require('fs')
2701
+ , dirname = path.dirname
2702
+ , basename = path.basename
2703
+ , join = path.join;
2704
+
2705
+ if (!this.filename)
2706
+ throw new Error('the "filename" option is required to extend templates');
2707
+
2708
+ var path = this.expect('extends').val.trim()
2709
+ , dir = dirname(this.filename);
2710
+
2711
+ var path = join(dir, path + '.jade')
2712
+ , str = fs.readFileSync(path, 'utf8')
2713
+ , parser = new Parser(str, path, this.options);
2714
+
2715
+ parser.blocks = this.blocks;
2716
+ parser.contexts = this.contexts;
2717
+ this.extending = parser;
2718
+
2719
+ // TODO: null node
2720
+ return new nodes.Literal('');
2721
+ },
2722
+
2723
+ /**
2724
+ * 'block' name block
2725
+ */
2726
+
2727
+ parseBlock: function(){
2728
+ var block = this.expect('block')
2729
+ , mode = block.mode
2730
+ , name = block.val.trim();
2731
+
2732
+ block = 'indent' == this.peek().type
2733
+ ? this.block()
2734
+ : new nodes.Block(new nodes.Literal(''));
2735
+
2736
+ var prev = this.blocks[name];
2737
+
2738
+ if (prev) {
2739
+ switch (prev.mode) {
2740
+ case 'append':
2741
+ block.nodes = block.nodes.concat(prev.nodes);
2742
+ prev = block;
2743
+ break;
2744
+ case 'prepend':
2745
+ block.nodes = prev.nodes.concat(block.nodes);
2746
+ prev = block;
2747
+ break;
2748
+ }
2749
+ }
2750
+
2751
+ block.mode = mode;
2752
+ return this.blocks[name] = prev || block;
2753
+ },
2754
+
2755
+ /**
2756
+ * include block?
2757
+ */
2758
+
2759
+ parseInclude: function(){
2760
+ var path = require('path')
2761
+ , fs = require('fs')
2762
+ , dirname = path.dirname
2763
+ , basename = path.basename
2764
+ , join = path.join;
2765
+
2766
+ var path = this.expect('include').val.trim()
2767
+ , dir = dirname(this.filename);
2768
+
2769
+ if (!this.filename)
2770
+ throw new Error('the "filename" option is required to use includes');
2771
+
2772
+ // no extension
2773
+ if (!~basename(path).indexOf('.')) {
2774
+ path += '.jade';
2775
+ }
2776
+
2777
+ // non-jade
2778
+ if ('.jade' != path.substr(-5)) {
2779
+ var path = join(dir, path)
2780
+ , str = fs.readFileSync(path, 'utf8');
2781
+ return new nodes.Literal(str);
2782
+ }
2783
+
2784
+ var path = join(dir, path)
2785
+ , str = fs.readFileSync(path, 'utf8')
2786
+ , parser = new Parser(str, path, this.options);
2787
+
2788
+ this.context(parser);
2789
+ var ast = parser.parse();
2790
+ this.context();
2791
+ ast.filename = path;
2792
+
2793
+ if ('indent' == this.peek().type) {
2794
+ ast.includeBlock().push(this.block());
2795
+ }
2796
+
2797
+ return ast;
2798
+ },
2799
+
2800
+ /**
2801
+ * mixin block
2802
+ */
2803
+
2804
+ parseMixin: function(){
2805
+ var tok = this.expect('mixin')
2806
+ , name = tok.val
2807
+ , args = tok.args;
2808
+ var block = 'indent' == this.peek().type
2809
+ ? this.block()
2810
+ : null;
2811
+ return new nodes.Mixin(name, args, block);
2812
+ },
2813
+
2814
+ /**
2815
+ * indent (text | newline)* outdent
2816
+ */
2817
+
2818
+ parseTextBlock: function(){
2819
+ var text = new nodes.Text;
2820
+ text.line = this.line();
2821
+ var spaces = this.expect('indent').val;
2822
+ if (null == this._spaces) this._spaces = spaces;
2823
+ var indent = Array(spaces - this._spaces + 1).join(' ');
2824
+ while ('outdent' != this.peek().type) {
2825
+ switch (this.peek().type) {
2826
+ case 'newline':
2827
+ text.push('\\n');
2828
+ this.advance();
2829
+ break;
2830
+ case 'indent':
2831
+ text.push('\\n');
2832
+ this.parseTextBlock().nodes.forEach(function(node){
2833
+ text.push(node);
2834
+ });
2835
+ text.push('\\n');
2836
+ break;
2837
+ default:
2838
+ text.push(indent + this.advance().val);
2839
+ }
2840
+ }
2841
+
2842
+ if (spaces == this._spaces) this._spaces = null;
2843
+ this.expect('outdent');
2844
+ return text;
2845
+ },
2846
+
2847
+ /**
2848
+ * indent expr* outdent
2849
+ */
2850
+
2851
+ block: function(){
2852
+ var block = new nodes.Block;
2853
+ block.line = this.line();
2854
+ this.expect('indent');
2855
+ while ('outdent' != this.peek().type) {
2856
+ if ('newline' == this.peek().type) {
2857
+ this.advance();
2858
+ } else {
2859
+ block.push(this.parseExpr());
2860
+ }
2861
+ }
2862
+ this.expect('outdent');
2863
+ return block;
2864
+ },
2865
+
2866
+ /**
2867
+ * tag (attrs | class | id)* (text | code | ':')? newline* block?
2868
+ */
2869
+
2870
+ parseTag: function(){
2871
+ // ast-filter look-ahead
2872
+ var i = 2;
2873
+ if ('attrs' == this.lookahead(i).type) ++i;
2874
+ if (':' == this.lookahead(i).type) {
2875
+ if ('indent' == this.lookahead(++i).type) {
2876
+ return this.parseASTFilter();
2877
+ }
2878
+ }
2879
+
2880
+ var name = this.advance().val
2881
+ , tag = new nodes.Tag(name)
2882
+ , dot;
2883
+
2884
+ tag.line = this.line();
2885
+
2886
+ // (attrs | class | id)*
2887
+ out:
2888
+ while (true) {
2889
+ switch (this.peek().type) {
2890
+ case 'id':
2891
+ case 'class':
2892
+ var tok = this.advance();
2893
+ tag.setAttribute(tok.type, "'" + tok.val + "'");
2894
+ continue;
2895
+ case 'attrs':
2896
+ var obj = this.advance().attrs
2897
+ , names = Object.keys(obj);
2898
+ for (var i = 0, len = names.length; i < len; ++i) {
2899
+ var name = names[i]
2900
+ , val = obj[name];
2901
+ tag.setAttribute(name, val);
2902
+ }
2903
+ continue;
2904
+ default:
2905
+ break out;
2906
+ }
2907
+ }
2908
+
2909
+ // check immediate '.'
2910
+ if ('.' == this.peek().val) {
2911
+ dot = tag.textOnly = true;
2912
+ this.advance();
2913
+ }
2914
+
2915
+ // (text | code | ':')?
2916
+ switch (this.peek().type) {
2917
+ case 'text':
2918
+ tag.text = this.parseText();
2919
+ break;
2920
+ case 'code':
2921
+ tag.code = this.parseCode();
2922
+ break;
2923
+ case ':':
2924
+ this.advance();
2925
+ tag.block = new nodes.Block;
2926
+ tag.block.push(this.parseTag());
2927
+ break;
2928
+ }
2929
+
2930
+ // newline*
2931
+ while ('newline' == this.peek().type) this.advance();
2932
+
2933
+ tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name);
2934
+
2935
+ // script special-case
2936
+ if ('script' == tag.name) {
2937
+ var type = tag.getAttribute('type');
2938
+ if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) {
2939
+ tag.textOnly = false;
2940
+ }
2941
+ }
2942
+
2943
+ // block?
2944
+ if ('indent' == this.peek().type) {
2945
+ if (tag.textOnly) {
2946
+ this.lexer.pipeless = true;
2947
+ tag.block = this.parseTextBlock();
2948
+ this.lexer.pipeless = false;
2949
+ } else {
2950
+ var block = this.block();
2951
+ if (tag.block) {
2952
+ for (var i = 0, len = block.nodes.length; i < len; ++i) {
2953
+ tag.block.push(block.nodes[i]);
2954
+ }
2955
+ } else {
2956
+ tag.block = block;
2957
+ }
2958
+ }
2959
+ }
2960
+
2961
+ return tag;
2962
+ }
2963
+ };
2964
+
2965
+ }); // module: parser.js
2966
+
2967
+ require.register("runtime.js", function(module, exports, require){
2968
+
2969
+ /*!
2970
+ * Jade - runtime
2971
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
2972
+ * MIT Licensed
2973
+ */
2974
+
2975
+ /**
2976
+ * Lame Array.isArray() polyfill for now.
2977
+ */
2978
+
2979
+ if (!Array.isArray) {
2980
+ Array.isArray = function(arr){
2981
+ return '[object Array]' == Object.prototype.toString.call(arr);
2982
+ };
2983
+ }
2984
+
2985
+ /**
2986
+ * Lame Object.keys() polyfill for now.
2987
+ */
2988
+
2989
+ if (!Object.keys) {
2990
+ Object.keys = function(obj){
2991
+ var arr = [];
2992
+ for (var key in obj) {
2993
+ if (obj.hasOwnProperty(key)) {
2994
+ arr.push(key);
2995
+ }
2996
+ }
2997
+ return arr;
2998
+ }
2999
+ }
3000
+
3001
+ /**
3002
+ * Render the given attributes object.
3003
+ *
3004
+ * @param {Object} obj
3005
+ * @return {String}
3006
+ * @api private
3007
+ */
3008
+
3009
+ exports.attrs = function attrs(obj){
3010
+ var buf = []
3011
+ , terse = obj.terse;
3012
+ delete obj.terse;
3013
+ var keys = Object.keys(obj)
3014
+ , len = keys.length;
3015
+ if (len) {
3016
+ buf.push('');
3017
+ for (var i = 0; i < len; ++i) {
3018
+ var key = keys[i]
3019
+ , val = obj[key];
3020
+ if ('boolean' == typeof val || null == val) {
3021
+ if (val) {
3022
+ terse
3023
+ ? buf.push(key)
3024
+ : buf.push(key + '="' + key + '"');
3025
+ }
3026
+ } else if ('class' == key && Array.isArray(val)) {
3027
+ buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
3028
+ } else {
3029
+ buf.push(key + '="' + exports.escape(val) + '"');
3030
+ }
3031
+ }
3032
+ }
3033
+ return buf.join(' ');
3034
+ };
3035
+
3036
+ /**
3037
+ * Escape the given string of `html`.
3038
+ *
3039
+ * @param {String} html
3040
+ * @return {String}
3041
+ * @api private
3042
+ */
3043
+
3044
+ exports.escape = function escape(html){
3045
+ return String(html)
3046
+ .replace(/&(?!\w+;)/g, '&amp;')
3047
+ .replace(/</g, '&lt;')
3048
+ .replace(/>/g, '&gt;')
3049
+ .replace(/"/g, '&quot;');
3050
+ };
3051
+
3052
+ /**
3053
+ * Re-throw the given `err` in context to the
3054
+ * the jade in `filename` at the given `lineno`.
3055
+ *
3056
+ * @param {Error} err
3057
+ * @param {String} filename
3058
+ * @param {String} lineno
3059
+ * @api private
3060
+ */
3061
+
3062
+ exports.rethrow = function rethrow(err, filename, lineno){
3063
+ if (!filename) throw err;
3064
+
3065
+ // If we can't catch the context we still output line and file
3066
+ try {
3067
+ var context = 3
3068
+ , str = require('fs').readFileSync(filename, 'utf8')
3069
+ , lines = str.split('\n')
3070
+ , start = Math.max(lineno - context, 0)
3071
+ , end = Math.min(lines.length, lineno + context);
3072
+
3073
+ // Error context
3074
+ var context = lines.slice(start, end).map(function(line, i){
3075
+ var curr = i + start + 1;
3076
+ return (curr == lineno ? ' > ' : ' ')
3077
+ + curr
3078
+ + '| '
3079
+ + line;
3080
+ }).join('\n') + '\n\n';
3081
+ } catch(failure) {
3082
+ var context = '';
3083
+ }
3084
+
3085
+ // Alter exception message
3086
+ err.path = filename;
3087
+ err.message = (filename || 'Jade') + ':' + lineno
3088
+ + '\n' + context + err.message;
3089
+ throw err;
3090
+ };
3091
+
3092
+ }); // module: runtime.js
3093
+
3094
+ require.register("self-closing.js", function(module, exports, require){
3095
+
3096
+ /*!
3097
+ * Jade - self closing tags
3098
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
3099
+ * MIT Licensed
3100
+ */
3101
+
3102
+ module.exports = [
3103
+ 'meta'
3104
+ , 'img'
3105
+ , 'link'
3106
+ , 'input'
3107
+ , 'area'
3108
+ , 'base'
3109
+ , 'col'
3110
+ , 'br'
3111
+ , 'hr'
3112
+ ];
3113
+ }); // module: self-closing.js
3114
+
3115
+ require.register("utils.js", function(module, exports, require){
3116
+
3117
+ /*!
3118
+ * Jade - utils
3119
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
3120
+ * MIT Licensed
3121
+ */
3122
+
3123
+ /**
3124
+ * Convert interpolation in the given string to JavaScript.
3125
+ *
3126
+ * @param {String} str
3127
+ * @return {String}
3128
+ * @api private
3129
+ */
3130
+
3131
+ var interpolate = exports.interpolate = function(str){
3132
+ return str.replace(/(\\)?([#!]){(.*?)}/g, function(str, escape, flag, code){
3133
+ return escape
3134
+ ? str
3135
+ : "' + "
3136
+ + ('!' == flag ? '' : 'escape')
3137
+ + "((interp = " + code.replace(/\\'/g, "'")
3138
+ + ") == null ? '' : interp) + '";
3139
+ });
3140
+ };
3141
+
3142
+ /**
3143
+ * Escape single quotes in `str`.
3144
+ *
3145
+ * @param {String} str
3146
+ * @return {String}
3147
+ * @api private
3148
+ */
3149
+
3150
+ var escape = exports.escape = function(str) {
3151
+ return str.replace(/'/g, "\\'");
3152
+ };
3153
+
3154
+ /**
3155
+ * Interpolate, and escape the given `str`.
3156
+ *
3157
+ * @param {String} str
3158
+ * @return {String}
3159
+ * @api private
3160
+ */
3161
+
3162
+ exports.text = function(str){
3163
+ return interpolate(escape(str));
3164
+ };
3165
+ }); // module: utils.js
3166
+
3167
+ window.jade = require("jade");
3168
+ })();