From 19476b173f899fab0aa54baea292878f4579a07c Mon Sep 17 00:00:00 2001 From: Kushagra Gour Date: Mon, 29 May 2017 02:53:47 +0530 Subject: [PATCH] wip --- src/index.html | 1 + src/lib/codemirror/mode/ruby/ruby.js | 295 +++ src/lib/haml.min.js | 2578 ++++++++++++++++++++++++++ src/script.js | 8 +- 4 files changed, 2881 insertions(+), 1 deletion(-) create mode 100644 src/lib/codemirror/mode/ruby/ruby.js create mode 100644 src/lib/haml.min.js diff --git a/src/index.html b/src/index.html index efb8c87..2e6fb57 100644 --- a/src/index.html +++ b/src/index.html @@ -54,6 +54,7 @@
  • HTML
  • Markdown
  • Pug
  • +
  • haml
  • diff --git a/src/lib/codemirror/mode/ruby/ruby.js b/src/lib/codemirror/mode/ruby/ruby.js new file mode 100644 index 0000000..68ebf12 --- /dev/null +++ b/src/lib/codemirror/mode/ruby/ruby.js @@ -0,0 +1,295 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("ruby", function(config) { + function wordObj(words) { + var o = {}; + for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; + return o; + } + var keywords = wordObj([ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", + "caller", "lambda", "proc", "public", "protected", "private", "require", "load", + "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__" + ]); + var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then", + "catch", "loop", "proc", "begin"]); + var dedentWords = wordObj(["end", "until"]); + var matching = {"[": "]", "{": "}", "(": ")"}; + var curPunc; + + function chain(newtok, stream, state) { + state.tokenize.push(newtok); + return newtok(stream, state); + } + + function tokenBase(stream, state) { + if (stream.sol() && stream.match("=begin") && stream.eol()) { + state.tokenize.push(readBlockComment); + return "comment"; + } + if (stream.eatSpace()) return null; + var ch = stream.next(), m; + if (ch == "`" || ch == "'" || ch == '"') { + return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); + } else if (ch == "/") { + if (regexpAhead(stream)) + return chain(readQuoted(ch, "string-2", true), stream, state); + else + return "operator"; + } else if (ch == "%") { + var style = "string", embed = true; + if (stream.eat("s")) style = "atom"; + else if (stream.eat(/[WQ]/)) style = "string"; + else if (stream.eat(/[r]/)) style = "string-2"; + else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } + var delim = stream.eat(/[^\w\s=]/); + if (!delim) return "operator"; + if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; + return chain(readQuoted(delim, style, embed, true), stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + return chain(readHereDoc(m[1]), stream, state); + } else if (ch == "0") { + if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); + else if (stream.eat("b")) stream.eatWhile(/[01]/); + else stream.eatWhile(/[0-7]/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); + return "number"; + } else if (ch == "?") { + while (stream.match(/^\\[CM]-/)) {} + if (stream.eat("\\")) stream.eatWhile(/\w/); + else stream.next(); + return "string"; + } else if (ch == ":") { + if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); + if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); + + // :> :>> :< :<< are valid symbols + if (stream.eat(/[\<\>]/)) { + stream.eat(/[\<\>]/); + return "atom"; + } + + // :+ :- :/ :* :| :& :! are valid symbols + if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) { + return "atom"; + } + + // Symbols can't start by a digit + if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) { + stream.eatWhile(/[\w$\xa1-\uffff]/); + // Only one ? ! = is allowed and only as the last character + stream.eat(/[\?\!\=]/); + return "atom"; + } + return "operator"; + } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) { + stream.eat("@"); + stream.eatWhile(/[\w\xa1-\uffff]/); + return "variable-2"; + } else if (ch == "$") { + if (stream.eat(/[a-zA-Z_]/)) { + stream.eatWhile(/[\w]/); + } else if (stream.eat(/\d/)) { + stream.eat(/\d/); + } else { + stream.next(); // Must be a special global like $: or $! + } + return "variable-3"; + } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) { + stream.eatWhile(/[\w\xa1-\uffff]/); + stream.eat(/[\?\!]/); + if (stream.eat(":")) return "atom"; + return "ident"; + } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { + curPunc = "|"; + return null; + } else if (/[\(\)\[\]{}\\;]/.test(ch)) { + curPunc = ch; + return null; + } else if (ch == "-" && stream.eat(">")) { + return "arrow"; + } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { + var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + if (ch == "." && !more) curPunc = "."; + return "operator"; + } else { + return null; + } + } + + function regexpAhead(stream) { + var start = stream.pos, depth = 0, next, found = false, escaped = false + while ((next = stream.next()) != null) { + if (!escaped) { + if ("[{(".indexOf(next) > -1) { + depth++ + } else if ("]})".indexOf(next) > -1) { + depth-- + if (depth < 0) break + } else if (next == "/" && depth == 0) { + found = true + break + } + escaped = next == "\\" + } else { + escaped = false + } + } + stream.backUp(stream.pos - start) + return found + } + + function tokenBaseUntilBrace(depth) { + if (!depth) depth = 1; + return function(stream, state) { + if (stream.peek() == "}") { + if (depth == 1) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } else { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1); + } + } else if (stream.peek() == "{") { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1); + } + return tokenBase(stream, state); + }; + } + function tokenBaseOnce() { + var alreadyCalled = false; + return function(stream, state) { + if (alreadyCalled) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + alreadyCalled = true; + return tokenBase(stream, state); + }; + } + function readQuoted(quote, style, embed, unescaped) { + return function(stream, state) { + var escaped = false, ch; + + if (state.context.type === 'read-quoted-paused') { + state.context = state.context.prev; + stream.eat("}"); + } + + while ((ch = stream.next()) != null) { + if (ch == quote && (unescaped || !escaped)) { + state.tokenize.pop(); + break; + } + if (embed && ch == "#" && !escaped) { + if (stream.eat("{")) { + if (quote == "}") { + state.context = {prev: state.context, type: 'read-quoted-paused'}; + } + state.tokenize.push(tokenBaseUntilBrace()); + break; + } else if (/[@\$]/.test(stream.peek())) { + state.tokenize.push(tokenBaseOnce()); + break; + } + } + escaped = !escaped && ch == "\\"; + } + return style; + }; + } + function readHereDoc(phrase) { + return function(stream, state) { + if (stream.match(phrase)) state.tokenize.pop(); + else stream.skipToEnd(); + return "string"; + }; + } + function readBlockComment(stream, state) { + if (stream.sol() && stream.match("=end") && stream.eol()) + state.tokenize.pop(); + stream.skipToEnd(); + return "comment"; + } + + return { + startState: function() { + return {tokenize: [tokenBase], + indented: 0, + context: {type: "top", indented: -config.indentUnit}, + continuedLine: false, + lastTok: null, + varList: false}; + }, + + token: function(stream, state) { + curPunc = null; + if (stream.sol()) state.indented = stream.indentation(); + var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + var thisTok = curPunc; + if (style == "ident") { + var word = stream.current(); + style = state.lastTok == "." ? "property" + : keywords.propertyIsEnumerable(stream.current()) ? "keyword" + : /^[A-Z]/.test(word) ? "tag" + : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" + : "variable"; + if (style == "keyword") { + thisTok = word; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + else if (word == "do" && state.context.indented < state.indented) + kwtype = "indent"; + } + } + if (curPunc || (style && style != "comment")) state.lastTok = thisTok; + if (curPunc == "|") state.varList = !state.varList; + + if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) + state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; + else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) + state.context = state.context.prev; + + if (stream.eol()) + state.continuedLine = (curPunc == "\\" || style == "operator"); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0); + var ct = state.context; + var closing = ct.type == matching[firstChar] || + ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); + return ct.indented + (closing ? 0 : config.indentUnit) + + (state.continuedLine ? config.indentUnit : 0); + }, + + electricInput: /^\s*(?:end|rescue|elsif|else|\})$/, + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("text/x-ruby", "ruby"); + +}); diff --git a/src/lib/haml.min.js b/src/lib/haml.min.js new file mode 100644 index 0000000..78a1c6d --- /dev/null +++ b/src/lib/haml.min.js @@ -0,0 +1,2578 @@ +// Generated by CoffeeScript 1.11.1 + +/* + clientside HAML compiler for Javascript and Coffeescript (Version 5) + + Copyright 2011-12, Ronald Holshausen (https://github.com/uglyog) + Released under the MIT License (http://www.opensource.org/licenses/MIT) + */ + +_ = { + trim: (s) => s.trim(), + rtrim: (s) => s.replace(/\s+$/, ''), + endsWith: (s, char) => s.indexOf(char, s.length - 1) !== -1 +}; +(function() { + var Buffer, CodeGenerator, CoffeeCodeGenerator, ElementGenerator, HamlRuntime, JsCodeGenerator, ProductionJsCodeGenerator, Tokeniser, filters, haml, root, + hasProp = {}.hasOwnProperty, + extend = 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; }; + + root = this; + + + /* + Haml runtime functions. These are used both by the compiler and the generated template functions + */ + + HamlRuntime = { + + /* + Taken from underscore.string.js escapeHTML, and replace the apos entity with character 39 so that it renders + correctly in IE7 + */ + escapeHTML: function(str) { + return String(str || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, "'"); + }, + + /* + Provides the implementation to preserve the whitespace as per the HAML reference + */ + perserveWhitespace: function(str) { + var i, out, re, result; + re = /<[a-zA-Z]+>[^<]*<\/[a-zA-Z]+>/g; + out = ''; + i = 0; + result = re.exec(str); + if (result) { + while (result) { + out += str.substring(i, result.index); + out += result[0].replace(/\n/g, ' '); + i = result.index + result[0].length; + result = re.exec(str); + } + out += str.substring(i); + } else { + out = str; + } + return out; + }, + + /* + Generates a error message including the current line in the source where the error occurred + */ + templateError: function(lineNumber, characterNumber, currentLine, error) { + var i, message; + message = error + " at line " + lineNumber + " and character " + characterNumber + ":\n" + currentLine + '\n'; + i = 0; + while (i < characterNumber - 1) { + message += '-'; + i++; + } + message += '^'; + return message; + }, + + /* + Generates the attributes for the element by combining all the various sources together + */ + generateElementAttributes: function(context, id, classes, objRefFn, attrList, attrFunction, lineNumber, characterNumber, currentLine, handleError) { + var attr, attributes, className, e, ex, hash, html, object, objectId, value; + if (handleError == null) { + handleError = this._raiseError; + } + attributes = {}; + attributes = this.combineAttributes(attributes, 'id', id); + if (classes.length > 0 && classes[0].length > 0) { + attributes = this.combineAttributes(attributes, 'class', classes); + } + if (attrList != null) { + for (attr in attrList) { + if (!hasProp.call(attrList, attr)) continue; + value = attrList[attr]; + attributes = this.combineAttributes(attributes, attr, value); + } + } + if (objRefFn != null) { + try { + object = objRefFn.call(context, context); + if (object != null) { + objectId = null; + if (object.id != null) { + objectId = object.id; + } else if (object.get) { + objectId = object.get('id'); + } + attributes = this.combineAttributes(attributes, 'id', objectId); + className = null; + if (object['class']) { + className = object['class']; + } else if (object.get) { + className = object.get('class'); + } + attributes = this.combineAttributes(attributes, 'class', className); + } + } catch (error1) { + e = error1; + handleError(haml.HamlRuntime.templateError(lineNumber, characterNumber, currentLine, "Error evaluating object reference - " + e)); + } + } + if (attrFunction != null) { + try { + hash = attrFunction.call(context, context); + if (hash != null) { + hash = this._flattenHash(null, hash); + for (attr in hash) { + if (!hasProp.call(hash, attr)) continue; + value = hash[attr]; + attributes = this.combineAttributes(attributes, attr, value); + } + } + } catch (error1) { + ex = error1; + handleError(haml.HamlRuntime.templateError(lineNumber, characterNumber, currentLine, "Error evaluating attribute hash - " + ex)); + } + } + html = ''; + if (attributes) { + for (attr in attributes) { + if (!hasProp.call(attributes, attr)) continue; + if (haml.hasValue(attributes[attr])) { + if ((attr === 'id' || attr === 'for') && attributes[attr] instanceof Array) { + html += ' ' + attr + '="' + [].concat(...attributes[attr]).join('-') + '"'; + } else if (attr === 'class' && attributes[attr] instanceof Array) { + html += ' ' + attr + '="' + [].concat(...attributes[attr]).join(' ') + '"'; + } else { + html += ' ' + attr + '="' + haml.attrValue(attr, attributes[attr]) + '"'; + } + } + } + } + return html; + }, + + /* + Returns a white space string with a length of indent * 2 + */ + indentText: function(indent) { + var i, text; + text = ''; + i = 0; + while (i < indent) { + text += ' '; + i++; + } + return text; + }, + + /* + Combines the attributes in the attributres hash with the given attribute and value + ID, FOR and CLASS attributes will expand to arrays when multiple values are provided + */ + combineAttributes: function(attributes, attrName, attrValue) { + var classes; + if (haml.hasValue(attrValue)) { + if (attrName === 'id' && attrValue.toString().length > 0) { + if (attributes && attributes.id instanceof Array) { + attributes.id.unshift(attrValue); + } else if (attributes && attributes.id) { + attributes.id = [attributes.id, attrValue]; + } else if (attributes) { + attributes.id = attrValue; + } else { + attributes = { + id: attrValue + }; + } + } else if (attrName === 'for' && attrValue.toString().length > 0) { + if (attributes && attributes['for'] instanceof Array) { + attributes['for'].unshift(attrValue); + } else if (attributes && attributes['for']) { + attributes['for'] = [attributes['for'], attrValue]; + } else if (attributes) { + attributes['for'] = attrValue; + } else { + attributes = { + 'for': attrValue + }; + } + } else if (attrName === 'class') { + classes = []; + if (attrValue instanceof Array) { + classes = classes.concat(attrValue); + } else { + classes.push(attrValue); + } + if (attributes && attributes['class']) { + attributes['class'] = attributes['class'].concat(classes); + } else if (attributes) { + attributes['class'] = classes; + } else { + attributes = { + 'class': classes + }; + } + } else if (attrName !== 'id') { + attributes || (attributes = {}); + attributes[attrName] = attrValue; + } + } + return attributes; + }, + + /* + Flattens a deeply nested hash into a single hash by combining the keys with a minus + */ + _flattenHash: function(rootKey, object) { + var attr, flattenedValue, key, keys, newKey, newValue, result, value; + result = {}; + if (this._isHash(object)) { + for (attr in object) { + if (!hasProp.call(object, attr)) continue; + value = object[attr]; + keys = []; + if (rootKey != null) { + keys.push(rootKey); + } + keys.push(attr); + key = keys.join('-'); + flattenedValue = this._flattenHash(key, value); + if (this._isHash(flattenedValue)) { + for (newKey in flattenedValue) { + if (!hasProp.call(flattenedValue, newKey)) continue; + newValue = flattenedValue[newKey]; + result[newKey] = newValue; + } + } else { + result[key] = flattenedValue; + } + } + } else if (rootKey != null) { + result[rootKey] = object; + } else { + result = object; + } + return result; + }, + _isHash: function(object) { + return (object != null) && typeof object === 'object' && !(object instanceof Array || object instanceof Date); + }, + _logError: function(message) { + return typeof console !== "undefined" && console !== null ? console.log(message) : void 0; + }, + _raiseError: function(message) { + throw new Error(message); + }, + + /* + trims the first number of characters from a string + */ + trim: function(str, chars) { + return str.substring(chars); + } + }; + + + /* + HAML Tokiniser: This class is responsible for parsing the haml source into tokens + */ + + Tokeniser = (function() { + Tokeniser.prototype.currentLineMatcher = /[^\n]*/g; + + Tokeniser.prototype.tokenMatchers = { + whitespace: /[ \t]+/g, + element: /%[a-zA-Z][a-zA-Z0-9]*/g, + idSelector: /#[a-zA-Z_\-][a-zA-Z0-9_\-]*/g, + classSelector: /\.[a-zA-Z0-9_\-]+/g, + identifier: /[a-zA-Z][a-zA-Z0-9\-]*/g, + quotedString: /[\'][^\'\n]*[\']/g, + quotedString2: /[\"][^\"\n]*[\"]/g, + comment: /\-#/g, + escapeHtml: /\&=/g, + unescapeHtml: /\!=/g, + objectReference: /\[[a-zA-Z_@][a-zA-Z0-9_]*\]/g, + doctype: /!!!/g, + continueLine: /\|\s*\n/g, + filter: /:\w+/g + }; + + function Tokeniser(options) { + var errorFn, successFn, template; + this.buffer = null; + this.bufferIndex = null; + this.prevToken = null; + this.token = null; + if (options.templateId != null) { + template = document.getElementById(options.templateId); + if (template) { + this.buffer = template.text; + this.bufferIndex = 0; + } else { + throw "Did not find a template with ID '" + options.templateId + "'"; + } + } else if (options.template != null) { + this.buffer = options.template; + this.bufferIndex = 0; + } else if (options.templateUrl != null) { + errorFn = function(jqXHR, textStatus, errorThrown) { + throw "Failed to fetch haml template at URL " + options.templateUrl + ": " + textStatus + " " + errorThrown; + }; + successFn = (function(_this) { + return function(data) { + _this.buffer = data; + return _this.bufferIndex = 0; + }; + })(this); + jQuery.ajax({ + url: options.templateUrl, + success: successFn, + error: errorFn, + dataType: 'text', + async: false, + beforeSend: function(xhr) { + return xhr.withCredentials = true; + } + }); + } + } + + + /* + Try to match a token with the given regexp + */ + + Tokeniser.prototype.matchToken = function(matcher) { + var result; + matcher.lastIndex = this.bufferIndex; + result = matcher.exec(this.buffer); + if ((result != null ? result.index : void 0) === this.bufferIndex) { + return result[0]; + } + }; + + + /* + Match a multi-character token + */ + + Tokeniser.prototype.matchMultiCharToken = function(matcher, token, tokenStr) { + var matched, ref; + if (!this.token) { + matched = this.matchToken(matcher); + if (matched) { + this.token = token; + this.token.tokenString = (ref = typeof tokenStr === "function" ? tokenStr(matched) : void 0) != null ? ref : matched; + this.token.matched = matched; + return this.advanceCharsInBuffer(matched.length); + } + } + }; + + + /* + Match a single character token + */ + + Tokeniser.prototype.matchSingleCharToken = function(ch, token) { + if (!this.token && this.buffer.charAt(this.bufferIndex) === ch) { + this.token = token; + this.token.tokenString = ch; + this.token.matched = ch; + return this.advanceCharsInBuffer(1); + } + }; + + + /* + Match and return the next token in the input buffer + */ + + Tokeniser.prototype.getNextToken = function() { + var ch, ch1, str; + if (isNaN(this.bufferIndex)) { + throw haml.HamlRuntime.templateError(this.lineNumber, this.characterNumber, this.currentLine, "An internal parser error has occurred in the HAML parser"); + } + this.prevToken = this.token; + this.token = null; + if (this.buffer === null || this.buffer.length === this.bufferIndex) { + this.token = { + eof: true, + token: 'EOF' + }; + } else { + this.initLine(); + if (!this.token) { + ch = this.buffer.charCodeAt(this.bufferIndex); + ch1 = this.buffer.charCodeAt(this.bufferIndex + 1); + if (ch === 10 || (ch === 13 && ch1 === 10)) { + this.token = { + eol: true, + token: 'EOL' + }; + if (ch === 13 && ch1 === 10) { + this.advanceCharsInBuffer(2); + this.token.matched = String.fromCharCode(ch) + String.fromCharCode(ch1); + } else { + this.advanceCharsInBuffer(1); + this.token.matched = String.fromCharCode(ch); + } + this.characterNumber = 0; + this.currentLine = this.getCurrentLine(); + } + } + this.matchMultiCharToken(this.tokenMatchers.whitespace, { + ws: true, + token: 'WS' + }); + this.matchMultiCharToken(this.tokenMatchers.continueLine, { + continueLine: true, + token: 'CONTINUELINE' + }); + this.matchMultiCharToken(this.tokenMatchers.element, { + element: true, + token: 'ELEMENT' + }, function(matched) { + return matched.substring(1); + }); + this.matchMultiCharToken(this.tokenMatchers.idSelector, { + idSelector: true, + token: 'ID' + }, function(matched) { + return matched.substring(1); + }); + this.matchMultiCharToken(this.tokenMatchers.classSelector, { + classSelector: true, + token: 'CLASS' + }, function(matched) { + return matched.substring(1); + }); + this.matchMultiCharToken(this.tokenMatchers.identifier, { + identifier: true, + token: 'IDENTIFIER' + }); + this.matchMultiCharToken(this.tokenMatchers.doctype, { + doctype: true, + token: 'DOCTYPE' + }); + this.matchMultiCharToken(this.tokenMatchers.filter, { + filter: true, + token: 'FILTER' + }, function(matched) { + return matched.substring(1); + }); + if (!this.token) { + str = this.matchToken(this.tokenMatchers.quotedString); + if (!str) { + str = this.matchToken(this.tokenMatchers.quotedString2); + } + if (str) { + this.token = { + string: true, + token: 'STRING', + tokenString: str.substring(1, str.length - 1), + matched: str + }; + this.advanceCharsInBuffer(str.length); + } + } + this.matchMultiCharToken(this.tokenMatchers.comment, { + comment: true, + token: 'COMMENT' + }); + this.matchMultiCharToken(this.tokenMatchers.escapeHtml, { + escapeHtml: true, + token: 'ESCAPEHTML' + }); + this.matchMultiCharToken(this.tokenMatchers.unescapeHtml, { + unescapeHtml: true, + token: 'UNESCAPEHTML' + }); + this.matchMultiCharToken(this.tokenMatchers.objectReference, { + objectReference: true, + token: 'OBJECTREFERENCE' + }, function(matched) { + return matched.substring(1, matched.length - 1); + }); + if (!this.token && this.buffer && this.buffer.charAt(this.bufferIndex) === '{') { + this.matchJavascriptHash(); + } + this.matchSingleCharToken('(', { + openBracket: true, + token: 'OPENBRACKET' + }); + this.matchSingleCharToken(')', { + closeBracket: true, + token: 'CLOSEBRACKET' + }); + this.matchSingleCharToken('=', { + equal: true, + token: 'EQUAL' + }); + this.matchSingleCharToken('/', { + slash: true, + token: 'SLASH' + }); + this.matchSingleCharToken('!', { + exclamation: true, + token: 'EXCLAMATION' + }); + this.matchSingleCharToken('-', { + minus: true, + token: 'MINUS' + }); + this.matchSingleCharToken('&', { + amp: true, + token: 'AMP' + }); + this.matchSingleCharToken('<', { + lt: true, + token: 'LT' + }); + this.matchSingleCharToken('>', { + gt: true, + token: 'GT' + }); + this.matchSingleCharToken('~', { + tilde: true, + token: 'TILDE' + }); + if (this.token === null) { + this.token = { + unknown: true, + token: 'UNKNOWN', + matched: this.buffer.charAt(this.bufferIndex) + }; + this.advanceCharsInBuffer(1); + } + } + return this.token; + }; + + + /* + Look ahead a number of tokens and return the token found + */ + + Tokeniser.prototype.lookAhead = function(numberOfTokens) { + var bufferIndex, characterNumber, currentLine, currentToken, i, lineNumber, prevToken, token; + token = null; + if (numberOfTokens > 0) { + currentToken = this.token; + prevToken = this.prevToken; + currentLine = this.currentLine; + lineNumber = this.lineNumber; + characterNumber = this.characterNumber; + bufferIndex = this.bufferIndex; + i = 0; + while (i++ < numberOfTokens) { + token = this.getNextToken(); + } + this.token = currentToken; + this.prevToken = prevToken; + this.currentLine = currentLine; + this.lineNumber = lineNumber; + this.characterNumber = characterNumber; + this.bufferIndex = bufferIndex; + } + return token; + }; + + + /* + Initilise the line and character counters + */ + + Tokeniser.prototype.initLine = function() { + if (!this.currentLine && this.currentLine !== "") { + this.currentLine = this.getCurrentLine(); + this.lineNumber = 1; + return this.characterNumber = 0; + } + }; + + + /* + Returns the current line in the input buffer + */ + + Tokeniser.prototype.getCurrentLine = function(index) { + var line; + this.currentLineMatcher.lastIndex = this.bufferIndex + (index != null ? index : 0); + line = this.currentLineMatcher.exec(this.buffer); + if (line) { + return line[0]; + } else { + return ''; + } + }; + + + /* + Returns an error string filled out with the line and character counters + */ + + Tokeniser.prototype.parseError = function(error) { + return haml.HamlRuntime.templateError(this.lineNumber, this.characterNumber, this.currentLine, error); + }; + + + /* + Skips to the end of the line and returns the string that was skipped + */ + + Tokeniser.prototype.skipToEOLorEOF = function() { + var contents, line, text; + text = ''; + if (!(this.token.eof || this.token.eol)) { + if (this.token.matched != null) { + text += this.token.matched; + } + this.currentLineMatcher.lastIndex = this.bufferIndex; + line = this.currentLineMatcher.exec(this.buffer); + if (line && line.index === this.bufferIndex) { + contents = (_.str || _).rtrim(line[0]); + if ((_.str || _).endsWith(contents, '|')) { + text += contents.substring(0, contents.length - 1); + this.advanceCharsInBuffer(contents.length - 1); + this.getNextToken(); + text += this.parseMultiLine(); + } else { + text += line[0]; + this.advanceCharsInBuffer(line[0].length); + this.getNextToken(); + } + } + } + return text; + }; + + + /* + Parses a multiline code block and returns the parsed text + */ + + Tokeniser.prototype.parseMultiLine = function() { + var contents, line, text; + text = ''; + while (this.token.continueLine) { + this.currentLineMatcher.lastIndex = this.bufferIndex; + line = this.currentLineMatcher.exec(this.buffer); + if (line && line.index === this.bufferIndex) { + contents = (_.str || _).rtrim(line[0]); + if ((_.str || _).endsWith(contents, '|')) { + text += contents.substring(0, contents.length - 1); + this.advanceCharsInBuffer(contents.length - 1); + } + this.getNextToken(); + } + } + return text; + }; + + + /* + Advances the input buffer pointer by a number of characters, updating the line and character counters + */ + + Tokeniser.prototype.advanceCharsInBuffer = function(numChars) { + var ch, ch1, i; + i = 0; + while (i < numChars) { + ch = this.buffer.charCodeAt(this.bufferIndex + i); + ch1 = this.buffer.charCodeAt(this.bufferIndex + i + 1); + if (ch === 13 && ch1 === 10) { + this.lineNumber++; + this.characterNumber = 0; + this.currentLine = this.getCurrentLine(i); + i++; + } else if (ch === 10) { + this.lineNumber++; + this.characterNumber = 0; + this.currentLine = this.getCurrentLine(i); + } else { + this.characterNumber++; + } + i++; + } + return this.bufferIndex += numChars; + }; + + + /* + Returns the current line and character counters + */ + + Tokeniser.prototype.currentParsePoint = function() { + return { + lineNumber: this.lineNumber, + characterNumber: this.characterNumber, + currentLine: this.currentLine + }; + }; + + + /* + Pushes back the current token onto the front of the input buffer + */ + + Tokeniser.prototype.pushBackToken = function() { + if (!this.token.eof) { + this.bufferIndex -= this.token.matched.length; + return this.token = this.prevToken; + } + }; + + + /* + Is the current token an end of line or end of input buffer + */ + + Tokeniser.prototype.isEolOrEof = function() { + return this.token.eol || this.token.eof; + }; + + + /* + Match a Javascript Hash {...} + */ + + Tokeniser.prototype.matchJavascriptHash = function() { + var braceCount, ch, chCode, characterNumberStart, currentIndent, i, lineNumberStart; + currentIndent = this.calculateCurrentIndent(); + i = this.bufferIndex + 1; + characterNumberStart = this.characterNumber; + lineNumberStart = this.lineNumber; + braceCount = 1; + while (i < this.buffer.length && (braceCount > 1 || this.buffer.charAt(i) !== '}')) { + ch = this.buffer.charAt(i); + chCode = this.buffer.charCodeAt(i); + if (ch === '{') { + braceCount++; + i++; + } else if (ch === '}') { + braceCount--; + i++; + } else if (chCode === 10 || chCode === 13) { + i++; + } else { + i++; + } + } + if (i === this.buffer.length) { + this.characterNumber = characterNumberStart + 1; + this.lineNumber = lineNumberStart; + throw this.parseError('Error parsing attribute hash - Did not find a terminating "}"'); + } else { + this.token = { + attributeHash: true, + token: 'ATTRHASH', + tokenString: this.buffer.substring(this.bufferIndex, i + 1), + matched: this.buffer.substring(this.bufferIndex, i + 1) + }; + return this.advanceCharsInBuffer(i - this.bufferIndex + 1); + } + }; + + + /* + Calculate the indent value of the current line + */ + + Tokeniser.prototype.calculateCurrentIndent = function() { + var result; + this.tokenMatchers.whitespace.lastIndex = 0; + result = this.tokenMatchers.whitespace.exec(this.currentLine); + if ((result != null ? result.index : void 0) === 0) { + return this.calculateIndent(result[0]); + } else { + return 0; + } + }; + + + /* + Calculate the indent level of the provided whitespace + */ + + Tokeniser.prototype.calculateIndent = function(whitespace) { + var i, indent; + indent = 0; + i = 0; + while (i < whitespace.length) { + if (whitespace.charCodeAt(i) === 9) { + indent += 2; + } else { + indent++; + } + i++; + } + return Math.floor((indent + 1) / 2); + }; + + return Tokeniser; + + })(); + + + /* + Provides buffering between the generated javascript and html contents + */ + + Buffer = (function() { + function Buffer(generator1) { + this.generator = generator1; + this.buffer = ''; + this.outputBuffer = ''; + } + + Buffer.prototype.append = function(str) { + if ((this.generator != null) && this.buffer.length === 0) { + this.generator.mark(); + } + if ((str != null ? str.length : void 0) > 0) { + return this.buffer += str; + } + }; + + Buffer.prototype.appendToOutputBuffer = function(str) { + if ((str != null ? str.length : void 0) > 0) { + this.flush(); + return this.outputBuffer += str; + } + }; + + Buffer.prototype.flush = function() { + var ref; + if (((ref = this.buffer) != null ? ref.length : void 0) > 0) { + this.outputBuffer += this.generator.generateFlush(this.buffer); + } + return this.buffer = ''; + }; + + Buffer.prototype.output = function() { + return this.outputBuffer; + }; + + Buffer.prototype.trimWhitespace = function() { + var ch, i; + if (this.buffer.length > 0) { + i = this.buffer.length - 1; + while (i > 0) { + ch = this.buffer.charAt(i); + if (this._isWhitespace(ch)) { + i--; + } else if (i > 1 && (ch === 'n' || ch === 't') && (this.buffer.charAt(i - 1) === '\\')) { + i -= 2; + } else { + break; + } + } + if (i > 0 && i < this.buffer.length - 1) { + return this.buffer = this.buffer.substring(0, i + 1); + } else if (i === 0 && this._isWhitespace(this.buffer.charAt(0))) { + return this.buffer = ''; + } + } + }; + + Buffer.prototype._isWhitespace = function(ch) { + return ch === ' ' || ch === '\t' || ch === '\n'; + }; + + return Buffer; + + })(); + + + /* + Common code shared across all code generators + */ + + CodeGenerator = (function() { + function CodeGenerator() {} + + CodeGenerator.prototype.embeddedCodeBlockMatcher = /#{([^}]*)}/g; + + CodeGenerator.prototype.closeElements = function(indent, elementStack, tokeniser, generator) { + var i, results; + i = elementStack.length - 1; + results = []; + while (i >= indent) { + results.push(this.closeElement(i--, elementStack, tokeniser, generator)); + } + return results; + }; + + CodeGenerator.prototype.closeElement = function(indent, elementStack, tokeniser, generator) { + var innerWhitespace, outerWhitespace; + if (elementStack[indent]) { + generator.setIndent(indent); + if (elementStack[indent].htmlComment) { + generator.outputBuffer.append(HamlRuntime.indentText(indent) + '-->' + elementStack[indent].eol); + } else if (elementStack[indent].htmlConditionalComment) { + generator.outputBuffer.append(HamlRuntime.indentText(indent) + '' + elementStack[indent].eol); + } else if (elementStack[indent].block) { + generator.closeOffCodeBlock(tokeniser); + } else if (elementStack[indent].fnBlock) { + generator.closeOffFunctionBlock(tokeniser); + } else { + innerWhitespace = !elementStack[indent].tagOptions || elementStack[indent].tagOptions.innerWhitespace; + if (innerWhitespace) { + generator.outputBuffer.append(HamlRuntime.indentText(indent)); + } else { + generator.outputBuffer.trimWhitespace(); + } + generator.outputBuffer.append(''); + outerWhitespace = !elementStack[indent].tagOptions || elementStack[indent].tagOptions.outerWhitespace; + if (haml._parentInnerWhitespace(elementStack, indent) && outerWhitespace) { + generator.outputBuffer.append('\n'); + } + } + elementStack[indent] = null; + return generator.mark(); + } + }; + + CodeGenerator.prototype.openElement = function(currentParsePoint, indent, identifier, id, classes, objectRef, attributeList, attributeHash, elementStack, tagOptions, generator) { + var element, parentInnerWhitespace, tagOuterWhitespace; + element = identifier.length === 0 ? "div" : identifier; + parentInnerWhitespace = haml._parentInnerWhitespace(elementStack, indent); + tagOuterWhitespace = !tagOptions || tagOptions.outerWhitespace; + if (!tagOuterWhitespace) { + generator.outputBuffer.trimWhitespace(); + } + if (indent > 0 && parentInnerWhitespace && tagOuterWhitespace) { + generator.outputBuffer.append(HamlRuntime.indentText(indent)); + } + generator.outputBuffer.append('<' + element); + if (attributeHash.length > 0 || objectRef.length > 0) { + generator.generateCodeForDynamicAttributes(id, classes, attributeList, attributeHash, objectRef, currentParsePoint); + } else { + generator.outputBuffer.append(HamlRuntime.generateElementAttributes(null, id, classes, null, attributeList, null, currentParsePoint.lineNumber, currentParsePoint.characterNumber, currentParsePoint.currentLine)); + } + if (tagOptions.selfClosingTag) { + generator.outputBuffer.append("/>"); + if (tagOptions.outerWhitespace) { + return generator.outputBuffer.append("\n"); + } + } else { + generator.outputBuffer.append(">"); + elementStack[indent] = { + tag: element, + tagOptions: tagOptions + }; + if (tagOptions.innerWhitespace) { + return generator.outputBuffer.append("\n"); + } + } + }; + + return CodeGenerator; + + })(); + + + /* + Code generator that generates a Javascript function body + */ + + JsCodeGenerator = (function(superClass) { + extend(JsCodeGenerator, superClass); + + function JsCodeGenerator(options1) { + this.options = options1; + this.outputBuffer = new haml.Buffer(this); + } + + + /* + Append a line with embedded javascript code + */ + + JsCodeGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(indentText + 'try {\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' var value = eval("' + (_.str || _).trim(expression).replace(/"/g, '\\"').replace(/\\n/g, '\\\\n') + '");\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' value = value === null ? "" : value;'); + if (escapeContents) { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.escapeHTML(String(value)));\n'); + } else if (perserveWhitespace) { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.perserveWhitespace(String(value)));\n'); + } else { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(String(value));\n'); + } + this.outputBuffer.appendToOutputBuffer(indentText + '} catch (e) {\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' handleError(haml.HamlRuntime.templateError(' + currentParsePoint.lineNumber + ', ' + currentParsePoint.characterNumber + ', "' + this.escapeCode(currentParsePoint.currentLine) + '",\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' "Error evaluating expression - " + e));\n'); + return this.outputBuffer.appendToOutputBuffer(indentText + '}\n'); + }; + + + /* + Initilising the output buffer with any variables or code + */ + + JsCodeGenerator.prototype.initOutput = function() { + var ref; + if ((ref = this.options) != null ? ref.tolerateFaults : void 0) { + this.outputBuffer.appendToOutputBuffer(' var handleError = haml.HamlRuntime._logError;'); + } else { + this.outputBuffer.appendToOutputBuffer(' var handleError = haml.HamlRuntime._raiseError;'); + } + return this.outputBuffer.appendToOutputBuffer('var html = [];\nvar hashFunction = null, hashObject = null, objRef = null, objRefFn = null;\nwith (context || {}) {'); + }; + + + /* + Flush and close the output buffer and return the contents + */ + + JsCodeGenerator.prototype.closeAndReturnOutput = function() { + this.outputBuffer.flush(); + return this.outputBuffer.output() + ' }\n return html.join("");\n'; + }; + + + /* + Append a line of code to the output buffer + */ + + JsCodeGenerator.prototype.appendCodeLine = function(line, eol) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent)); + this.outputBuffer.appendToOutputBuffer(line); + return this.outputBuffer.appendToOutputBuffer(eol); + }; + + + /* + Does the current line end with a function declaration? + */ + + JsCodeGenerator.prototype.lineMatchesStartFunctionBlock = function(line) { + return line.match(/function\s*\((,?\s*\w+)*\)\s*\{\s*$/); + }; + + + /* + Does the current line end with a starting code block + */ + + JsCodeGenerator.prototype.lineMatchesStartBlock = function(line) { + return line.match(/\{\s*$/); + }; + + + /* + Generate the code to close off a code block + */ + + JsCodeGenerator.prototype.closeOffCodeBlock = function(tokeniser) { + if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) { + this.outputBuffer.flush(); + return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '}\n'); + } + }; + + + /* + Generate the code to close off a function parameter + */ + + JsCodeGenerator.prototype.closeOffFunctionBlock = function(tokeniser) { + if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) { + this.outputBuffer.flush(); + return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '});\n'); + } + }; + + + /* + Generate the code for dynamic attributes ({} form) + */ + + JsCodeGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) { + this.outputBuffer.flush(); + if (attributeHash.length > 0) { + attributeHash = this.replaceReservedWordsInHash(attributeHash); + this.outputBuffer.appendToOutputBuffer(' hashFunction = function () { return eval("hashObject = ' + attributeHash.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"); };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' hashFunction = null;\n'); + } + if (objectRef.length > 0) { + this.outputBuffer.appendToOutputBuffer(' objRefFn = function () { return eval("objRef = ' + objectRef.replace(/"/g, '\\"') + '"); };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' objRefFn = null;\n'); + } + 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) + '", handleError));\n'); + }; + + + /* + Clean any reserved words in the given hash + */ + + JsCodeGenerator.prototype.replaceReservedWordsInHash = function(hash) { + var j, len, ref, reservedWord, resultHash; + resultHash = hash; + ref = ['class', 'for']; + for (j = 0, len = ref.length; j < len; j++) { + reservedWord = ref[j]; + resultHash = resultHash.replace(reservedWord + ':', '"' + reservedWord + '":'); + } + return resultHash; + }; + + + /* + Escape the line so it is safe to put into a javascript string + */ + + JsCodeGenerator.prototype.escapeCode = function(jsStr) { + return jsStr.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); + }; + + + /* + Generate a function from the function body + */ + + JsCodeGenerator.prototype.generateJsFunction = function(functionBody) { + var e; + try { + return new Function('context', functionBody); + } catch (error1) { + e = error1; + throw "Incorrect embedded code has resulted in an invalid Haml function - " + e + "\nGenerated Function:\n" + functionBody; + } + }; + + + /* + Generate the code required to support a buffer flush + */ + + JsCodeGenerator.prototype.generateFlush = function(bufferStr) { + return ' html.push("' + this.escapeCode(bufferStr) + '");\n'; + }; + + + /* + Set the current indent level + */ + + JsCodeGenerator.prototype.setIndent = function(indent) { + return this.indent = indent; + }; + + + /* + Save the current indent level if required + */ + + JsCodeGenerator.prototype.mark = function() {}; + + + /* + Append the text contents to the buffer, expanding any embedded code + */ + + JsCodeGenerator.prototype.appendTextContents = function(text, shouldInterpolate, currentParsePoint, options) { + if (options == null) { + options = {}; + } + if (shouldInterpolate && text.match(/#{[^}]*}/)) { + return this.interpolateString(text, currentParsePoint, options); + } else { + return this.outputBuffer.append(this.processText(text, options)); + } + }; + + + /* + Interpolate any embedded code in the text + */ + + JsCodeGenerator.prototype.interpolateString = function(text, currentParsePoint, options) { + var index, precheedingChar, precheedingChar2, result; + index = 0; + result = this.embeddedCodeBlockMatcher.exec(text); + while (result) { + if (result.index > 0) { + precheedingChar = text.charAt(result.index - 1); + } + if (result.index > 1) { + precheedingChar2 = text.charAt(result.index - 2); + } + if (precheedingChar === '\\' && precheedingChar2 !== '\\') { + if (result.index !== 0) { + this.outputBuffer.append(this.processText(text.substring(index, result.index - 1), options)); + } + this.outputBuffer.append(this.processText(result[0]), options); + } else { + this.outputBuffer.append(this.processText(text.substring(index, result.index)), options); + this.appendEmbeddedCode(HamlRuntime.indentText(this.indent + 1), result[1], options.escapeHTML, options.perserveWhitespace, currentParsePoint); + } + index = this.embeddedCodeBlockMatcher.lastIndex; + result = this.embeddedCodeBlockMatcher.exec(text); + } + if (index < text.length) { + return this.outputBuffer.append(this.processText(text.substring(index), options)); + } + }; + + + /* + process text based on escape and preserve flags + */ + + JsCodeGenerator.prototype.processText = function(text, options) { + if (options != null ? options.escapeHTML : void 0) { + return haml.HamlRuntime.escapeHTML(text); + } else if (options != null ? options.perserveWhitespace : void 0) { + return haml.HamlRuntime.perserveWhitespace(text); + } else { + return text; + } + }; + + return JsCodeGenerator; + + })(CodeGenerator); + + + /* + Code generator that generates javascript code without runtime evaluation + */ + + ProductionJsCodeGenerator = (function(superClass) { + extend(ProductionJsCodeGenerator, superClass); + + function ProductionJsCodeGenerator() { + return ProductionJsCodeGenerator.__super__.constructor.apply(this, arguments); + } + + + /* + Append a line with embedded javascript code + */ + + ProductionJsCodeGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(indentText + ' value = ' + (_.str || _).trim(expression) + ';\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' value = value === null ? "" : value;'); + if (escapeContents) { + return this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.escapeHTML(String(value)));\n'); + } else if (perserveWhitespace) { + return this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.perserveWhitespace(String(value)));\n'); + } else { + return this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(String(value));\n'); + } + }; + + + /* + Generate the code for dynamic attributes ({} form) + */ + + ProductionJsCodeGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) { + this.outputBuffer.flush(); + if (attributeHash.length > 0) { + attributeHash = this.replaceReservedWordsInHash(attributeHash); + this.outputBuffer.appendToOutputBuffer(' hashFunction = function () { return ' + attributeHash + '; };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' hashFunction = null;\n'); + } + if (objectRef.length > 0) { + this.outputBuffer.appendToOutputBuffer(' objRefFn = function () { return ' + objectRef + '; };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' objRefFn = null;\n'); + } + 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'); + }; + + + /* + Initilising the output buffer with any variables or code + */ + + ProductionJsCodeGenerator.prototype.initOutput = function() { + return this.outputBuffer.appendToOutputBuffer(' var html = [];\n' + ' var hashFunction = null, hashObject = null, objRef = null, objRefFn = null, value= null;\n with (context || {}) {\n'); + }; + + return ProductionJsCodeGenerator; + + })(JsCodeGenerator); + + + /* + Code generator that generates a coffeescript function body + */ + + CoffeeCodeGenerator = (function(superClass) { + extend(CoffeeCodeGenerator, superClass); + + function CoffeeCodeGenerator(options1) { + this.options = options1; + this.outputBuffer = new haml.Buffer(this); + } + + CoffeeCodeGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) { + var indent; + this.outputBuffer.flush(); + indent = this.calcCodeIndent(); + this.outputBuffer.appendToOutputBuffer(indent + "try\n"); + this.outputBuffer.appendToOutputBuffer(indent + " exp = CoffeeScript.compile('" + expression.replace(/'/g, "\\'").replace(/\\n/g, '\\\\n') + "', bare: true)\n"); + this.outputBuffer.appendToOutputBuffer(indent + " value = eval(exp)\n"); + this.outputBuffer.appendToOutputBuffer(indent + " value ?= ''\n"); + if (escapeContents) { + this.outputBuffer.appendToOutputBuffer(indent + " html.push(haml.HamlRuntime.escapeHTML(String(value)))\n"); + } else if (perserveWhitespace) { + this.outputBuffer.appendToOutputBuffer(indent + " html.push(haml.HamlRuntime.perserveWhitespace(String(value)))\n"); + } else { + this.outputBuffer.appendToOutputBuffer(indent + " html.push(String(value))\n"); + } + this.outputBuffer.appendToOutputBuffer(indent + "catch e \n"); + this.outputBuffer.appendToOutputBuffer(indent + " handleError new Error(haml.HamlRuntime.templateError(" + currentParsePoint.lineNumber + ", " + currentParsePoint.characterNumber + ", '" + this.escapeCode(currentParsePoint.currentLine) + "',\n"); + return this.outputBuffer.appendToOutputBuffer(indent + " 'Error evaluating expression - ' + e))\n"); + }; + + CoffeeCodeGenerator.prototype.initOutput = function() { + var ref; + if ((ref = this.options) != null ? ref.tolerateFaults : void 0) { + this.outputBuffer.appendToOutputBuffer('handleError = haml.HamlRuntime._logError\n'); + } else { + this.outputBuffer.appendToOutputBuffer('handleError = haml.HamlRuntime._raiseError\n'); + } + return this.outputBuffer.appendToOutputBuffer('html = []\n'); + }; + + CoffeeCodeGenerator.prototype.closeAndReturnOutput = function() { + this.outputBuffer.flush(); + return this.outputBuffer.output() + 'return html.join("")\n'; + }; + + CoffeeCodeGenerator.prototype.appendCodeLine = function(line, eol) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(this.calcCodeIndent()); + this.outputBuffer.appendToOutputBuffer((_.str || _).trim(line)); + this.outputBuffer.appendToOutputBuffer(eol); + return this.prevCodeIndent = this.indent; + }; + + CoffeeCodeGenerator.prototype.lineMatchesStartFunctionBlock = function(line) { + return line.match(/\) [\-=]>\s*$/); + }; + + CoffeeCodeGenerator.prototype.lineMatchesStartBlock = function(line) { + return true; + }; + + CoffeeCodeGenerator.prototype.closeOffCodeBlock = function(tokeniser) { + return this.outputBuffer.flush(); + }; + + CoffeeCodeGenerator.prototype.closeOffFunctionBlock = function(tokeniser) { + return this.outputBuffer.flush(); + }; + + CoffeeCodeGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) { + var indent; + this.outputBuffer.flush(); + indent = this.calcCodeIndent(); + if (attributeHash.length > 0) { + attributeHash = this.replaceReservedWordsInHash(attributeHash); + this.outputBuffer.appendToOutputBuffer(indent + "hashFunction = () -> s = CoffeeScript.compile('" + attributeHash.replace(/'/g, "\\'").replace(/\n/g, '\\n') + "', bare: true); eval 'hashObject = ' + s\n"); + } else { + this.outputBuffer.appendToOutputBuffer(indent + "hashFunction = null\n"); + } + if (objectRef.length > 0) { + this.outputBuffer.appendToOutputBuffer(indent + "objRefFn = () -> s = CoffeeScript.compile('" + objectRef.replace(/'/g, "\\'") + "', bare: true); eval 'objRef = ' + s\n"); + } else { + this.outputBuffer.appendToOutputBuffer(indent + "objRefFn = null\n"); + } + return this.outputBuffer.appendToOutputBuffer(indent + "html.push(haml.HamlRuntime.generateElementAttributes(this, '" + id + "', ['" + classes.join("','") + "'], objRefFn ? null, " + JSON.stringify(attributeList) + ", hashFunction ? null, " + currentParsePoint.lineNumber + ", " + currentParsePoint.characterNumber + ", '" + this.escapeCode(currentParsePoint.currentLine) + "', handleError))\n"); + }; + + CoffeeCodeGenerator.prototype.replaceReservedWordsInHash = function(hash) { + var j, len, ref, reservedWord, resultHash; + resultHash = hash; + ref = ['class', 'for']; + for (j = 0, len = ref.length; j < len; j++) { + reservedWord = ref[j]; + resultHash = resultHash.replace(reservedWord + ':', "'" + reservedWord + "':"); + } + return resultHash; + }; + + + /* + Escapes the string for insertion into the generated code. Embedded code blocks in strings must not be escaped + */ + + CoffeeCodeGenerator.prototype.escapeCode = function(str) { + var index, outString, precheedingChar, precheedingChar2, result; + outString = ''; + index = 0; + result = this.embeddedCodeBlockMatcher.exec(str); + while (result) { + if (result.index > 0) { + precheedingChar = str.charAt(result.index - 1); + } + if (result.index > 1) { + precheedingChar2 = str.charAt(result.index - 2); + } + if (precheedingChar === '\\' && precheedingChar2 !== '\\') { + if (result.index !== 0) { + outString += this._escapeText(str.substring(index, result.index - 1)); + } + outString += this._escapeText('\\' + result[0]); + } else { + outString += this._escapeText(str.substring(index, result.index)); + outString += result[0]; + } + index = this.embeddedCodeBlockMatcher.lastIndex; + result = this.embeddedCodeBlockMatcher.exec(str); + } + if (index < str.length) { + outString += this._escapeText(str.substring(index)); + } + return outString; + }; + + CoffeeCodeGenerator.prototype._escapeText = function(text) { + return text.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/"/g, '\\\"').replace(/\n/g, '\\n').replace(/(^|[^\\]{2})\\\\#{/g, '$1\\#{'); + }; + + + /* + Generates the javascript function by compiling the given code with coffeescript compiler + */ + + CoffeeCodeGenerator.prototype.generateJsFunction = function(functionBody) { + var e, fn; + try { + fn = CoffeeScript.compile(functionBody, { + bare: true + }); + return new Function(fn); + } catch (error1) { + e = error1; + throw "Incorrect embedded code has resulted in an invalid Haml function - " + e + "\nGenerated Function:\n" + fn; + } + }; + + CoffeeCodeGenerator.prototype.generateFlush = function(bufferStr) { + return this.calcCodeIndent() + "html.push('" + this.escapeCode(bufferStr) + "')\n"; + }; + + CoffeeCodeGenerator.prototype.setIndent = function(indent) { + return this.indent = indent; + }; + + CoffeeCodeGenerator.prototype.mark = function() { + return this.prevIndent = this.indent; + }; + + CoffeeCodeGenerator.prototype.calcCodeIndent = function() { + var codeIndent, i, j, ref, ref1, ref2; + codeIndent = 0; + for (i = j = 0, ref = this.indent; 0 <= ref ? j <= ref : j >= ref; i = 0 <= ref ? ++j : --j) { + if (((ref1 = this.elementStack[i]) != null ? ref1.block : void 0) || ((ref2 = this.elementStack[i]) != null ? ref2.fnBlock : void 0)) { + codeIndent += 1; + } + } + return HamlRuntime.indentText(codeIndent); + }; + + + /* + Append the text contents to the buffer (interpolating embedded code not required for coffeescript) + */ + + CoffeeCodeGenerator.prototype.appendTextContents = function(text, shouldInterpolate, currentParsePoint, options) { + var prefix, suffix; + if (shouldInterpolate && text.match(/#{[^}]*}/)) { + this.outputBuffer.flush(); + prefix = suffix = ''; + if (options != null ? options.escapeHTML : void 0) { + prefix = 'haml.HamlRuntime.escapeHTML('; + suffix = ')'; + } else if (options != null ? options.perserveWhitespace : void 0) { + prefix = 'haml.HamlRuntime.perserveWhitespace('; + suffix = ')'; + } + return this.outputBuffer.appendToOutputBuffer(this.calcCodeIndent() + 'html.push(' + prefix + '"' + this.escapeCode(text) + '"' + suffix + ')\n'); + } else { + if (options != null ? options.escapeHTML : void 0) { + text = haml.HamlRuntime.escapeHTML(text); + } + if (options != null ? options.perserveWhitespace : void 0) { + text = haml.HamlRuntime.perserveWhitespace(text); + } + return this.outputBuffer.append(text); + } + }; + + return CoffeeCodeGenerator; + + })(CodeGenerator); + + if (!Array.prototype.peek) { + Array.prototype.peek = function() { + return this[this.length - 1]; + }; + } + + + /* + Code generator that generates a Javascript function body + to generate document elements. + */ + + ElementGenerator = (function(superClass) { + extend(ElementGenerator, superClass); + + function ElementGenerator(options1) { + this.options = options1; + this.outputBuffer = new haml.Buffer(this); + } + + + /* + Initilising the output buffer with any variables or code + */ + + ElementGenerator.prototype.initOutput = function() { + var ref; + if ((ref = this.options) != null ? ref.tolerateFaults : void 0) { + this.outputBuffer.appendToOutputBuffer(' var handleError = haml.HamlRuntime._logError;'); + } else { + this.outputBuffer.appendToOutputBuffer(' var handleError = haml.HamlRuntime._raiseError;'); + } + return this.outputBuffer.appendToOutputBuffer('var hashFunction = null, hashObject = null, objRef = null, objRefFn = null, elm = null, parents = [];\nwith (context || {}) {\n'); + }; + + + /* + Flush and close the output buffer and return the contents + */ + + ElementGenerator.prototype.closeAndReturnOutput = function() { + var ret; + this.outputBuffer.flush(); + ret = this.outputBuffer.output() + ' }\n return elm; '; + return ret; + }; + + ElementGenerator.prototype._indent = function(indent) { + if (indent > 0) { + return this.outputBuffer.append(HamlRuntime.indentText(indent)); + } + }; + + ElementGenerator.prototype.openElement = function(currentParsePoint, indent, identifier, id, classes, objectRef, attributeList, attributeHash, elementStack, tagOptions, generator) { + var element, parentInnerWhitespace, tagOuterWhitespace; + element = identifier.length === 0 ? "div" : identifier; + parentInnerWhitespace = haml._parentInnerWhitespace(elementStack, indent); + tagOuterWhitespace = !tagOptions || tagOptions.outerWhitespace; + if (!tagOuterWhitespace) { + this.outputBuffer.trimWhitespace(); + } + this._indent(indent); + this.outputBuffer.append('elm = document.createElement("' + element + '");\n'); + this._indent(indent); + this.outputBuffer.append('if (parents.peek()) parents.peek().appendChild(elm);\n'); + this._indent(indent); + this.outputBuffer.append('parents.push(elm);\n'); + if (id) { + this._indent(indent); + this.outputBuffer.append('elm.setAttribute("id", "' + id + '");\n'); + } + if (classes && classes.length > 0) { + this._indent(indent); + this.outputBuffer.append('elm.setAttribute("class", "' + classes.join(' ') + '");\n'); + } + if (attributeHash.length > 0) { + this._indent(indent); + this.outputBuffer.append('hashFunction = eval("(' + attributeHash.replace(/"/g, '\\"').replace(/\n/g, '\\n') + ')");'); + this._indent(indent); + this.outputBuffer.append('for(var index in hashFunction) { if (hashFunction.hasOwnProperty(index)) { elm.setAttribute(index, hashFunction[index]);}}\n'); + } + return elementStack[indent] = { + element: element + }; + }; + + ElementGenerator.prototype.closeElement = function(indent, elementStack, tokeniser, generator) { + if (elementStack[indent] && elementStack[indent].element) { + this._indent(indent); + this.outputBuffer.append('elm = parents.pop();\n'); + elementStack[indent] = null; + return generator.mark(); + } + }; + + + /* + Append a line with embedded javascript code + */ + + ElementGenerator.prototype.appendEmbeddedCode = function(indentText, expression, escapeContents, perserveWhitespace, currentParsePoint) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(indentText + 'try {\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' var value = eval("' + (_.str || _).trim(expression).replace(/"/g, '\\"').replace(/\\n/g, '\\\\n') + '");\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' elm.appendChild( (typeof value === "string") ? document.createTextNode(value) : value);\n'); + if (false) { + if (escapeContents) { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.escapeHTML(String(value)));\n'); + } else if (perserveWhitespace) { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(haml.HamlRuntime.perserveWhitespace(String(value)));\n'); + } else { + this.outputBuffer.appendToOutputBuffer(indentText + ' html.push(String(value));\n'); + } + } + this.outputBuffer.appendToOutputBuffer(indentText + '} catch (e) {\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' handleError(haml.HamlRuntime.templateError(' + currentParsePoint.lineNumber + ', ' + currentParsePoint.characterNumber + ', "' + this.escapeCode(currentParsePoint.currentLine) + '",\n'); + this.outputBuffer.appendToOutputBuffer(indentText + ' "Error evaluating expression - " + e));\n'); + return this.outputBuffer.appendToOutputBuffer(indentText + '}\n'); + }; + + + /* + Append a line of code to the output buffer + */ + + ElementGenerator.prototype.appendCodeLine = function(line, eol) { + this.outputBuffer.flush(); + this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent)); + this.outputBuffer.appendToOutputBuffer(line); + return this.outputBuffer.appendToOutputBuffer(eol); + }; + + + /* + Does the current line end with a function declaration? + */ + + ElementGenerator.prototype.lineMatchesStartFunctionBlock = function(line) { + return line.match(/function\s*\((,?\s*\w+)*\)\s*\{\s*$/); + }; + + + /* + Does the current line end with a starting code block + */ + + ElementGenerator.prototype.lineMatchesStartBlock = function(line) { + return line.match(/\{\s*$/); + }; + + + /* + Generate the code to close off a code block + */ + + ElementGenerator.prototype.closeOffCodeBlock = function(tokeniser) { + if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) { + this.outputBuffer.flush(); + return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '}\n'); + } + }; + + + /* + Generate the code to close off a function parameter + */ + + ElementGenerator.prototype.closeOffFunctionBlock = function(tokeniser) { + if (!(tokeniser.token.minus && tokeniser.matchToken(/\s*\}/g))) { + this.outputBuffer.flush(); + return this.outputBuffer.appendToOutputBuffer(HamlRuntime.indentText(this.indent) + '});\n'); + } + }; + + + /* + Generate the code for dynamic attributes ({} form) + */ + + ElementGenerator.prototype.generateCodeForDynamicAttributes = function(id, classes, attributeList, attributeHash, objectRef, currentParsePoint) { + this.outputBuffer.flush(); + if (attributeHash.length > 0) { + attributeHash = this.replaceReservedWordsInHash(attributeHash); + this.outputBuffer.appendToOutputBuffer(' hashFunction = function () { return eval("hashObject = ' + attributeHash.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"); };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' hashFunction = null;\n'); + } + if (objectRef.length > 0) { + this.outputBuffer.appendToOutputBuffer(' objRefFn = function () { return eval("objRef = ' + objectRef.replace(/"/g, '\\"') + '"); };\n'); + } else { + this.outputBuffer.appendToOutputBuffer(' objRefFn = null;\n'); + } + 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) + '", handleError));\n'); + }; + + + /* + Clean any reserved words in the given hash + */ + + ElementGenerator.prototype.replaceReservedWordsInHash = function(hash) { + var j, len, ref, reservedWord, resultHash; + resultHash = hash; + ref = ['class', 'for']; + for (j = 0, len = ref.length; j < len; j++) { + reservedWord = ref[j]; + resultHash = resultHash.replace(reservedWord + ':', '"' + reservedWord + '":'); + } + return resultHash; + }; + + + /* + Escape the line so it is safe to put into a javascript string + */ + + ElementGenerator.prototype.escapeCode = function(jsStr) { + return jsStr.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); + }; + + + /* + Generate a function from the function body + */ + + ElementGenerator.prototype.generateJsFunction = function(functionBody) { + var e; + try { + return new Function('context', functionBody); + } catch (error1) { + e = error1; + throw "Incorrect embedded code has resulted in an invalid Haml function - " + e + "\nGenerated Function:\n" + functionBody; + } + }; + + + /* + Generate the code required to support a buffer flush + */ + + ElementGenerator.prototype.generateFlush = function(bufferStr) { + return bufferStr; + }; + + + /* + Set the current indent level + */ + + ElementGenerator.prototype.setIndent = function(indent) { + return this.indent = indent; + }; + + + /* + Save the current indent level if required + */ + + ElementGenerator.prototype.mark = function() {}; + + + /* + Append the text contents to the buffer, expanding any embedded code + */ + + ElementGenerator.prototype.appendTextContents = function(text, shouldInterpolate, currentParsePoint, options) { + if (options == null) { + options = {}; + } + if (shouldInterpolate && text.match(/#{[^}]*}/)) { + return this.interpolateString(text, currentParsePoint, options); + } else { + return this.outputBuffer.append('elm.appendChild(document.createTextNode("' + this.processText(text, options) + '"));\n'); + } + }; + + + /* + Interpolate any embedded code in the text + */ + + ElementGenerator.prototype.interpolateString = function(text, currentParsePoint, options) { + var index, precheedingChar, precheedingChar2, result; + index = 0; + result = this.embeddedCodeBlockMatcher.exec(text); + while (result) { + if (result.index > 0) { + precheedingChar = text.charAt(result.index - 1); + } + if (result.index > 1) { + precheedingChar2 = text.charAt(result.index - 2); + } + if (precheedingChar === '\\' && precheedingChar2 !== '\\') { + if (result.index !== 0) { + this.outputBuffer.append(this.processText(text.substring(index, result.index - 1), options)); + } + this.outputBuffer.append(this.processText(result[0]), options); + } else { + this.outputBuffer.append(this.processText(text.substring(index, result.index)), options); + this.appendEmbeddedCode(HamlRuntime.indentText(this.indent + 1), result[1], options.escapeHTML, options.perserveWhitespace, currentParsePoint); + } + index = this.embeddedCodeBlockMatcher.lastIndex; + result = this.embeddedCodeBlockMatcher.exec(text); + } + if (index < text.length) { + return this.outputBuffer.append(this.processText(text.substring(index), options)); + } + }; + + + /* + process text based on escape and preserve flags + */ + + ElementGenerator.prototype.processText = function(text, options) { + var t; + if (options != null ? options.escapeHTML : void 0) { + t = haml.HamlRuntime.escapeHTML(text); + } else if (options != null ? options.perserveWhitespace : void 0) { + t = haml.HamlRuntime.perserveWhitespace(text); + } else { + t = text; + } + return t; + }; + + return ElementGenerator; + + })(CodeGenerator); + + + /* + HAML filters are functions that take 3 parameters + contents: The contents block for the filter an array of lines of text + generator: The current generator for the compiled function + indent: The current indent level + currentParsePoint: line and character counters for the current parse point in the input buffer + */ + + filters = { + + /* + Plain filter, just renders the text in the block + */ + plain: function(contents, generator, indent, currentParsePoint) { + var j, len, line; + for (j = 0, len = contents.length; j < len; j++) { + line = contents[j]; + generator.appendTextContents(haml.HamlRuntime.indentText(indent - 1) + line + '\n', true, currentParsePoint); + } + return true; + }, + + /* + Wraps the filter block in a javascript tag + */ + javascript: function(contents, generator, indent, currentParsePoint) { + var j, len, line; + generator.outputBuffer.append(haml.HamlRuntime.indentText(indent) + "\n"); + }, + + /* + Wraps the filter block in a style tag + */ + css: function(contents, generator, indent, currentParsePoint) { + var j, len, line; + generator.outputBuffer.append(haml.HamlRuntime.indentText(indent) + "\n"); + }, + + /* + Wraps the filter block in a CDATA tag + */ + cdata: function(contents, generator, indent, currentParsePoint) { + var j, len, line; + generator.outputBuffer.append(haml.HamlRuntime.indentText(indent) + "\n"); + }, + + /* + Preserve filter, preserved blocks of text aren't indented, and newlines are replaced with the HTML escape code for newlines + */ + preserve: function(contents, generator, indent, currentParsePoint) { + var line; + generator.appendTextContents(haml.HamlRuntime.indentText(indent), false, currentParsePoint); + return generator.appendTextContents(((function() { + var j, len, results; + results = []; + for (j = 0, len = contents.length; j < len; j++) { + line = contents[j]; + results.push(haml.HamlRuntime.trim(line, 2)); + } + return results; + })()).join(' ') + '\n', true, currentParsePoint); + }, + + /* + Escape filter, renders the text in the block with html escaped + */ + escaped: function(contents, generator, indent, currentParsePoint) { + var j, len, line; + for (j = 0, len = contents.length; j < len; j++) { + line = contents[j]; + generator.appendTextContents(haml.HamlRuntime.indentText(indent - 1) + line + '\n', true, currentParsePoint, { + escapeHTML: true + }); + } + return true; + } + }; + + + /* + Main haml compiler implemtation + */ + + haml = { + + /* + Compiles the haml provided in the parameters to a Javascipt function + + Parameter: + String: Looks for a haml template in dom with this ID + Option Hash: The following options determines how haml sources and compiles the template + source - This contains the template in string form + sourceId - This contains the element ID in the dom which contains the haml source + sourceUrl - This contains the URL where the template can be fetched from + outputFormat - This determines what is returned, and has the following values: + string - The javascript source code + function - A javascript function (default) + generator - Which code generator to use + javascript (default) + coffeescript + productionjavascript + elementgenerator + tolerateErrors - switch the compiler into fault tolerant mode (defaults to false) + + Returns a javascript function + */ + compileHaml: function(options) { + var codeGenerator, result, tokinser; + if (typeof options === 'string') { + return this._compileHamlTemplate(options, new haml.JsCodeGenerator()); + } else { + codeGenerator = (function() { + switch (options.generator) { + case 'coffeescript': + return new haml.CoffeeCodeGenerator(options); + case 'productionjavascript': + return new haml.ProductionJsCodeGenerator(options); + case 'elementgenerator': + return new haml.ElementGenerator(options); + default: + return new haml.JsCodeGenerator(options); + } + })(); + if (options.source != null) { + tokinser = new haml.Tokeniser({ + template: options.source + }); + } else if (options.sourceId != null) { + tokinser = new haml.Tokeniser({ + templateId: options.sourceId + }); + } else if (options.sourceUrl != null) { + tokinser = new haml.Tokeniser({ + templateUrl: options.sourceUrl + }); + } else { + throw "No template source specified for compileHaml. You need to provide a source, sourceId or sourceUrl option"; + } + result = this._compileHamlToJs(tokinser, codeGenerator, options); + if (options.outputFormat !== 'string') { + return codeGenerator.generateJsFunction(result); + } else { + return "function (context) {\n" + result + "}\n"; + } + } + }, + + /* + Compiles the haml in the script block with ID templateId using the coffeescript generator + Returns a javascript function + */ + compileCoffeeHaml: function(templateId) { + return this._compileHamlTemplate(templateId, new haml.CoffeeCodeGenerator()); + }, + + /* + Compiles the haml in the passed in string + Returns a javascript function + */ + compileStringToJs: function(string) { + var codeGenerator, result; + codeGenerator = new haml.JsCodeGenerator(); + result = this._compileHamlToJs(new haml.Tokeniser({ + template: string + }), codeGenerator); + return codeGenerator.generateJsFunction(result); + }, + + /* + Compiles the haml in the passed in string using the coffeescript generator + Returns a javascript function + */ + compileCoffeeHamlFromString: function(string) { + var codeGenerator, result; + codeGenerator = new haml.CoffeeCodeGenerator(); + result = this._compileHamlToJs(new haml.Tokeniser({ + template: string + }), codeGenerator); + return codeGenerator.generateJsFunction(result); + }, + + /* + Compiles the haml in the passed in string + Returns the javascript function source + + This is mainly used for precompiling the haml templates so they can be packaged. + */ + compileHamlToJsString: function(string) { + var result; + result = 'function (context) {\n'; + result += this._compileHamlToJs(new haml.Tokeniser({ + template: string + }), new haml.JsCodeGenerator()); + return result += '}\n'; + }, + _compileHamlTemplate: function(templateId, codeGenerator) { + var fn, result; + haml.cache || (haml.cache = {}); + if (haml.cache[templateId]) { + return haml.cache[templateId]; + } + result = this._compileHamlToJs(new haml.Tokeniser({ + templateId: templateId + }), codeGenerator); + fn = codeGenerator.generateJsFunction(result); + haml.cache[templateId] = fn; + return fn; + }, + _compileHamlToJs: function(tokeniser, generator, options) { + var e, indent; + if (options == null) { + options = {}; + } + generator.elementStack = []; + generator.initOutput(); + tokeniser.getNextToken(); + while (!tokeniser.token.eof) { + if (!tokeniser.token.eol) { + try { + indent = this._whitespace(tokeniser); + generator.setIndent(indent); + if (tokeniser.token.eol) { + generator.outputBuffer.append(HamlRuntime.indentText(indent) + tokeniser.token.matched); + tokeniser.getNextToken(); + } else if (tokeniser.token.doctype) { + this._doctype(tokeniser, indent, generator); + } else if (tokeniser.token.exclamation) { + this._ignoredLine(tokeniser, indent, generator.elementStack, generator); + } else if (tokeniser.token.equal || tokeniser.token.escapeHtml || tokeniser.token.unescapeHtml || tokeniser.token.tilde) { + this._embeddedJs(tokeniser, indent, generator.elementStack, { + innerWhitespace: true + }, generator); + } else if (tokeniser.token.minus) { + this._jsLine(tokeniser, indent, generator.elementStack, generator); + } else if (tokeniser.token.comment || tokeniser.token.slash) { + this._commentLine(tokeniser, indent, generator.elementStack, generator); + } else if (tokeniser.token.amp) { + this._escapedLine(tokeniser, indent, generator.elementStack, generator); + } else if (tokeniser.token.filter) { + this._filter(tokeniser, indent, generator, options); + } else { + this._templateLine(tokeniser, generator.elementStack, indent, generator, options); + } + } catch (error1) { + e = error1; + this._handleError(options, { + skipTo: true + }, tokeniser, e); + } + } else { + generator.outputBuffer.append(tokeniser.token.matched); + tokeniser.getNextToken(); + } + } + generator.closeElements(0, generator.elementStack, tokeniser, generator); + return generator.closeAndReturnOutput(); + }, + _doctype: function(tokeniser, indent, generator) { + var contents, params; + if (tokeniser.token.doctype) { + generator.outputBuffer.append(HamlRuntime.indentText(indent)); + tokeniser.getNextToken(); + if (tokeniser.token.ws) { + tokeniser.getNextToken(); + } + contents = tokeniser.skipToEOLorEOF(); + if (contents && contents.length > 0) { + params = contents.split(/\s+/); + switch (params[0]) { + case 'XML': + if (params.length > 1) { + generator.outputBuffer.append(""); + } else { + generator.outputBuffer.append(""); + } + break; + case 'Strict': + generator.outputBuffer.append(''); + break; + case 'Frameset': + generator.outputBuffer.append(''); + break; + case '5': + generator.outputBuffer.append(''); + break; + case '1.1': + generator.outputBuffer.append(''); + break; + case 'Basic': + generator.outputBuffer.append(''); + break; + case 'Mobile': + generator.outputBuffer.append(''); + break; + case 'RDFa': + generator.outputBuffer.append(''); + } + } else { + generator.outputBuffer.append(''); + } + generator.outputBuffer.append(this._newline(tokeniser)); + return tokeniser.getNextToken(); + } + }, + _filter: function(tokeniser, indent, generator, options) { + var filter, filterBlock, i, line; + if (tokeniser.token.filter) { + filter = tokeniser.token.tokenString; + if (!haml.filters[filter]) { + this._handleError(options, { + skipTo: indent + }, tokeniser, tokeniser.parseError("Filter '" + filter + "' not registered. Filter functions need to be added to 'haml.filters'.")); + return; + } + tokeniser.skipToEOLorEOF(); + tokeniser.getNextToken(); + i = haml._whitespace(tokeniser); + filterBlock = []; + while (!tokeniser.token.eof && i > indent) { + tokeniser.pushBackToken(); + line = tokeniser.skipToEOLorEOF(); + filterBlock.push(HamlRuntime.trim(line, 2 * indent)); + tokeniser.getNextToken(); + i = haml._whitespace(tokeniser); + } + haml.filters[filter](filterBlock, generator, indent, tokeniser.currentParsePoint()); + return tokeniser.pushBackToken(); + } + }, + _commentLine: function(tokeniser, indent, elementStack, generator) { + var contents, i; + if (tokeniser.token.comment) { + tokeniser.skipToEOLorEOF(); + tokeniser.getNextToken(); + i = this._whitespace(tokeniser); + while (!tokeniser.token.eof && i > indent) { + tokeniser.skipToEOLorEOF(); + tokeniser.getNextToken(); + i = this._whitespace(tokeniser); + } + if (i > 0) { + return tokeniser.pushBackToken(); + } + } else if (tokeniser.token.slash) { + generator.closeElements(indent, elementStack, tokeniser, generator); + generator.outputBuffer.append(HamlRuntime.indentText(indent)); + generator.outputBuffer.append("