cshaml-sprockets 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/LICENSE +8 -0
- data/README.md +54 -0
- data/Rakefile +1 -0
- data/cs-haml-sprockets.gemspec +27 -0
- data/lib/cshaml-sprockets.rb +35 -0
- data/lib/cshaml-sprockets/engine.rb +7 -0
- data/lib/cshaml-sprockets/version.rb +5 -0
- data/spec/lib/cshaml-sprockets_spec.rb +29 -0
- data/spec/spec_helper.rb +2 -0
- data/vendor/assets/javascripts/haml.js +1872 -0
- data/vendor/assets/javascripts/json2.js +487 -0
- data/vendor/assets/javascripts/underscore.js +999 -0
- data/vendor/assets/javascripts/underscore.string.js +331 -0
- metadata +96 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Copyright (c) 2012 Boris Nadion, Astrails Ltd
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
8
|
+
|
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Using clientside-haml-js with Sprockets and Rails 3.1/3.2
|
2
|
+
|
3
|
+
## About
|
4
|
+
|
5
|
+
Inspired by [haml-sprockets]. Use [clientside-haml-js] to render your haml templates with javascript/coffeescript and haml.
|
6
|
+
|
7
|
+
## How to use it?
|
8
|
+
|
9
|
+
The gem includes [clientside-haml-js], [JSON-js] for IE, [underscore] and [underscore.string]. You would not have to download it separately. To use this gem, you need to do the following:
|
10
|
+
|
11
|
+
In the `Gemfile`, add the following line.
|
12
|
+
|
13
|
+
gem "cshaml-sprockets"
|
14
|
+
|
15
|
+
In `app/assets/javascripts/application.js` add the following line before `//= require_tree .`
|
16
|
+
|
17
|
+
//=require json2
|
18
|
+
//=require underscore
|
19
|
+
//=require underscore.string
|
20
|
+
//=require haml
|
21
|
+
|
22
|
+
Now, you can create cshamljs files under `app/assets/javascripts/templates` folder. You can create the templates folder, if it does not already exist.
|
23
|
+
|
24
|
+
// code for app/assets/javascripts/templates/hello.jst.cshamljs
|
25
|
+
%h1 Hello HAML
|
26
|
+
|
27
|
+
Or to use CoffeeScript in haml templates name the file
|
28
|
+
|
29
|
+
// code for app/assets/javascripts/templates/hello.jst.cshamlcoffee
|
30
|
+
%h1= @model.title
|
31
|
+
|
32
|
+
You can now access the template anywhere in your javascript or coffeescript code.
|
33
|
+
|
34
|
+
JST["templates/hello"](model: model)
|
35
|
+
|
36
|
+
This should give you back the string `"<h1>model title</h1>"`.
|
37
|
+
|
38
|
+
Refer to [clientside-haml-js] for more details.
|
39
|
+
|
40
|
+
## LICENSE
|
41
|
+
|
42
|
+
This is distributed under the MIT license.
|
43
|
+
|
44
|
+
## Copyright
|
45
|
+
(c) 2012 Boris Nadion, [Astrails] Ltd
|
46
|
+
|
47
|
+
|
48
|
+
[HAML]: http://haml-lang.com/
|
49
|
+
[clientside-haml-js]: https://github.com/creationix/haml-js
|
50
|
+
[haml-sprockets]: https://github.com/dharanasoft/haml-sprockets
|
51
|
+
[underscore]: http://documentcloud.github.com/underscore/
|
52
|
+
[underscore.string]: http://epeli.github.com/underscore.string/
|
53
|
+
[JSON-js]: https://github.com/douglascrockford/JSON-js
|
54
|
+
[Astrails]: http://astrails.com
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "cshaml-sprockets/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "cshaml-sprockets"
|
7
|
+
s.version = Cshaml::Sprockets::VERSION
|
8
|
+
s.authors = ["Boris Nadion"]
|
9
|
+
s.email = ["boris@astrails.com"]
|
10
|
+
s.homepage = "https://github.com/astrails/cshaml-sprockets"
|
11
|
+
s.summary = %q{Use the awesome https://github.com/uglyog/clientside-haml-js javascript templating lib in Ruby}
|
12
|
+
s.description = %q{Use the JST processor and have haml code read in and appended to application.js}
|
13
|
+
|
14
|
+
s.rubyforge_project = "cshaml-sprockets"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "tilt", "~> 1.3"
|
24
|
+
s.add_runtime_dependency "sprockets", "~> 2.1.2"
|
25
|
+
|
26
|
+
s.add_development_dependency 'rspec'
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "cshaml-sprockets/version"
|
2
|
+
require 'tilt'
|
3
|
+
require 'sprockets'
|
4
|
+
module Cshaml
|
5
|
+
module Sprockets
|
6
|
+
class Template < ::Tilt::Template
|
7
|
+
def self.engine_initialized?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
def initialize_engine
|
11
|
+
end
|
12
|
+
def prepare
|
13
|
+
end
|
14
|
+
|
15
|
+
def _evaluate(haml_code, generator = nil)
|
16
|
+
haml_code = haml_code.gsub(/\\/,"\\\\").gsub(/\'/,"\\\\'").gsub(/\n/,"\\n")
|
17
|
+
"haml.compileHaml({source: '#{haml_code}'#{ generator ? ", generator: '#{generator}'" : "" }})"
|
18
|
+
end
|
19
|
+
|
20
|
+
def evaluate(scope, locals, &block)
|
21
|
+
_evaluate(data.dup)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class CoffeeTemplate < Template
|
26
|
+
def evaluate(scope, locals, &block)
|
27
|
+
_evaluate(data.dup, 'coffeescript')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
Sprockets::Engines
|
33
|
+
Sprockets.register_engine '.cshamljs', Cshaml::Sprockets::Template
|
34
|
+
Sprockets.register_engine '.cshamlcoffee', Cshaml::Sprockets::CoffeeTemplate
|
35
|
+
require 'cshaml-sprockets/engine' if defined?(Rails)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Cshaml::Sprockets::CoffeeTemplate do
|
4
|
+
describe 'evaluate' do
|
5
|
+
def process(data)
|
6
|
+
described_class.new { data }.render
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should support coffeescript" do
|
10
|
+
process(%{test}).should include(%{haml.compileHaml({source: 'test', generator: 'coffeescript'}})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe Cshaml::Sprockets::Template do
|
16
|
+
describe 'evaluate' do
|
17
|
+
def process(data)
|
18
|
+
described_class.new { data }.render
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should escape the string correctly' do
|
22
|
+
process(%{test}).should include(%{haml.compileHaml({source: 'test'}})
|
23
|
+
process(%{test "test"}).should include(%{haml.compileHaml({source: 'test "test"'}})
|
24
|
+
process(%{test\ntest}).should include(%{haml.compileHaml({source: 'test\\ntest'}})
|
25
|
+
process(%{test 'test'}).should include(%{haml.compileHaml({source: 'test \\'test\\''}})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,1872 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
clientside HAML compiler for Javascript and Coffeescript (Version 4)
|
4
|
+
|
5
|
+
Copyright 2011-12, Ronald Holshausen (https://github.com/uglyog)
|
6
|
+
Released under the MIT License (http://www.opensource.org/licenses/MIT)
|
7
|
+
*/
|
8
|
+
|
9
|
+
(function() {
|
10
|
+
var Buffer, CodeGenerator, CoffeeCodeGenerator, HamlRuntime, JsCodeGenerator, Tokeniser, filters, root,
|
11
|
+
__hasProp = Object.prototype.hasOwnProperty,
|
12
|
+
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
|
13
|
+
|
14
|
+
root = this;
|
15
|
+
|
16
|
+
/*
|
17
|
+
Haml runtime functions. These are used both by the compiler and the generated template functions
|
18
|
+
*/
|
19
|
+
|
20
|
+
HamlRuntime = {
|
21
|
+
/*
|
22
|
+
Taken from underscore.string.js escapeHTML, and replace the apos entity with character 39 so that it renders
|
23
|
+
correctly in IE7
|
24
|
+
*/
|
25
|
+
escapeHTML: function(str) {
|
26
|
+
return String(str || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
|
27
|
+
},
|
28
|
+
/*
|
29
|
+
Provides the implementation to preserve the whitespace as per the HAML reference
|
30
|
+
*/
|
31
|
+
perserveWhitespace: function(str) {
|
32
|
+
var i, out, re, result;
|
33
|
+
re = /<[a-zA-Z]+>[^<]*<\/[a-zA-Z]+>/g;
|
34
|
+
out = '';
|
35
|
+
i = 0;
|
36
|
+
result = re.exec(str);
|
37
|
+
if (result) {
|
38
|
+
while (result) {
|
39
|
+
out += str.substring(i, result.index);
|
40
|
+
out += result[0].replace(/\n/g, '
');
|
41
|
+
i = result.index + result[0].length;
|
42
|
+
result = re.exec(str);
|
43
|
+
}
|
44
|
+
out += str.substring(i);
|
45
|
+
} else {
|
46
|
+
out = str;
|
47
|
+
}
|
48
|
+
return out;
|
49
|
+
},
|
50
|
+
/*
|
51
|
+
Generates a error message including the current line in the source where the error occurred
|
52
|
+
*/
|
53
|
+
templateError: function(lineNumber, characterNumber, currentLine, error) {
|
54
|
+
var i, message;
|
55
|
+
message = error + " at line " + lineNumber + " and character " + characterNumber + ":\n" + currentLine + '\n';
|
56
|
+
i = 0;
|
57
|
+
while (i < characterNumber - 1) {
|
58
|
+
message += '-';
|
59
|
+
i++;
|
60
|
+
}
|
61
|
+
message += '^';
|
62
|
+
return message;
|
63
|
+
},
|
64
|
+
/*
|
65
|
+
Generates the attributes for the element by combining all the various sources together
|
66
|
+
*/
|
67
|
+
generateElementAttributes: function(context, id, classes, objRefFn, attrList, attrFunction, lineNumber, characterNumber, currentLine) {
|
68
|
+
var attr, attributes, className, dataAttr, dataAttributes, hash, html, object, objectId;
|
69
|
+
attributes = {};
|
70
|
+
attributes = this.combineAttributes(attributes, 'id', id);
|
71
|
+
if (classes.length > 0 && classes[0].length > 0) {
|
72
|
+
attributes = this.combineAttributes(attributes, 'class', classes);
|
73
|
+
}
|
74
|
+
if (attrList) {
|
75
|
+
for (attr in attrList) {
|
76
|
+
if (!__hasProp.call(attrList, attr)) continue;
|
77
|
+
attributes = this.combineAttributes(attributes, attr, attrList[attr]);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
if (objRefFn) {
|
81
|
+
try {
|
82
|
+
object = objRefFn.call(context, context);
|
83
|
+
if (object) {
|
84
|
+
objectId = null;
|
85
|
+
if (object.id) {
|
86
|
+
objectId = object.id;
|
87
|
+
} else if (object.get) {
|
88
|
+
objectId = object.get('id');
|
89
|
+
}
|
90
|
+
attributes = this.combineAttributes(attributes, 'id', objectId);
|
91
|
+
className = null;
|
92
|
+
if (object['class']) {
|
93
|
+
className = object['class'];
|
94
|
+
} else if (object.get) {
|
95
|
+
className = object.get('class');
|
96
|
+
}
|
97
|
+
attributes = this.combineAttributes(attributes, 'class', className);
|
98
|
+
}
|
99
|
+
} catch (e) {
|
100
|
+
throw haml.HamlRuntime.templateError(lineNumber, characterNumber, currentLine, "Error evaluating object reference - " + e);
|
101
|
+
}
|
102
|
+
}
|
103
|
+
if (attrFunction) {
|
104
|
+
try {
|
105
|
+
hash = attrFunction.call(context, context);
|
106
|
+
if (hash) {
|
107
|
+
for (attr in hash) {
|
108
|
+
if (!__hasProp.call(hash, attr)) continue;
|
109
|
+
if (attr === 'data') {
|
110
|
+
dataAttributes = hash[attr];
|
111
|
+
for (dataAttr in dataAttributes) {
|
112
|
+
if (!__hasProp.call(dataAttributes, dataAttr)) continue;
|
113
|
+
attributes = this.combineAttributes(attributes, 'data-' + dataAttr, dataAttributes[dataAttr]);
|
114
|
+
}
|
115
|
+
} else {
|
116
|
+
attributes = this.combineAttributes(attributes, attr, hash[attr]);
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
} catch (ex) {
|
121
|
+
throw haml.HamlRuntime.templateError(lineNumber, characterNumber, currentLine, "Error evaluating attribute hash - " + ex);
|
122
|
+
}
|
123
|
+
}
|
124
|
+
html = '';
|
125
|
+
if (attributes) {
|
126
|
+
for (attr in attributes) {
|
127
|
+
if (!__hasProp.call(attributes, attr)) continue;
|
128
|
+
if (haml.hasValue(attributes[attr])) {
|
129
|
+
if ((attr === 'id' || attr === 'for') && attributes[attr] instanceof Array) {
|
130
|
+
html += ' ' + attr + '="' + _(attributes[attr]).flatten().join('-') + '"';
|
131
|
+
} else if (attr === 'class' && attributes[attr] instanceof Array) {
|
132
|
+
html += ' ' + attr + '="' + _(attributes[attr]).flatten().join(' ') + '"';
|
133
|
+
} else {
|
134
|
+
html += ' ' + attr + '="' + haml.attrValue(attr, attributes[attr]) + '"';
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
138
|
+
}
|
139
|
+
return html;
|
140
|
+
},
|
141
|
+
/*
|
142
|
+
Returns a white space string with a length of indent * 2
|
143
|
+
*/
|
144
|
+
indentText: function(indent) {
|
145
|
+
var i, text;
|
146
|
+
text = '';
|
147
|
+
i = 0;
|
148
|
+
while (i < indent) {
|
149
|
+
text += ' ';
|
150
|
+
i++;
|
151
|
+
}
|
152
|
+
return text;
|
153
|
+
},
|
154
|
+
/*
|
155
|
+
Combines the attributes in the attributres hash with the given attribute and value
|
156
|
+
ID, FOR and CLASS attributes will expand to arrays when multiple values are provided
|
157
|
+
*/
|
158
|
+
combineAttributes: function(attributes, attrName, attrValue) {
|
159
|
+
var classes;
|
160
|
+
if (haml.hasValue(attrValue)) {
|
161
|
+
if (attrName === 'id' && attrValue.toString().length > 0) {
|
162
|
+
if (attributes && attributes.id instanceof Array) {
|
163
|
+
attributes.id.unshift(attrValue);
|
164
|
+
} else if (attributes && attributes.id) {
|
165
|
+
attributes.id = [attributes.id, attrValue];
|
166
|
+
} else if (attributes) {
|
167
|
+
attributes.id = attrValue;
|
168
|
+
} else {
|
169
|
+
attributes = {
|
170
|
+
id: attrValue
|
171
|
+
};
|
172
|
+
}
|
173
|
+
} else if (attrName === 'for' && attrValue.toString().length > 0) {
|
174
|
+
if (attributes && attributes['for'] instanceof Array) {
|
175
|
+
attributes['for'].unshift(attrValue);
|
176
|
+
} else if (attributes && attributes['for']) {
|
177
|
+
attributes['for'] = [attributes['for'], attrValue];
|
178
|
+
} else if (attributes) {
|
179
|
+
attributes['for'] = attrValue;
|
180
|
+
} else {
|
181
|
+
attributes = {
|
182
|
+
'for': attrValue
|
183
|
+
};
|
184
|
+
}
|
185
|
+
} else if (attrName === 'class') {
|
186
|
+
classes = [];
|
187
|
+
if (attrValue instanceof Array) {
|
188
|
+
classes = classes.concat(attrValue);
|
189
|
+
} else {
|
190
|
+
classes.push(attrValue);
|
191
|
+
}
|
192
|
+
if (attributes && attributes['class']) {
|
193
|
+
attributes['class'] = attributes['class'].concat(classes);
|
194
|
+
} else if (attributes) {
|
195
|
+
attributes['class'] = classes;
|
196
|
+
} else {
|
197
|
+
attributes = {
|
198
|
+
'class': classes
|
199
|
+
};
|
200
|
+
}
|
201
|
+
} else if (attrName !== 'id') {
|
202
|
+
attributes || (attributes = {});
|
203
|
+
attributes[attrName] = attrValue;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
return attributes;
|
207
|
+
}
|
208
|
+
};
|
209
|
+
|
210
|
+
/*
|
211
|
+
HAML Tokiniser: This class is responsible for parsing the haml source into tokens
|
212
|
+
*/
|
213
|
+
|
214
|
+
Tokeniser = (function() {
|
215
|
+
|
216
|
+
Tokeniser.prototype.currentLineMatcher = /[^\n]*/g;
|
217
|
+
|
218
|
+
Tokeniser.prototype.tokenMatchers = {
|
219
|
+
whitespace: /[ \t]+/g,
|
220
|
+
element: /%[a-zA-Z][a-zA-Z0-9]*/g,
|
221
|
+
idSelector: /#[a-zA-Z_\-][a-zA-Z0-9_\-]*/g,
|
222
|
+
classSelector: /\.[a-zA-Z0-9_\-]+/g,
|
223
|
+
identifier: /[a-zA-Z][a-zA-Z0-9\-]*/g,
|
224
|
+
quotedString: /[\'][^\'\n]*[\']/g,
|
225
|
+
quotedString2: /[\"][^\"\n]*[\"]/g,
|
226
|
+
comment: /\-#/g,
|
227
|
+
escapeHtml: /\&=/g,
|
228
|
+
unescapeHtml: /\!=/g,
|
229
|
+
objectReference: /\[[a-zA-Z_@][a-zA-Z0-9_]*\]/g,
|
230
|
+
doctype: /!!!/g,
|
231
|
+
continueLine: /\|\s*\n/g,
|
232
|
+
filter: /:\w+/g
|
233
|
+
};
|
234
|
+
|
235
|
+
function Tokeniser(options) {
|
236
|
+
var errorFn, successFn, template,
|
237
|
+
_this = this;
|
238
|
+
this.buffer = null;
|
239
|
+
this.bufferIndex = null;
|
240
|
+
this.prevToken = null;
|
241
|
+
this.token = null;
|
242
|
+
if (options.templateId != null) {
|
243
|
+
template = document.getElementById(options.templateId);
|
244
|
+
if (template) {
|
245
|
+
this.buffer = template.text;
|
246
|
+
this.bufferIndex = 0;
|
247
|
+
} else {
|
248
|
+
throw "Did not find a template with ID '" + options.templateId + "'";
|
249
|
+
}
|
250
|
+
} else if (options.template != null) {
|
251
|
+
this.buffer = options.template;
|
252
|
+
this.bufferIndex = 0;
|
253
|
+
} else if (options.templateUrl != null) {
|
254
|
+
errorFn = function(jqXHR, textStatus, errorThrown) {
|
255
|
+
throw "Failed to fetch haml template at URL " + options.templateUrl + ": " + textStatus + " " + errorThrown;
|
256
|
+
};
|
257
|
+
successFn = function(data) {
|
258
|
+
_this.buffer = data;
|
259
|
+
return _this.bufferIndex = 0;
|
260
|
+
};
|
261
|
+
jQuery.ajax({
|
262
|
+
url: options.templateUrl,
|
263
|
+
success: successFn,
|
264
|
+
error: errorFn,
|
265
|
+
dataType: 'text',
|
266
|
+
async: false,
|
267
|
+
xhrFields: {
|
268
|
+
withCredentials: true
|
269
|
+
}
|
270
|
+
});
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
/*
|
275
|
+
Try to match a token with the given regexp
|
276
|
+
*/
|
277
|
+
|
278
|
+
Tokeniser.prototype.matchToken = function(matcher) {
|
279
|
+
var result;
|
280
|
+
matcher.lastIndex = this.bufferIndex;
|
281
|
+
result = matcher.exec(this.buffer);
|
282
|
+
if ((result != null ? result.index : void 0) === this.bufferIndex) {
|
283
|
+
return result[0];
|
284
|
+
}
|
285
|
+
};
|
286
|
+
|
287
|
+
/*
|
288
|
+
Match a multi-character token
|
289
|
+
*/
|
290
|
+
|
291
|
+
Tokeniser.prototype.matchMultiCharToken = function(matcher, token, tokenStr) {
|
292
|
+
var matched, _ref;
|
293
|
+
if (!this.token) {
|
294
|
+
matched = this.matchToken(matcher);
|
295
|
+
if (matched) {
|
296
|
+
this.token = token;
|
297
|
+
this.token.tokenString = (_ref = typeof tokenStr === "function" ? tokenStr(matched) : void 0) != null ? _ref : matched;
|
298
|
+
this.token.matched = matched;
|
299
|
+
return this.advanceCharsInBuffer(matched.length);
|
300
|
+
}
|
301
|
+
}
|
302
|
+
};
|
303
|
+
|
304
|
+
/*
|
305
|
+
Match a single character token
|
306
|
+
*/
|
307
|
+
|
308
|
+
Tokeniser.prototype.matchSingleCharToken = function(ch, token) {
|
309
|
+
if (!this.token && this.buffer.charAt(this.bufferIndex) === ch) {
|
310
|
+
this.token = token;
|
311
|
+
this.token.tokenString = ch;
|
312
|
+
this.token.matched = ch;
|
313
|
+
return this.advanceCharsInBuffer(1);
|
314
|
+
}
|
315
|
+
};
|
316
|
+
|
317
|
+
/*
|
318
|
+
Match and return the next token in the input buffer
|
319
|
+
*/
|
320
|
+
|
321
|
+
Tokeniser.prototype.getNextToken = function() {
|
322
|
+
var braceCount, ch, ch1, characterNumberStart, i, lineNumberStart, str;
|
323
|
+
if (isNaN(this.bufferIndex)) {
|
324
|
+
throw haml.HamlRuntime.templateError(this.lineNumber, this.characterNumber, this.currentLine, "An internal parser error has occurred in the HAML parser");
|
325
|
+
}
|
326
|
+
this.prevToken = this.token;
|
327
|
+
this.token = null;
|
328
|
+
if (this.buffer === null || this.buffer.length === this.bufferIndex) {
|
329
|
+
this.token = {
|
330
|
+
eof: true,
|
331
|
+
token: 'EOF'
|
332
|
+
};
|
333
|
+
} else {
|
334
|
+
this.initLine();
|
335
|
+
if (!this.token) {
|
336
|
+
ch = this.buffer.charCodeAt(this.bufferIndex);
|
337
|
+
ch1 = this.buffer.charCodeAt(this.bufferIndex + 1);
|
338
|
+
if (ch === 10 || (ch === 13 && ch1 === 10)) {
|
339
|
+
this.token = {
|
340
|
+
eol: true,
|
341
|
+
token: 'EOL'
|
342
|
+
};
|
343
|
+
if (ch === 13 && ch1 === 10) {
|
344
|
+
this.advanceCharsInBuffer(2);
|
345
|
+
this.token.matched = String.fromCharCode(ch) + String.fromCharCode(ch1);
|
346
|
+
} else {
|
347
|
+
this.advanceCharsInBuffer(1);
|
348
|
+
this.token.matched = String.fromCharCode(ch);
|
349
|
+
}
|
350
|
+
this.characterNumber = 0;
|
351
|
+
this.currentLine = this.getCurrentLine();
|
352
|
+
}
|
353
|
+
}
|
354
|
+
this.matchMultiCharToken(this.tokenMatchers.whitespace, {
|
355
|
+
ws: true,
|
356
|
+
token: 'WS'
|
357
|
+
});
|
358
|
+
this.matchMultiCharToken(this.tokenMatchers.continueLine, {
|
359
|
+
continueLine: true,
|
360
|
+
token: 'CONTINUELINE'
|
361
|
+
});
|
362
|
+
this.matchMultiCharToken(this.tokenMatchers.element, {
|
363
|
+
element: true,
|
364
|
+
token: 'ELEMENT'
|
365
|
+
}, function(matched) {
|
366
|
+
return matched.substring(1);
|
367
|
+
});
|
368
|
+
this.matchMultiCharToken(this.tokenMatchers.idSelector, {
|
369
|
+
idSelector: true,
|
370
|
+
token: 'ID'
|
371
|
+
}, function(matched) {
|
372
|
+
return matched.substring(1);
|
373
|
+
});
|
374
|
+
this.matchMultiCharToken(this.tokenMatchers.classSelector, {
|
375
|
+
classSelector: true,
|
376
|
+
token: 'CLASS'
|
377
|
+
}, function(matched) {
|
378
|
+
return matched.substring(1);
|
379
|
+
});
|
380
|
+
this.matchMultiCharToken(this.tokenMatchers.identifier, {
|
381
|
+
identifier: true,
|
382
|
+
token: 'IDENTIFIER'
|
383
|
+
});
|
384
|
+
this.matchMultiCharToken(this.tokenMatchers.doctype, {
|
385
|
+
doctype: true,
|
386
|
+
token: 'DOCTYPE'
|
387
|
+
});
|
388
|
+
this.matchMultiCharToken(this.tokenMatchers.filter, {
|
389
|
+
filter: true,
|
390
|
+
token: 'FILTER'
|
391
|
+
}, function(matched) {
|
392
|
+
return matched.substring(1);
|
393
|
+
});
|
394
|
+
if (!this.token) {
|
395
|
+
str = this.matchToken(this.tokenMatchers.quotedString);
|
396
|
+
if (!str) str = this.matchToken(this.tokenMatchers.quotedString2);
|
397
|
+
if (str) {
|
398
|
+
this.token = {
|
399
|
+
string: true,
|
400
|
+
token: 'STRING',
|
401
|
+
tokenString: str.substring(1, str.length - 1),
|
402
|
+
matched: str
|
403
|
+
};
|
404
|
+
this.advanceCharsInBuffer(str.length);
|
405
|
+
}
|
406
|
+
}
|
407
|
+
this.matchMultiCharToken(this.tokenMatchers.comment, {
|
408
|
+
comment: true,
|
409
|
+
token: 'COMMENT'
|
410
|
+
});
|
411
|
+
this.matchMultiCharToken(this.tokenMatchers.escapeHtml, {
|
412
|
+
escapeHtml: true,
|
413
|
+
token: 'ESCAPEHTML'
|
414
|
+
});
|
415
|
+
this.matchMultiCharToken(this.tokenMatchers.unescapeHtml, {
|
416
|
+
unescapeHtml: true,
|
417
|
+
token: 'UNESCAPEHTML'
|
418
|
+
});
|
419
|
+
this.matchMultiCharToken(this.tokenMatchers.objectReference, {
|
420
|
+
objectReference: true,
|
421
|
+
token: 'OBJECTREFERENCE'
|
422
|
+
}, function(matched) {
|
423
|
+
return matched.substring(1, matched.length - 1);
|
424
|
+
});
|
425
|
+
if (!this.token) {
|
426
|
+
if (this.buffer && this.buffer.charAt(this.bufferIndex) === '{') {
|
427
|
+
i = this.bufferIndex + 1;
|
428
|
+
characterNumberStart = this.characterNumber;
|
429
|
+
lineNumberStart = this.lineNumber;
|
430
|
+
braceCount = 1;
|
431
|
+
while (i < this.buffer.length && (braceCount > 1 || this.buffer.charAt(i) !== '}')) {
|
432
|
+
if (this.buffer.charAt(i) === '{') {
|
433
|
+
braceCount++;
|
434
|
+
} else if (this.buffer.charAt(i) === '}') {
|
435
|
+
braceCount--;
|
436
|
+
}
|
437
|
+
i++;
|
438
|
+
}
|
439
|
+
if (i === this.buffer.length) {
|
440
|
+
this.characterNumber = characterNumberStart + 1;
|
441
|
+
this.lineNumber = lineNumberStart;
|
442
|
+
throw this.parseError('Error parsing attribute hash - Did not find a terminating "}"');
|
443
|
+
} else {
|
444
|
+
this.token = {
|
445
|
+
attributeHash: true,
|
446
|
+
token: 'ATTRHASH',
|
447
|
+
tokenString: this.buffer.substring(this.bufferIndex, i + 1),
|
448
|
+
matched: this.buffer.substring(this.bufferIndex, i + 1)
|
449
|
+
};
|
450
|
+
this.advanceCharsInBuffer(i - this.bufferIndex + 1);
|
451
|
+
}
|
452
|
+
}
|
453
|
+
}
|
454
|
+
this.matchSingleCharToken('(', {
|
455
|
+
openBracket: true,
|
456
|
+
token: 'OPENBRACKET'
|
457
|
+
});
|
458
|
+
this.matchSingleCharToken(')', {
|
459
|
+
closeBracket: true,
|
460
|
+
token: 'CLOSEBRACKET'
|
461
|
+
});
|
462
|
+
this.matchSingleCharToken('=', {
|
463
|
+
equal: true,
|
464
|
+
token: 'EQUAL'
|
465
|
+
});
|
466
|
+
this.matchSingleCharToken('/', {
|
467
|
+
slash: true,
|
468
|
+
token: 'SLASH'
|
469
|
+
});
|
470
|
+
this.matchSingleCharToken('!', {
|
471
|
+
exclamation: true,
|
472
|
+
token: 'EXCLAMATION'
|
473
|
+
});
|
474
|
+
this.matchSingleCharToken('-', {
|
475
|
+
minus: true,
|
476
|
+
token: 'MINUS'
|
477
|
+
});
|
478
|
+
this.matchSingleCharToken('&', {
|
479
|
+
amp: true,
|
480
|
+
token: 'AMP'
|
481
|
+
});
|
482
|
+
this.matchSingleCharToken('<', {
|
483
|
+
lt: true,
|
484
|
+
token: 'LT'
|
485
|
+
});
|
486
|
+
this.matchSingleCharToken('>', {
|
487
|
+
gt: true,
|
488
|
+
token: 'GT'
|
489
|
+
});
|
490
|
+
this.matchSingleCharToken('~', {
|
491
|
+
tilde: true,
|
492
|
+
token: 'TILDE'
|
493
|
+
});
|
494
|
+
if (this.token === null) {
|
495
|
+
this.token = {
|
496
|
+
unknown: true,
|
497
|
+
token: 'UNKNOWN'
|
498
|
+
};
|
499
|
+
}
|
500
|
+
}
|
501
|
+
return this.token;
|
502
|
+
};
|
503
|
+
|
504
|
+
/*
|
505
|
+
Look ahead a number of tokens and return the token found
|
506
|
+
*/
|
507
|
+
|
508
|
+
Tokeniser.prototype.lookAhead = function(numberOfTokens) {
|
509
|
+
var bufferIndex, characterNumber, currentLine, currentToken, i, lineNumber, prevToken, token;
|
510
|
+
token = null;
|
511
|
+
if (numberOfTokens > 0) {
|
512
|
+
currentToken = this.token;
|
513
|
+
prevToken = this.prevToken;
|
514
|
+
currentLine = this.currentLine;
|
515
|
+
lineNumber = this.lineNumber;
|
516
|
+
characterNumber = this.characterNumber;
|
517
|
+
bufferIndex = this.bufferIndex;
|
518
|
+
i = 0;
|
519
|
+
while (i++ < numberOfTokens) {
|
520
|
+
token = this.getNextToken();
|
521
|
+
}
|
522
|
+
this.token = currentToken;
|
523
|
+
this.prevToken = prevToken;
|
524
|
+
this.currentLine = currentLine;
|
525
|
+
this.lineNumber = lineNumber;
|
526
|
+
this.characterNumber = characterNumber;
|
527
|
+
this.bufferIndex = bufferIndex;
|
528
|
+
}
|
529
|
+
return token;
|
530
|
+
};
|
531
|
+
|
532
|
+
/*
|
533
|
+
Initilise the line and character counters
|
534
|
+
*/
|
535
|
+
|
536
|
+
Tokeniser.prototype.initLine = function() {
|
537
|
+
if (!this.currentLine && this.currentLine !== "") {
|
538
|
+
this.currentLine = this.getCurrentLine();
|
539
|
+
this.lineNumber = 1;
|
540
|
+
return this.characterNumber = 0;
|
541
|
+
}
|
542
|
+
};
|
543
|
+
|
544
|
+
/*
|
545
|
+
Returns the current line in the input buffer
|
546
|
+
*/
|
547
|
+
|
548
|
+
Tokeniser.prototype.getCurrentLine = function(index) {
|
549
|
+
var line;
|
550
|
+
this.currentLineMatcher.lastIndex = this.bufferIndex + (index != null ? index : 0);
|
551
|
+
line = this.currentLineMatcher.exec(this.buffer);
|
552
|
+
if (line) {
|
553
|
+
return line[0];
|
554
|
+
} else {
|
555
|
+
return '';
|
556
|
+
}
|
557
|
+
};
|
558
|
+
|
559
|
+
/*
|
560
|
+
Returns an error string filled out with the line and character counters
|
561
|
+
*/
|
562
|
+
|
563
|
+
Tokeniser.prototype.parseError = function(error) {
|
564
|
+
return haml.HamlRuntime.templateError(this.lineNumber, this.characterNumber, this.currentLine, error);
|
565
|
+
};
|
566
|
+
|
567
|
+
/*
|
568
|
+
Skips to the end of the line and returns the string that was skipped
|
569
|
+
*/
|
570
|
+
|
571
|
+
Tokeniser.prototype.skipToEOLorEOF = function() {
|
572
|
+
var contents, line, text;
|
573
|
+
text = '';
|
574
|
+
if (!(this.token.eof || this.token.eol)) {
|
575
|
+
if (!this.token.unknown) text += this.token.matched;
|
576
|
+
this.currentLineMatcher.lastIndex = this.bufferIndex;
|
577
|
+
line = this.currentLineMatcher.exec(this.buffer);
|
578
|
+
if (line && line.index === this.bufferIndex) {
|
579
|
+
contents = _(line[0]).rtrim();
|
580
|
+
if (_(contents).endsWith('|')) {
|
581
|
+
text += contents.substring(0, contents.length - 1);
|
582
|
+
this.advanceCharsInBuffer(contents.length - 1);
|
583
|
+
this.getNextToken();
|
584
|
+
text += this.parseMultiLine();
|
585
|
+
} else {
|
586
|
+
text += line[0];
|
587
|
+
this.advanceCharsInBuffer(line[0].length);
|
588
|
+
this.getNextToken();
|
589
|
+
}
|
590
|
+
}
|
591
|
+
}
|
592
|
+
return text;
|
593
|
+
};
|
594
|
+
|
595
|
+
/*
|
596
|
+
Parses a multiline code block and returns the parsed text
|
597
|
+
*/
|
598
|
+
|
599
|
+
Tokeniser.prototype.parseMultiLine = function() {
|
600
|
+
var contents, line, text;
|
601
|
+
text = '';
|
602
|
+
while (this.token.continueLine) {
|
603
|
+
this.currentLineMatcher.lastIndex = this.bufferIndex;
|
604
|
+
line = this.currentLineMatcher.exec(this.buffer);
|
605
|
+
if (line && line.index === this.bufferIndex) {
|
606
|
+
contents = _(line[0]).rtrim();
|
607
|
+
if (_(contents).endsWith('|')) {
|
608
|
+
text += contents.substring(0, contents.length - 1);
|
609
|
+
this.advanceCharsInBuffer(contents.length - 1);
|
610
|
+
}
|
611
|
+
this.getNextToken();
|
612
|
+
}
|
613
|
+
}
|
614
|
+
return text;
|
615
|
+
};
|
616
|
+
|
617
|
+
/*
|
618
|
+
Advances the input buffer pointer by a number of characters, updating the line and character counters
|
619
|
+
*/
|
620
|
+
|
621
|
+
Tokeniser.prototype.advanceCharsInBuffer = function(numChars) {
|
622
|
+
var ch, ch1, i;
|
623
|
+
i = 0;
|
624
|
+
while (i < numChars) {
|
625
|
+
ch = this.buffer.charCodeAt(this.bufferIndex + i);
|
626
|
+
ch1 = this.buffer.charCodeAt(this.bufferIndex + i + 1);
|
627
|
+
if (ch === 13 && ch1 === 10) {
|
628
|
+
this.lineNumber++;
|
629
|
+
this.characterNumber = 0;
|
630
|
+
this.currentLine = this.getCurrentLine(i);
|
631
|
+
i++;
|
632
|
+
} else if (ch === 10) {
|
633
|
+
this.lineNumber++;
|
634
|
+
this.characterNumber = 0;
|
635
|
+
this.currentLine = this.getCurrentLine(i);
|
636
|
+
} else {
|
637
|
+
this.characterNumber++;
|
638
|
+
}
|
639
|
+
i++;
|
640
|
+
}
|
641
|
+
return this.bufferIndex += numChars;
|
642
|
+
};
|
643
|
+
|
644
|
+
/*
|
645
|
+
Returns the current line and character counters
|
646
|
+
*/
|
647
|
+
|
648
|
+
Tokeniser.prototype.currentParsePoint = function() {
|
649
|
+
return {
|
650
|
+
lineNumber: this.lineNumber,
|
651
|
+
characterNumber: this.characterNumber,
|
652
|
+
currentLine: this.currentLine
|
653
|
+
};
|
654
|
+
};
|
655
|
+
|
656
|
+
/*
|
657
|
+
Pushes back the current token onto the front of the input buffer
|
658
|
+
*/
|
659
|
+
|
660
|
+
Tokeniser.prototype.pushBackToken = function() {
|
661
|
+
if (!this.token.unknown && !this.token.eof) {
|
662
|
+
this.bufferIndex -= this.token.matched.length;
|
663
|
+
return this.token = this.prevToken;
|
664
|
+
}
|
665
|
+
};
|
666
|
+
|
667
|
+
/*
|
668
|
+
Is the current token an end of line or end of input buffer
|
669
|
+
*/
|
670
|
+
|
671
|
+
Tokeniser.prototype.isEolOrEof = function() {
|
672
|
+
return this.token.eol || this.token.eof;
|
673
|
+
};
|
674
|
+
|
675
|
+
return Tokeniser;
|
676
|
+
|
677
|
+
})();
|
678
|
+
|
679
|
+
/*
|
680
|
+
Provides buffering between the generated javascript and html contents
|
681
|
+
*/
|
682
|
+
|
683
|
+
Buffer = (function() {
|
684
|
+
|
685
|
+
function Buffer(generator) {
|
686
|
+
this.generator = generator;
|
687
|
+
this.buffer = '';
|
688
|
+
this.outputBuffer = '';
|
689
|
+
}
|
690
|
+
|
691
|
+
Buffer.prototype.append = function(str) {
|
692
|
+
if (this.buffer.length === 0) this.generator.mark();
|
693
|
+
if (str && str.length > 0) return this.buffer += str;
|
694
|
+
};
|
695
|
+
|
696
|
+
Buffer.prototype.appendToOutputBuffer = function(str) {
|
697
|
+
if (str && str.length > 0) {
|
698
|
+
this.flush();
|
699
|
+
return this.outputBuffer += str;
|
700
|
+
}
|
701
|
+
};
|
702
|
+
|
703
|
+
Buffer.prototype.flush = function() {
|
704
|
+
if (this.buffer && this.buffer.length > 0) {
|
705
|
+
this.outputBuffer += this.generator.generateFlush(this.buffer);
|
706
|
+
}
|
707
|
+
return this.buffer = '';
|
708
|
+
};
|
709
|
+
|
710
|
+
Buffer.prototype.output = function() {
|
711
|
+
return this.outputBuffer;
|
712
|
+
};
|
713
|
+
|
714
|
+
Buffer.prototype.trimWhitespace = function() {
|
715
|
+
var ch, i;
|
716
|
+
if (this.buffer.length > 0) {
|
717
|
+
i = this.buffer.length - 1;
|
718
|
+
while (i > 0) {
|
719
|
+
ch = this.buffer.charAt(i);
|
720
|
+
if (ch === ' ' || ch === '\t' || ch === '\n') {
|
721
|
+
i--;
|
722
|
+
} else if (i > 1 && (ch === 'n' || ch === 't') && (this.buffer.charAt(i - 1) === '\\')) {
|
723
|
+
i -= 2;
|
724
|
+
} else {
|
725
|
+
break;
|
726
|
+
}
|
727
|
+
}
|
728
|
+
if (i > 0 && i < this.buffer.length - 1) {
|
729
|
+
return this.buffer = this.buffer.substring(0, i + 1);
|
730
|
+
} else if (i === 0) {
|
731
|
+
return this.buffer = '';
|
732
|
+
}
|
733
|
+
}
|
734
|
+
};
|
735
|
+
|
736
|
+
return Buffer;
|
737
|
+
|
738
|
+
})();
|
739
|
+
|
740
|
+
/*
|
741
|
+
Common code shared across all code generators
|
742
|
+
*/
|
743
|
+
|
744
|
+
CodeGenerator = (function() {
|
745
|
+
|
746
|
+
function CodeGenerator() {}
|
747
|
+
|
748
|
+
CodeGenerator.prototype.embeddedCodeBlockMatcher = /#{([^}]*)}/g;
|
749
|
+
|
750
|
+
return CodeGenerator;
|
751
|
+
|
752
|
+
})();
|
753
|
+
|
754
|
+
/*
|
755
|
+
Code generator that generates a Javascript function body
|
756
|
+
*/
|
757
|
+
|
758
|
+
JsCodeGenerator = (function(_super) {
|
759
|
+
|
760
|
+
__extends(JsCodeGenerator, _super);
|
761
|
+
|
762
|
+
function JsCodeGenerator() {
|
763
|
+
this.outputBuffer = new haml.Buffer(this);
|
764
|
+
}
|
765
|
+
|
766
|
+
/*
|
767
|
+
Append a line with embedded javascript code
|
768
|
+
*/
|
769
|
+
|
770
|
+
JsCodeGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) {
|
771
|
+
this.outputBuffer.flush();
|
772
|
+
this.outputBuffer.appendToOutputBuffer(indentText + 'try {\n');
|
773
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' var value = eval("' + expression.replace(/"/g, '\\"').replace(/\\n/g, '\\\\n') + '");\n');
|
774
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' value = value === null ? "" : value;');
|
775
|
+
if (escapeContents) {
|
776
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.escapeHTML(String(value)));\n');
|
777
|
+
} else if (perserveWhitespace) {
|
778
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.perserveWhitespace(String(value)));\n');
|
779
|
+
} else {
|
780
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(String(value));\n');
|
781
|
+
}
|
782
|
+
this.outputBuffer.appendToOutputBuffer(indentText + '} catch (e) {\n');
|
783
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' throw new Error(haml.HamlRuntime.templateError(' + currentParsePoint.lineNumber + ', ' + currentParsePoint.characterNumber + ', "' + this.escapeCode(currentParsePoint.currentLine) + '",\n');
|
784
|
+
this.outputBuffer.appendToOutputBuffer(indentText + ' "Error evaluating expression - " + e));\n');
|
785
|
+
return this.outputBuffer.appendToOutputBuffer(indentText + '}\n');
|
786
|
+
};
|
787
|
+
|
788
|
+
/*
|
789
|
+
Initilising the output buffer with any variables or code
|
790
|
+
*/
|
791
|
+
|
792
|
+
JsCodeGenerator.prototype.initOutput = function() {
|
793
|
+
return this.outputBuffer.appendToOutputBuffer(' var html = [];\n' + ' var hashFunction = null, hashObject = null, objRef = null, objRefFn = null;\n with (context || {}) {\n');
|
794
|
+
};
|
795
|
+
|
796
|
+
/*
|
797
|
+
Flush and close the output buffer and return the contents
|
798
|
+
*/
|
799
|
+
|
800
|
+
JsCodeGenerator.prototype.closeAndReturnOutput = function() {
|
801
|
+
this.outputBuffer.flush();
|
802
|
+
return this.outputBuffer.output() + ' }\n return html.join("");\n';
|
803
|
+
};
|
804
|
+
|
805
|
+
/*
|
806
|
+
Append a line of code to the output buffer
|
807
|
+
*/
|
808
|
+
|
809
|
+
JsCodeGenerator.prototype.appendCodeLine = function(line, eol) {
|
810
|
+
this.outputBuffer.flush();
|
811
|
+
this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent));
|
812
|
+
this.outputBuffer.appendToOutputBuffer(line);
|
813
|
+
return this.outputBuffer.appendToOutputBuffer(eol);
|
814
|
+
};
|
815
|
+
|
816
|
+
/*
|
817
|
+
Does the current line end with a function declaration?
|
818
|
+
*/
|
819
|
+
|
820
|
+
JsCodeGenerator.prototype.lineMatchesStartFunctionBlock = function(line) {
|
821
|
+
return line.match(/function\s*\((,?\s*\w+)*\)\s*\{\s*$/);
|
822
|
+
};
|
823
|
+
|
824
|
+
/*
|
825
|
+
Does the current line end with a starting code block
|
826
|
+
*/
|
827
|
+
|
828
|
+
JsCodeGenerator.prototype.lineMatchesStartBlock = function(line) {
|
829
|
+
return line.match(/\{\s*$/);
|
830
|
+
};
|
831
|
+
|
832
|
+
/*
|
833
|
+
Generate the code to close off a code block
|
834
|
+
*/
|
835
|
+
|
836
|
+
JsCodeGenerator.prototype.closeOffCodeBlock = function(tokeniser) {
|
837
|
+
if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) {
|
838
|
+
this.outputBuffer.flush();
|
839
|
+
return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '}\n');
|
840
|
+
}
|
841
|
+
};
|
842
|
+
|
843
|
+
/*
|
844
|
+
Generate the code to close off a function parameter
|
845
|
+
*/
|
846
|
+
|
847
|
+
JsCodeGenerator.prototype.closeOffFunctionBlock = function(tokeniser) {
|
848
|
+
if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) {
|
849
|
+
this.outputBuffer.flush();
|
850
|
+
return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '});\n');
|
851
|
+
}
|
852
|
+
};
|
853
|
+
|
854
|
+
/*
|
855
|
+
Generate the code for dynamic attributes ({} form)
|
856
|
+
*/
|
857
|
+
|
858
|
+
JsCodeGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) {
|
859
|
+
this.outputBuffer.flush();
|
860
|
+
if (attributeHash.length > 0) {
|
861
|
+
attributeHash = this.replaceReservedWordsInHash(attributeHash);
|
862
|
+
this.outputBuffer.appendToOutputBuffer(' hashFunction = function () { return eval("hashObject = ' + attributeHash.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"); };\n');
|
863
|
+
}
|
864
|
+
if (objectRef.length > 0) {
|
865
|
+
this.outputBuffer.appendToOutputBuffer(' objRefFn = function () { return eval("objRef = ' + objectRef.replace(/"/g, '\\"') + '"); };\n');
|
866
|
+
}
|
867
|
+
return this.outputBuffer.appendToOutputBuffer(' html.push(haml.HamlRuntime.generateElementAttributes(context, "' + id + '", ["' + classes.join('","') + '"], objRefFn, ' + JSON.stringify(attributeList) + ', hashFunction, ' + currentParsePoint.lineNumber + ', ' + currentParsePoint.characterNumber + ', "' + this.escapeCode(currentParsePoint.currentLine) + '"));\n');
|
868
|
+
};
|
869
|
+
|
870
|
+
/*
|
871
|
+
Clean any reserved words in the given hash
|
872
|
+
*/
|
873
|
+
|
874
|
+
JsCodeGenerator.prototype.replaceReservedWordsInHash = function(hash) {
|
875
|
+
var reservedWord, resultHash, _i, _len, _ref;
|
876
|
+
resultHash = hash;
|
877
|
+
_ref = ['class', 'for'];
|
878
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
879
|
+
reservedWord = _ref[_i];
|
880
|
+
resultHash = resultHash.replace(reservedWord + ':', '"' + reservedWord + '":');
|
881
|
+
}
|
882
|
+
return resultHash;
|
883
|
+
};
|
884
|
+
|
885
|
+
/*
|
886
|
+
Escape the line so it is safe to put into a javascript string
|
887
|
+
*/
|
888
|
+
|
889
|
+
JsCodeGenerator.prototype.escapeCode = function(jsStr) {
|
890
|
+
return jsStr.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
891
|
+
};
|
892
|
+
|
893
|
+
/*
|
894
|
+
Generate a function from the function body
|
895
|
+
*/
|
896
|
+
|
897
|
+
JsCodeGenerator.prototype.generateJsFunction = function(functionBody) {
|
898
|
+
try {
|
899
|
+
return new Function('context', functionBody);
|
900
|
+
} catch (e) {
|
901
|
+
throw "Incorrect embedded code has resulted in an invalid Haml function - " + e + "\nGenerated Function:\n" + functionBody;
|
902
|
+
}
|
903
|
+
};
|
904
|
+
|
905
|
+
/*
|
906
|
+
Generate the code required to support a buffer flush
|
907
|
+
*/
|
908
|
+
|
909
|
+
JsCodeGenerator.prototype.generateFlush = function(bufferStr) {
|
910
|
+
return ' html.push("' + this.escapeCode(bufferStr) + '");\n';
|
911
|
+
};
|
912
|
+
|
913
|
+
/*
|
914
|
+
Set the current indent level
|
915
|
+
*/
|
916
|
+
|
917
|
+
JsCodeGenerator.prototype.setIndent = function(indent) {
|
918
|
+
return this.indent = indent;
|
919
|
+
};
|
920
|
+
|
921
|
+
/*
|
922
|
+
Save the current indent level if required
|
923
|
+
*/
|
924
|
+
|
925
|
+
JsCodeGenerator.prototype.mark = function() {};
|
926
|
+
|
927
|
+
/*
|
928
|
+
Append the text contents to the buffer, expanding any embedded code
|
929
|
+
*/
|
930
|
+
|
931
|
+
JsCodeGenerator.prototype.appendTextContents = function(text, shouldInterpolate, currentParsePoint, options) {
|
932
|
+
if (options == null) options = {};
|
933
|
+
if (shouldInterpolate && text.match(/#{[^}]*}/)) {
|
934
|
+
return this.interpolateString(text, currentParsePoint, options);
|
935
|
+
} else {
|
936
|
+
return this.outputBuffer.append(this.processText(text, options));
|
937
|
+
}
|
938
|
+
};
|
939
|
+
|
940
|
+
/*
|
941
|
+
Interpolate any embedded code in the text
|
942
|
+
*/
|
943
|
+
|
944
|
+
JsCodeGenerator.prototype.interpolateString = function(text, currentParsePoint, options) {
|
945
|
+
var index, precheedingChar, precheedingChar2, result;
|
946
|
+
index = 0;
|
947
|
+
result = this.embeddedCodeBlockMatcher.exec(text);
|
948
|
+
while (result) {
|
949
|
+
if (result.index > 0) precheedingChar = text.charAt(result.index - 1);
|
950
|
+
if (result.index > 1) precheedingChar2 = text.charAt(result.index - 2);
|
951
|
+
if (precheedingChar === '\\' && precheedingChar2 !== '\\') {
|
952
|
+
if (result.index !== 0) {
|
953
|
+
this.outputBuffer.append(this.processText(text.substring(index, result.index - 1), options));
|
954
|
+
}
|
955
|
+
this.outputBuffer.append(this.processText(result[0]), options);
|
956
|
+
} else {
|
957
|
+
this.outputBuffer.append(this.processText(text.substring(index, result.index)), options);
|
958
|
+
this.appendEmbeddedCode(HamlRuntime.indentText(this.indent + 1), result[1], options.escapeHTML, options.perserveWhitespace, currentParsePoint);
|
959
|
+
}
|
960
|
+
index = this.embeddedCodeBlockMatcher.lastIndex;
|
961
|
+
result = this.embeddedCodeBlockMatcher.exec(text);
|
962
|
+
}
|
963
|
+
if (index < text.length) {
|
964
|
+
return this.outputBuffer.append(this.processText(text.substring(index), options));
|
965
|
+
}
|
966
|
+
};
|
967
|
+
|
968
|
+
/*
|
969
|
+
process text based on escape and preserve flags
|
970
|
+
*/
|
971
|
+
|
972
|
+
JsCodeGenerator.prototype.processText = function(text, options) {
|
973
|
+
if (options != null ? options.escapeHTML : void 0) {
|
974
|
+
return haml.HamlRuntime.escapeHTML(text);
|
975
|
+
} else if (options != null ? options.perserveWhitespace : void 0) {
|
976
|
+
return haml.HamlRuntime.perserveWhitespace(text);
|
977
|
+
} else {
|
978
|
+
return text;
|
979
|
+
}
|
980
|
+
};
|
981
|
+
|
982
|
+
return JsCodeGenerator;
|
983
|
+
|
984
|
+
})(CodeGenerator);
|
985
|
+
|
986
|
+
/*
|
987
|
+
Code generator that generates a coffeescript function body
|
988
|
+
*/
|
989
|
+
|
990
|
+
CoffeeCodeGenerator = (function(_super) {
|
991
|
+
|
992
|
+
__extends(CoffeeCodeGenerator, _super);
|
993
|
+
|
994
|
+
function CoffeeCodeGenerator() {
|
995
|
+
this.outputBuffer = new haml.Buffer(this);
|
996
|
+
}
|
997
|
+
|
998
|
+
CoffeeCodeGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) {
|
999
|
+
var indent;
|
1000
|
+
this.outputBuffer.flush();
|
1001
|
+
indent = this.calcCodeIndent();
|
1002
|
+
this.outputBuffer.appendToOutputBuffer(indent + "try\n");
|
1003
|
+
this.outputBuffer.appendToOutputBuffer(indent + " exp = CoffeeScript.compile('" + expression.replace(/'/g, "\\'").replace(/\\n/g, '\\\\n') + "', bare: true)\n");
|
1004
|
+
this.outputBuffer.appendToOutputBuffer(indent + " value = eval(exp)\n");
|
1005
|
+
this.outputBuffer.appendToOutputBuffer(indent + " value ?= ''\n");
|
1006
|
+
if (escapeContents) {
|
1007
|
+
this.outputBuffer.appendToOutputBuffer(indent + " html.push(haml.HamlRuntime.escapeHTML(String(value)))\n");
|
1008
|
+
} else if (perserveWhitespace) {
|
1009
|
+
this.outputBuffer.appendToOutputBuffer(indent + " html.push(haml.HamlRuntime.perserveWhitespace(String(value)))\n");
|
1010
|
+
} else {
|
1011
|
+
this.outputBuffer.appendToOutputBuffer(indent + " html.push(String(value))\n");
|
1012
|
+
}
|
1013
|
+
this.outputBuffer.appendToOutputBuffer(indent + "catch e \n");
|
1014
|
+
this.outputBuffer.appendToOutputBuffer(indent + " throw new Error(haml.HamlRuntime.templateError(" + currentParsePoint.lineNumber + ", " + currentParsePoint.characterNumber + ", '" + this.escapeCode(currentParsePoint.currentLine) + "',\n");
|
1015
|
+
return this.outputBuffer.appendToOutputBuffer(indent + " 'Error evaluating expression - ' + e))\n");
|
1016
|
+
};
|
1017
|
+
|
1018
|
+
CoffeeCodeGenerator.prototype.initOutput = function() {
|
1019
|
+
return this.outputBuffer.appendToOutputBuffer('html = []\n');
|
1020
|
+
};
|
1021
|
+
|
1022
|
+
CoffeeCodeGenerator.prototype.closeAndReturnOutput = function() {
|
1023
|
+
this.outputBuffer.flush();
|
1024
|
+
return this.outputBuffer.output() + 'return html.join("")\n';
|
1025
|
+
};
|
1026
|
+
|
1027
|
+
CoffeeCodeGenerator.prototype.appendCodeLine = function(line, eol) {
|
1028
|
+
this.outputBuffer.flush();
|
1029
|
+
if ((this.prevCodeIndent != null) && this.prevCodeIndent < this.indent) {
|
1030
|
+
this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent - this.prevCodeIndent));
|
1031
|
+
}
|
1032
|
+
this.outputBuffer.appendToOutputBuffer(_(line).trim());
|
1033
|
+
this.outputBuffer.appendToOutputBuffer(eol);
|
1034
|
+
return this.prevCodeIndent = this.indent;
|
1035
|
+
};
|
1036
|
+
|
1037
|
+
CoffeeCodeGenerator.prototype.lineMatchesStartFunctionBlock = function(line) {
|
1038
|
+
return line.match(/\) [\-=]>\s*$/);
|
1039
|
+
};
|
1040
|
+
|
1041
|
+
CoffeeCodeGenerator.prototype.lineMatchesStartBlock = function(line) {
|
1042
|
+
return true;
|
1043
|
+
};
|
1044
|
+
|
1045
|
+
CoffeeCodeGenerator.prototype.closeOffCodeBlock = function(tokeniser) {
|
1046
|
+
return this.outputBuffer.flush();
|
1047
|
+
};
|
1048
|
+
|
1049
|
+
CoffeeCodeGenerator.prototype.closeOffFunctionBlock = function(tokeniser) {
|
1050
|
+
return this.outputBuffer.flush();
|
1051
|
+
};
|
1052
|
+
|
1053
|
+
CoffeeCodeGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) {
|
1054
|
+
this.outputBuffer.flush();
|
1055
|
+
if (attributeHash.length > 0) {
|
1056
|
+
attributeHash = this.replaceReservedWordsInHash(attributeHash);
|
1057
|
+
this.outputBuffer.appendToOutputBuffer("hashFunction = () -> s = CoffeeScript.compile('" + attributeHash.replace(/'/g, "\\'").replace(/\n/g, '\\n') + "', bare: true); eval 'hashObject = ' + s\n");
|
1058
|
+
}
|
1059
|
+
if (objectRef.length > 0) {
|
1060
|
+
this.outputBuffer.appendToOutputBuffer("objRefFn = () -> s = CoffeeScript.compile('" + objectRef.replace(/'/g, "\\'") + "', bare: true); eval 'objRef = ' + s\n");
|
1061
|
+
}
|
1062
|
+
return this.outputBuffer.appendToOutputBuffer("html.push(haml.HamlRuntime.generateElementAttributes(this, '" + id + "', ['" + classes.join("','") + "'], objRefFn ? null, " + JSON.stringify(attributeList) + ", hashFunction ? null, " + currentParsePoint.lineNumber + ", " + currentParsePoint.characterNumber + ", '" + this.escapeCode(currentParsePoint.currentLine) + "'))\n");
|
1063
|
+
};
|
1064
|
+
|
1065
|
+
CoffeeCodeGenerator.prototype.replaceReservedWordsInHash = function(hash) {
|
1066
|
+
var reservedWord, resultHash, _i, _len, _ref;
|
1067
|
+
resultHash = hash;
|
1068
|
+
_ref = ['class', 'for'];
|
1069
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
1070
|
+
reservedWord = _ref[_i];
|
1071
|
+
resultHash = resultHash.replace(reservedWord + ':', "'" + reservedWord + "':");
|
1072
|
+
}
|
1073
|
+
return resultHash;
|
1074
|
+
};
|
1075
|
+
|
1076
|
+
/*
|
1077
|
+
Escapes the string for insertion into the generated code. Embedded code blocks in strings must not be escaped
|
1078
|
+
*/
|
1079
|
+
|
1080
|
+
CoffeeCodeGenerator.prototype.escapeCode = function(str) {
|
1081
|
+
var index, outString, precheedingChar, precheedingChar2, result;
|
1082
|
+
outString = '';
|
1083
|
+
index = 0;
|
1084
|
+
result = this.embeddedCodeBlockMatcher.exec(str);
|
1085
|
+
while (result) {
|
1086
|
+
if (result.index > 0) precheedingChar = str.charAt(result.index - 1);
|
1087
|
+
if (result.index > 1) precheedingChar2 = str.charAt(result.index - 2);
|
1088
|
+
if (precheedingChar === '\\' && precheedingChar2 !== '\\') {
|
1089
|
+
if (result.index !== 0) {
|
1090
|
+
outString += this._escapeText(str.substring(index, result.index - 1));
|
1091
|
+
}
|
1092
|
+
outString += this._escapeText('\\' + result[0]);
|
1093
|
+
} else {
|
1094
|
+
outString += this._escapeText(str.substring(index, result.index));
|
1095
|
+
outString += result[0];
|
1096
|
+
}
|
1097
|
+
index = this.embeddedCodeBlockMatcher.lastIndex;
|
1098
|
+
result = this.embeddedCodeBlockMatcher.exec(str);
|
1099
|
+
}
|
1100
|
+
if (index < str.length) outString += this._escapeText(str.substring(index));
|
1101
|
+
return outString;
|
1102
|
+
};
|
1103
|
+
|
1104
|
+
CoffeeCodeGenerator.prototype._escapeText = function(text) {
|
1105
|
+
return text.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/"/g, '\\\"').replace(/\n/g, '\\n').replace(/(^|[^\\]{2})\\\\#{/g, '$1\\#{');
|
1106
|
+
};
|
1107
|
+
|
1108
|
+
/*
|
1109
|
+
Generates the javascript function by compiling the given code with coffeescript compiler
|
1110
|
+
*/
|
1111
|
+
|
1112
|
+
CoffeeCodeGenerator.prototype.generateJsFunction = function(functionBody) {
|
1113
|
+
var fn;
|
1114
|
+
try {
|
1115
|
+
fn = CoffeeScript.compile(functionBody, {
|
1116
|
+
bare: true
|
1117
|
+
});
|
1118
|
+
return new Function(fn);
|
1119
|
+
} catch (e) {
|
1120
|
+
throw "Incorrect embedded code has resulted in an invalid Haml function - " + e + "\nGenerated Function:\n" + fn;
|
1121
|
+
}
|
1122
|
+
};
|
1123
|
+
|
1124
|
+
CoffeeCodeGenerator.prototype.generateFlush = function(bufferStr) {
|
1125
|
+
return this.calcCodeIndent() + "html.push('" + this.escapeCode(bufferStr) + "')\n";
|
1126
|
+
};
|
1127
|
+
|
1128
|
+
CoffeeCodeGenerator.prototype.setIndent = function(indent) {
|
1129
|
+
return this.indent = indent;
|
1130
|
+
};
|
1131
|
+
|
1132
|
+
CoffeeCodeGenerator.prototype.mark = function() {
|
1133
|
+
return this.prevIndent = this.indent;
|
1134
|
+
};
|
1135
|
+
|
1136
|
+
CoffeeCodeGenerator.prototype.calcCodeIndent = function() {
|
1137
|
+
if ((this.prevCodeIndent != null) && this.prevIndent > this.prevCodeIndent) {
|
1138
|
+
return HamlRuntime.indentText(this.prevIndent - this.prevCodeIndent);
|
1139
|
+
} else {
|
1140
|
+
return '';
|
1141
|
+
}
|
1142
|
+
};
|
1143
|
+
|
1144
|
+
/*
|
1145
|
+
Append the text contents to the buffer (interpolating embedded code not required for coffeescript)
|
1146
|
+
*/
|
1147
|
+
|
1148
|
+
CoffeeCodeGenerator.prototype.appendTextContents = function(text, shouldInterpolate, currentParsePoint, options) {
|
1149
|
+
var prefix, suffix;
|
1150
|
+
if (shouldInterpolate && text.match(/#{[^}]*}/)) {
|
1151
|
+
this.outputBuffer.flush();
|
1152
|
+
prefix = suffix = '';
|
1153
|
+
if (options != null ? options.escapeHTML : void 0) {
|
1154
|
+
prefix = 'haml.HamlRuntime.escapeHTML(';
|
1155
|
+
suffix = ')';
|
1156
|
+
} else if (options != null ? options.perserveWhitespace : void 0) {
|
1157
|
+
prefix = 'haml.HamlRuntime.perserveWhitespace(';
|
1158
|
+
suffix = ')';
|
1159
|
+
}
|
1160
|
+
return this.outputBuffer.appendToOutputBuffer(this.calcCodeIndent() + 'html.push(' + prefix + '"' + this.escapeCode(text) + '"' + suffix + ')\n');
|
1161
|
+
} else {
|
1162
|
+
if (options != null ? options.escapeHTML : void 0) {
|
1163
|
+
text = haml.HamlRuntime.escapeHTML(text);
|
1164
|
+
}
|
1165
|
+
if (options != null ? options.perserveWhitespace : void 0) {
|
1166
|
+
text = haml.HamlRuntime.perserveWhitespace(text);
|
1167
|
+
}
|
1168
|
+
return this.outputBuffer.append(text);
|
1169
|
+
}
|
1170
|
+
};
|
1171
|
+
|
1172
|
+
return CoffeeCodeGenerator;
|
1173
|
+
|
1174
|
+
})(CodeGenerator);
|
1175
|
+
|
1176
|
+
/*
|
1177
|
+
HAML filters are functions that take 3 parameters
|
1178
|
+
contents: The contents block for the filter an array of lines of text
|
1179
|
+
generator: The current generator for the compiled function
|
1180
|
+
indentText: A whitespace string specifying the current indent level
|
1181
|
+
currentParsePoint: line and character counters for the current parse point in the input buffer
|
1182
|
+
*/
|
1183
|
+
|
1184
|
+
filters = {
|
1185
|
+
/*
|
1186
|
+
Plain filter, just renders the text in the block
|
1187
|
+
*/
|
1188
|
+
plain: function(contents, generator, indentText, currentParsePoint) {
|
1189
|
+
var line, _i, _len;
|
1190
|
+
for (_i = 0, _len = contents.length; _i < _len; _i++) {
|
1191
|
+
line = contents[_i];
|
1192
|
+
generator.appendTextContents(indentText + line + '\n', true, currentParsePoint);
|
1193
|
+
}
|
1194
|
+
return true;
|
1195
|
+
},
|
1196
|
+
/*
|
1197
|
+
Wraps the filter block in a javascript tag
|
1198
|
+
*/
|
1199
|
+
javascript: function(contents, generator, indentText, currentParsePoint) {
|
1200
|
+
var line, _i, _len;
|
1201
|
+
generator.outputBuffer.append(indentText + "<script type=\"text/javascript\">\n");
|
1202
|
+
generator.outputBuffer.append(indentText + "//<![CDATA[\n");
|
1203
|
+
for (_i = 0, _len = contents.length; _i < _len; _i++) {
|
1204
|
+
line = contents[_i];
|
1205
|
+
generator.appendTextContents(indentText + line + '\n', true, currentParsePoint);
|
1206
|
+
}
|
1207
|
+
generator.outputBuffer.append(indentText + "//]]>\n");
|
1208
|
+
return generator.outputBuffer.append(indentText + "</script>\n");
|
1209
|
+
},
|
1210
|
+
/*
|
1211
|
+
Wraps the filter block in a style tag
|
1212
|
+
*/
|
1213
|
+
css: function(contents, generator, indentText, currentParsePoint) {
|
1214
|
+
var line, _i, _len;
|
1215
|
+
generator.outputBuffer.append(indentText + "<style type=\"text/css\">\n");
|
1216
|
+
generator.outputBuffer.append(indentText + "/*<![CDATA[*/\n");
|
1217
|
+
for (_i = 0, _len = contents.length; _i < _len; _i++) {
|
1218
|
+
line = contents[_i];
|
1219
|
+
generator.appendTextContents(indentText + line + '\n', true, currentParsePoint);
|
1220
|
+
}
|
1221
|
+
generator.outputBuffer.append(indentText + "/*]]>*/\n");
|
1222
|
+
return generator.outputBuffer.append(indentText + "</style>\n");
|
1223
|
+
},
|
1224
|
+
/*
|
1225
|
+
Wraps the filter block in a CDATA tag
|
1226
|
+
*/
|
1227
|
+
cdata: function(contents, generator, indentText, currentParsePoint) {
|
1228
|
+
var line, _i, _len;
|
1229
|
+
generator.outputBuffer.append(indentText + "<![CDATA[\n");
|
1230
|
+
for (_i = 0, _len = contents.length; _i < _len; _i++) {
|
1231
|
+
line = contents[_i];
|
1232
|
+
generator.appendTextContents(indentText + line + '\n', true, currentParsePoint);
|
1233
|
+
}
|
1234
|
+
return generator.outputBuffer.append(indentText + "]]>\n");
|
1235
|
+
},
|
1236
|
+
/*
|
1237
|
+
Preserve filter, preserved blocks of text aren't indented, and newlines are replaced with the HTML escape code for newlines
|
1238
|
+
*/
|
1239
|
+
preserve: function(contents, generator, indentText, currentParsePoint) {
|
1240
|
+
return generator.appendTextContents(contents.join('\n') + '\n', true, currentParsePoint, {
|
1241
|
+
perserveWhitespace: true
|
1242
|
+
});
|
1243
|
+
},
|
1244
|
+
/*
|
1245
|
+
Escape filter, renders the text in the block with html escaped
|
1246
|
+
*/
|
1247
|
+
escape: function(contents, generator, indentText, currentParsePoint) {
|
1248
|
+
var line, _i, _len;
|
1249
|
+
for (_i = 0, _len = contents.length; _i < _len; _i++) {
|
1250
|
+
line = contents[_i];
|
1251
|
+
generator.appendTextContents(indentText + line + '\n', true, currentParsePoint, {
|
1252
|
+
escapeHTML: true
|
1253
|
+
});
|
1254
|
+
}
|
1255
|
+
return true;
|
1256
|
+
}
|
1257
|
+
};
|
1258
|
+
|
1259
|
+
/*
|
1260
|
+
Main haml compiler implemtation
|
1261
|
+
*/
|
1262
|
+
|
1263
|
+
root.haml = {
|
1264
|
+
/*
|
1265
|
+
Compiles the haml provided in the parameters to a Javascipt function
|
1266
|
+
|
1267
|
+
Parameter:
|
1268
|
+
String: Looks for a haml template in dom with this ID
|
1269
|
+
Option Hash: The following options determines how haml sources and compiles the template
|
1270
|
+
source - This contains the template in string form
|
1271
|
+
sourceId - This contains the element ID in the dom which contains the haml source
|
1272
|
+
sourceUrl - This contains the URL where the template can be fetched from
|
1273
|
+
outputFormat - This determines what is returned, and has the following values:
|
1274
|
+
string - The javascript source code
|
1275
|
+
function - A javascript function (default)
|
1276
|
+
generator - Which code generator to use
|
1277
|
+
javascript (default)
|
1278
|
+
coffeescript
|
1279
|
+
|
1280
|
+
Returns a javascript function
|
1281
|
+
*/
|
1282
|
+
compileHaml: function(options) {
|
1283
|
+
var codeGenerator, result, tokinser;
|
1284
|
+
if (typeof options === 'string') {
|
1285
|
+
return this._compileHamlTemplate(options, new haml.JsCodeGenerator());
|
1286
|
+
} else {
|
1287
|
+
if (options.generator !== 'coffeescript') {
|
1288
|
+
codeGenerator = new haml.JsCodeGenerator();
|
1289
|
+
} else {
|
1290
|
+
codeGenerator = new haml.CoffeeCodeGenerator();
|
1291
|
+
}
|
1292
|
+
if (options.source != null) {
|
1293
|
+
tokinser = new haml.Tokeniser({
|
1294
|
+
template: options.source
|
1295
|
+
});
|
1296
|
+
} else if (options.sourceId != null) {
|
1297
|
+
tokinser = new haml.Tokeniser({
|
1298
|
+
templateId: options.sourceId
|
1299
|
+
});
|
1300
|
+
} else if (options.sourceUrl != null) {
|
1301
|
+
tokinser = new haml.Tokeniser({
|
1302
|
+
templateUrl: options.sourceUrl
|
1303
|
+
});
|
1304
|
+
} else {
|
1305
|
+
throw "No template source specified for compileHaml. You need to provide a source, sourceId or sourceUrl option";
|
1306
|
+
}
|
1307
|
+
result = this._compileHamlToJs(tokinser, codeGenerator);
|
1308
|
+
if (options.outputFormat !== 'string') {
|
1309
|
+
return codeGenerator.generateJsFunction(result);
|
1310
|
+
} else {
|
1311
|
+
return "function (context) {\n" + result + "}\n";
|
1312
|
+
}
|
1313
|
+
}
|
1314
|
+
},
|
1315
|
+
/*
|
1316
|
+
Compiles the haml in the script block with ID templateId using the coffeescript generator
|
1317
|
+
Returns a javascript function
|
1318
|
+
*/
|
1319
|
+
compileCoffeeHaml: function(templateId) {
|
1320
|
+
return this._compileHamlTemplate(templateId, new haml.CoffeeCodeGenerator());
|
1321
|
+
},
|
1322
|
+
/*
|
1323
|
+
Compiles the haml in the passed in string
|
1324
|
+
Returns a javascript function
|
1325
|
+
*/
|
1326
|
+
compileStringToJs: function(string) {
|
1327
|
+
var codeGenerator, result;
|
1328
|
+
codeGenerator = new haml.JsCodeGenerator();
|
1329
|
+
result = this._compileHamlToJs(new haml.Tokeniser({
|
1330
|
+
template: string
|
1331
|
+
}), codeGenerator);
|
1332
|
+
return codeGenerator.generateJsFunction(result);
|
1333
|
+
},
|
1334
|
+
/*
|
1335
|
+
Compiles the haml in the passed in string using the coffeescript generator
|
1336
|
+
Returns a javascript function
|
1337
|
+
*/
|
1338
|
+
compileCoffeeHamlFromString: function(string) {
|
1339
|
+
var codeGenerator, result;
|
1340
|
+
codeGenerator = new haml.CoffeeCodeGenerator();
|
1341
|
+
result = this._compileHamlToJs(new haml.Tokeniser({
|
1342
|
+
template: string
|
1343
|
+
}), codeGenerator);
|
1344
|
+
return codeGenerator.generateJsFunction(result);
|
1345
|
+
},
|
1346
|
+
/*
|
1347
|
+
Compiles the haml in the passed in string
|
1348
|
+
Returns the javascript function source
|
1349
|
+
|
1350
|
+
This is mainly used for precompiling the haml templates so they can be packaged.
|
1351
|
+
*/
|
1352
|
+
compileHamlToJsString: function(string) {
|
1353
|
+
var result;
|
1354
|
+
result = 'function (context) {\n';
|
1355
|
+
result += this._compileHamlToJs(new haml.Tokeniser({
|
1356
|
+
template: string
|
1357
|
+
}), new haml.JsCodeGenerator());
|
1358
|
+
return result += '}\n';
|
1359
|
+
},
|
1360
|
+
_compileHamlTemplate: function(templateId, codeGenerator) {
|
1361
|
+
var fn, result;
|
1362
|
+
haml.cache || (haml.cache = {});
|
1363
|
+
if (haml.cache[templateId]) return haml.cache[templateId];
|
1364
|
+
result = this._compileHamlToJs(new haml.Tokeniser({
|
1365
|
+
templateId: templateId
|
1366
|
+
}), codeGenerator);
|
1367
|
+
fn = codeGenerator.generateJsFunction(result);
|
1368
|
+
haml.cache[templateId] = fn;
|
1369
|
+
return fn;
|
1370
|
+
},
|
1371
|
+
_compileHamlToJs: function(tokeniser, generator) {
|
1372
|
+
var elementStack, indent;
|
1373
|
+
elementStack = [];
|
1374
|
+
generator.initOutput();
|
1375
|
+
tokeniser.getNextToken();
|
1376
|
+
while (!tokeniser.token.eof) {
|
1377
|
+
if (!tokeniser.token.eol) {
|
1378
|
+
indent = this._whitespace(tokeniser);
|
1379
|
+
generator.setIndent(indent);
|
1380
|
+
if (tokeniser.token.eol) {
|
1381
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent) + tokeniser.token.matched);
|
1382
|
+
tokeniser.getNextToken();
|
1383
|
+
} else if (tokeniser.token.doctype) {
|
1384
|
+
this._doctype(tokeniser, indent, generator);
|
1385
|
+
} else if (tokeniser.token.exclamation) {
|
1386
|
+
this._ignoredLine(tokeniser, indent, elementStack, generator);
|
1387
|
+
} else if (tokeniser.token.equal || tokeniser.token.escapeHtml || tokeniser.token.unescapeHtml || tokeniser.token.tilde) {
|
1388
|
+
this._embeddedJs(tokeniser, indent, elementStack, {
|
1389
|
+
innerWhitespace: true
|
1390
|
+
}, generator);
|
1391
|
+
} else if (tokeniser.token.minus) {
|
1392
|
+
this._jsLine(tokeniser, indent, elementStack, generator);
|
1393
|
+
} else if (tokeniser.token.comment || tokeniser.token.slash) {
|
1394
|
+
this._commentLine(tokeniser, indent, elementStack, generator);
|
1395
|
+
} else if (tokeniser.token.amp) {
|
1396
|
+
this._escapedLine(tokeniser, indent, elementStack, generator);
|
1397
|
+
} else if (tokeniser.token.filter) {
|
1398
|
+
this._filter(tokeniser, indent, generator);
|
1399
|
+
} else {
|
1400
|
+
this._templateLine(tokeniser, elementStack, indent, generator);
|
1401
|
+
}
|
1402
|
+
} else {
|
1403
|
+
generator.outputBuffer.append(tokeniser.token.matched);
|
1404
|
+
tokeniser.getNextToken();
|
1405
|
+
}
|
1406
|
+
}
|
1407
|
+
this._closeElements(0, elementStack, tokeniser, generator);
|
1408
|
+
return generator.closeAndReturnOutput();
|
1409
|
+
},
|
1410
|
+
_doctype: function(tokeniser, indent, generator) {
|
1411
|
+
var contents, params;
|
1412
|
+
if (tokeniser.token.doctype) {
|
1413
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent));
|
1414
|
+
tokeniser.getNextToken();
|
1415
|
+
if (tokeniser.token.ws) tokeniser.getNextToken();
|
1416
|
+
contents = tokeniser.skipToEOLorEOF();
|
1417
|
+
if (contents && contents.length > 0) {
|
1418
|
+
params = contents.split(/\s+/);
|
1419
|
+
switch (params[0]) {
|
1420
|
+
case 'XML':
|
1421
|
+
if (params.length > 1) {
|
1422
|
+
generator.outputBuffer.append("<?xml version='1.0' encoding='" + params[1] + "' ?>");
|
1423
|
+
} else {
|
1424
|
+
generator.outputBuffer.append("<?xml version='1.0' encoding='utf-8' ?>");
|
1425
|
+
}
|
1426
|
+
break;
|
1427
|
+
case 'Strict':
|
1428
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">');
|
1429
|
+
break;
|
1430
|
+
case 'Frameset':
|
1431
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">');
|
1432
|
+
break;
|
1433
|
+
case '5':
|
1434
|
+
generator.outputBuffer.append('<!DOCTYPE html>');
|
1435
|
+
break;
|
1436
|
+
case '1.1':
|
1437
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">');
|
1438
|
+
break;
|
1439
|
+
case 'Basic':
|
1440
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">');
|
1441
|
+
break;
|
1442
|
+
case 'Mobile':
|
1443
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">');
|
1444
|
+
break;
|
1445
|
+
case 'RDFa':
|
1446
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">');
|
1447
|
+
}
|
1448
|
+
} else {
|
1449
|
+
generator.outputBuffer.append('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">');
|
1450
|
+
}
|
1451
|
+
generator.outputBuffer.append(this._newline(tokeniser));
|
1452
|
+
return tokeniser.getNextToken();
|
1453
|
+
}
|
1454
|
+
},
|
1455
|
+
_filter: function(tokeniser, indent, generator) {
|
1456
|
+
var filter, filterBlock, i, line;
|
1457
|
+
if (tokeniser.token.filter) {
|
1458
|
+
filter = tokeniser.token.tokenString;
|
1459
|
+
if (!haml.filters[filter]) {
|
1460
|
+
throw tokeniser.parseError("Filter '" + filter + "' not registered. Filter functions need to be added to 'haml.filters'.");
|
1461
|
+
}
|
1462
|
+
tokeniser.skipToEOLorEOF();
|
1463
|
+
tokeniser.getNextToken();
|
1464
|
+
i = haml._whitespace(tokeniser);
|
1465
|
+
filterBlock = [];
|
1466
|
+
while (!tokeniser.token.eof && i > indent) {
|
1467
|
+
line = tokeniser.skipToEOLorEOF();
|
1468
|
+
filterBlock.push(haml.HamlRuntime.indentText(i - indent - 1) + line);
|
1469
|
+
tokeniser.getNextToken();
|
1470
|
+
i = haml._whitespace(tokeniser);
|
1471
|
+
}
|
1472
|
+
haml.filters[filter](filterBlock, generator, haml.HamlRuntime.indentText(indent), tokeniser.currentParsePoint());
|
1473
|
+
return tokeniser.pushBackToken();
|
1474
|
+
}
|
1475
|
+
},
|
1476
|
+
_commentLine: function(tokeniser, indent, elementStack, generator) {
|
1477
|
+
var contents, i;
|
1478
|
+
if (tokeniser.token.comment) {
|
1479
|
+
tokeniser.skipToEOLorEOF();
|
1480
|
+
tokeniser.getNextToken();
|
1481
|
+
i = this._whitespace(tokeniser);
|
1482
|
+
while (!tokeniser.token.eof && i > indent) {
|
1483
|
+
tokeniser.skipToEOLorEOF();
|
1484
|
+
tokeniser.getNextToken();
|
1485
|
+
i = this._whitespace(tokeniser);
|
1486
|
+
}
|
1487
|
+
if (i > 0) return tokeniser.pushBackToken();
|
1488
|
+
} else if (tokeniser.token.slash) {
|
1489
|
+
haml._closeElements(indent, elementStack, tokeniser, generator);
|
1490
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent));
|
1491
|
+
generator.outputBuffer.append("<!--");
|
1492
|
+
tokeniser.getNextToken();
|
1493
|
+
contents = tokeniser.skipToEOLorEOF();
|
1494
|
+
if (contents && contents.length > 0) {
|
1495
|
+
generator.outputBuffer.append(contents);
|
1496
|
+
}
|
1497
|
+
if (contents && _(contents).startsWith('[') && contents.match(/\]\s*$/)) {
|
1498
|
+
elementStack[indent] = {
|
1499
|
+
htmlConditionalComment: true,
|
1500
|
+
eol: this._newline(tokeniser)
|
1501
|
+
};
|
1502
|
+
generator.outputBuffer.append(">");
|
1503
|
+
} else {
|
1504
|
+
elementStack[indent] = {
|
1505
|
+
htmlComment: true,
|
1506
|
+
eol: this._newline(tokeniser)
|
1507
|
+
};
|
1508
|
+
}
|
1509
|
+
if (haml._tagHasContents(indent, tokeniser)) {
|
1510
|
+
generator.outputBuffer.append("\n");
|
1511
|
+
}
|
1512
|
+
return tokeniser.getNextToken();
|
1513
|
+
}
|
1514
|
+
},
|
1515
|
+
_escapedLine: function(tokeniser, indent, elementStack, generator) {
|
1516
|
+
var contents;
|
1517
|
+
if (tokeniser.token.amp) {
|
1518
|
+
haml._closeElements(indent, elementStack, tokeniser, generator);
|
1519
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent));
|
1520
|
+
tokeniser.getNextToken();
|
1521
|
+
contents = tokeniser.skipToEOLorEOF();
|
1522
|
+
if (contents && contents.length > 0) {
|
1523
|
+
generator.outputBuffer.append(haml.HamlRuntime.escapeHTML(contents));
|
1524
|
+
}
|
1525
|
+
generator.outputBuffer.append(this._newline(tokeniser));
|
1526
|
+
return tokeniser.getNextToken();
|
1527
|
+
}
|
1528
|
+
},
|
1529
|
+
_ignoredLine: function(tokeniser, indent, elementStack, generator) {
|
1530
|
+
var contents;
|
1531
|
+
if (tokeniser.token.exclamation) {
|
1532
|
+
tokeniser.getNextToken();
|
1533
|
+
if (tokeniser.token.ws) indent += haml._whitespace(tokeniser);
|
1534
|
+
haml._closeElements(indent, elementStack, tokeniser, generator);
|
1535
|
+
contents = tokeniser.skipToEOLorEOF();
|
1536
|
+
return generator.outputBuffer.append(HamlRuntime.indentText(indent) + contents);
|
1537
|
+
}
|
1538
|
+
},
|
1539
|
+
_embeddedJs: function(tokeniser, indent, elementStack, tagOptions, generator) {
|
1540
|
+
var currentParsePoint, escapeHtml, expression, indentText, perserveWhitespace;
|
1541
|
+
if (elementStack) {
|
1542
|
+
haml._closeElements(indent, elementStack, tokeniser, generator);
|
1543
|
+
}
|
1544
|
+
if (tokeniser.token.equal || tokeniser.token.escapeHtml || tokeniser.token.unescapeHtml || tokeniser.token.tilde) {
|
1545
|
+
escapeHtml = tokeniser.token.escapeHtml || tokeniser.token.equal;
|
1546
|
+
perserveWhitespace = tokeniser.token.tilde;
|
1547
|
+
currentParsePoint = tokeniser.currentParsePoint();
|
1548
|
+
tokeniser.getNextToken();
|
1549
|
+
expression = tokeniser.skipToEOLorEOF();
|
1550
|
+
indentText = HamlRuntime.indentText(indent);
|
1551
|
+
if (!tagOptions || tagOptions.innerWhitespace) {
|
1552
|
+
generator.outputBuffer.append(indentText);
|
1553
|
+
}
|
1554
|
+
generator.appendEmbeddedCode(indentText, expression, escapeHtml, perserveWhitespace, currentParsePoint);
|
1555
|
+
if (!tagOptions || tagOptions.innerWhitespace) {
|
1556
|
+
generator.outputBuffer.append(this._newline(tokeniser));
|
1557
|
+
if (tokeniser.token.eol) return tokeniser.getNextToken();
|
1558
|
+
}
|
1559
|
+
}
|
1560
|
+
},
|
1561
|
+
_jsLine: function(tokeniser, indent, elementStack, generator) {
|
1562
|
+
var line;
|
1563
|
+
if (tokeniser.token.minus) {
|
1564
|
+
haml._closeElements(indent, elementStack, tokeniser, generator);
|
1565
|
+
tokeniser.getNextToken();
|
1566
|
+
line = tokeniser.skipToEOLorEOF();
|
1567
|
+
generator.setIndent(indent);
|
1568
|
+
generator.appendCodeLine(line, this._newline(tokeniser));
|
1569
|
+
if (tokeniser.token.eol) tokeniser.getNextToken();
|
1570
|
+
if (generator.lineMatchesStartFunctionBlock(line)) {
|
1571
|
+
return elementStack[indent] = {
|
1572
|
+
fnBlock: true
|
1573
|
+
};
|
1574
|
+
} else if (generator.lineMatchesStartBlock(line)) {
|
1575
|
+
return elementStack[indent] = {
|
1576
|
+
block: true
|
1577
|
+
};
|
1578
|
+
}
|
1579
|
+
}
|
1580
|
+
},
|
1581
|
+
_templateLine: function(tokeniser, elementStack, indent, generator) {
|
1582
|
+
var attrList, attributesHash, classes, contents, currentParsePoint, hasContents, id, identifier, indentText, lineHasElement, objectRef, shouldInterpolate, tagOptions;
|
1583
|
+
if (!tokeniser.token.eol) {
|
1584
|
+
this._closeElements(indent, elementStack, tokeniser, generator);
|
1585
|
+
}
|
1586
|
+
identifier = this._element(tokeniser);
|
1587
|
+
id = this._idSelector(tokeniser);
|
1588
|
+
classes = this._classSelector(tokeniser);
|
1589
|
+
objectRef = this._objectReference(tokeniser);
|
1590
|
+
attrList = this._attributeList(tokeniser);
|
1591
|
+
currentParsePoint = tokeniser.currentParsePoint();
|
1592
|
+
attributesHash = this._attributeHash(tokeniser);
|
1593
|
+
tagOptions = {
|
1594
|
+
selfClosingTag: false,
|
1595
|
+
innerWhitespace: true,
|
1596
|
+
outerWhitespace: true
|
1597
|
+
};
|
1598
|
+
lineHasElement = this._lineHasElement(identifier, id, classes);
|
1599
|
+
if (tokeniser.token.slash) {
|
1600
|
+
tagOptions.selfClosingTag = true;
|
1601
|
+
tokeniser.getNextToken();
|
1602
|
+
}
|
1603
|
+
if (tokeniser.token.gt && lineHasElement) {
|
1604
|
+
tagOptions.outerWhitespace = false;
|
1605
|
+
tokeniser.getNextToken();
|
1606
|
+
}
|
1607
|
+
if (tokeniser.token.lt && lineHasElement) {
|
1608
|
+
tagOptions.innerWhitespace = false;
|
1609
|
+
tokeniser.getNextToken();
|
1610
|
+
}
|
1611
|
+
if (lineHasElement) {
|
1612
|
+
if (!tagOptions.selfClosingTag) {
|
1613
|
+
tagOptions.selfClosingTag = haml._isSelfClosingTag(identifier) && !haml._tagHasContents(indent, tokeniser);
|
1614
|
+
}
|
1615
|
+
this._openElement(currentParsePoint, indent, identifier, id, classes, objectRef, attrList, attributesHash, elementStack, tagOptions, generator);
|
1616
|
+
}
|
1617
|
+
hasContents = false;
|
1618
|
+
if (tokeniser.token.ws) tokeniser.getNextToken();
|
1619
|
+
if (tokeniser.token.equal || tokeniser.token.escapeHtml || tokeniser.token.unescapeHtml) {
|
1620
|
+
this._embeddedJs(tokeniser, indent + 1, null, tagOptions, generator);
|
1621
|
+
hasContents = true;
|
1622
|
+
} else {
|
1623
|
+
contents = '';
|
1624
|
+
shouldInterpolate = false;
|
1625
|
+
if (tokeniser.token.exclamation) {
|
1626
|
+
tokeniser.getNextToken();
|
1627
|
+
contents = tokeniser.skipToEOLorEOF();
|
1628
|
+
} else {
|
1629
|
+
contents = tokeniser.skipToEOLorEOF();
|
1630
|
+
if (contents.match(/^\\%/)) contents = contents.substring(1);
|
1631
|
+
shouldInterpolate = true;
|
1632
|
+
}
|
1633
|
+
hasContents = contents.length > 0;
|
1634
|
+
if (hasContents) {
|
1635
|
+
if (tagOptions.innerWhitespace && lineHasElement || (!lineHasElement && haml._parentInnerWhitespace(elementStack, indent))) {
|
1636
|
+
indentText = HamlRuntime.indentText(identifier.length > 0 ? indent + 1 : indent);
|
1637
|
+
} else {
|
1638
|
+
indentText = '';
|
1639
|
+
contents = _(contents).trim();
|
1640
|
+
}
|
1641
|
+
generator.appendTextContents(indentText + contents, shouldInterpolate, currentParsePoint);
|
1642
|
+
generator.outputBuffer.append(this._newline(tokeniser));
|
1643
|
+
}
|
1644
|
+
this._eolOrEof(tokeniser);
|
1645
|
+
}
|
1646
|
+
if (tagOptions.selfClosingTag && hasContents) {
|
1647
|
+
throw haml.HamlRuntime.templateError(currentParsePoint.lineNumber, currentParsePoint.characterNumber, currentParsePoint.currentLine, "A self-closing tag can not have any contents");
|
1648
|
+
}
|
1649
|
+
},
|
1650
|
+
_attributeHash: function(tokeniser) {
|
1651
|
+
var attr;
|
1652
|
+
attr = '';
|
1653
|
+
if (tokeniser.token.attributeHash) {
|
1654
|
+
attr = tokeniser.token.tokenString;
|
1655
|
+
tokeniser.getNextToken();
|
1656
|
+
}
|
1657
|
+
return attr;
|
1658
|
+
},
|
1659
|
+
_objectReference: function(tokeniser) {
|
1660
|
+
var attr;
|
1661
|
+
attr = '';
|
1662
|
+
if (tokeniser.token.objectReference) {
|
1663
|
+
attr = tokeniser.token.tokenString;
|
1664
|
+
tokeniser.getNextToken();
|
1665
|
+
}
|
1666
|
+
return attr;
|
1667
|
+
},
|
1668
|
+
_attributeList: function(tokeniser) {
|
1669
|
+
var attr, attrList;
|
1670
|
+
attrList = {};
|
1671
|
+
if (tokeniser.token.openBracket) {
|
1672
|
+
tokeniser.getNextToken();
|
1673
|
+
while (!tokeniser.token.closeBracket) {
|
1674
|
+
attr = haml._attribute(tokeniser);
|
1675
|
+
if (attr) {
|
1676
|
+
attrList[attr.name] = attr.value;
|
1677
|
+
} else {
|
1678
|
+
tokeniser.getNextToken();
|
1679
|
+
}
|
1680
|
+
if (tokeniser.token.ws || tokeniser.token.eol) {
|
1681
|
+
tokeniser.getNextToken();
|
1682
|
+
} else if (!tokeniser.token.closeBracket && !tokeniser.token.identifier) {
|
1683
|
+
throw tokeniser.parseError("Expecting either an attribute name to continue the attibutes or a closing " + "bracket to end");
|
1684
|
+
}
|
1685
|
+
}
|
1686
|
+
tokeniser.getNextToken();
|
1687
|
+
}
|
1688
|
+
return attrList;
|
1689
|
+
},
|
1690
|
+
_attribute: function(tokeniser) {
|
1691
|
+
var attr, name;
|
1692
|
+
attr = null;
|
1693
|
+
if (tokeniser.token.identifier) {
|
1694
|
+
name = tokeniser.token.tokenString;
|
1695
|
+
tokeniser.getNextToken();
|
1696
|
+
haml._whitespace(tokeniser);
|
1697
|
+
if (!tokeniser.token.equal) {
|
1698
|
+
throw tokeniser.parseError("Expected '=' after attribute name");
|
1699
|
+
}
|
1700
|
+
tokeniser.getNextToken();
|
1701
|
+
haml._whitespace(tokeniser);
|
1702
|
+
if (!tokeniser.token.string && !tokeniser.token.identifier) {
|
1703
|
+
throw tokeniser.parseError("Expected a quoted string or an identifier for the attribute value");
|
1704
|
+
}
|
1705
|
+
attr = {
|
1706
|
+
name: name,
|
1707
|
+
value: tokeniser.token.tokenString
|
1708
|
+
};
|
1709
|
+
tokeniser.getNextToken();
|
1710
|
+
}
|
1711
|
+
return attr;
|
1712
|
+
},
|
1713
|
+
_closeElement: function(indent, elementStack, tokeniser, generator) {
|
1714
|
+
var innerWhitespace, outerWhitespace;
|
1715
|
+
if (elementStack[indent]) {
|
1716
|
+
generator.setIndent(indent);
|
1717
|
+
if (elementStack[indent].htmlComment) {
|
1718
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent) + '-->' + elementStack[indent].eol);
|
1719
|
+
} else if (elementStack[indent].htmlConditionalComment) {
|
1720
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent) + '<![endif]-->' + elementStack[indent].eol);
|
1721
|
+
} else if (elementStack[indent].block) {
|
1722
|
+
generator.closeOffCodeBlock(tokeniser);
|
1723
|
+
} else if (elementStack[indent].fnBlock) {
|
1724
|
+
generator.closeOffFunctionBlock(tokeniser);
|
1725
|
+
} else {
|
1726
|
+
innerWhitespace = !elementStack[indent].tagOptions || elementStack[indent].tagOptions.innerWhitespace;
|
1727
|
+
if (innerWhitespace) {
|
1728
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent));
|
1729
|
+
} else {
|
1730
|
+
generator.outputBuffer.trimWhitespace();
|
1731
|
+
}
|
1732
|
+
generator.outputBuffer.append('</' + elementStack[indent].tag + '>');
|
1733
|
+
outerWhitespace = !elementStack[indent].tagOptions || elementStack[indent].tagOptions.outerWhitespace;
|
1734
|
+
if (haml._parentInnerWhitespace(elementStack, indent) && outerWhitespace) {
|
1735
|
+
generator.outputBuffer.append('\n');
|
1736
|
+
}
|
1737
|
+
}
|
1738
|
+
elementStack[indent] = null;
|
1739
|
+
return generator.mark();
|
1740
|
+
}
|
1741
|
+
},
|
1742
|
+
_closeElements: function(indent, elementStack, tokeniser, generator) {
|
1743
|
+
var i, _results;
|
1744
|
+
i = elementStack.length - 1;
|
1745
|
+
_results = [];
|
1746
|
+
while (i >= indent) {
|
1747
|
+
_results.push(this._closeElement(i--, elementStack, tokeniser, generator));
|
1748
|
+
}
|
1749
|
+
return _results;
|
1750
|
+
},
|
1751
|
+
_openElement: function(currentParsePoint, indent, identifier, id, classes, objectRef, attributeList, attributeHash, elementStack, tagOptions, generator) {
|
1752
|
+
var element, parentInnerWhitespace, tagOuterWhitespace;
|
1753
|
+
element = identifier.length === 0 ? "div" : identifier;
|
1754
|
+
parentInnerWhitespace = this._parentInnerWhitespace(elementStack, indent);
|
1755
|
+
tagOuterWhitespace = !tagOptions || tagOptions.outerWhitespace;
|
1756
|
+
if (!tagOuterWhitespace) generator.outputBuffer.trimWhitespace();
|
1757
|
+
if (indent > 0 && parentInnerWhitespace && tagOuterWhitespace) {
|
1758
|
+
generator.outputBuffer.append(HamlRuntime.indentText(indent));
|
1759
|
+
}
|
1760
|
+
generator.outputBuffer.append('<' + element);
|
1761
|
+
if (attributeHash.length > 0 || objectRef.length > 0) {
|
1762
|
+
generator.generateCodeForDynamicAttributes(id, classes, attributeList, attributeHash, objectRef, currentParsePoint);
|
1763
|
+
} else {
|
1764
|
+
generator.outputBuffer.append(HamlRuntime.generateElementAttributes(null, id, classes, null, attributeList, null, currentParsePoint.lineNumber, currentParsePoint.characterNumber, currentParsePoint.currentLine));
|
1765
|
+
}
|
1766
|
+
if (tagOptions.selfClosingTag) {
|
1767
|
+
generator.outputBuffer.append("/>");
|
1768
|
+
if (tagOptions.outerWhitespace) return generator.outputBuffer.append("\n");
|
1769
|
+
} else {
|
1770
|
+
generator.outputBuffer.append(">");
|
1771
|
+
elementStack[indent] = {
|
1772
|
+
tag: element,
|
1773
|
+
tagOptions: tagOptions
|
1774
|
+
};
|
1775
|
+
if (tagOptions.innerWhitespace) return generator.outputBuffer.append("\n");
|
1776
|
+
}
|
1777
|
+
},
|
1778
|
+
_isSelfClosingTag: function(tag) {
|
1779
|
+
return tag === 'meta' || tag === 'img' || tag === 'link' || tag === 'script' || tag === 'br' || tag === 'hr';
|
1780
|
+
},
|
1781
|
+
_tagHasContents: function(indent, tokeniser) {
|
1782
|
+
var nextToken;
|
1783
|
+
if (!tokeniser.isEolOrEof()) {
|
1784
|
+
return true;
|
1785
|
+
} else {
|
1786
|
+
nextToken = tokeniser.lookAhead(1);
|
1787
|
+
return nextToken.ws && nextToken.tokenString.length / 2 > indent;
|
1788
|
+
}
|
1789
|
+
},
|
1790
|
+
_parentInnerWhitespace: function(elementStack, indent) {
|
1791
|
+
return indent === 0 || (!elementStack[indent - 1] || !elementStack[indent - 1].tagOptions || elementStack[indent - 1].tagOptions.innerWhitespace);
|
1792
|
+
},
|
1793
|
+
_lineHasElement: function(identifier, id, classes) {
|
1794
|
+
return identifier.length > 0 || id.length > 0 || classes.length > 0;
|
1795
|
+
},
|
1796
|
+
hasValue: function(value) {
|
1797
|
+
return (value != null) && value !== false;
|
1798
|
+
},
|
1799
|
+
attrValue: function(attr, value) {
|
1800
|
+
if (attr === 'selected' || attr === 'checked' || attr === 'disabled') {
|
1801
|
+
return attr;
|
1802
|
+
} else {
|
1803
|
+
return value;
|
1804
|
+
}
|
1805
|
+
},
|
1806
|
+
_whitespace: function(tokeniser) {
|
1807
|
+
var indent;
|
1808
|
+
indent = 0;
|
1809
|
+
if (tokeniser.token.ws) {
|
1810
|
+
indent = tokeniser.token.tokenString.length / 2;
|
1811
|
+
tokeniser.getNextToken();
|
1812
|
+
}
|
1813
|
+
return indent;
|
1814
|
+
},
|
1815
|
+
_element: function(tokeniser) {
|
1816
|
+
var identifier;
|
1817
|
+
identifier = '';
|
1818
|
+
if (tokeniser.token.element) {
|
1819
|
+
identifier = tokeniser.token.tokenString;
|
1820
|
+
tokeniser.getNextToken();
|
1821
|
+
}
|
1822
|
+
return identifier;
|
1823
|
+
},
|
1824
|
+
_eolOrEof: function(tokeniser) {
|
1825
|
+
if (tokeniser.token.eol || tokeniser.token.continueLine) {
|
1826
|
+
return tokeniser.getNextToken();
|
1827
|
+
} else if (!tokeniser.token.eof) {
|
1828
|
+
throw tokeniser.parseError("Expected EOL or EOF");
|
1829
|
+
}
|
1830
|
+
},
|
1831
|
+
_idSelector: function(tokeniser) {
|
1832
|
+
var id;
|
1833
|
+
id = '';
|
1834
|
+
if (tokeniser.token.idSelector) {
|
1835
|
+
id = tokeniser.token.tokenString;
|
1836
|
+
tokeniser.getNextToken();
|
1837
|
+
}
|
1838
|
+
return id;
|
1839
|
+
},
|
1840
|
+
_classSelector: function(tokeniser) {
|
1841
|
+
var classes;
|
1842
|
+
classes = [];
|
1843
|
+
while (tokeniser.token.classSelector) {
|
1844
|
+
classes.push(tokeniser.token.tokenString);
|
1845
|
+
tokeniser.getNextToken();
|
1846
|
+
}
|
1847
|
+
return classes;
|
1848
|
+
},
|
1849
|
+
_newline: function(tokeniser) {
|
1850
|
+
if (tokeniser.token.eol) {
|
1851
|
+
return tokeniser.token.matched;
|
1852
|
+
} else if (tokeniser.token.continueLine) {
|
1853
|
+
return tokeniser.token.matched.substring(1);
|
1854
|
+
} else {
|
1855
|
+
return "\n";
|
1856
|
+
}
|
1857
|
+
}
|
1858
|
+
};
|
1859
|
+
|
1860
|
+
root.haml.Tokeniser = Tokeniser;
|
1861
|
+
|
1862
|
+
root.haml.Buffer = Buffer;
|
1863
|
+
|
1864
|
+
root.haml.JsCodeGenerator = JsCodeGenerator;
|
1865
|
+
|
1866
|
+
root.haml.CoffeeCodeGenerator = CoffeeCodeGenerator;
|
1867
|
+
|
1868
|
+
root.haml.HamlRuntime = HamlRuntime;
|
1869
|
+
|
1870
|
+
root.haml.filters = filters;
|
1871
|
+
|
1872
|
+
}).call(this);
|