rails-hamljs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 Vaughn Draughon
2
+
3
+ Permission is hereby granted, free of charge, to any
4
+ person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the
6
+ Software without restriction, including without limitation
7
+ the rights to use, copy, modify, merge, publish, distribute,
8
+ sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall
13
+ be included in all copies or substantial portions of the
14
+ Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
19
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
20
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ ## Haml JavaScript (JST) Templates for Rails
2
+
3
+ Lets you use https://github.com/uglyog/clientside-haml-js templates in the Rails asset pipeline.
4
+
5
+ No need to download haml.js, this gem includes the haml.js file for you.
6
+
7
+ * Requires ExecJS
8
+ * Requires CoffeeScript
9
+
10
+ ## Why another Haml in JavaScript gem?
11
+
12
+ Uglyog's library lets you use coffeescript, which allows the embedded JavaScript in your Haml templates to be much more beautiful and Ruby-like. While I'm not the biggest fan of coffeescript for actual javascript coding, it's the perfect solution for haml templates.
13
+
14
+ The other gems use creationix's library.
15
+
16
+ The gem hooks in to the asset pipeline to give you precompiled haml templates which are actually blazing fast for the user and should be perfectly compatible with production environments.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your `Gemfile`
21
+
22
+ gem 'rails-hamljs', :git => 'git://github.com/rocksolidwebdesign/rails-hamljs'
23
+
24
+ ## Usage
25
+
26
+ Simply add JST templates with an extension of `.haml` to your javascript templates directory
27
+
28
+ app/assets/javascripts/templates/foo.jst.haml
29
+
30
+ And of course, be sure to
31
+
32
+ //= require_tree ./templates
33
+
34
+ in the correct order so that the global `JST` variable is available.
35
+
36
+ Then, as with other JST templates such as ECO and EJS, you may access your templates from the `JST` object:
37
+
38
+ var template = JST["templates/foo"];
39
+
40
+ var htmlOutput = template({
41
+ someVar: "someValue",
42
+ someOtherVar: "someOtherVal"
43
+ });
44
+
45
+ ## TODO
46
+
47
+ Configuration:
48
+
49
+ Toggle whose library to use (creationix or uglyog)
50
+ Toggle coffeescript (if using uglyog's library)
@@ -0,0 +1,109 @@
1
+ require 'rails-hamljs/version'
2
+ require 'tilt'
3
+ require 'sprockets'
4
+ require 'open-uri'
5
+
6
+ module Hamljs
7
+ class Template < Tilt::Template
8
+ JS_UNESCAPES = {
9
+ '\\' => '\\',
10
+ "'" => "'",
11
+ 'r' => "\r",
12
+ 'n' => "\n",
13
+ 't' => "\t",
14
+ 'u2028' => "\u2028",
15
+ 'u2029' => "\u2029"
16
+ }
17
+
18
+ JS_ESCAPES = JS_UNESCAPES.invert
19
+ JS_UNESCAPE_PATTERN = /\\(#{Regexp.union(JS_UNESCAPES.keys)})/
20
+ JS_ESCAPE_PATTERN = Regexp.union(JS_ESCAPES.keys)
21
+
22
+ def self.engine_initialized?
23
+ true
24
+ end
25
+
26
+ def initialize_engine
27
+ end
28
+
29
+ def prepare
30
+ end
31
+
32
+ def get_coffeescript_source
33
+ @@coffeescript_source ||= open('http://jashkenas.github.com/coffee-script/extras/coffee-script.js').read
34
+ end
35
+
36
+ def get_underscore_source
37
+ #@@underscore_source ||= open('http://underscorejs.org/underscore-min.js').read
38
+ @@underscore_source ||= File.read(File.expand_path("../../vendor/assets/javascripts/uglyog-haml-js/ext/underscore.js", __FILE__))
39
+ end
40
+
41
+ def get_underscore_string_source
42
+ #@@underscore_string_source ||= open('http://epeli.github.com/underscore.string/dist/underscore.string.min.js').read
43
+ @@underscore_string_source ||= File.read(File.expand_path("../../vendor/assets/javascripts/uglyog-haml-js/ext/underscore.string.js", __FILE__))
44
+ end
45
+
46
+ def get_hamljs_source
47
+ @@hamljs_source ||= get_uglyog_hamljs_source
48
+ end
49
+
50
+ def get_jsonjs_source
51
+ @@jsonjs_source ||= File.read(File.expand_path("../../vendor/assets/javascripts/uglyog-haml-js/ext/json2.js", __FILE__))
52
+ end
53
+
54
+ def get_creationix_hamljs_source
55
+ @@creationix_hamljs_source ||= File.read(File.expand_path("../../vendor/assets/javascripts/creationix-haml-js/haml.js", __FILE__))
56
+ end
57
+
58
+ def get_uglyog_hamljs_source
59
+ @@uglyog_hamljs_source ||= [
60
+ get_jsonjs_source,
61
+ get_underscore_source,
62
+ get_underscore_string_source,
63
+ File.read(File.expand_path("../../vendor/assets/javascripts/uglyog-haml-js/haml.min.js", __FILE__))
64
+ #open('https://raw.github.com/uglyog/clientside-haml-js/master/lib/haml.min.js').read
65
+ ].join(" ")
66
+
67
+ #@@uglyog_hamljs_source ||= File.read(File.expand_path("../../vendor/assets/javascripts/uglyog-haml-js/haml.min.js", __FILE__))
68
+ end
69
+
70
+ def compile(source, options = {})
71
+ s = source.dup
72
+
73
+ cmd = "haml.compileHaml({source: \"#{js_escape!(s)}\", generator: 'coffeescript'}).toString()"
74
+
75
+ lib_sources = [
76
+ get_coffeescript_source,
77
+ get_hamljs_source
78
+ ]
79
+
80
+ context = ExecJS.compile(lib_sources.join(" "))
81
+ result = context.eval(cmd)
82
+
83
+ result
84
+ end
85
+
86
+ def evaluate(scope, locals, &block)
87
+ source = data.dup
88
+ return compile(source)
89
+ end
90
+
91
+ protected
92
+ def js_escape!(source)
93
+ puts "JS_ESCAPE_PATTERN: #{}"
94
+ puts JS_ESCAPE_PATTERN.inspect
95
+ puts "JS_ESCAPES:"
96
+ puts JS_ESCAPES.inspect
97
+ source.gsub!(/\n/, '\n')
98
+ source.gsub!(/\r/, '\r')
99
+ source.gsub!(/\t/, '\t')
100
+ source.gsub!(/"/, '\"')
101
+ source.gsub!(/\u2028/, '\u2028')
102
+ source.gsub!(/\u2029/, '\u2029')
103
+ source
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ Sprockets.register_engine '.hamljs', Hamljs::Template
@@ -0,0 +1,4 @@
1
+ module Hamljs
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Hamljs
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,703 @@
1
+ var Haml;
2
+
3
+ (function () {
4
+
5
+ var matchers, self_close_tags, embedder, forceXML, escaperName, escapeHtmlByDefault;
6
+
7
+ function html_escape(text) {
8
+ return (text + "").
9
+ replace(/&/g, "&amp;").
10
+ replace(/</g, "&lt;").
11
+ replace(/>/g, "&gt;").
12
+ replace(/\"/g, "&quot;");
13
+ }
14
+
15
+ function render_attribs(attribs) {
16
+ var key, value, result = [];
17
+ for (key in attribs) {
18
+ if (key !== '_content' && attribs.hasOwnProperty(key)) {
19
+ switch (attribs[key]) {
20
+ case 'undefined':
21
+ case 'false':
22
+ case 'null':
23
+ case '""':
24
+ break;
25
+ default:
26
+ try {
27
+ value = JSON.parse("[" + attribs[key] +"]")[0];
28
+ if (value === true) {
29
+ value = key;
30
+ } else if (typeof value === 'string' && embedder.test(value)) {
31
+ value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"';
32
+ } else {
33
+ value = html_escape(value);
34
+ }
35
+ result.push(" " + key + '=\\"' + value + '\\"');
36
+ } catch (e) {
37
+ result.push(" " + key + '=\\"" + '+escaperName+'(' + attribs[key] + ') + "\\"');
38
+ }
39
+ }
40
+ }
41
+ }
42
+ return result.join("");
43
+ }
44
+
45
+ // Parse the attribute block using a state machine
46
+ function parse_attribs(line) {
47
+ var attributes = {},
48
+ l = line.length,
49
+ i, c,
50
+ count = 1,
51
+ quote = false,
52
+ skip = false,
53
+ open, close, joiner, seperator,
54
+ pair = {
55
+ start: 1,
56
+ middle: null,
57
+ end: null
58
+ };
59
+
60
+ if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) {
61
+ return {
62
+ _content: line[0] === ' ' ? line.substr(1, l) : line
63
+ };
64
+ }
65
+ open = line.charAt(0);
66
+ close = (open === '{') ? '}' : ')';
67
+ joiner = (open === '{') ? ':' : '=';
68
+ seperator = (open === '{') ? ',' : ' ';
69
+
70
+ function process_pair() {
71
+ if (typeof pair.start === 'number' &&
72
+ typeof pair.middle === 'number' &&
73
+ typeof pair.end === 'number') {
74
+ var key = line.substr(pair.start, pair.middle - pair.start).trim(),
75
+ value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim();
76
+ attributes[key] = value;
77
+ }
78
+ pair = {
79
+ start: null,
80
+ middle: null,
81
+ end: null
82
+ };
83
+ }
84
+
85
+ for (i = 1; count > 0; i += 1) {
86
+
87
+ // If we reach the end of the line, then there is a problem
88
+ if (i > l) {
89
+ throw "Malformed attribute block";
90
+ }
91
+
92
+ c = line.charAt(i);
93
+ if (skip) {
94
+ skip = false;
95
+ } else {
96
+ if (quote) {
97
+ if (c === '\\') {
98
+ skip = true;
99
+ }
100
+ if (c === quote) {
101
+ quote = false;
102
+ }
103
+ } else {
104
+ if (c === '"' || c === "'") {
105
+ quote = c;
106
+ }
107
+
108
+ if (count === 1) {
109
+ if (c === joiner) {
110
+ pair.middle = i;
111
+ }
112
+ if (c === seperator || c === close) {
113
+ pair.end = i;
114
+ process_pair();
115
+ if (c === seperator) {
116
+ pair.start = i + 1;
117
+ }
118
+ }
119
+ }
120
+
121
+ if (c === open || c === "(") {
122
+ count += 1;
123
+ }
124
+ if (c === close || (count > 1 && c === ")")) {
125
+ count -= 1;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ attributes._content = line.substr(i, line.length);
131
+ return attributes;
132
+ }
133
+
134
+ // Split interpolated strings into an array of literals and code fragments.
135
+ function parse_interpol(value) {
136
+ var items = [],
137
+ pos = 0,
138
+ next = 0,
139
+ match;
140
+ while (true) {
141
+ // Match up to embedded string
142
+ next = value.substr(pos).search(embedder);
143
+ if (next < 0) {
144
+ if (pos < value.length) {
145
+ items.push(JSON.stringify(value.substr(pos)));
146
+ }
147
+ break;
148
+ }
149
+ items.push(JSON.stringify(value.substr(pos, next)));
150
+ pos += next;
151
+
152
+ // Match embedded string
153
+ match = value.substr(pos).match(embedder);
154
+ next = match[0].length;
155
+ if (next < 0) { break; }
156
+ if(match[1] === "#"){
157
+ items.push(escaperName+"("+(match[2] || match[3])+")");
158
+ }else{
159
+ //unsafe!!!
160
+ items.push(match[2] || match[3]);
161
+ }
162
+
163
+ pos += next;
164
+ }
165
+ return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
166
+ }
167
+
168
+ // Used to find embedded code in interpolated strings.
169
+ embedder = /([#!])\{([^}]*)\}/;
170
+
171
+ self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"];
172
+
173
+ // All matchers' regexps should capture leading whitespace in first capture
174
+ // and trailing content in last capture
175
+ matchers = [
176
+ // html tags
177
+ {
178
+ name: "html tags",
179
+ regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
180
+ process: function () {
181
+ var line_beginning, tag, classes, ids, attribs, content, whitespaceSpecifier, whitespace={}, output;
182
+ line_beginning = this.matches[2];
183
+ classes = line_beginning.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
184
+ ids = line_beginning.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
185
+ tag = line_beginning.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi);
186
+
187
+ // Default to <div> tag
188
+ tag = tag ? tag[0].substr(1, tag[0].length) : 'div';
189
+
190
+ attribs = this.matches[3];
191
+ if (attribs) {
192
+ attribs = parse_attribs(attribs);
193
+ if (attribs._content) {
194
+ var leader0 = attribs._content.charAt(0),
195
+ leader1 = attribs._content.charAt(1),
196
+ leaderLength = 0;
197
+
198
+ if(leader0 == "<"){
199
+ leaderLength++;
200
+ whitespace.inside = true;
201
+ if(leader1 == ">"){
202
+ leaderLength++;
203
+ whitespace.around = true;
204
+ }
205
+ }else if(leader0 == ">"){
206
+ leaderLength++;
207
+ whitespace.around = true;
208
+ if(leader1 == "<"){
209
+ leaderLength++;
210
+ whitespace.inside = true;
211
+ }
212
+ }
213
+ attribs._content = attribs._content.substr(leaderLength);
214
+ //once we've identified the tag and its attributes, the rest is content.
215
+ // this is currently trimmed for neatness.
216
+ this.contents.unshift(attribs._content.trim());
217
+ delete(attribs._content);
218
+ }
219
+ } else {
220
+ attribs = {};
221
+ }
222
+
223
+ if (classes) {
224
+ classes = classes.map(function (klass) {
225
+ return klass.substr(1, klass.length);
226
+ }).join(' ');
227
+ if (attribs['class']) {
228
+ try {
229
+ attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class']));
230
+ } catch (e) {
231
+ attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class'];
232
+ }
233
+ } else {
234
+ attribs['class'] = JSON.stringify(classes);
235
+ }
236
+ }
237
+ if (ids) {
238
+ ids = ids.map(function (id) {
239
+ return id.substr(1, id.length);
240
+ }).join(' ');
241
+ if (attribs.id) {
242
+ attribs.id = JSON.stringify(ids + " ") + attribs.id;
243
+ } else {
244
+ attribs.id = JSON.stringify(ids);
245
+ }
246
+ }
247
+
248
+ attribs = render_attribs(attribs);
249
+
250
+ content = this.render_contents();
251
+ if (content === '""') {
252
+ content = '';
253
+ }
254
+
255
+ if(whitespace.inside){
256
+ if(content.length==0){
257
+ content='" "'
258
+ }else{
259
+ try{ //remove quotes if they are there
260
+ content = '" '+JSON.parse(content)+' "';
261
+ }catch(e){
262
+ content = '" "+\n'+content+'+\n" "';
263
+ }
264
+ }
265
+ }
266
+
267
+ if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
268
+ output = '"<' + tag + attribs + '>"' +
269
+ (content.length > 0 ? ' + \n' + content : "") +
270
+ ' + \n"</' + tag + '>"';
271
+ } else {
272
+ output = '"<' + tag + attribs + ' />"';
273
+ }
274
+
275
+ if(whitespace.around){
276
+ //output now contains '"<b>hello</b>"'
277
+ //we need to crack it open to insert whitespace.
278
+ output = '" '+output.substr(1, output.length - 2)+' "';
279
+ }
280
+
281
+ return output;
282
+ }
283
+ },
284
+
285
+ // each loops
286
+ {
287
+ name: "each loop",
288
+ regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i,
289
+ process: function () {
290
+ var ivar = this.matches[2] || '__key__', // index
291
+ vvar = this.matches[3], // value
292
+ avar = this.matches[4], // array
293
+ rvar = '__result__'; // results
294
+
295
+ if (this.matches[5]) {
296
+ this.contents.unshift(this.matches[5]);
297
+ }
298
+ return '(function () { ' +
299
+ 'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' +
300
+ 'for (' + ivar + ' in ' + avar + ') { ' +
301
+ 'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' +
302
+ vvar + ' = ' + avar + '[' + ivar + ']; ' +
303
+ rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' +
304
+ '} } return ' + rvar + '.join(""); }).call(this)';
305
+ }
306
+ },
307
+
308
+ // if statements
309
+ {
310
+ name: "if",
311
+ regexp: /^(\s*):if\s+(.*)\s*$/i,
312
+ process: function () {
313
+ var condition = this.matches[2];
314
+ this.pushIfCondition([condition]);
315
+ return '(function () { ' +
316
+ 'if (' + condition + ') { ' +
317
+ 'return (\n' + (this.render_contents() || '') + '\n);' +
318
+ '} else { return ""; } }).call(this)';
319
+ }
320
+ },
321
+
322
+ // else if statements
323
+ {
324
+ name: "else if",
325
+ regexp: /^(\s*):else if\s+(.*)\s*$/i,
326
+ process: function () {
327
+ var condition = this.matches[2],
328
+ conditionsArray = this.getIfConditions()[this.getIfConditions().length - 1],
329
+ ifArray = [],
330
+ ifStatement;
331
+ for (var i=0, l=conditionsArray.length; i<l; i++) {
332
+ ifArray.push('! ' + conditionsArray[i]);
333
+ }
334
+ conditionsArray.push(condition);
335
+ ifArray.push(condition);
336
+ ifStatement = 'if (' + ifArray.join(' && ') + ') { ';
337
+ return '(function () { ' +
338
+ ifStatement +
339
+ 'return (\n' + (this.render_contents() || '') + '\n);' +
340
+ '} else { return ""; } }).call(this)';
341
+ }
342
+ },
343
+
344
+ // else statements
345
+ {
346
+ name: "else",
347
+ regexp: /^(\s*):else\s*$/i,
348
+ process: function () {
349
+ var conditionsArray = this.popIfCondition(),
350
+ ifArray = [],
351
+ ifStatement;
352
+ for (var i=0, l=conditionsArray.length; i<l; i++) {
353
+ ifArray.push('! ' + conditionsArray[i]);
354
+ }
355
+ ifStatement = 'if (' + ifArray.join(' && ') + ') { ';
356
+ return '(function () { ' +
357
+ ifStatement +
358
+ 'return (\n' + (this.render_contents() || '') + '\n);' +
359
+ '} else { return ""; } }).call(this)';
360
+ }
361
+ },
362
+
363
+ // silent-comments
364
+ {
365
+ name: "silent-comments",
366
+ regexp: /^(\s*)-#\s*(.*)\s*$/i,
367
+ process: function () {
368
+ return '""';
369
+ }
370
+ },
371
+
372
+ //html-comments
373
+ {
374
+ name: "silent-comments",
375
+ regexp: /^(\s*)\/\s*(.*)\s*$/i,
376
+ process: function () {
377
+ this.contents.unshift(this.matches[2]);
378
+
379
+ return '"<!--'+this.contents.join('\\n')+'-->"';
380
+ }
381
+ },
382
+
383
+ // raw js
384
+ {
385
+ name: "rawjs",
386
+ regexp: /^(\s*)-\s*(.*)\s*$/i,
387
+ process: function () {
388
+ this.contents.unshift(this.matches[2]);
389
+ return '"";' + this.contents.join("\n")+"; _$output = _$output ";
390
+ }
391
+ },
392
+
393
+ // raw js
394
+ {
395
+ name: "pre",
396
+ regexp: /^(\s*):pre(\s+(.*)|$)/i,
397
+ process: function () {
398
+ this.contents.unshift(this.matches[2]);
399
+ return '"<pre>"+\n' + JSON.stringify(this.contents.join("\n"))+'+\n"</pre>"';
400
+ }
401
+ },
402
+
403
+ // declarations
404
+ {
405
+ name: "doctype",
406
+ regexp: /^()!!!(?:\s*(.*))\s*$/,
407
+ process: function () {
408
+ var line = '';
409
+ switch ((this.matches[2] || '').toLowerCase()) {
410
+ case '':
411
+ // XHTML 1.0 Transitional
412
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
413
+ break;
414
+ case 'strict':
415
+ case '1.0':
416
+ // XHTML 1.0 Strict
417
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
418
+ break;
419
+ case 'frameset':
420
+ // XHTML 1.0 Frameset
421
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
422
+ break;
423
+ case '5':
424
+ // XHTML 5
425
+ line = '<!DOCTYPE html>';
426
+ break;
427
+ case '1.1':
428
+ // XHTML 1.1
429
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
430
+ break;
431
+ case 'basic':
432
+ // XHTML Basic 1.1
433
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">';
434
+ break;
435
+ case 'mobile':
436
+ // XHTML Mobile 1.2
437
+ line = '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">';
438
+ break;
439
+ case 'xml':
440
+ // XML
441
+ line = "<?xml version='1.0' encoding='utf-8' ?>";
442
+ break;
443
+ case 'xml iso-8859-1':
444
+ // XML iso-8859-1
445
+ line = "<?xml version='1.0' encoding='iso-8859-1' ?>";
446
+ break;
447
+ }
448
+ return JSON.stringify(line + "\n");
449
+ }
450
+ },
451
+
452
+ // Embedded markdown. Needs to be added to exports externally.
453
+ {
454
+ name: "markdown",
455
+ regexp: /^(\s*):markdown\s*$/i,
456
+ process: function () {
457
+ return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
458
+ }
459
+ },
460
+
461
+ // script blocks
462
+ {
463
+ name: "script",
464
+ regexp: /^(\s*):(?:java)?script\s*$/,
465
+ process: function () {
466
+ return parse_interpol('\n<script type="text/javascript">\n' +
467
+ '//<![CDATA[\n' +
468
+ this.contents.join("\n") +
469
+ "\n//]]>\n</script>\n");
470
+ }
471
+ },
472
+
473
+ // css blocks
474
+ {
475
+ name: "css",
476
+ regexp: /^(\s*):css\s*$/,
477
+ process: function () {
478
+ return JSON.stringify('<style type="text/css">\n' +
479
+ this.contents.join("\n") +
480
+ "\n</style>");
481
+ }
482
+ }
483
+
484
+ ];
485
+
486
+ function compile(lines) {
487
+ var block = false,
488
+ output = [],
489
+ ifConditions = [];
490
+
491
+ // If lines is a string, turn it into an array
492
+ if (typeof lines === 'string') {
493
+ lines = lines.trim().replace(/\n\r|\r/g, '\n').split('\n');
494
+ }
495
+
496
+ lines.forEach(function(line) {
497
+ var match, found = false;
498
+
499
+ // Collect all text as raw until outdent
500
+ if (block) {
501
+ match = block.check_indent.exec(line);
502
+ if (match) {
503
+ block.contents.push(match[1] || "");
504
+ return;
505
+ } else {
506
+ output.push(block.process());
507
+ block = false;
508
+ }
509
+ }
510
+
511
+ matchers.forEach(function (matcher) {
512
+ if (!found) {
513
+ match = matcher.regexp.exec(line);
514
+ if (match) {
515
+ block = {
516
+ contents: [],
517
+ indent_level: (match[1]),
518
+ matches: match,
519
+ check_indent: new RegExp("^(?:\\s*|" + match[1] + " (.*))$"),
520
+ process: matcher.process,
521
+ getIfConditions: function() {
522
+ return ifConditions;
523
+ },
524
+ pushIfCondition: function(condition) {
525
+ ifConditions.push(condition);
526
+ },
527
+ popIfCondition: function() {
528
+ return ifConditions.pop();
529
+ },
530
+ render_contents: function () {
531
+ return compile(this.contents);
532
+ }
533
+ };
534
+ found = true;
535
+ }
536
+ }
537
+ });
538
+
539
+ // Match plain text
540
+ if (!found) {
541
+ output.push(function () {
542
+ // Escaped plain text
543
+ if (line[0] === '\\') {
544
+ return parse_interpol(line.substr(1, line.length));
545
+ }
546
+
547
+
548
+ function escapedLine(){
549
+ try {
550
+ return escaperName+'('+JSON.stringify(JSON.parse(line)) +')';
551
+ } catch (e2) {
552
+ return escaperName+'(' + line + ')';
553
+ }
554
+ }
555
+
556
+ function unescapedLine(){
557
+ try {
558
+ return parse_interpol(JSON.parse(line));
559
+ } catch (e) {
560
+ return line;
561
+ }
562
+ }
563
+
564
+ // always escaped
565
+ if((line.substr(0, 2) === "&=")) {
566
+ line = line.substr(2, line.length).trim();
567
+ return escapedLine();
568
+ }
569
+
570
+ //never escaped
571
+ if((line.substr(0, 2) === "!=")) {
572
+ line = line.substr(2, line.length).trim();
573
+ return unescapedLine();
574
+ }
575
+
576
+ // sometimes escaped
577
+ if ( (line[0] === '=')) {
578
+ line = line.substr(1, line.length).trim();
579
+ if(escapeHtmlByDefault){
580
+ return escapedLine();
581
+ }else{
582
+ return unescapedLine();
583
+ }
584
+ }
585
+
586
+ // Plain text
587
+ return parse_interpol(line);
588
+ }());
589
+ }
590
+
591
+ });
592
+ if (block) {
593
+ output.push(block.process());
594
+ }
595
+
596
+ var txt = output.filter(function (part) { return part && part.length > 0}).join(" +\n");
597
+ if(txt.length == 0){
598
+ txt = '""';
599
+ }
600
+ return txt;
601
+ };
602
+
603
+ function optimize(js) {
604
+ var new_js = [], buffer = [], part, end;
605
+
606
+ function flush() {
607
+ if (buffer.length > 0) {
608
+ new_js.push(JSON.stringify(buffer.join("")) + end);
609
+ buffer = [];
610
+ }
611
+ }
612
+ js.replace(/\n\r|\r/g, '\n').split('\n').forEach(function (line) {
613
+ part = line.match(/^(\".*\")(\s*\+\s*)?$/);
614
+ if (!part) {
615
+ flush();
616
+ new_js.push(line);
617
+ return;
618
+ }
619
+ end = part[2] || "";
620
+ part = part[1];
621
+ try {
622
+ buffer.push(JSON.parse(part));
623
+ } catch (e) {
624
+ flush();
625
+ new_js.push(line);
626
+ }
627
+ });
628
+ flush();
629
+ return new_js.join("\n");
630
+ };
631
+
632
+ function render(text, options) {
633
+ options = options || {};
634
+ text = text || "";
635
+ var js = compile(text, options);
636
+ if (options.optimize) {
637
+ js = Haml.optimize(js);
638
+ }
639
+ return execute(js, options.context || Haml, options.locals);
640
+ };
641
+
642
+ function execute(js, self, locals) {
643
+ return (function () {
644
+ with(locals || {}) {
645
+ try {
646
+ var _$output;
647
+ eval("_$output =" + js );
648
+ return _$output; //set in eval
649
+ } catch (e) {
650
+ return "\n<pre class='error'>" + html_escape(e.stack) + "</pre>\n";
651
+ }
652
+
653
+ }
654
+ }).call(self);
655
+ };
656
+
657
+ Haml = function Haml(haml, config) {
658
+ if(typeof(config) != "object"){
659
+ forceXML = config;
660
+ config = {};
661
+ }
662
+
663
+ var escaper;
664
+ if(config.customEscape){
665
+ escaper = "";
666
+ escaperName = config.customEscape;
667
+ }else{
668
+ escaper = html_escape.toString() + "\n";
669
+ escaperName = "html_escape";
670
+ }
671
+
672
+ escapeHtmlByDefault = (config.escapeHtmlByDefault || config.escapeHTML || config.escape_html);
673
+
674
+ var js = optimize(compile(haml));
675
+
676
+ var str = "with(locals || {}) {\n" +
677
+ " try {\n" +
678
+ " var _$output=" + js + ";\n return _$output;" +
679
+ " } catch (e) {\n" +
680
+ " return \"\\n<pre class='error'>\" + "+escaperName+"(e.stack) + \"</pre>\\n\";\n" +
681
+ " }\n" +
682
+ "}"
683
+
684
+ try{
685
+ var f = new Function("locals", escaper + str );
686
+ return f;
687
+ }catch(e){
688
+ if ( typeof(console) !== 'undefined' ) { console.error(str); }
689
+ throw e;
690
+ }
691
+ }
692
+
693
+ Haml.compile = compile;
694
+ Haml.optimize = optimize;
695
+ Haml.render = render;
696
+ Haml.execute = execute;
697
+ Haml.html_escape = html_escape;
698
+ }());
699
+
700
+ // Hook into module system
701
+ if (typeof module !== 'undefined') {
702
+ module.exports = Haml;
703
+ }