jader 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +8 -8
- data/jader.gemspec +2 -1
- data/lib/jader/compiler.rb +53 -24
- data/lib/jader/configuration.rb +9 -0
- data/lib/jader/renderer.rb +11 -0
- data/lib/jader/serialize.rb +28 -9
- data/lib/jader/source.rb +6 -0
- data/lib/jader/template.rb +7 -8
- data/lib/jader/version.rb +1 -1
- data/spec/compiler_spec.rb +5 -3
- data/spec/template_spec.rb +5 -3
- data/vendor/assets/javascripts/jade/jade.js +668 -215
- data/vendor/assets/javascripts/jade/runtime.js +77 -26
- metadata +19 -2
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -43,7 +43,7 @@ The most significant differences between using standard server-side Ruby-based e
|
|
43
43
|
|
44
44
|
Our template code should look like this:
|
45
45
|
|
46
|
-
```
|
46
|
+
```jade
|
47
47
|
ul.users
|
48
48
|
each user in users
|
49
49
|
li.user= user.name
|
@@ -58,7 +58,7 @@ Since Rails doesn't expect server-side templates to live under `app/assets` we n
|
|
58
58
|
|
59
59
|
Inside your `application_controller.rb` add the following:
|
60
60
|
|
61
|
-
```
|
61
|
+
```ruby
|
62
62
|
class ApplicationController < ActionController::Base
|
63
63
|
protect_from_forgery
|
64
64
|
|
@@ -131,7 +131,7 @@ Jader does not assume your Rails models should be serialized by default. Instead
|
|
131
131
|
|
132
132
|
To enable this behaviour, consider the following example:
|
133
133
|
|
134
|
-
```
|
134
|
+
```ruby
|
135
135
|
class User < ActiveRecord::Base
|
136
136
|
|
137
137
|
include Jader::Serialize
|
@@ -149,7 +149,7 @@ By default, calling `jade_serializable` with no arguments will serialize all you
|
|
149
149
|
|
150
150
|
Consider the following code:
|
151
151
|
|
152
|
-
```
|
152
|
+
```ruby
|
153
153
|
# define our model
|
154
154
|
class User < ActiveRecord::Base
|
155
155
|
|
@@ -180,7 +180,7 @@ which attributes should always be serialized, and whether we'd like these attrib
|
|
180
180
|
Consider the following code:
|
181
181
|
|
182
182
|
|
183
|
-
```
|
183
|
+
```ruby
|
184
184
|
# define our models
|
185
185
|
|
186
186
|
class Favorite < ActiveRecord::Base
|
@@ -221,7 +221,7 @@ To only serialize the specified attributes, call `jade_serializable` with `:merg
|
|
221
221
|
|
222
222
|
Invokation format for `jade_serializable` is:
|
223
223
|
|
224
|
-
```
|
224
|
+
```ruby
|
225
225
|
jade_serializable :attr1, :attr2, :attr3 ...., :merge => true/false
|
226
226
|
```
|
227
227
|
|
@@ -284,7 +284,7 @@ For the sake of this example, we assume `gem 'i18n-js'` is installed in our appl
|
|
284
284
|
|
285
285
|
Our `application.js` file will then include:
|
286
286
|
|
287
|
-
```
|
287
|
+
``` javascript
|
288
288
|
//= require i18n
|
289
289
|
//= require i18n/translations
|
290
290
|
```
|
@@ -332,7 +332,7 @@ Jader is built upon the wonderful work of [Boris Staal](https://github.com/round
|
|
332
332
|
- [ruby-haml-js](https://github.com/dnagir/ruby-haml-js)
|
333
333
|
- [tilt-jade](https://github.com/therabidbanana/tilt-jade)
|
334
334
|
|
335
|
-
Boris Staal's Jade Rubygem
|
335
|
+
Boris Staal's [Jade](https://github.com/roundlake/jade/) Rubygem was developed as a successor to tilt-jade to improve following:
|
336
336
|
|
337
337
|
* Add debugging capabilities (slightly different build technique)
|
338
338
|
* Support exact Jade.JS lib without modifications
|
data/jader.gemspec
CHANGED
@@ -18,11 +18,12 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency '
|
21
|
+
s.add_dependency 'libv8'
|
22
22
|
s.add_dependency 'tilt'
|
23
23
|
s.add_dependency 'sprockets'
|
24
24
|
s.add_development_dependency 'rspec'
|
25
25
|
s.add_development_dependency 'rspec-rails'
|
26
26
|
s.add_development_dependency 'rails', '>= 3.1'
|
27
27
|
s.add_development_dependency 'pry'
|
28
|
+
s.add_development_dependency 'yard'
|
28
29
|
end
|
data/lib/jader/compiler.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'v8'
|
2
2
|
|
3
3
|
module Jader
|
4
4
|
class Compiler
|
5
5
|
attr_accessor :options
|
6
6
|
|
7
|
+
# Initialize Jader template compiler. @see https://github.com/visionmedia/jade#options
|
8
|
+
# @param [Hash] options Jade compiler options
|
9
|
+
# @option options [Boolean] :client run Jade compiler in client / server mode (default `true`)
|
10
|
+
# @option options [Boolean] :compileDebug run Jade compiler in debug mode(default `false`)
|
7
11
|
def initialize(options={})
|
8
12
|
@options = {
|
9
13
|
:client => true,
|
@@ -11,6 +15,8 @@ module Jader
|
|
11
15
|
}.merge options
|
12
16
|
end
|
13
17
|
|
18
|
+
# Jade template engine Javascript source code used to compile templates in ExecJS
|
19
|
+
# @return [String] Jade source code
|
14
20
|
def source
|
15
21
|
@source ||= %{
|
16
22
|
var window = {};
|
@@ -19,40 +25,63 @@ module Jader
|
|
19
25
|
}
|
20
26
|
end
|
21
27
|
|
22
|
-
|
23
|
-
|
28
|
+
# V8 context with Jade code compiled
|
29
|
+
# @yield [context] V8::Context compiled Jade source code in V8 context
|
30
|
+
def v8_context
|
31
|
+
V8::C::Locker() do
|
32
|
+
context = V8::Context.new
|
33
|
+
context.eval(source)
|
34
|
+
yield context
|
35
|
+
end
|
24
36
|
end
|
25
37
|
|
38
|
+
# Jade Javascript engine version
|
39
|
+
# @return [String] version of Jade javascript engine installed in `vendor/assets/javascripts`
|
26
40
|
def jade_version
|
27
|
-
context
|
41
|
+
v8_context do |context|
|
42
|
+
context.eval("jade.version")
|
43
|
+
end
|
28
44
|
end
|
29
45
|
|
46
|
+
# Compile a Jade template for client-side use with JST
|
47
|
+
# @param [String, File] template Jade template file or text to compile
|
48
|
+
# @param [String] file_name name of template file used to resolve mixins inclusion
|
49
|
+
# @return [String] Jade template compiled into Javascript and wrapped inside an anonymous function for JST
|
30
50
|
def compile(template, file_name = '')
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
function(locals){
|
39
|
-
#{tmpl}
|
40
|
-
}
|
41
|
-
}
|
51
|
+
v8_context do |context|
|
52
|
+
template = template.read if template.respond_to?(:read)
|
53
|
+
file_name.match(/views\/([^\/]+)\//)
|
54
|
+
controller_name = $1 || nil
|
55
|
+
combo = (template_mixins(controller_name) << template).join("\n").to_json
|
56
|
+
context.eval("jade.compile(#{combo},#{@options.to_json})").to_s.sub('function anonymous','function')
|
57
|
+
end
|
42
58
|
end
|
43
59
|
|
60
|
+
# Compile and evaluate a Jade template for server-side rendering
|
61
|
+
# @param [String] template Jade template text to render
|
62
|
+
# @param [String] controller_name name of Rails controller that's rendering the template
|
63
|
+
# @param [Hash] vars controller instance variables passed to the template
|
64
|
+
# @return [String] HTML output of compiled Jade template
|
44
65
|
def render(template, controller_name, vars = {})
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
66
|
+
v8_context do |context|
|
67
|
+
context.eval(Jader.configuration.includes.join("\n"))
|
68
|
+
combo = (template_mixins(controller_name) << template).join("\n").to_json
|
69
|
+
context.eval("var fn = jade.compile(#{combo})")
|
70
|
+
context.eval("fn(#{vars.to_jade.to_json})")
|
71
|
+
end
|
72
|
+
#tmpl = context.eval("jade.precompile(#{combo}, #{@options.to_json})")
|
73
|
+
#context.eval(%{
|
74
|
+
# function(locals){
|
75
|
+
# #{Jader::Source::runtime}
|
76
|
+
# #{Jader.configuration.includes.join("\n")}
|
77
|
+
# #{tmpl}
|
78
|
+
# }.call(null,#{vars.to_jade.to_json})
|
79
|
+
#})
|
54
80
|
end
|
55
81
|
|
82
|
+
# Jade template mixins for a given controller
|
83
|
+
# @param [String] controller_name name of Rails controller rendering a Jade template
|
84
|
+
# @return [Array<String>] array of Jade mixins to use with a Jade template rendered by a Rails controller
|
56
85
|
def template_mixins(controller_name)
|
57
86
|
mixins = []
|
58
87
|
unless Jader.configuration.mixins_path.nil?
|
data/lib/jader/configuration.rb
CHANGED
@@ -3,14 +3,23 @@ module Jader
|
|
3
3
|
attr_accessor :configuration
|
4
4
|
end
|
5
5
|
|
6
|
+
# Configure Jader
|
7
|
+
# @yield [config] Jader::Configuration instance
|
8
|
+
# @example
|
9
|
+
# Jader.configure do |config|
|
10
|
+
# config.mixins_path = Rails.root.join('app','assets','javascripts','helpers')
|
11
|
+
# config.includes << IO.read Rails.root.join('app','assets','javascripts','util.js')
|
12
|
+
# end
|
6
13
|
def self.configure
|
7
14
|
self.configuration ||= Configuration.new
|
8
15
|
yield(configuration)
|
9
16
|
end
|
10
17
|
|
18
|
+
# Jader configuration class
|
11
19
|
class Configuration
|
12
20
|
attr_accessor :mixins_path, :includes
|
13
21
|
|
22
|
+
# Initialize Jader::Configuration class with default values
|
14
23
|
def initialize
|
15
24
|
@mixins_path = nil
|
16
25
|
@includes = []
|
data/lib/jader/renderer.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
module Jader
|
2
|
+
# Server side Jade templates renderer
|
2
3
|
module Renderer
|
3
4
|
|
5
|
+
# Convert Jade template to HTML output for rendering as a Rails view
|
6
|
+
# @param [String] template_text Jade template text to convert
|
7
|
+
# @param [String] controller_name name of Rails controller rendering the view
|
8
|
+
# @param [Hash] vars controller instance variables passed to the template
|
9
|
+
# @return [String] HTML output of evaluated template
|
10
|
+
# @see Jader::Compiler#render
|
4
11
|
def self.convert_template(template_text, controller_name, vars = {})
|
5
12
|
compiler = Jader::Compiler.new :client => true
|
6
13
|
compiler.render(template_text, controller_name, vars)
|
7
14
|
end
|
8
15
|
|
16
|
+
# Prepare controller instance variables for the template and execute template conversion.
|
17
|
+
# Called as an ActionView::Template registered template
|
18
|
+
# @param [ActionView::Template] template currently rendered ActionView::Template instance
|
19
|
+
# @see Jader::Renderer#convert_template
|
9
20
|
def self.call(template)
|
10
21
|
template.source.gsub!(/\#\{([^\}]+)\}/,"\\\#{\\1}") # escape Jade's #{somevariable} syntax
|
11
22
|
%{
|
data/lib/jader/serialize.rb
CHANGED
@@ -4,6 +4,15 @@ module Jader
|
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
|
7
|
+
# Enable serialization on ActiveModel classes
|
8
|
+
# @param [Array<Symbol>] args model attribute names to serialize
|
9
|
+
# @param [Hash] args options serializing mode
|
10
|
+
# @option args [Boolean] :merge should serialized attributes be merged with `self.attributes`
|
11
|
+
# @example
|
12
|
+
# class User < ActiveRecord::Base
|
13
|
+
# include Jader::Serialize
|
14
|
+
# jade_serializable :name, :email, :merge => false
|
15
|
+
# end
|
7
16
|
def jade_serializable(*args)
|
8
17
|
serialize = {
|
9
18
|
:attrs => [],
|
@@ -21,10 +30,23 @@ module Jader
|
|
21
30
|
|
22
31
|
end
|
23
32
|
|
33
|
+
#nodoc
|
24
34
|
def self.included(base)
|
25
35
|
base.extend ClassMethods
|
26
36
|
end
|
27
37
|
|
38
|
+
# Serialize instance attributes to a Hash based on serializable attributes defined on Model class.
|
39
|
+
# @return [Hash] hash of model instance attributes
|
40
|
+
def to_jade
|
41
|
+
h = {:model => self.class.name.downcase}
|
42
|
+
self.jade_attributes.each do |attr|
|
43
|
+
h[attr] = self.send(attr)
|
44
|
+
end
|
45
|
+
h
|
46
|
+
end
|
47
|
+
|
48
|
+
# List of Model attributes that should be serialized when called `to_jade` on Model instance
|
49
|
+
# @return [Array] list of serializable attributes
|
28
50
|
def jade_attributes
|
29
51
|
s = self.class.class_variable_get(:@@serialize)
|
30
52
|
if s[:merge]
|
@@ -34,20 +56,12 @@ module Jader
|
|
34
56
|
end
|
35
57
|
attrs.collect{|attr| attr.to_sym}.uniq
|
36
58
|
end
|
37
|
-
|
38
|
-
def to_jade
|
39
|
-
h = {:model => self.class.name.downcase}
|
40
|
-
self.jade_attributes.each do |attr|
|
41
|
-
h[attr] = self.send(attr)
|
42
|
-
end
|
43
|
-
h
|
44
|
-
end
|
45
|
-
|
46
59
|
end
|
47
60
|
|
48
61
|
end
|
49
62
|
|
50
63
|
class Object
|
64
|
+
# Serialize Object to Jade format. Invoke `self.to_jade` if instance responds to `to_jade`
|
51
65
|
def to_ice
|
52
66
|
if self.respond_to? :to_a
|
53
67
|
self.to_a.to_jade
|
@@ -57,6 +71,7 @@ class Object
|
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
74
|
+
#nodoc
|
60
75
|
[FalseClass, TrueClass, Numeric, String].each do |cls|
|
61
76
|
cls.class_eval do
|
62
77
|
def to_jade
|
@@ -66,12 +81,16 @@ end
|
|
66
81
|
end
|
67
82
|
|
68
83
|
class Array
|
84
|
+
|
85
|
+
# Serialize Array to Jade format. Invoke `to_jade` on array members
|
69
86
|
def to_jade
|
70
87
|
map {|a| a.respond_to?(:to_jade) ? a.to_jade : a }
|
71
88
|
end
|
72
89
|
end
|
73
90
|
|
74
91
|
class Hash
|
92
|
+
|
93
|
+
# Serialize Hash to Jade format. Invoke `to_jade` on members
|
75
94
|
def to_jade
|
76
95
|
res = {}
|
77
96
|
each_pair do |key, value|
|
data/lib/jader/source.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
module Jader
|
2
|
+
# Jade template engine Javascript source code
|
2
3
|
module Source
|
4
|
+
|
5
|
+
# Jade source code
|
3
6
|
def self.jade
|
4
7
|
IO.read jade_path
|
5
8
|
end
|
6
9
|
|
10
|
+
# Jade runtime source code
|
7
11
|
def self.runtime
|
8
12
|
IO.read runtime_path
|
9
13
|
end
|
10
14
|
|
15
|
+
# Jade source code path
|
11
16
|
def self.jade_path
|
12
17
|
File.expand_path("../../../vendor/assets/javascripts/jade/jade.js", __FILE__)
|
13
18
|
end
|
14
19
|
|
20
|
+
# Jade runtime source code path
|
15
21
|
def self.runtime_path
|
16
22
|
File.expand_path("../../../vendor/assets/javascripts/jade/runtime.js", __FILE__)
|
17
23
|
end
|
data/lib/jader/template.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
require 'tilt/template'
|
2
2
|
|
3
3
|
module Jader
|
4
|
+
# Jader Tilt template for use with JST
|
4
5
|
class Template < Tilt::Template
|
5
6
|
self.default_mime_type = 'application/javascript'
|
6
7
|
|
8
|
+
# Ensure V8 is available when engine is initialized
|
7
9
|
def self.engine_initialized?
|
8
|
-
defined? ::
|
10
|
+
defined? ::V8
|
9
11
|
end
|
10
12
|
|
13
|
+
# Require 'execjs' when initializing engine
|
11
14
|
def initialize_engine
|
12
|
-
require_template_library '
|
15
|
+
require_template_library 'v8'
|
13
16
|
end
|
14
17
|
|
15
18
|
def prepare
|
16
19
|
end
|
17
20
|
|
21
|
+
# Evaluate the template. Compiles the template for JST
|
22
|
+
# @return [String] JST-compliant compiled version of the Jade template being rendered
|
18
23
|
def evaluate(scope, locals, &block)
|
19
|
-
compile_function
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def compile_function
|
25
24
|
Jader::Compiler.new(:filename => file).compile(data, file)
|
26
25
|
end
|
27
26
|
|
data/lib/jader/version.rb
CHANGED
data/spec/compiler_spec.rb
CHANGED
@@ -12,12 +12,14 @@ describe Jader::Compiler do
|
|
12
12
|
}
|
13
13
|
end
|
14
14
|
|
15
|
-
it "should contain
|
16
|
-
@compiler.context
|
15
|
+
it "should contain v8 context" do
|
16
|
+
@compiler.v8_context do |context|
|
17
|
+
context.eval("typeof window.jade").should == 'object'
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
it "should define Jade.JS compiler version" do
|
20
|
-
@compiler.jade_version.should == "0.
|
22
|
+
@compiler.jade_version.should == "0.27.2"
|
21
23
|
end
|
22
24
|
|
23
25
|
it "should compile small thing" do
|
data/spec/template_spec.rb
CHANGED
@@ -21,16 +21,18 @@ describe Jader::Compiler do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
it 'should work fine with JST' do
|
24
|
-
context =
|
24
|
+
context = V8::Context.new
|
25
|
+
context.eval %{
|
25
26
|
#{asset_for('application.js').to_s}
|
26
27
|
html = JST['sample']({name: 'Yorik'})
|
27
28
|
}
|
28
|
-
context.eval('html').should == "<!DOCTYPE html><head><title>Hello, Yorik :)</title></head><body>Yap, it works
|
29
|
+
context.eval('html').should == "<!DOCTYPE html><head><title>Hello, Yorik :)</title></head><body>Yap, it works</body>"
|
29
30
|
end
|
30
31
|
|
31
32
|
it 'should use mixins in JST' do
|
32
33
|
phrase = 'Hi There'
|
33
|
-
context =
|
34
|
+
context = V8::Context.new
|
35
|
+
context.eval %{
|
34
36
|
#{asset_for('application.js').to_s}
|
35
37
|
html = JST['views/users/index']({phrase: '#{phrase}'})
|
36
38
|
}
|
@@ -30,7 +30,7 @@ require.register = function (path, fn){
|
|
30
30
|
|
31
31
|
require.relative = function (parent) {
|
32
32
|
return function(p){
|
33
|
-
if ('.' != p
|
33
|
+
if ('.' != p.charAt(0)) return require(p);
|
34
34
|
|
35
35
|
var path = parent.split('/')
|
36
36
|
, segs = p.split('/');
|
@@ -63,10 +63,10 @@ var nodes = require('./nodes')
|
|
63
63
|
, filters = require('./filters')
|
64
64
|
, doctypes = require('./doctypes')
|
65
65
|
, selfClosing = require('./self-closing')
|
66
|
-
,
|
66
|
+
, runtime = require('./runtime')
|
67
67
|
, utils = require('./utils');
|
68
68
|
|
69
|
-
|
69
|
+
|
70
70
|
if (!Object.keys) {
|
71
71
|
Object.keys = function(obj){
|
72
72
|
var arr = [];
|
@@ -76,9 +76,9 @@ var nodes = require('./nodes')
|
|
76
76
|
}
|
77
77
|
}
|
78
78
|
return arr;
|
79
|
-
}
|
79
|
+
}
|
80
80
|
}
|
81
|
-
|
81
|
+
|
82
82
|
if (!String.prototype.trimLeft) {
|
83
83
|
String.prototype.trimLeft = function(){
|
84
84
|
return this.replace(/^\s+/, '');
|
@@ -103,6 +103,7 @@ var Compiler = module.exports = function Compiler(node, options) {
|
|
103
103
|
this.pp = options.pretty || false;
|
104
104
|
this.debug = false !== options.compileDebug;
|
105
105
|
this.indents = 0;
|
106
|
+
this.parentIndents = 0;
|
106
107
|
if (options.doctype) this.setDoctype(options.doctype);
|
107
108
|
};
|
108
109
|
|
@@ -111,16 +112,17 @@ var Compiler = module.exports = function Compiler(node, options) {
|
|
111
112
|
*/
|
112
113
|
|
113
114
|
Compiler.prototype = {
|
114
|
-
|
115
|
+
|
115
116
|
/**
|
116
117
|
* Compile parse tree to JavaScript.
|
117
118
|
*
|
118
119
|
* @api public
|
119
120
|
*/
|
120
|
-
|
121
|
+
|
121
122
|
compile: function(){
|
122
123
|
this.buf = ['var interp;'];
|
123
|
-
this.
|
124
|
+
if (this.pp) this.buf.push("var __indent = [];");
|
125
|
+
this.lastBufferedIdx = -1;
|
124
126
|
this.visit(this.node);
|
125
127
|
return this.buf.join('\n');
|
126
128
|
},
|
@@ -133,15 +135,14 @@ Compiler.prototype = {
|
|
133
135
|
* @param {string} name
|
134
136
|
* @api public
|
135
137
|
*/
|
136
|
-
|
138
|
+
|
137
139
|
setDoctype: function(name){
|
138
|
-
|
139
|
-
doctype =
|
140
|
-
this.
|
141
|
-
this.terse = '5' == name || 'html' == name;
|
140
|
+
name = (name && name.toLowerCase()) || 'default';
|
141
|
+
this.doctype = doctypes[name] || '<!DOCTYPE ' + name + '>';
|
142
|
+
this.terse = this.doctype.toLowerCase() == '<!doctype html>';
|
142
143
|
this.xml = 0 == this.doctype.indexOf('<?xml');
|
143
144
|
},
|
144
|
-
|
145
|
+
|
145
146
|
/**
|
146
147
|
* Buffer the given `str` optionally escaped.
|
147
148
|
*
|
@@ -149,10 +150,10 @@ Compiler.prototype = {
|
|
149
150
|
* @param {Boolean} esc
|
150
151
|
* @api public
|
151
152
|
*/
|
152
|
-
|
153
|
+
|
153
154
|
buffer: function(str, esc){
|
154
155
|
if (esc) str = utils.escape(str);
|
155
|
-
|
156
|
+
|
156
157
|
if (this.lastBufferedIdx == this.buf.length) {
|
157
158
|
this.lastBuffered += str;
|
158
159
|
this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
|
@@ -160,23 +161,40 @@ Compiler.prototype = {
|
|
160
161
|
this.buf.push("buf.push('" + str + "');");
|
161
162
|
this.lastBuffered = str;
|
162
163
|
this.lastBufferedIdx = this.buf.length;
|
163
|
-
}
|
164
|
+
}
|
164
165
|
},
|
165
|
-
|
166
|
+
|
167
|
+
/**
|
168
|
+
* Buffer an indent based on the current `indent`
|
169
|
+
* property and an additional `offset`.
|
170
|
+
*
|
171
|
+
* @param {Number} offset
|
172
|
+
* @param {Boolean} newline
|
173
|
+
* @api public
|
174
|
+
*/
|
175
|
+
|
176
|
+
prettyIndent: function(offset, newline){
|
177
|
+
offset = offset || 0;
|
178
|
+
newline = newline ? '\\n' : '';
|
179
|
+
this.buffer(newline + Array(this.indents + offset).join(' '));
|
180
|
+
if (this.parentIndents)
|
181
|
+
this.buf.push("buf.push.apply(buf, __indent);");
|
182
|
+
},
|
183
|
+
|
166
184
|
/**
|
167
185
|
* Visit `node`.
|
168
186
|
*
|
169
187
|
* @param {Node} node
|
170
188
|
* @api public
|
171
189
|
*/
|
172
|
-
|
190
|
+
|
173
191
|
visit: function(node){
|
174
192
|
var debug = this.debug;
|
175
193
|
|
176
194
|
if (debug) {
|
177
195
|
this.buf.push('__jade.unshift({ lineno: ' + node.line
|
178
196
|
+ ', filename: ' + (node.filename
|
179
|
-
?
|
197
|
+
? JSON.stringify(node.filename)
|
180
198
|
: '__jade[0].filename')
|
181
199
|
+ ' });');
|
182
200
|
}
|
@@ -192,14 +210,14 @@ Compiler.prototype = {
|
|
192
210
|
|
193
211
|
if (debug) this.buf.push('__jade.shift();');
|
194
212
|
},
|
195
|
-
|
213
|
+
|
196
214
|
/**
|
197
215
|
* Visit `node`.
|
198
216
|
*
|
199
217
|
* @param {Node} node
|
200
218
|
* @api public
|
201
219
|
*/
|
202
|
-
|
220
|
+
|
203
221
|
visitNode: function(node){
|
204
222
|
var name = node.constructor.name
|
205
223
|
|| node.constructor.toString().match(/function ([^(\s]+)()/)[1];
|
@@ -221,7 +239,7 @@ Compiler.prototype = {
|
|
221
239
|
this.buf.push('}');
|
222
240
|
this.withinCase = _;
|
223
241
|
},
|
224
|
-
|
242
|
+
|
225
243
|
/**
|
226
244
|
* Visit when `node`.
|
227
245
|
*
|
@@ -259,12 +277,34 @@ Compiler.prototype = {
|
|
259
277
|
*/
|
260
278
|
|
261
279
|
visitBlock: function(block){
|
262
|
-
var len = block.nodes.length
|
280
|
+
var len = block.nodes.length
|
281
|
+
, escape = this.escape
|
282
|
+
, pp = this.pp
|
283
|
+
|
284
|
+
// Block keyword has a special meaning in mixins
|
285
|
+
if (this.parentIndents && block.mode) {
|
286
|
+
if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
|
287
|
+
this.buf.push('block && block();');
|
288
|
+
if (pp) this.buf.push("__indent.pop();")
|
289
|
+
return;
|
290
|
+
}
|
291
|
+
|
292
|
+
// Pretty print multi-line text
|
293
|
+
if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
|
294
|
+
this.prettyIndent(1, true);
|
295
|
+
|
263
296
|
for (var i = 0; i < len; ++i) {
|
297
|
+
// Pretty print text
|
298
|
+
if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
|
299
|
+
this.prettyIndent(1, false);
|
300
|
+
|
264
301
|
this.visit(block.nodes[i]);
|
302
|
+
// Multiple text nodes are separated by newlines
|
303
|
+
if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
|
304
|
+
this.buffer('\\n');
|
265
305
|
}
|
266
306
|
},
|
267
|
-
|
307
|
+
|
268
308
|
/**
|
269
309
|
* Visit `doctype`. Sets terse mode to `true` when html 5
|
270
310
|
* is used, causing self-closing tags to end with ">" vs "/>",
|
@@ -273,7 +313,7 @@ Compiler.prototype = {
|
|
273
313
|
* @param {Doctype} doctype
|
274
314
|
* @api public
|
275
315
|
*/
|
276
|
-
|
316
|
+
|
277
317
|
visitDoctype: function(doctype){
|
278
318
|
if (doctype && (doctype.val || !this.doctype)) {
|
279
319
|
this.setDoctype(doctype.val || 'default');
|
@@ -293,14 +333,62 @@ Compiler.prototype = {
|
|
293
333
|
|
294
334
|
visitMixin: function(mixin){
|
295
335
|
var name = mixin.name.replace(/-/g, '_') + '_mixin'
|
296
|
-
, args = mixin.args || ''
|
336
|
+
, args = mixin.args || ''
|
337
|
+
, block = mixin.block
|
338
|
+
, attrs = mixin.attrs
|
339
|
+
, pp = this.pp;
|
340
|
+
|
341
|
+
if (mixin.call) {
|
342
|
+
if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
|
343
|
+
if (block || attrs.length) {
|
344
|
+
|
345
|
+
this.buf.push(name + '.call({');
|
346
|
+
|
347
|
+
if (block) {
|
348
|
+
this.buf.push('block: function(){');
|
349
|
+
|
350
|
+
// Render block with no indents, dynamically added when rendered
|
351
|
+
this.parentIndents++;
|
352
|
+
var _indents = this.indents;
|
353
|
+
this.indents = 0;
|
354
|
+
this.visit(mixin.block);
|
355
|
+
this.indents = _indents;
|
356
|
+
this.parentIndents--;
|
357
|
+
|
358
|
+
if (attrs.length) {
|
359
|
+
this.buf.push('},');
|
360
|
+
} else {
|
361
|
+
this.buf.push('}');
|
362
|
+
}
|
363
|
+
}
|
297
364
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
365
|
+
if (attrs.length) {
|
366
|
+
var val = this.attrs(attrs);
|
367
|
+
if (val.inherits) {
|
368
|
+
this.buf.push('attributes: merge({' + val.buf
|
369
|
+
+ '}, attributes), escaped: merge(' + val.escaped + ', escaped, true)');
|
370
|
+
} else {
|
371
|
+
this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped);
|
372
|
+
}
|
373
|
+
}
|
374
|
+
|
375
|
+
if (args) {
|
376
|
+
this.buf.push('}, ' + args + ');');
|
377
|
+
} else {
|
378
|
+
this.buf.push('});');
|
379
|
+
}
|
380
|
+
|
381
|
+
} else {
|
382
|
+
this.buf.push(name + '(' + args + ');');
|
383
|
+
}
|
384
|
+
if (pp) this.buf.push("__indent.pop();")
|
302
385
|
} else {
|
303
|
-
this.buf.push(name + '(' + args + ')
|
386
|
+
this.buf.push('var ' + name + ' = function(' + args + '){');
|
387
|
+
this.buf.push('var block = this.block, attributes = this.attributes || {}, escaped = this.escaped || {};');
|
388
|
+
this.parentIndents++;
|
389
|
+
this.visit(block);
|
390
|
+
this.parentIndents--;
|
391
|
+
this.buf.push('};');
|
304
392
|
}
|
305
393
|
},
|
306
394
|
|
@@ -311,10 +399,13 @@ Compiler.prototype = {
|
|
311
399
|
* @param {Tag} tag
|
312
400
|
* @api public
|
313
401
|
*/
|
314
|
-
|
402
|
+
|
315
403
|
visitTag: function(tag){
|
316
404
|
this.indents++;
|
317
|
-
var name = tag.name
|
405
|
+
var name = tag.name
|
406
|
+
, pp = this.pp;
|
407
|
+
|
408
|
+
if (tag.buffer) name = "' + (" + name + ") + '";
|
318
409
|
|
319
410
|
if (!this.hasCompiledTag) {
|
320
411
|
if (!this.hasCompiledDoctype && 'html' == name) {
|
@@ -324,11 +415,10 @@ Compiler.prototype = {
|
|
324
415
|
}
|
325
416
|
|
326
417
|
// pretty print
|
327
|
-
if (
|
328
|
-
this.
|
329
|
-
}
|
418
|
+
if (pp && !tag.isInline())
|
419
|
+
this.prettyIndent(0, true);
|
330
420
|
|
331
|
-
if (~selfClosing.indexOf(name) && !this.xml) {
|
421
|
+
if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) {
|
332
422
|
this.buffer('<' + name);
|
333
423
|
this.visitAttributes(tag.attrs);
|
334
424
|
this.terse
|
@@ -344,27 +434,25 @@ Compiler.prototype = {
|
|
344
434
|
this.buffer('<' + name + '>');
|
345
435
|
}
|
346
436
|
if (tag.code) this.visitCode(tag.code);
|
347
|
-
if (tag.text) this.buffer(utils.text(tag.text.nodes[0].trimLeft()));
|
348
437
|
this.escape = 'pre' == tag.name;
|
349
438
|
this.visit(tag.block);
|
350
439
|
|
351
440
|
// pretty print
|
352
|
-
if (
|
353
|
-
this.
|
354
|
-
}
|
441
|
+
if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
|
442
|
+
this.prettyIndent(0, true);
|
355
443
|
|
356
444
|
this.buffer('</' + name + '>');
|
357
445
|
}
|
358
446
|
this.indents--;
|
359
447
|
},
|
360
|
-
|
448
|
+
|
361
449
|
/**
|
362
450
|
* Visit `filter`, throwing when the filter does not exist.
|
363
451
|
*
|
364
452
|
* @param {Filter} filter
|
365
453
|
* @api public
|
366
454
|
*/
|
367
|
-
|
455
|
+
|
368
456
|
visitFilter: function(filter){
|
369
457
|
var fn = filters[filter.name];
|
370
458
|
|
@@ -376,48 +464,50 @@ Compiler.prototype = {
|
|
376
464
|
throw new Error('unknown filter ":' + filter.name + '"');
|
377
465
|
}
|
378
466
|
}
|
467
|
+
|
379
468
|
if (filter.isASTFilter) {
|
380
469
|
this.buf.push(fn(filter.block, this, filter.attrs));
|
381
470
|
} else {
|
382
|
-
var text = filter.block.nodes.join('');
|
471
|
+
var text = filter.block.nodes.map(function(node){ return node.val }).join('\n');
|
472
|
+
filter.attrs = filter.attrs || {};
|
473
|
+
filter.attrs.filename = this.options.filename;
|
383
474
|
this.buffer(utils.text(fn(text, filter.attrs)));
|
384
475
|
}
|
385
476
|
},
|
386
|
-
|
477
|
+
|
387
478
|
/**
|
388
479
|
* Visit `text` node.
|
389
480
|
*
|
390
481
|
* @param {Text} text
|
391
482
|
* @api public
|
392
483
|
*/
|
393
|
-
|
484
|
+
|
394
485
|
visitText: function(text){
|
395
|
-
text = utils.text(text.
|
486
|
+
text = utils.text(text.val.replace(/\\/g, '\\\\'));
|
396
487
|
if (this.escape) text = escape(text);
|
397
488
|
this.buffer(text);
|
398
|
-
this.buffer('\\n');
|
399
489
|
},
|
400
|
-
|
490
|
+
|
401
491
|
/**
|
402
492
|
* Visit a `comment`, only buffering when the buffer flag is set.
|
403
493
|
*
|
404
494
|
* @param {Comment} comment
|
405
495
|
* @api public
|
406
496
|
*/
|
407
|
-
|
497
|
+
|
408
498
|
visitComment: function(comment){
|
409
499
|
if (!comment.buffer) return;
|
410
|
-
if (this.pp) this.
|
500
|
+
if (this.pp) this.prettyIndent(1, true);
|
411
501
|
this.buffer('<!--' + utils.escape(comment.val) + '-->');
|
412
502
|
},
|
413
|
-
|
503
|
+
|
414
504
|
/**
|
415
505
|
* Visit a `BlockComment`.
|
416
506
|
*
|
417
507
|
* @param {Comment} comment
|
418
508
|
* @api public
|
419
509
|
*/
|
420
|
-
|
510
|
+
|
421
511
|
visitBlockComment: function(comment){
|
422
512
|
if (!comment.buffer) return;
|
423
513
|
if (0 == comment.val.trim().indexOf('if')) {
|
@@ -430,7 +520,7 @@ Compiler.prototype = {
|
|
430
520
|
this.buffer('-->');
|
431
521
|
}
|
432
522
|
},
|
433
|
-
|
523
|
+
|
434
524
|
/**
|
435
525
|
* Visit `code`, respecting buffer / escape flags.
|
436
526
|
* If the code is followed by a block, wrap it in
|
@@ -439,7 +529,7 @@ Compiler.prototype = {
|
|
439
529
|
* @param {Code} code
|
440
530
|
* @api public
|
441
531
|
*/
|
442
|
-
|
532
|
+
|
443
533
|
visitCode: function(code){
|
444
534
|
// Wrap code blocks with {}.
|
445
535
|
// we only wrap unbuffered code blocks ATM
|
@@ -463,26 +553,39 @@ Compiler.prototype = {
|
|
463
553
|
if (!code.buffer) this.buf.push('}');
|
464
554
|
}
|
465
555
|
},
|
466
|
-
|
556
|
+
|
467
557
|
/**
|
468
558
|
* Visit `each` block.
|
469
559
|
*
|
470
560
|
* @param {Each} each
|
471
561
|
* @api public
|
472
562
|
*/
|
473
|
-
|
563
|
+
|
474
564
|
visitEach: function(each){
|
475
565
|
this.buf.push(''
|
476
566
|
+ '// iterate ' + each.obj + '\n'
|
477
|
-
+ '(function(){\n'
|
478
|
-
+ ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
|
567
|
+
+ ';(function(){\n'
|
568
|
+
+ ' if (\'number\' == typeof ' + each.obj + '.length) {\n');
|
569
|
+
|
570
|
+
if (each.alternative) {
|
571
|
+
this.buf.push(' if (' + each.obj + '.length) {');
|
572
|
+
}
|
573
|
+
|
574
|
+
this.buf.push(''
|
479
575
|
+ ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
|
480
576
|
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
|
481
577
|
|
482
578
|
this.visit(each.block);
|
483
579
|
|
580
|
+
this.buf.push(' }\n');
|
581
|
+
|
582
|
+
if (each.alternative) {
|
583
|
+
this.buf.push(' } else {');
|
584
|
+
this.visit(each.alternative);
|
585
|
+
this.buf.push(' }');
|
586
|
+
}
|
587
|
+
|
484
588
|
this.buf.push(''
|
485
|
-
+ ' }\n'
|
486
589
|
+ ' } else {\n'
|
487
590
|
+ ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
|
488
591
|
+ ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){'
|
@@ -494,21 +597,43 @@ Compiler.prototype = {
|
|
494
597
|
|
495
598
|
this.buf.push(' }\n }\n}).call(this);\n');
|
496
599
|
},
|
497
|
-
|
600
|
+
|
498
601
|
/**
|
499
602
|
* Visit `attrs`.
|
500
603
|
*
|
501
604
|
* @param {Array} attrs
|
502
605
|
* @api public
|
503
606
|
*/
|
504
|
-
|
607
|
+
|
505
608
|
visitAttributes: function(attrs){
|
609
|
+
var val = this.attrs(attrs);
|
610
|
+
if (val.inherits) {
|
611
|
+
this.buf.push("buf.push(attrs(merge({ " + val.buf +
|
612
|
+
" }, attributes), merge(" + val.escaped + ", escaped, true)));");
|
613
|
+
} else if (val.constant) {
|
614
|
+
eval('var buf={' + val.buf + '};');
|
615
|
+
this.buffer(runtime.attrs(buf, JSON.parse(val.escaped)), true);
|
616
|
+
} else {
|
617
|
+
this.buf.push("buf.push(attrs({ " + val.buf + " }, " + val.escaped + "));");
|
618
|
+
}
|
619
|
+
},
|
620
|
+
|
621
|
+
/**
|
622
|
+
* Compile attributes.
|
623
|
+
*/
|
624
|
+
|
625
|
+
attrs: function(attrs){
|
506
626
|
var buf = []
|
507
|
-
, classes = []
|
627
|
+
, classes = []
|
628
|
+
, escaped = {}
|
629
|
+
, constant = attrs.every(function(attr){ return isConstant(attr.val) })
|
630
|
+
, inherits = false;
|
508
631
|
|
509
632
|
if (this.terse) buf.push('terse: true');
|
510
633
|
|
511
634
|
attrs.forEach(function(attr){
|
635
|
+
if (attr.name == 'attributes') return inherits = true;
|
636
|
+
escaped[attr.name] = attr.escaped;
|
512
637
|
if (attr.name == 'class') {
|
513
638
|
classes.push('(' + attr.val + ')');
|
514
639
|
} else {
|
@@ -522,12 +647,40 @@ Compiler.prototype = {
|
|
522
647
|
buf.push("class: " + classes);
|
523
648
|
}
|
524
649
|
|
525
|
-
|
526
|
-
|
527
|
-
|
650
|
+
return {
|
651
|
+
buf: buf.join(', ').replace('class:', '"class":'),
|
652
|
+
escaped: JSON.stringify(escaped),
|
653
|
+
inherits: inherits,
|
654
|
+
constant: constant
|
655
|
+
};
|
528
656
|
}
|
529
657
|
};
|
530
658
|
|
659
|
+
/**
|
660
|
+
* Check if expression can be evaluated to a constant
|
661
|
+
*
|
662
|
+
* @param {String} expression
|
663
|
+
* @return {Boolean}
|
664
|
+
* @api private
|
665
|
+
*/
|
666
|
+
|
667
|
+
function isConstant(val){
|
668
|
+
// Check strings/literals
|
669
|
+
if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val))
|
670
|
+
return true;
|
671
|
+
|
672
|
+
// Check numbers
|
673
|
+
if (!isNaN(Number(val)))
|
674
|
+
return true;
|
675
|
+
|
676
|
+
// Check arrays
|
677
|
+
var matches;
|
678
|
+
if (matches = /^ *\[(.*)\] *$/.exec(val))
|
679
|
+
return matches[1].split(',').every(isConstant);
|
680
|
+
|
681
|
+
return false;
|
682
|
+
}
|
683
|
+
|
531
684
|
/**
|
532
685
|
* Escape the given string of `html`.
|
533
686
|
*
|
@@ -556,8 +709,8 @@ require.register("doctypes.js", function(module, exports, require){
|
|
556
709
|
|
557
710
|
module.exports = {
|
558
711
|
'5': '<!DOCTYPE html>'
|
712
|
+
, 'default': '<!DOCTYPE html>'
|
559
713
|
, '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
714
|
, 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
562
715
|
, 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
563
716
|
, 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
@@ -661,7 +814,7 @@ module.exports = {
|
|
661
814
|
|
662
815
|
coffeescript: function(str){
|
663
816
|
str = str.replace(/\\n/g, '\n');
|
664
|
-
var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
|
817
|
+
var js = require('coffee-script').compile(str).replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
|
665
818
|
return '<script type="text/javascript">\\n' + js + '</script>';
|
666
819
|
}
|
667
820
|
};
|
@@ -719,7 +872,7 @@ var Parser = require('./parser')
|
|
719
872
|
* Library version.
|
720
873
|
*/
|
721
874
|
|
722
|
-
exports.version = '0.
|
875
|
+
exports.version = '0.27.2';
|
723
876
|
|
724
877
|
/**
|
725
878
|
* Expose self closing tags.
|
@@ -770,7 +923,7 @@ exports.Lexer = Lexer;
|
|
770
923
|
exports.nodes = require('./nodes');
|
771
924
|
|
772
925
|
/**
|
773
|
-
* Jade runtime
|
926
|
+
* Jade runtime helpers.
|
774
927
|
*/
|
775
928
|
|
776
929
|
exports.runtime = runtime;
|
@@ -817,10 +970,24 @@ function parse(str, options){
|
|
817
970
|
}
|
818
971
|
|
819
972
|
/**
|
820
|
-
*
|
973
|
+
* Strip any UTF-8 BOM off of the start of `str`, if it exists.
|
974
|
+
*
|
975
|
+
* @param {String} str
|
976
|
+
* @return {String}
|
977
|
+
* @api private
|
978
|
+
*/
|
979
|
+
|
980
|
+
function stripBOM(str){
|
981
|
+
return 0xFEFF == str.charCodeAt(0)
|
982
|
+
? str.substring(1)
|
983
|
+
: str;
|
984
|
+
}
|
985
|
+
|
986
|
+
/**
|
987
|
+
* Compile a `Function` representation of the given jade `str`.
|
821
988
|
*
|
822
989
|
* Options:
|
823
|
-
*
|
990
|
+
*
|
824
991
|
* - `compileDebug` when `false` debugging code is stripped from the compiled template
|
825
992
|
* - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
|
826
993
|
* for use with the Jade client-side runtime.js
|
@@ -831,7 +998,7 @@ function parse(str, options){
|
|
831
998
|
* @api public
|
832
999
|
*/
|
833
1000
|
|
834
|
-
exports.
|
1001
|
+
exports.compile = function(str, options){
|
835
1002
|
var options = options || {}
|
836
1003
|
, client = options.client
|
837
1004
|
, filename = options.filename
|
@@ -839,52 +1006,31 @@ exports.precompile = function(str, options){
|
|
839
1006
|
: 'undefined'
|
840
1007
|
, fn;
|
841
1008
|
|
1009
|
+
str = stripBOM(String(str));
|
1010
|
+
|
842
1011
|
if (options.compileDebug !== false) {
|
843
1012
|
fn = [
|
844
1013
|
'var __jade = [{ lineno: 1, filename: ' + filename + ' }];'
|
845
1014
|
, 'try {'
|
846
|
-
, parse(
|
1015
|
+
, parse(str, options)
|
847
1016
|
, '} catch (err) {'
|
848
1017
|
, ' rethrow(err, __jade[0].filename, __jade[0].lineno);'
|
849
1018
|
, '}'
|
850
1019
|
].join('\n');
|
851
1020
|
} else {
|
852
|
-
fn = parse(
|
1021
|
+
fn = parse(str, options);
|
853
1022
|
}
|
854
1023
|
|
855
1024
|
if (client) {
|
856
|
-
fn = '
|
1025
|
+
fn = 'attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;\n' + fn;
|
857
1026
|
}
|
858
|
-
|
859
|
-
return fn;
|
860
|
-
}
|
861
1027
|
|
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));
|
1028
|
+
fn = new Function('locals, attrs, escape, rethrow, merge', fn);
|
883
1029
|
|
884
1030
|
if (client) return fn;
|
885
1031
|
|
886
1032
|
return function(locals){
|
887
|
-
return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow);
|
1033
|
+
return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow, runtime.merge);
|
888
1034
|
};
|
889
1035
|
};
|
890
1036
|
|
@@ -968,6 +1114,8 @@ require.register("lexer.js", function(module, exports, require){
|
|
968
1114
|
* MIT Licensed
|
969
1115
|
*/
|
970
1116
|
|
1117
|
+
var utils = require('./utils');
|
1118
|
+
|
971
1119
|
/**
|
972
1120
|
* Initialize `Lexer` with the given `str`.
|
973
1121
|
*
|
@@ -1084,9 +1232,9 @@ Lexer.prototype = {
|
|
1084
1232
|
, nend = 0
|
1085
1233
|
, pos = 0;
|
1086
1234
|
for (var i = 0, len = str.length; i < len; ++i) {
|
1087
|
-
if (start == str
|
1235
|
+
if (start == str.charAt(i)) {
|
1088
1236
|
++nstart;
|
1089
|
-
} else if (end == str
|
1237
|
+
} else if (end == str.charAt(i)) {
|
1090
1238
|
if (++nend == nstart) {
|
1091
1239
|
pos = i;
|
1092
1240
|
break;
|
@@ -1128,6 +1276,19 @@ Lexer.prototype = {
|
|
1128
1276
|
}
|
1129
1277
|
},
|
1130
1278
|
|
1279
|
+
/**
|
1280
|
+
* Blank line.
|
1281
|
+
*/
|
1282
|
+
|
1283
|
+
blank: function() {
|
1284
|
+
var captures;
|
1285
|
+
if (captures = /^\n *\n/.exec(this.input)) {
|
1286
|
+
this.consume(captures[0].length - 1);
|
1287
|
+
if (this.pipeless) return this.tok('text', '');
|
1288
|
+
return this.next();
|
1289
|
+
}
|
1290
|
+
},
|
1291
|
+
|
1131
1292
|
/**
|
1132
1293
|
* Comment.
|
1133
1294
|
*/
|
@@ -1141,14 +1302,26 @@ Lexer.prototype = {
|
|
1141
1302
|
return tok;
|
1142
1303
|
}
|
1143
1304
|
},
|
1144
|
-
|
1305
|
+
|
1306
|
+
/**
|
1307
|
+
* Interpolated tag.
|
1308
|
+
*/
|
1309
|
+
|
1310
|
+
interpolation: function() {
|
1311
|
+
var captures;
|
1312
|
+
if (captures = /^#\{(.*?)\}/.exec(this.input)) {
|
1313
|
+
this.consume(captures[0].length);
|
1314
|
+
return this.tok('interpolation', captures[1]);
|
1315
|
+
}
|
1316
|
+
},
|
1317
|
+
|
1145
1318
|
/**
|
1146
1319
|
* Tag.
|
1147
1320
|
*/
|
1148
1321
|
|
1149
1322
|
tag: function() {
|
1150
1323
|
var captures;
|
1151
|
-
if (captures = /^(\w[-:\w]*)/.exec(this.input)) {
|
1324
|
+
if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
|
1152
1325
|
this.consume(captures[0].length);
|
1153
1326
|
var tok, name = captures[1];
|
1154
1327
|
if (':' == name[name.length - 1]) {
|
@@ -1159,6 +1332,7 @@ Lexer.prototype = {
|
|
1159
1332
|
} else {
|
1160
1333
|
tok = this.tok('tag', name);
|
1161
1334
|
}
|
1335
|
+
tok.selfClosing = !! captures[2];
|
1162
1336
|
return tok;
|
1163
1337
|
}
|
1164
1338
|
},
|
@@ -1200,15 +1374,15 @@ Lexer.prototype = {
|
|
1200
1374
|
*/
|
1201
1375
|
|
1202
1376
|
text: function() {
|
1203
|
-
return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
|
1377
|
+
return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text');
|
1204
1378
|
},
|
1205
1379
|
|
1206
1380
|
/**
|
1207
1381
|
* Extends.
|
1208
1382
|
*/
|
1209
1383
|
|
1210
|
-
extends: function() {
|
1211
|
-
return this.scan(/^extends +([^\n]+)/, 'extends');
|
1384
|
+
"extends": function() {
|
1385
|
+
return this.scan(/^extends? +([^\n]+)/, 'extends');
|
1212
1386
|
},
|
1213
1387
|
|
1214
1388
|
/**
|
@@ -1249,11 +1423,12 @@ Lexer.prototype = {
|
|
1249
1423
|
|
1250
1424
|
block: function() {
|
1251
1425
|
var captures;
|
1252
|
-
if (captures = /^block
|
1426
|
+
if (captures = /^block\b *(?:(prepend|append) +)?([^\n]*)/.exec(this.input)) {
|
1253
1427
|
this.consume(captures[0].length);
|
1254
1428
|
var mode = captures[1] || 'replace'
|
1255
1429
|
, name = captures[2]
|
1256
1430
|
, tok = this.tok('block', name);
|
1431
|
+
|
1257
1432
|
tok.mode = mode;
|
1258
1433
|
return tok;
|
1259
1434
|
}
|
@@ -1279,7 +1454,7 @@ Lexer.prototype = {
|
|
1279
1454
|
* Case.
|
1280
1455
|
*/
|
1281
1456
|
|
1282
|
-
case: function() {
|
1457
|
+
"case": function() {
|
1283
1458
|
return this.scan(/^case +([^\n]+)/, 'case');
|
1284
1459
|
},
|
1285
1460
|
|
@@ -1295,7 +1470,7 @@ Lexer.prototype = {
|
|
1295
1470
|
* Default.
|
1296
1471
|
*/
|
1297
1472
|
|
1298
|
-
default: function() {
|
1473
|
+
"default": function() {
|
1299
1474
|
return this.scan(/^default */, 'default');
|
1300
1475
|
},
|
1301
1476
|
|
@@ -1313,6 +1488,28 @@ Lexer.prototype = {
|
|
1313
1488
|
}
|
1314
1489
|
},
|
1315
1490
|
|
1491
|
+
/**
|
1492
|
+
* Call mixin.
|
1493
|
+
*/
|
1494
|
+
|
1495
|
+
call: function(){
|
1496
|
+
var captures;
|
1497
|
+
if (captures = /^\+([-\w]+)/.exec(this.input)) {
|
1498
|
+
this.consume(captures[0].length);
|
1499
|
+
var tok = this.tok('call', captures[1]);
|
1500
|
+
|
1501
|
+
// Check for args (not attributes)
|
1502
|
+
if (captures = /^ *\((.*?)\)/.exec(this.input)) {
|
1503
|
+
if (!/^ *[-\w]+ *=/.test(captures[1])) {
|
1504
|
+
this.consume(captures[0].length);
|
1505
|
+
tok.args = captures[1];
|
1506
|
+
}
|
1507
|
+
}
|
1508
|
+
|
1509
|
+
return tok;
|
1510
|
+
}
|
1511
|
+
},
|
1512
|
+
|
1316
1513
|
/**
|
1317
1514
|
* Mixin.
|
1318
1515
|
*/
|
@@ -1353,7 +1550,7 @@ Lexer.prototype = {
|
|
1353
1550
|
* While.
|
1354
1551
|
*/
|
1355
1552
|
|
1356
|
-
while: function() {
|
1553
|
+
"while": function() {
|
1357
1554
|
var captures;
|
1358
1555
|
if (captures = /^while +([^\n]+)/.exec(this.input)) {
|
1359
1556
|
this.consume(captures[0].length);
|
@@ -1398,17 +1595,19 @@ Lexer.prototype = {
|
|
1398
1595
|
*/
|
1399
1596
|
|
1400
1597
|
attrs: function() {
|
1401
|
-
if ('(' == this.input
|
1598
|
+
if ('(' == this.input.charAt(0)) {
|
1402
1599
|
var index = this.indexOfDelimiters('(', ')')
|
1403
1600
|
, str = this.input.substr(1, index-1)
|
1404
1601
|
, tok = this.tok('attrs')
|
1405
1602
|
, len = str.length
|
1406
1603
|
, colons = this.colons
|
1407
1604
|
, states = ['key']
|
1605
|
+
, escapedAttr
|
1408
1606
|
, key = ''
|
1409
1607
|
, val = ''
|
1410
1608
|
, quote
|
1411
|
-
, c
|
1609
|
+
, c
|
1610
|
+
, p;
|
1412
1611
|
|
1413
1612
|
function state(){
|
1414
1613
|
return states[states.length - 1];
|
@@ -1422,6 +1621,7 @@ Lexer.prototype = {
|
|
1422
1621
|
|
1423
1622
|
this.consume(index + 1);
|
1424
1623
|
tok.attrs = {};
|
1624
|
+
tok.escaped = {};
|
1425
1625
|
|
1426
1626
|
function parse(c) {
|
1427
1627
|
var real = c;
|
@@ -1442,7 +1642,9 @@ Lexer.prototype = {
|
|
1442
1642
|
val = val.trim();
|
1443
1643
|
key = key.trim();
|
1444
1644
|
if ('' == key) return;
|
1445
|
-
|
1645
|
+
key = key.replace(/^['"]|['"]$/g, '').replace('!', '');
|
1646
|
+
tok.escaped[key] = escapedAttr;
|
1647
|
+
tok.attrs[key] = '' == val
|
1446
1648
|
? true
|
1447
1649
|
: interpolate(val);
|
1448
1650
|
key = val = '';
|
@@ -1461,6 +1663,7 @@ Lexer.prototype = {
|
|
1461
1663
|
val += real;
|
1462
1664
|
break;
|
1463
1665
|
default:
|
1666
|
+
escapedAttr = '!' != p;
|
1464
1667
|
states.push('val');
|
1465
1668
|
}
|
1466
1669
|
break;
|
@@ -1521,14 +1724,20 @@ Lexer.prototype = {
|
|
1521
1724
|
val += c;
|
1522
1725
|
}
|
1523
1726
|
}
|
1727
|
+
p = c;
|
1524
1728
|
}
|
1525
1729
|
|
1526
1730
|
for (var i = 0; i < len; ++i) {
|
1527
|
-
parse(str
|
1731
|
+
parse(str.charAt(i));
|
1528
1732
|
}
|
1529
1733
|
|
1530
1734
|
parse(',');
|
1531
1735
|
|
1736
|
+
if ('/' == this.input.charAt(0)) {
|
1737
|
+
this.consume(1);
|
1738
|
+
tok.selfClosing = true;
|
1739
|
+
}
|
1740
|
+
|
1532
1741
|
return tok;
|
1533
1742
|
}
|
1534
1743
|
},
|
@@ -1639,22 +1848,25 @@ Lexer.prototype = {
|
|
1639
1848
|
|
1640
1849
|
next: function() {
|
1641
1850
|
return this.deferred()
|
1851
|
+
|| this.blank()
|
1642
1852
|
|| this.eos()
|
1643
1853
|
|| this.pipelessText()
|
1644
1854
|
|| this.yield()
|
1645
1855
|
|| this.doctype()
|
1646
|
-
|| this.
|
1856
|
+
|| this.interpolation()
|
1857
|
+
|| this["case"]()
|
1647
1858
|
|| this.when()
|
1648
|
-
|| this
|
1649
|
-
|| this
|
1859
|
+
|| this["default"]()
|
1860
|
+
|| this["extends"]()
|
1650
1861
|
|| this.append()
|
1651
1862
|
|| this.prepend()
|
1652
1863
|
|| this.block()
|
1653
1864
|
|| this.include()
|
1654
1865
|
|| this.mixin()
|
1866
|
+
|| this.call()
|
1655
1867
|
|| this.conditional()
|
1656
1868
|
|| this.each()
|
1657
|
-
|| this
|
1869
|
+
|| this["while"]()
|
1658
1870
|
|| this.assignment()
|
1659
1871
|
|| this.tag()
|
1660
1872
|
|| this.filter()
|
@@ -1671,6 +1883,89 @@ Lexer.prototype = {
|
|
1671
1883
|
|
1672
1884
|
}); // module: lexer.js
|
1673
1885
|
|
1886
|
+
require.register("nodes/attrs.js", function(module, exports, require){
|
1887
|
+
|
1888
|
+
/*!
|
1889
|
+
* Jade - nodes - Attrs
|
1890
|
+
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
1891
|
+
* MIT Licensed
|
1892
|
+
*/
|
1893
|
+
|
1894
|
+
/**
|
1895
|
+
* Module dependencies.
|
1896
|
+
*/
|
1897
|
+
|
1898
|
+
var Node = require('./node'),
|
1899
|
+
Block = require('./block');
|
1900
|
+
|
1901
|
+
/**
|
1902
|
+
* Initialize a `Attrs` node.
|
1903
|
+
*
|
1904
|
+
* @api public
|
1905
|
+
*/
|
1906
|
+
|
1907
|
+
var Attrs = module.exports = function Attrs() {
|
1908
|
+
this.attrs = [];
|
1909
|
+
};
|
1910
|
+
|
1911
|
+
/**
|
1912
|
+
* Inherit from `Node`.
|
1913
|
+
*/
|
1914
|
+
|
1915
|
+
Attrs.prototype = new Node;
|
1916
|
+
Attrs.prototype.constructor = Attrs;
|
1917
|
+
|
1918
|
+
|
1919
|
+
/**
|
1920
|
+
* Set attribute `name` to `val`, keep in mind these become
|
1921
|
+
* part of a raw js object literal, so to quote a value you must
|
1922
|
+
* '"quote me"', otherwise or example 'user.name' is literal JavaScript.
|
1923
|
+
*
|
1924
|
+
* @param {String} name
|
1925
|
+
* @param {String} val
|
1926
|
+
* @param {Boolean} escaped
|
1927
|
+
* @return {Tag} for chaining
|
1928
|
+
* @api public
|
1929
|
+
*/
|
1930
|
+
|
1931
|
+
Attrs.prototype.setAttribute = function(name, val, escaped){
|
1932
|
+
this.attrs.push({ name: name, val: val, escaped: escaped });
|
1933
|
+
return this;
|
1934
|
+
};
|
1935
|
+
|
1936
|
+
/**
|
1937
|
+
* Remove attribute `name` when present.
|
1938
|
+
*
|
1939
|
+
* @param {String} name
|
1940
|
+
* @api public
|
1941
|
+
*/
|
1942
|
+
|
1943
|
+
Attrs.prototype.removeAttribute = function(name){
|
1944
|
+
for (var i = 0, len = this.attrs.length; i < len; ++i) {
|
1945
|
+
if (this.attrs[i] && this.attrs[i].name == name) {
|
1946
|
+
delete this.attrs[i];
|
1947
|
+
}
|
1948
|
+
}
|
1949
|
+
};
|
1950
|
+
|
1951
|
+
/**
|
1952
|
+
* Get attribute value by `name`.
|
1953
|
+
*
|
1954
|
+
* @param {String} name
|
1955
|
+
* @return {String}
|
1956
|
+
* @api public
|
1957
|
+
*/
|
1958
|
+
|
1959
|
+
Attrs.prototype.getAttribute = function(name){
|
1960
|
+
for (var i = 0, len = this.attrs.length; i < len; ++i) {
|
1961
|
+
if (this.attrs[i] && this.attrs[i].name == name) {
|
1962
|
+
return this.attrs[i].val;
|
1963
|
+
}
|
1964
|
+
}
|
1965
|
+
};
|
1966
|
+
|
1967
|
+
}); // module: nodes/attrs.js
|
1968
|
+
|
1674
1969
|
require.register("nodes/block-comment.js", function(module, exports, require){
|
1675
1970
|
|
1676
1971
|
/*!
|
@@ -1743,6 +2038,12 @@ Block.prototype = new Node;
|
|
1743
2038
|
Block.prototype.constructor = Block;
|
1744
2039
|
|
1745
2040
|
|
2041
|
+
/**
|
2042
|
+
* Block flag.
|
2043
|
+
*/
|
2044
|
+
|
2045
|
+
Block.prototype.isBlock = true;
|
2046
|
+
|
1746
2047
|
/**
|
1747
2048
|
* Replace the nodes in `other` with the nodes
|
1748
2049
|
* in `this` block.
|
@@ -1812,6 +2113,21 @@ Block.prototype.includeBlock = function(){
|
|
1812
2113
|
return ret;
|
1813
2114
|
};
|
1814
2115
|
|
2116
|
+
/**
|
2117
|
+
* Return a clone of this block.
|
2118
|
+
*
|
2119
|
+
* @return {Block}
|
2120
|
+
* @api private
|
2121
|
+
*/
|
2122
|
+
|
2123
|
+
Block.prototype.clone = function(){
|
2124
|
+
var clone = new Block;
|
2125
|
+
for (var i = 0, len = this.nodes.length; i < len; ++i) {
|
2126
|
+
clone.push(this.nodes[i].clone());
|
2127
|
+
}
|
2128
|
+
return clone;
|
2129
|
+
};
|
2130
|
+
|
1815
2131
|
|
1816
2132
|
}); // module: nodes/block.js
|
1817
2133
|
|
@@ -2045,7 +2361,7 @@ var Filter = module.exports = function Filter(name, block, attrs) {
|
|
2045
2361
|
this.name = name;
|
2046
2362
|
this.block = block;
|
2047
2363
|
this.attrs = attrs;
|
2048
|
-
this.isASTFilter = block
|
2364
|
+
this.isASTFilter = !block.nodes.every(function(node){ return node.isText });
|
2049
2365
|
};
|
2050
2366
|
|
2051
2367
|
/**
|
@@ -2104,7 +2420,8 @@ var Node = require('./node');
|
|
2104
2420
|
|
2105
2421
|
var Literal = module.exports = function Literal(str) {
|
2106
2422
|
this.str = str
|
2107
|
-
.replace(
|
2423
|
+
.replace(/\\/g, "\\\\")
|
2424
|
+
.replace(/\n|\r\n/g, "\\n")
|
2108
2425
|
.replace(/'/g, "\\'");
|
2109
2426
|
};
|
2110
2427
|
|
@@ -2130,7 +2447,7 @@ require.register("nodes/mixin.js", function(module, exports, require){
|
|
2130
2447
|
* Module dependencies.
|
2131
2448
|
*/
|
2132
2449
|
|
2133
|
-
var
|
2450
|
+
var Attrs = require('./attrs');
|
2134
2451
|
|
2135
2452
|
/**
|
2136
2453
|
* Initialize a new `Mixin` with `name` and `block`.
|
@@ -2141,17 +2458,19 @@ var Node = require('./node');
|
|
2141
2458
|
* @api public
|
2142
2459
|
*/
|
2143
2460
|
|
2144
|
-
var Mixin = module.exports = function Mixin(name, args, block){
|
2461
|
+
var Mixin = module.exports = function Mixin(name, args, block, call){
|
2145
2462
|
this.name = name;
|
2146
2463
|
this.args = args;
|
2147
2464
|
this.block = block;
|
2465
|
+
this.attrs = [];
|
2466
|
+
this.call = call;
|
2148
2467
|
};
|
2149
2468
|
|
2150
2469
|
/**
|
2151
|
-
* Inherit from `
|
2470
|
+
* Inherit from `Attrs`.
|
2152
2471
|
*/
|
2153
2472
|
|
2154
|
-
Mixin.prototype = new
|
2473
|
+
Mixin.prototype = new Attrs;
|
2155
2474
|
Mixin.prototype.constructor = Mixin;
|
2156
2475
|
|
2157
2476
|
|
@@ -2173,6 +2492,18 @@ require.register("nodes/node.js", function(module, exports, require){
|
|
2173
2492
|
*/
|
2174
2493
|
|
2175
2494
|
var Node = module.exports = function Node(){};
|
2495
|
+
|
2496
|
+
/**
|
2497
|
+
* Clone this node (return itself)
|
2498
|
+
*
|
2499
|
+
* @return {Node}
|
2500
|
+
* @api private
|
2501
|
+
*/
|
2502
|
+
|
2503
|
+
Node.prototype.clone = function(){
|
2504
|
+
return this;
|
2505
|
+
};
|
2506
|
+
|
2176
2507
|
}); // module: nodes/node.js
|
2177
2508
|
|
2178
2509
|
require.register("nodes/tag.js", function(module, exports, require){
|
@@ -2187,8 +2518,9 @@ require.register("nodes/tag.js", function(module, exports, require){
|
|
2187
2518
|
* Module dependencies.
|
2188
2519
|
*/
|
2189
2520
|
|
2190
|
-
var
|
2191
|
-
Block = require('./block')
|
2521
|
+
var Attrs = require('./attrs'),
|
2522
|
+
Block = require('./block'),
|
2523
|
+
inlineTags = require('../inline-tags');
|
2192
2524
|
|
2193
2525
|
/**
|
2194
2526
|
* Initialize a `Tag` node with the given tag `name` and optional `block`.
|
@@ -2205,60 +2537,73 @@ var Tag = module.exports = function Tag(name, block) {
|
|
2205
2537
|
};
|
2206
2538
|
|
2207
2539
|
/**
|
2208
|
-
* Inherit from `
|
2540
|
+
* Inherit from `Attrs`.
|
2209
2541
|
*/
|
2210
2542
|
|
2211
|
-
Tag.prototype = new
|
2543
|
+
Tag.prototype = new Attrs;
|
2212
2544
|
Tag.prototype.constructor = Tag;
|
2213
2545
|
|
2214
2546
|
|
2215
2547
|
/**
|
2216
|
-
*
|
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.
|
2548
|
+
* Clone this tag.
|
2219
2549
|
*
|
2220
|
-
* @
|
2221
|
-
* @
|
2222
|
-
* @return {Tag} for chaining
|
2223
|
-
* @api public
|
2550
|
+
* @return {Tag}
|
2551
|
+
* @api private
|
2224
2552
|
*/
|
2225
2553
|
|
2226
|
-
Tag.prototype.
|
2227
|
-
this.
|
2228
|
-
|
2554
|
+
Tag.prototype.clone = function(){
|
2555
|
+
var clone = new Tag(this.name, this.block.clone());
|
2556
|
+
clone.line = this.line;
|
2557
|
+
clone.attrs = this.attrs;
|
2558
|
+
clone.textOnly = this.textOnly;
|
2559
|
+
return clone;
|
2229
2560
|
};
|
2230
2561
|
|
2231
2562
|
/**
|
2232
|
-
*
|
2563
|
+
* Check if this tag is an inline tag.
|
2233
2564
|
*
|
2234
|
-
* @
|
2235
|
-
* @api
|
2565
|
+
* @return {Boolean}
|
2566
|
+
* @api private
|
2236
2567
|
*/
|
2237
2568
|
|
2238
|
-
Tag.prototype.
|
2239
|
-
|
2240
|
-
if (this.attrs[i] && this.attrs[i].name == name) {
|
2241
|
-
delete this.attrs[i];
|
2242
|
-
}
|
2243
|
-
}
|
2569
|
+
Tag.prototype.isInline = function(){
|
2570
|
+
return ~inlineTags.indexOf(this.name);
|
2244
2571
|
};
|
2245
2572
|
|
2246
2573
|
/**
|
2247
|
-
*
|
2574
|
+
* Check if this tag's contents can be inlined. Used for pretty printing.
|
2248
2575
|
*
|
2249
|
-
* @
|
2250
|
-
* @
|
2251
|
-
* @api public
|
2576
|
+
* @return {Boolean}
|
2577
|
+
* @api private
|
2252
2578
|
*/
|
2253
2579
|
|
2254
|
-
Tag.prototype.
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2580
|
+
Tag.prototype.canInline = function(){
|
2581
|
+
var nodes = this.block.nodes;
|
2582
|
+
|
2583
|
+
function isInline(node){
|
2584
|
+
// Recurse if the node is a block
|
2585
|
+
if (node.isBlock) return node.nodes.every(isInline);
|
2586
|
+
return node.isText || (node.isInline && node.isInline());
|
2587
|
+
}
|
2588
|
+
|
2589
|
+
// Empty tag
|
2590
|
+
if (!nodes.length) return true;
|
2591
|
+
|
2592
|
+
// Text-only or inline-only tag
|
2593
|
+
if (1 == nodes.length) return isInline(nodes[0]);
|
2594
|
+
|
2595
|
+
// Multi-line inline-only tag
|
2596
|
+
if (this.block.nodes.every(isInline)) {
|
2597
|
+
for (var i = 1, len = nodes.length; i < len; ++i) {
|
2598
|
+
if (nodes[i-1].isText && nodes[i].isText)
|
2599
|
+
return false;
|
2258
2600
|
}
|
2601
|
+
return true;
|
2259
2602
|
}
|
2603
|
+
|
2604
|
+
// Mixed tag
|
2605
|
+
return false;
|
2260
2606
|
};
|
2261
|
-
|
2262
2607
|
}); // module: nodes/tag.js
|
2263
2608
|
|
2264
2609
|
require.register("nodes/text.js", function(module, exports, require){
|
@@ -2283,8 +2628,8 @@ var Node = require('./node');
|
|
2283
2628
|
*/
|
2284
2629
|
|
2285
2630
|
var Text = module.exports = function Text(line) {
|
2286
|
-
this.
|
2287
|
-
if ('string' == typeof line) this.
|
2631
|
+
this.val = '';
|
2632
|
+
if ('string' == typeof line) this.val = line;
|
2288
2633
|
};
|
2289
2634
|
|
2290
2635
|
/**
|
@@ -2296,17 +2641,10 @@ Text.prototype.constructor = Text;
|
|
2296
2641
|
|
2297
2642
|
|
2298
2643
|
/**
|
2299
|
-
*
|
2300
|
-
*
|
2301
|
-
* @param {Node} node
|
2302
|
-
* @return {Number}
|
2303
|
-
* @api public
|
2644
|
+
* Flag as text.
|
2304
2645
|
*/
|
2305
2646
|
|
2306
|
-
Text.prototype.
|
2307
|
-
return this.nodes.push(node);
|
2308
|
-
};
|
2309
|
-
|
2647
|
+
Text.prototype.isText = true;
|
2310
2648
|
}); // module: nodes/text.js
|
2311
2649
|
|
2312
2650
|
require.register("parser.js", function(module, exports, require){
|
@@ -2338,6 +2676,7 @@ var Parser = exports = module.exports = function Parser(str, filename, options){
|
|
2338
2676
|
this.lexer = new Lexer(str, options);
|
2339
2677
|
this.filename = filename;
|
2340
2678
|
this.blocks = {};
|
2679
|
+
this.mixins = {};
|
2341
2680
|
this.options = options;
|
2342
2681
|
this.contexts = [this];
|
2343
2682
|
};
|
@@ -2446,6 +2785,9 @@ Parser.prototype = {
|
|
2446
2785
|
this.context(parser);
|
2447
2786
|
var ast = parser.parse();
|
2448
2787
|
this.context();
|
2788
|
+
// hoist mixins
|
2789
|
+
for (var name in this.mixins)
|
2790
|
+
ast.unshift(this.mixins[name]);
|
2449
2791
|
return ast;
|
2450
2792
|
}
|
2451
2793
|
|
@@ -2493,6 +2835,7 @@ Parser.prototype = {
|
|
2493
2835
|
* | yield
|
2494
2836
|
* | id
|
2495
2837
|
* | class
|
2838
|
+
* | interpolation
|
2496
2839
|
*/
|
2497
2840
|
|
2498
2841
|
parseExpr: function(){
|
@@ -2525,6 +2868,10 @@ Parser.prototype = {
|
|
2525
2868
|
return this.parseEach();
|
2526
2869
|
case 'code':
|
2527
2870
|
return this.parseCode();
|
2871
|
+
case 'call':
|
2872
|
+
return this.parseCall();
|
2873
|
+
case 'interpolation':
|
2874
|
+
return this.parseInterpolation();
|
2528
2875
|
case 'yield':
|
2529
2876
|
this.advance();
|
2530
2877
|
var block = new nodes.Block;
|
@@ -2688,6 +3035,10 @@ Parser.prototype = {
|
|
2688
3035
|
, node = new nodes.Each(tok.code, tok.val, tok.key);
|
2689
3036
|
node.line = this.line();
|
2690
3037
|
node.block = this.block();
|
3038
|
+
if (this.peek().type == 'code' && this.peek().val == 'else') {
|
3039
|
+
this.advance();
|
3040
|
+
node.alternative = this.block();
|
3041
|
+
}
|
2691
3042
|
return node;
|
2692
3043
|
},
|
2693
3044
|
|
@@ -2784,6 +3135,8 @@ Parser.prototype = {
|
|
2784
3135
|
var path = join(dir, path)
|
2785
3136
|
, str = fs.readFileSync(path, 'utf8')
|
2786
3137
|
, parser = new Parser(str, path, this.options);
|
3138
|
+
parser.blocks = this.blocks;
|
3139
|
+
parser.mixins = this.mixins;
|
2787
3140
|
|
2788
3141
|
this.context(parser);
|
2789
3142
|
var ast = parser.parse();
|
@@ -2797,6 +3150,21 @@ Parser.prototype = {
|
|
2797
3150
|
return ast;
|
2798
3151
|
},
|
2799
3152
|
|
3153
|
+
/**
|
3154
|
+
* call ident block
|
3155
|
+
*/
|
3156
|
+
|
3157
|
+
parseCall: function(){
|
3158
|
+
var tok = this.expect('call')
|
3159
|
+
, name = tok.val
|
3160
|
+
, args = tok.args
|
3161
|
+
, mixin = new nodes.Mixin(name, args, new nodes.Block, true);
|
3162
|
+
|
3163
|
+
this.tag(mixin);
|
3164
|
+
if (mixin.block.isEmpty()) mixin.block = null;
|
3165
|
+
return mixin;
|
3166
|
+
},
|
3167
|
+
|
2800
3168
|
/**
|
2801
3169
|
* mixin block
|
2802
3170
|
*/
|
@@ -2804,11 +3172,18 @@ Parser.prototype = {
|
|
2804
3172
|
parseMixin: function(){
|
2805
3173
|
var tok = this.expect('mixin')
|
2806
3174
|
, name = tok.val
|
2807
|
-
, args = tok.args
|
2808
|
-
|
2809
|
-
|
2810
|
-
|
2811
|
-
|
3175
|
+
, args = tok.args
|
3176
|
+
, mixin;
|
3177
|
+
|
3178
|
+
// definition
|
3179
|
+
if ('indent' == this.peek().type) {
|
3180
|
+
mixin = new nodes.Mixin(name, args, this.block(), false);
|
3181
|
+
this.mixins[name] = mixin;
|
3182
|
+
return mixin;
|
3183
|
+
// call
|
3184
|
+
} else {
|
3185
|
+
return new nodes.Mixin(name, args, null, true);
|
3186
|
+
}
|
2812
3187
|
},
|
2813
3188
|
|
2814
3189
|
/**
|
@@ -2816,32 +3191,31 @@ Parser.prototype = {
|
|
2816
3191
|
*/
|
2817
3192
|
|
2818
3193
|
parseTextBlock: function(){
|
2819
|
-
var
|
2820
|
-
|
3194
|
+
var block = new nodes.Block;
|
3195
|
+
block.line = this.line();
|
2821
3196
|
var spaces = this.expect('indent').val;
|
2822
3197
|
if (null == this._spaces) this._spaces = spaces;
|
2823
3198
|
var indent = Array(spaces - this._spaces + 1).join(' ');
|
2824
3199
|
while ('outdent' != this.peek().type) {
|
2825
3200
|
switch (this.peek().type) {
|
2826
3201
|
case 'newline':
|
2827
|
-
text.push('\\n');
|
2828
3202
|
this.advance();
|
2829
3203
|
break;
|
2830
3204
|
case 'indent':
|
2831
|
-
text.push('\\n');
|
2832
3205
|
this.parseTextBlock().nodes.forEach(function(node){
|
2833
|
-
|
3206
|
+
block.push(node);
|
2834
3207
|
});
|
2835
|
-
text.push('\\n');
|
2836
3208
|
break;
|
2837
3209
|
default:
|
2838
|
-
text.
|
3210
|
+
var text = new nodes.Text(indent + this.advance().val);
|
3211
|
+
text.line = this.line();
|
3212
|
+
block.push(text);
|
2839
3213
|
}
|
2840
3214
|
}
|
2841
3215
|
|
2842
3216
|
if (spaces == this._spaces) this._spaces = null;
|
2843
3217
|
this.expect('outdent');
|
2844
|
-
return
|
3218
|
+
return block;
|
2845
3219
|
},
|
2846
3220
|
|
2847
3221
|
/**
|
@@ -2863,6 +3237,17 @@ Parser.prototype = {
|
|
2863
3237
|
return block;
|
2864
3238
|
},
|
2865
3239
|
|
3240
|
+
/**
|
3241
|
+
* interpolation (attrs | class | id)* (text | code | ':')? newline* block?
|
3242
|
+
*/
|
3243
|
+
|
3244
|
+
parseInterpolation: function(){
|
3245
|
+
var tok = this.advance();
|
3246
|
+
var tag = new nodes.Tag(tok.val);
|
3247
|
+
tag.buffer = true;
|
3248
|
+
return this.tag(tag);
|
3249
|
+
},
|
3250
|
+
|
2866
3251
|
/**
|
2867
3252
|
* tag (attrs | class | id)* (text | code | ':')? newline* block?
|
2868
3253
|
*/
|
@@ -2877,9 +3262,20 @@ Parser.prototype = {
|
|
2877
3262
|
}
|
2878
3263
|
}
|
2879
3264
|
|
2880
|
-
var
|
2881
|
-
, tag = new nodes.Tag(
|
2882
|
-
|
3265
|
+
var tok = this.advance()
|
3266
|
+
, tag = new nodes.Tag(tok.val);
|
3267
|
+
|
3268
|
+
tag.selfClosing = tok.selfClosing;
|
3269
|
+
|
3270
|
+
return this.tag(tag);
|
3271
|
+
},
|
3272
|
+
|
3273
|
+
/**
|
3274
|
+
* Parse tag.
|
3275
|
+
*/
|
3276
|
+
|
3277
|
+
tag: function(tag){
|
3278
|
+
var dot;
|
2883
3279
|
|
2884
3280
|
tag.line = this.line();
|
2885
3281
|
|
@@ -2893,12 +3289,17 @@ Parser.prototype = {
|
|
2893
3289
|
tag.setAttribute(tok.type, "'" + tok.val + "'");
|
2894
3290
|
continue;
|
2895
3291
|
case 'attrs':
|
2896
|
-
var
|
3292
|
+
var tok = this.advance()
|
3293
|
+
, obj = tok.attrs
|
3294
|
+
, escaped = tok.escaped
|
2897
3295
|
, names = Object.keys(obj);
|
3296
|
+
|
3297
|
+
if (tok.selfClosing) tag.selfClosing = true;
|
3298
|
+
|
2898
3299
|
for (var i = 0, len = names.length; i < len; ++i) {
|
2899
3300
|
var name = names[i]
|
2900
3301
|
, val = obj[name];
|
2901
|
-
tag.setAttribute(name, val);
|
3302
|
+
tag.setAttribute(name, val, escaped[name]);
|
2902
3303
|
}
|
2903
3304
|
continue;
|
2904
3305
|
default:
|
@@ -2915,7 +3316,7 @@ Parser.prototype = {
|
|
2915
3316
|
// (text | code | ':')?
|
2916
3317
|
switch (this.peek().type) {
|
2917
3318
|
case 'text':
|
2918
|
-
tag.
|
3319
|
+
tag.block.push(this.parseText());
|
2919
3320
|
break;
|
2920
3321
|
case 'code':
|
2921
3322
|
tag.code = this.parseCode();
|
@@ -2923,7 +3324,7 @@ Parser.prototype = {
|
|
2923
3324
|
case ':':
|
2924
3325
|
this.advance();
|
2925
3326
|
tag.block = new nodes.Block;
|
2926
|
-
tag.block.push(this.
|
3327
|
+
tag.block.push(this.parseExpr());
|
2927
3328
|
break;
|
2928
3329
|
}
|
2929
3330
|
|
@@ -2995,41 +3396,97 @@ if (!Object.keys) {
|
|
2995
3396
|
}
|
2996
3397
|
}
|
2997
3398
|
return arr;
|
2998
|
-
}
|
3399
|
+
}
|
3400
|
+
}
|
3401
|
+
|
3402
|
+
/**
|
3403
|
+
* Merge two attribute objects giving precedence
|
3404
|
+
* to values in object `b`. Classes are special-cased
|
3405
|
+
* allowing for arrays and merging/joining appropriately
|
3406
|
+
* resulting in a string.
|
3407
|
+
*
|
3408
|
+
* @param {Object} a
|
3409
|
+
* @param {Object} b
|
3410
|
+
* @return {Object} a
|
3411
|
+
* @api private
|
3412
|
+
*/
|
3413
|
+
|
3414
|
+
exports.merge = function merge(a, b) {
|
3415
|
+
var ac = a['class'];
|
3416
|
+
var bc = b['class'];
|
3417
|
+
|
3418
|
+
if (ac || bc) {
|
3419
|
+
ac = ac || [];
|
3420
|
+
bc = bc || [];
|
3421
|
+
if (!Array.isArray(ac)) ac = [ac];
|
3422
|
+
if (!Array.isArray(bc)) bc = [bc];
|
3423
|
+
ac = ac.filter(nulls);
|
3424
|
+
bc = bc.filter(nulls);
|
3425
|
+
a['class'] = ac.concat(bc).join(' ');
|
3426
|
+
}
|
3427
|
+
|
3428
|
+
for (var key in b) {
|
3429
|
+
if (key != 'class') {
|
3430
|
+
a[key] = b[key];
|
3431
|
+
}
|
3432
|
+
}
|
3433
|
+
|
3434
|
+
return a;
|
3435
|
+
};
|
3436
|
+
|
3437
|
+
/**
|
3438
|
+
* Filter null `val`s.
|
3439
|
+
*
|
3440
|
+
* @param {Mixed} val
|
3441
|
+
* @return {Mixed}
|
3442
|
+
* @api private
|
3443
|
+
*/
|
3444
|
+
|
3445
|
+
function nulls(val) {
|
3446
|
+
return val != null;
|
2999
3447
|
}
|
3000
3448
|
|
3001
3449
|
/**
|
3002
3450
|
* Render the given attributes object.
|
3003
3451
|
*
|
3004
3452
|
* @param {Object} obj
|
3453
|
+
* @param {Object} escaped
|
3005
3454
|
* @return {String}
|
3006
3455
|
* @api private
|
3007
3456
|
*/
|
3008
3457
|
|
3009
|
-
exports.attrs = function attrs(obj){
|
3458
|
+
exports.attrs = function attrs(obj, escaped){
|
3010
3459
|
var buf = []
|
3011
3460
|
, terse = obj.terse;
|
3461
|
+
|
3012
3462
|
delete obj.terse;
|
3013
3463
|
var keys = Object.keys(obj)
|
3014
3464
|
, len = keys.length;
|
3465
|
+
|
3015
3466
|
if (len) {
|
3016
3467
|
buf.push('');
|
3017
3468
|
for (var i = 0; i < len; ++i) {
|
3018
3469
|
var key = keys[i]
|
3019
3470
|
, val = obj[key];
|
3471
|
+
|
3020
3472
|
if ('boolean' == typeof val || null == val) {
|
3021
3473
|
if (val) {
|
3022
3474
|
terse
|
3023
3475
|
? buf.push(key)
|
3024
3476
|
: buf.push(key + '="' + key + '"');
|
3025
3477
|
}
|
3478
|
+
} else if (0 == key.indexOf('data') && 'string' != typeof val) {
|
3479
|
+
buf.push(key + "='" + JSON.stringify(val) + "'");
|
3026
3480
|
} else if ('class' == key && Array.isArray(val)) {
|
3027
3481
|
buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
|
3028
|
-
} else {
|
3482
|
+
} else if (escaped && escaped[key]) {
|
3029
3483
|
buf.push(key + '="' + exports.escape(val) + '"');
|
3484
|
+
} else {
|
3485
|
+
buf.push(key + '="' + val + '"');
|
3030
3486
|
}
|
3031
3487
|
}
|
3032
3488
|
}
|
3489
|
+
|
3033
3490
|
return buf.join(' ');
|
3034
3491
|
};
|
3035
3492
|
|
@@ -3043,7 +3500,7 @@ exports.attrs = function attrs(obj){
|
|
3043
3500
|
|
3044
3501
|
exports.escape = function escape(html){
|
3045
3502
|
return String(html)
|
3046
|
-
.replace(/&(
|
3503
|
+
.replace(/&(?!(\w+|\#\d+);)/g, '&')
|
3047
3504
|
.replace(/</g, '<')
|
3048
3505
|
.replace(/>/g, '>')
|
3049
3506
|
.replace(/"/g, '"');
|
@@ -3062,30 +3519,25 @@ exports.escape = function escape(html){
|
|
3062
3519
|
exports.rethrow = function rethrow(err, filename, lineno){
|
3063
3520
|
if (!filename) throw err;
|
3064
3521
|
|
3065
|
-
|
3066
|
-
|
3067
|
-
|
3068
|
-
|
3069
|
-
|
3070
|
-
|
3071
|
-
|
3072
|
-
|
3073
|
-
|
3074
|
-
|
3075
|
-
|
3076
|
-
|
3077
|
-
|
3078
|
-
|
3079
|
-
+ line;
|
3080
|
-
}).join('\n') + '\n\n';
|
3081
|
-
} catch(failure) {
|
3082
|
-
var context = '';
|
3083
|
-
}
|
3522
|
+
var context = 3
|
3523
|
+
, str = require('fs').readFileSync(filename, 'utf8')
|
3524
|
+
, lines = str.split('\n')
|
3525
|
+
, start = Math.max(lineno - context, 0)
|
3526
|
+
, end = Math.min(lines.length, lineno + context);
|
3527
|
+
|
3528
|
+
// Error context
|
3529
|
+
var context = lines.slice(start, end).map(function(line, i){
|
3530
|
+
var curr = i + start + 1;
|
3531
|
+
return (curr == lineno ? ' > ' : ' ')
|
3532
|
+
+ curr
|
3533
|
+
+ '| '
|
3534
|
+
+ line;
|
3535
|
+
}).join('\n');
|
3084
3536
|
|
3085
3537
|
// Alter exception message
|
3086
3538
|
err.path = filename;
|
3087
|
-
err.message = (filename || 'Jade') + ':' + lineno
|
3088
|
-
+ '\n' + context + err.message;
|
3539
|
+
err.message = (filename || 'Jade') + ':' + lineno
|
3540
|
+
+ '\n' + context + '\n\n' + err.message;
|
3089
3541
|
throw err;
|
3090
3542
|
};
|
3091
3543
|
|
@@ -3104,6 +3556,7 @@ module.exports = [
|
|
3104
3556
|
, 'img'
|
3105
3557
|
, 'link'
|
3106
3558
|
, 'input'
|
3559
|
+
, 'source'
|
3107
3560
|
, 'area'
|
3108
3561
|
, 'base'
|
3109
3562
|
, 'col'
|