jader 0.0.3 → 0.0.4
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.
- 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'
|