cshaml-sprockets 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *swp
2
+ *~
3
+ *bak
4
+ *.gem
5
+ .bundle
6
+ Gemfile.lock
7
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
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,7 @@
1
+ module Cshaml
2
+ module Sprockets
3
+ class Engine < ::Rails::Engine
4
+ # just to get the vendor directories included into assets
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Cshaml
2
+ module Sprockets
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -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
+
@@ -0,0 +1,2 @@
1
+ require 'cshaml-sprockets'
2
+
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, "&#39;");
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, '&#x000A;');
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);