Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added haml-js directly

  • Loading branch information...
commit ad53ffdbc2da2cd3a5e5ec168a14c27991c17311 1 parent ca9cf08
@liammclennan authored
Showing with 1,086 additions and 0 deletions.
  1. +47 −0 packages/haml-js/CHANGELOG.markdown
  2. +22 −0 packages/haml-js/LICENSE
  3. +205 −0 packages/haml-js/README.markdown
  4. +35 −0 packages/haml-js/lib/cli.js
  5. +506 −0 packages/haml-js/lib/haml.js
  6. +11 −0 packages/haml-js/package.json
  7. +4 −0 packages/haml-js/test/alt_attribs.haml
  8. +1 −0  packages/haml-js/test/alt_attribs.html
  9. +5 −0 packages/haml-js/test/div_nesting.haml
  10. +1 −0  packages/haml-js/test/div_nesting.html
  11. +5 −0 packages/haml-js/test/doctype.haml
  12. +5 −0 packages/haml-js/test/doctype.html
  13. +6 −0 packages/haml-js/test/embedded_code.haml
  14. +7 −0 packages/haml-js/test/embedded_code.html
  15. +7 −0 packages/haml-js/test/embedded_code.js
  16. +12 −0 packages/haml-js/test/foreach.haml
  17. +1 −0  packages/haml-js/test/foreach.html
  18. +10 −0 packages/haml-js/test/foreach.js
  19. +1 −0  packages/haml-js/test/meta.haml
  20. +1 −0  packages/haml-js/test/meta.html
  21. +6 −0 packages/haml-js/test/nanline.haml
  22. +2 −0  packages/haml-js/test/nanline.html
  23. +6 −0 packages/haml-js/test/nested_context.haml
  24. +1 −0  packages/haml-js/test/nested_context.html
  25. +8 −0 packages/haml-js/test/nested_context.js
  26. +8 −0 packages/haml-js/test/no_self_close_div.haml
  27. +9 −0 packages/haml-js/test/no_self_close_div.html
  28. +8 −0 packages/haml-js/test/non-string-attribs.haml
  29. +1 −0  packages/haml-js/test/non-string-attribs.html
  30. +12 −0 packages/haml-js/test/script_css.haml
  31. +15 −0 packages/haml-js/test/script_css.html
  32. +8 −0 packages/haml-js/test/self_close.haml
  33. +1 −0  packages/haml-js/test/self_close.html
  34. +5 −0 packages/haml-js/test/self_close.js
  35. +14 −0 packages/haml-js/test/standard.haml
  36. +3 −0  packages/haml-js/test/standard.html
  37. +12 −0 packages/haml-js/test/standard.js
  38. +23 −0 packages/haml-js/test/test-commonjs.js
  39. +52 −0 packages/haml-js/test/test.js
View
47 packages/haml-js/CHANGELOG.markdown
@@ -0,0 +1,47 @@
+# HAML-JS Changelog
+
+- **v0.2.5** - *2010-05-06* - NPM support
+
+ Fixed to work with Node Package Manager
+
+- **v0.2.4** - *2010-04-16* - Bug fixes, XML support
+
+ Allow for commas in calls to helpers in attributes. Also make haml more XML friendly.
+
+- **v0.2.3** - *2010-04-10* - Bug fixes
+
+ Fixed an issue where "content" html attributes got munched. (This broke meta tags)
+
+- **v0.2.2** - *2010-04-05* - Bug fixes
+
+ Fixed two issues where the parser incorrectly parsed blank lines and extra spaces in attribute blocks.
+
+- **v0.2.1** - *2010-04-01* - Minor speed tweak
+
+ `Haml()` now caches the eval step so that there is no eval in executing a compiled template. This should make things a bit faster.
+
+- **v0.2.0** - *2010-03-31* - Function based API, Safe whitespace, Code interpolation.
+
+ At the request of some users, I've removed the new insertion into the generated html. This means that most html will be on one long line, but as an added advantage you won't have that extra whitespace next to your anchor labels messing up your visual display.
+
+ Also I added string interpolation to every place I could fit it. This means you can do crazy stuff like interpolate within strings in attributes, in the body on plain text sections, and of course in javascript and css plugin blocks.
+
+ In order to tame the API, I deprecated the four old interfaces `compile`, `optimize`, `execute` and `render`. The new API is that the Haml/exports object itself is now a function that takes in haml text and outputs a compiled, optimized, ready to execute function.
+
+- **0.1.2** - *2010-02-03* - Bug fixes, plugin aliases, CommonJS, and more...
+
+ This is a big release with many improvements. First haml-js is now a CommonJS module and is in the Tusk repository. Thanks to Tom Robinson for helping with that. Some of the plugins got aliases for people who didn't like the original name. For example, you can now do `:javascript` instead of `:script` and `:for` instead of `:each`. There were many bug fixes now that the code is starting to be actually used by myself and others.
+
+- **0.1.1** - *2010-01-09* - Add :css and :script plugins
+
+ Added two quick plugins that make working with javascript and css much easier.
+
+ - **0.1.0** - *2010-01-09* - Complete Rewrite
+
+ Rewrote the compiler to be recursive and compile to JavaScript code instead of JSON data structures. This fixes all the outstanding bugs and simplifies the code. Pending is restoring the `:script` and `:css` plugins.
+
+ - **0.0.1** - *2009-12-16* - Initial release
+
+ Change how haml is packaged. It is a pure JS function with no node dependencies. There is an exports hook for commonjs usability. It's now the responsibility of the script user to acquire the haml text.
+
+
View
22 packages/haml-js/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2009 Tim Caswell
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
View
205 packages/haml-js/README.markdown
@@ -0,0 +1,205 @@
+# haml-js - Server side templating language for JavaScript
+
+Ever wanted to use the excellent HAML syntax on a javascript project? Me too, so I made one!. This has most of the same functionality as the traditional [haml][].
+
+## About the language
+
+Here is the first example(with a little extra added) from the [haml][] site converted to haml-js:
+
+**haml-js**
+
+ !!! XML
+ !!! strict
+ %html{ xmlns: "http://www.w3.org/1999/xhtml" }
+ %head
+ %title Sample haml template
+ %body
+ .profile
+ .left.column
+ #date= print_date()
+ #address= current_user.address
+ .right.column
+ #email= current_user.email
+ #bio= current_user.bio
+
+**html**
+
+ <?xml version='1.0' encoding='utf-8' ?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml"><head><title>Sample haml template
+ </title></head><body><div class="profile"><div class="left column"><div id="date">January 1, 2009
+ </div><div id="address">Richardson, TX
+ </div></div><div class="right column"><div id="email">tim@creationix.com
+ </div><div id="bio">Experienced software professional...
+ </div></div></div></body></html>
+
+Note that this works almost the same as ruby's [haml][], but doesn't pretty print the html. This would greatly slow down and complicate the code. If you really want pretty printed html, then I suggest writing one using the xml parser library and process the resulting html..
+
+## API
+
+### Haml(haml) -> template(locals) -> html
+
+This is the new (as of 0.2.0) way to generate haml templates. A haml template is a live function that takes in "this" context and a "locals" variable. This compile step takes a few milliseconds to complete so it should be done at startup and the resulting function should be cached. Then to use the template function you simply call it with the desired local variables and it will output html at blazing speeds (we're talking millions per second on my 13" MBP)
+
+Compile and store a template:
+
+ var main = Haml(main_haml);
+
+Then use it whenever you need a new version:
+
+ main({name: "Tim", age: 28});
+
+That's it. Haml templating made easy!
+
+If you want to store the generated javascript to a file to skip the compile step later on you can either decompile the template function or use the `compile` and `optimize` advanced functions directly.
+
+
+### Haml.compile(text) -> JavaScript compiled template
+
+Given a haml template as raw text, this compiles it to a javascript expression
+that can later be eval'ed to get the final HTML.
+
+The following input:
+
+ #home
+ = title
+ %ul.menu
+ %li Go Home
+ %li Go Back
+
+Produces the following JavaScript expression:
+
+ "<div id=\"home\">" +
+ title +
+ "\n" +
+ "<ul class=\"menu\">" +
+ "<li>" +
+ "Go Home\n" +
+ "</li>" +
+ "<li>" +
+ "Go Back\n" +
+ "</li>" +
+ "</ul>" +
+ "</div>"
+
+### Haml.optimize(js) -> optimized JavaScript expression
+
+Takes the output of compile and optimizes it to run faster with the tradeoff of longer compile time. This is useful for framework developers wanting to use haml in their framework and want to cache the compiled templates for performance.
+
+With the previous input it outputs:
+
+ "<div id=\"home\">" +
+ title +
+ "\n<ul class=\"menu\"><li>Go Home\n</li><li>Go Back\n</li></ul></div>"
+
+Notice how congruent static strings are merged into a single string literal when possible.
+
+### Haml.execute(js, context, locals) -> Executes a compiles template
+
+Context is the value of `this` in the template, and locals is a hash of local variables.
+
+### Haml.render(text, options) -> html text
+
+This is a convenience function that compiles and executes to html in one shot. Most casual users will want to use this function exclusively.
+
+The `text` parameter is the haml source already read from a file.
+
+The three recognized `options` are:
+
+ - **context**: This is the `this` context within the haml template.
+ - **locals**: This is an object that's used in the `with` scope. Basically it creates local variables and function accessible to the haml template.
+ - **optimize**: This is a flag to tell the compiler to use the extra optimizations.
+
+See [test.js][] for an example usage of Haml.render
+
+## Code interpolation
+
+New in version 0.2.0 there is string interpolation throughout. This means that the body of regular text areas can have embedded code. This is true for attributes and the contents of plugins like javascript and markdown also. If you notice an area that doesn't support interpolation and it should then send me a note and I'll add it.
+
+## Plugins
+
+There are plugins in the parser for things like inline script tags, css blocks, and support for if statements and for loops.
+
+### `:if` statements
+
+`if` statements evaluate a condition for truthiness (as opposed to a strict comparison to `true`) and includes the content inside the block if it's truthy.
+
+ :if todolist.length > 20
+ %p Oh my, you are a busy fellow!
+
+### `:each` loops
+
+`:each` loops allow you to loop over a collection including a block of content once for each item. You need to what variable to pull the data from and where to put the index and value. The index variable is optional and defaults to `__key__`.
+
+Here is an example over a simple array.
+
+ %ul.todolist
+ :each item in todolist
+ %li= item.description
+
+You can loop over the keys and values of objects too (Note the inner `:each` loop)
+
+ :each item in data
+ :if item.age < 100
+ %dl
+ :each name, value in item
+ %dt&= name
+ %dd&= value
+
+### `:css` and `:javascript` helpers.
+
+It's easy to embed javascript and css blocks in an haml document.
+
+ %head
+ :javascript
+ function greet(message) {
+ alert("Message from MCP: " + message);
+ }
+ %title Script and Css test
+ :css
+ body {
+ color: pink;
+ }
+ %body{ onload: "greet(\"I'm Pink\")" } COLOR ME PINK
+
+This compiles to the following HTML:
+
+ <head>
+ <script type="text/javascript">
+ //<![CDATA[
+ function greet(message) {
+ alert("Message from MCP: " + message);
+ }
+ //]]>
+ </script>
+ <title>Script and Css test
+ </title>
+ <style type="text/css">
+ body {
+ color: pink;
+ }
+ </style>
+ </head><body onload="greet(&quot;I'm Pink&quot;)"> COLOR ME PINK
+ </body>
+
+## Get Involved
+
+If you want to use this project and something is missing then send me a message. I'm very busy and have several open source projects I manage. I'll contribute to this project as I have time, but if there is more interest for some particular aspect, I'll work on it a lot faster. Also you're welcome to fork this project and send me patches/pull-requests.
+
+## About Performance
+
+The haml compiler isn't built for speed, it's built for maintainability. The actual generated templates, however are blazing fast. I benchmarked them with over 65 million renders per second on a small (20 line) template with some dynamic data on my laptop. Compare this to the 629 compiles per second I got out of the compiler. The idea is that you pre-compile your templates and reuse them on every request. While 629 per second is nothing compared to 65 million, that still means that your server with over 600 different views can boot up in about a second. I think that's fine for something that only happens every few weeks.
+
+## License
+
+Haml-js is [licensed][] under the [MIT license][].
+
+[MIT license]: http://creativecommons.org/licenses/MIT/
+[licensed]: http://github.com/creationix/haml-js/blob/master/LICENSE
+[jquery-haml]: http://github.com/creationix/jquery-haml
+[haml]: http://haml-lang.com/
+[test.js]: http://github.com/creationix/haml-js/blob/master/test/test.js
+
+
+
+
View
35 packages/haml-js/lib/cli.js
@@ -0,0 +1,35 @@
+
+var Haml = require('./haml');
+
+var readUntilEnd = function(stream, callback) {
+ var chunks = [];
+ stream.on('data', function(chunk) {
+ chunks.push(chunk.toString('utf-8'));
+ });
+ stream.on('end', function() {
+ callback(chunks.join(''));
+ });
+}
+
+readUntilEnd(process.openStdin(), function(haml) {
+ var result;
+
+ if (haml.length == 0) {
+ console.log("Error: HAML expected on stdin")
+ process.exit(1);
+ }
+
+ // --html
+ if ((process.argv.length >= 3) && (process.argv[2] == '--html')) {
+ result = Haml.render(haml);
+ }
+
+ // --js
+ else {
+ result = Haml.optimize(
+ Haml.compile(
+ haml));
+ }
+
+ process.stdout.write(result);
+});
View
506 packages/haml-js/lib/haml.js
@@ -0,0 +1,506 @@
+var Haml;
+
+(function () {
+
+ var matchers, self_close_tags, embedder, forceXML;
+
+ function html_escape(text) {
+ return (text + "").
+ replace(/&/g, "&amp;").
+ replace(/</g, "&lt;").
+ replace(/>/g, "&gt;").
+ replace(/\"/g, "&quot;");
+ }
+
+ function render_attribs(attribs) {
+ var key, value, result = [];
+ for (key in attribs) {
+ if (key !== '_content' && attribs.hasOwnProperty(key)) {
+ switch (attribs[key]) {
+ case 'undefined':
+ case 'false':
+ case 'null':
+ case '""':
+ break;
+ default:
+ try {
+ value = JSON.parse("[" + attribs[key] +"]")[0];
+ if (value === true) {
+ value = key;
+ } else if (typeof value === 'string' && embedder.test(value)) {
+ value = '" +\n' + parse_interpol(html_escape(value)) + ' +\n"';
+ } else {
+ value = html_escape(value);
+ }
+ result.push(" " + key + '=\\"' + value + '\\"');
+ } catch (e) {
+ result.push(" " + key + '=\\"" + html_escape(' + attribs[key] + ') + "\\"');
+ }
+ }
+ }
+ }
+ return result.join("");
+ }
+
+ // Parse the attribute block using a state machine
+ function parse_attribs(line) {
+ var attributes = {},
+ l = line.length,
+ i, c,
+ count = 1,
+ quote = false,
+ skip = false,
+ open, close, joiner, seperator,
+ pair = {
+ start: 1,
+ middle: null,
+ end: null
+ };
+
+ if (!(l > 0 && (line.charAt(0) === '{' || line.charAt(0) === '('))) {
+ return {
+ _content: line[0] === ' ' ? line.substr(1, l) : line
+ };
+ }
+ open = line.charAt(0);
+ close = (open === '{') ? '}' : ')';
+ joiner = (open === '{') ? ':' : '=';
+ seperator = (open === '{') ? ',' : ' ';
+
+ function process_pair() {
+ if (typeof pair.start === 'number' &&
+ typeof pair.middle === 'number' &&
+ typeof pair.end === 'number') {
+ var key = line.substr(pair.start, pair.middle - pair.start).trim(),
+ value = line.substr(pair.middle + 1, pair.end - pair.middle - 1).trim();
+ attributes[key] = value;
+ }
+ pair = {
+ start: null,
+ middle: null,
+ end: null
+ };
+ }
+
+ for (i = 1; count > 0; i += 1) {
+
+ // If we reach the end of the line, then there is a problem
+ if (i > l) {
+ throw "Malformed attribute block";
+ }
+
+ c = line.charAt(i);
+ if (skip) {
+ skip = false;
+ } else {
+ if (quote) {
+ if (c === '\\') {
+ skip = true;
+ }
+ if (c === quote) {
+ quote = false;
+ }
+ } else {
+ if (c === '"' || c === "'") {
+ quote = c;
+ }
+
+ if (count === 1) {
+ if (c === joiner) {
+ pair.middle = i;
+ }
+ if (c === seperator || c === close) {
+ pair.end = i;
+ process_pair();
+ if (c === seperator) {
+ pair.start = i + 1;
+ }
+ }
+ }
+
+ if (c === open || c === "(") {
+ count += 1;
+ }
+ if (c === close || (count > 1 && c === ")")) {
+ count -= 1;
+ }
+ }
+ }
+ }
+ attributes._content = line.substr(i, line.length);
+ return attributes;
+ }
+
+ // Split interpolated strings into an array of literals and code fragments.
+ function parse_interpol(value) {
+ var items = [],
+ pos = 0,
+ next = 0,
+ match;
+ while (true) {
+ // Match up to embedded string
+ next = value.substr(pos).search(embedder);
+ if (next < 0) {
+ if (pos < value.length) {
+ items.push(JSON.stringify(value.substr(pos)));
+ }
+ break;
+ }
+ items.push(JSON.stringify(value.substr(pos, next)));
+ pos += next;
+
+ // Match embedded string
+ match = value.substr(pos).match(embedder);
+ next = match[0].length;
+ if (next < 0) { break; }
+ items.push(match[1] || match[2]);
+ pos += next;
+ }
+ return items.filter(function (part) { return part && part.length > 0}).join(" +\n");
+ }
+
+ // Used to find embedded code in interpolated strings.
+ embedder = /\#\{([^}]*)\}/;
+
+ self_close_tags = ["meta", "img", "link", "br", "hr", "input", "area", "base"];
+
+ // All matchers' regexps should capture leading whitespace in first capture
+ // and trailing content in last capture
+ matchers = [
+ // html tags
+ {
+ regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
+ process: function () {
+ var tag, classes, ids, attribs, content;
+ tag = this.matches[2];
+ classes = tag.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
+ ids = tag.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
+ tag = tag.match(/\%([a-z_\-][a-z0-9_:\-]*)/gi);
+
+ // Default to <div> tag
+ tag = tag ? tag[0].substr(1, tag[0].length) : 'div';
+
+ attribs = this.matches[3];
+ if (attribs) {
+ attribs = parse_attribs(attribs);
+ if (attribs._content) {
+ this.contents.unshift(attribs._content.trim());
+ delete(attribs._content);
+ }
+ } else {
+ attribs = {};
+ }
+
+ if (classes) {
+ classes = classes.map(function (klass) {
+ return klass.substr(1, klass.length);
+ }).join(' ');
+ if (attribs['class']) {
+ try {
+ attribs['class'] = JSON.stringify(classes + " " + JSON.parse(attribs['class']));
+ } catch (e) {
+ attribs['class'] = JSON.stringify(classes + " ") + " + " + attribs['class'];
+ }
+ } else {
+ attribs['class'] = JSON.stringify(classes);
+ }
+ }
+ if (ids) {
+ ids = ids.map(function (id) {
+ return id.substr(1, id.length);
+ }).join(' ');
+ if (attribs.id) {
+ attribs.id = JSON.stringify(ids + " ") + attribs.id;
+ } else {
+ attribs.id = JSON.stringify(ids);
+ }
+ }
+
+ attribs = render_attribs(attribs);
+
+ content = this.render_contents();
+ if (content === '""') {
+ content = '';
+ }
+
+ if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
+ return '"<' + tag + attribs + '>"' +
+ (content.length > 0 ? ' + \n' + content : "") +
+ ' + \n"</' + tag + '>"';
+ } else {
+ return '"<' + tag + attribs + ' />"';
+ }
+ }
+ },
+
+ // each loops
+ {
+ regexp: /^(\s*)(?::for|:each)\s+(?:([a-z_][a-z_\-]*),\s*)?([a-z_][a-z_\-]*)\s+in\s+(.*)(\s*)$/i,
+ process: function () {
+ var ivar = this.matches[2] || '__key__', // index
+ vvar = this.matches[3], // value
+ avar = this.matches[4], // array
+ rvar = '__result__'; // results
+
+ if (this.matches[5]) {
+ this.contents.unshift(this.matches[5]);
+ }
+ return '(function () { ' +
+ 'var ' + rvar + ' = [], ' + ivar + ', ' + vvar + '; ' +
+ 'for (' + ivar + ' in ' + avar + ') { ' +
+ 'if (' + avar + '.hasOwnProperty(' + ivar + ')) { ' +
+ vvar + ' = ' + avar + '[' + ivar + ']; ' +
+ rvar + '.push(\n' + (this.render_contents() || "''") + '\n); ' +
+ '} } return ' + rvar + '.join(""); }).call(this)';
+ }
+ },
+
+ // if statements
+ {
+ regexp: /^(\s*):if\s+(.*)\s*$/i,
+ process: function () {
+ var condition = this.matches[2];
+ return '(function () { ' +
+ 'if (' + condition + ') { ' +
+ 'return (\n' + (this.render_contents() || '') + '\n);' +
+ '} else { return ""; } }).call(this)';
+ }
+ },
+
+ // declarations
+ {
+ regexp: /^()!!!(?:\s*(.*))\s*$/,
+ process: function () {
+ var line = '';
+ switch ((this.matches[2] || '').toLowerCase()) {
+ case '':
+ // XHTML 1.0 Transitional
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
+ break;
+ case 'strict':
+ case '1.0':
+ // XHTML 1.0 Strict
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
+ break;
+ case 'frameset':
+ // XHTML 1.0 Frameset
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">';
+ break;
+ case '5':
+ // XHTML 5
+ line = '<!DOCTYPE html>';
+ break;
+ case '1.1':
+ // XHTML 1.1
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
+ break;
+ case 'basic':
+ // XHTML Basic 1.1
+ line = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">';
+ break;
+ case 'mobile':
+ // XHTML Mobile 1.2
+ line = '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">';
+ break;
+ case 'xml':
+ // XML
+ line = "<?xml version='1.0' encoding='utf-8' ?>";
+ break;
+ case 'xml iso-8859-1':
+ // XML iso-8859-1
+ line = "<?xml version='1.0' encoding='iso-8859-1' ?>";
+ break;
+ }
+ return JSON.stringify(line + "\n");
+ }
+ },
+
+ // Embedded markdown. Needs to be added to exports externally.
+ {
+ regexp: /^(\s*):markdown\s*$/i,
+ process: function () {
+ return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
+ }
+ },
+
+ // script blocks
+ {
+ regexp: /^(\s*):(?:java)?script\s*$/,
+ process: function () {
+ return parse_interpol('\n<script type="text/javascript">\n' +
+ '//<![CDATA[\n' +
+ this.contents.join("\n") +
+ "\n//]]>\n</script>\n");
+ }
+ },
+
+ // css blocks
+ {
+ regexp: /^(\s*):css\s*$/,
+ process: function () {
+ return JSON.stringify('\n<style type="text/css">\n' +
+ this.contents.join("\n") +
+ "\n</style>\n");
+ }
+ },
+
+ ];
+
+ function compile(lines) {
+ var block = false,
+ output = [];
+
+ // If lines is a string, turn it into an array
+ if (typeof lines === 'string') {
+ lines = lines.trim().replace(/\n\r|\r/g, '\n').split('\n');
+ }
+
+ lines.forEach(function(line) {
+ var match, found = false;
+
+ // Collect all text as raw until outdent
+ if (block) {
+ match = block.check_indent.exec(line);
+ if (match) {
+ block.contents.push(match[1] || "");
+ return;
+ } else {
+ output.push(block.process());
+ block = false;
+ }
+ }
+
+ matchers.forEach(function (matcher) {
+ if (!found) {
+ match = matcher.regexp.exec(line);
+ if (match) {
+ block = {
+ contents: [],
+ matches: match,
+ check_indent: new RegExp("^(?:\\s*|" + match[1] + " (.*))$"),
+ process: matcher.process,
+ render_contents: function () {
+ return compile(this. contents);
+ }
+ };
+ found = true;
+ }
+ }
+ });
+
+ // Match plain text
+ if (!found) {
+ output.push(function () {
+ // Escaped plain text
+ if (line[0] === '\\') {
+ return parse_interpol(line.substr(1, line.length));
+ }
+
+ // Plain variable data
+ if (line[0] === '=') {
+ line = line.substr(1, line.length).trim();
+ try {
+ return parse_interpol(JSON.parse(line));
+ } catch (e) {
+ return line;
+ }
+ }
+
+ // HTML escape variable data
+ if (line.substr(0, 2) === "&=") {
+ line = line.substr(2, line.length).trim();
+ try {
+ return JSON.stringify(html_escape(JSON.parse(line)));
+ } catch (e2) {
+ return 'html_escape(' + line + ')';
+ }
+ }
+
+ // Plain text
+ return parse_interpol(line);
+ }());
+ }
+
+ });
+ if (block) {
+ output.push(block.process());
+ }
+ return output.filter(function (part) { return part && part.length > 0}).join(" +\n");
+ };
+
+ function optimize(js) {
+ var new_js = [], buffer = [], part, end;
+
+ function flush() {
+ if (buffer.length > 0) {
+ new_js.push(JSON.stringify(buffer.join("")) + end);
+ buffer = [];
+ }
+ }
+ js.replace(/\n\r|\r/g, '\n').split('\n').forEach(function (line) {
+ part = line.match(/^(\".*\")(\s*\+\s*)?$/);
+ if (!part) {
+ flush();
+ new_js.push(line);
+ return;
+ }
+ end = part[2] || "";
+ part = part[1];
+ try {
+ buffer.push(JSON.parse(part));
+ } catch (e) {
+ flush();
+ new_js.push(line);
+ }
+ });
+ flush();
+ return new_js.join("\n");
+ };
+
+ function render(text, options) {
+ options = options || {};
+ text = text || "";
+ var js = compile(text);
+ if (options.optimize) {
+ js = Haml.optimize(js);
+ }
+ return execute(js, options.context || Haml, options.locals);
+ };
+
+ function execute(js, self, locals) {
+ return (function () {
+ with(locals || {}) {
+ try {
+ return eval("(" + js + ")");
+ } catch (e) {
+ return "\n<pre class='error'>" + html_escape(e.stack) + "</pre>\n";
+ }
+
+ }
+ }).call(self);
+ };
+
+ Haml = function Haml(haml, xml) {
+ forceXML = xml;
+ var js = optimize(compile(haml));
+ return new Function("locals",
+ html_escape + "\n" +
+ "with(locals || {}) {\n" +
+ " try {\n" +
+ " return (" + js + ");\n" +
+ " } catch (e) {\n" +
+ " return \"\\n<pre class='error'>\" + html_escape(e.stack) + \"</pre>\\n\";\n" +
+ " }\n" +
+ "}");
+ }
+ Haml.compile = compile;
+ Haml.optimize = optimize;
+ Haml.render = render;
+ Haml.execute = execute;
+
+}());
+
+// Hook into module system
+if (typeof module !== 'undefined') {
+ module.exports = Haml;
+}
View
11 packages/haml-js/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "haml",
+ "description": "Haml ported to server-side Javascript. This is a traditional server-side templating language.",
+ "keywords": ["haml", "template"],
+ "main" : "./lib/haml",
+ "bin": {
+ "haml-js": "./lib/cli.js"
+ },
+ "author": "Tim Caswell <tim@creationix.com>",
+ "version": "0.2.5"
+}
View
4 packages/haml-js/test/alt_attribs.haml
@@ -0,0 +1,4 @@
+%tag(name="value" name2=true ns:tag=100)
+%input#space-end(type="hidden" value="3" )
+%input#space-start( type="hidden" value="3" )
+%input#space-middle(type="hidden" value="3")
View
1  packages/haml-js/test/alt_attribs.html
@@ -0,0 +1 @@
+<tag name="value" name2="name2" ns:tag="100"></tag><input type="hidden" value="3" id="space-end" /><input type="hidden" value="3" id="space-start" /><input type="hidden" value="3" id="space-middle" />
View
5 packages/haml-js/test/div_nesting.haml
@@ -0,0 +1,5 @@
+%div
+ Does not close properly
+ %div Nested same level as next div
+%div
+ Will be nested, but should be top level
View
1  packages/haml-js/test/div_nesting.html
@@ -0,0 +1 @@
+<div>Does not close properly<div>Nested same level as next div</div></div><div>Will be nested, but should be top level</div>
View
5 packages/haml-js/test/doctype.haml
@@ -0,0 +1,5 @@
+!!!
+!!! strict
+!!! 1.1
+!!! 5
+!!! xml
View
5 packages/haml-js/test/doctype.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<!DOCTYPE html>
+<?xml version='1.0' encoding='utf-8' ?>
View
6 packages/haml-js/test/embedded_code.haml
@@ -0,0 +1,6 @@
+%head
+ :javascript
+ Page.chapter = #{JSON.stringify(chapter)};
+%body
+ %h1 Welcome #{name}
+ %div{class: "div_#{id}"}
View
7 packages/haml-js/test/embedded_code.html
@@ -0,0 +1,7 @@
+<head>
+<script type="text/javascript">
+//<![CDATA[
+Page.chapter = {"name":"Ninja","page":42};
+//]]>
+</script>
+</head><body><h1>Welcome Tim</h1><div class="div_42"></div></body>
View
7 packages/haml-js/test/embedded_code.js
@@ -0,0 +1,7 @@
+{
+ locals: {
+ chapter: {name: "Ninja", page: 42},
+ name: "Tim",
+ id: 42
+ }
+}
View
12 packages/haml-js/test/foreach.haml
@@ -0,0 +1,12 @@
+:each color in colors
+ .preview{style: "color: " + color + ";"}&= name
+:each item in data
+ :if item.age < 100
+ %dl
+ :each name, value in item
+ %dt&= name
+ %dd&= value
+:each number in [1,2,3,4,5,6,7]
+ = number
+:for word in "Hello World".split(" ")
+ = word
View
1  packages/haml-js/test/foreach.html
@@ -0,0 +1 @@
+<div style="color: #f80;" class="preview">My Rainbow</div><div style="color: #08f;" class="preview">My Rainbow</div><div style="color: #4f4;" class="preview">My Rainbow</div><dl><dt>name</dt><dd>Tim Caswell</dd><dt>age</dt><dd>27</dd></dl>1234567HelloWorld
View
10 packages/haml-js/test/foreach.js
@@ -0,0 +1,10 @@
+{
+ locals: {
+ colors: ["#f80", "#08f", "#4f4"],
+ name: "My Rainbow",
+ data: [
+ {name: "Tim Caswell", age: 27},
+ {name: "John Smith", age: 107},
+ ]
+ }
+}
View
1  packages/haml-js/test/meta.haml
@@ -0,0 +1 @@
+%meta(http-equiv="content-type" content="text/html; charset=UTF-8")
View
1  packages/haml-js/test/meta.html
@@ -0,0 +1 @@
+<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
View
6 packages/haml-js/test/nanline.haml
@@ -0,0 +1,6 @@
+!!! 5
+%html
+ %head
+ %title atomix
+
+ %script(src='atomix_xlib.js')
View
2  packages/haml-js/test/nanline.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html><head><title>atomix</title><script src="atomix_xlib.js"></script></head></html>
View
6 packages/haml-js/test/nested_context.haml
@@ -0,0 +1,6 @@
+%p= this.name
+#main
+ :each item in items
+ .item= this.name + ": " + item
+ :if items
+ #cool= this.name
View
1  packages/haml-js/test/nested_context.html
@@ -0,0 +1 @@
+<p>Frank</p><div id="main"><div class="item">Frank: 1</div><div class="item">Frank: 2</div><div class="item">Frank: 3</div><div id="cool">Frank</div></div>
View
8 packages/haml-js/test/nested_context.js
@@ -0,0 +1,8 @@
+{
+ context: {
+ name: "Frank"
+ },
+ locals: {
+ items: [1,2,3]
+ }
+}
View
8 packages/haml-js/test/no_self_close_div.haml
@@ -0,0 +1,8 @@
+%html
+ %body
+ %div#a
+ %div I do not self close.
+ :javascript
+ (function(){
+ document.getElementById('a').textContent='I self close';
+ })();
View
9 packages/haml-js/test/no_self_close_div.html
@@ -0,0 +1,9 @@
+<html><body><div id="a"></div><div>I do not self close.</div>
+<script type="text/javascript">
+//<![CDATA[
+(function(){
+ document.getElementById('a').textContent='I self close';
+})();
+//]]>
+</script>
+</body></html>
View
8 packages/haml-js/test/non-string-attribs.haml
@@ -0,0 +1,8 @@
+#plain= "Plain Text"
+#escaped&= "<escaped>"
+%input{checked: true}
+%input{checked: false}
+%input{checked: null}
+%input{checked: undefined}
+%input{checked: 0}
+%input{checked: ""}
View
1  packages/haml-js/test/non-string-attribs.html
@@ -0,0 +1 @@
+<div id="plain">Plain Text</div><div id="escaped">&lt;escaped&gt;</div><input checked="checked" /><input /><input /><input /><input checked="0" /><input />
View
12 packages/haml-js/test/script_css.haml
@@ -0,0 +1,12 @@
+%head
+ :javascript
+ function greet(message) {
+ alert("Message from MCP: " + message);
+ }
+ %title Script and Css test
+ :css
+ body {
+ color: pink;
+ }
+%body{onload: "greet(\"I'm Pink\")"} COLOR ME PINK
+
View
15 packages/haml-js/test/script_css.html
@@ -0,0 +1,15 @@
+<head>
+<script type="text/javascript">
+//<![CDATA[
+function greet(message) {
+ alert("Message from MCP: " + message);
+}
+//]]>
+</script>
+<title>Script and Css test</title>
+<style type="text/css">
+body {
+ color: pink;
+}
+</style>
+</head><body onload="greet(&quot;I'm Pink&quot;)">COLOR ME PINK</body>
View
8 packages/haml-js/test/self_close.haml
@@ -0,0 +1,8 @@
+%html
+ %head
+ :if url !='/'
+ %script
+ %meta{name: "test", value:"Monkey"}
+ %body
+ %a{ href: url }
+ link
View
1  packages/haml-js/test/self_close.html
@@ -0,0 +1 @@
+<html><head><script></script><meta name="test" value="Monkey" /></head><body><a href="http://nodejs.org/">link</a></body></html>
View
5 packages/haml-js/test/self_close.js
@@ -0,0 +1,5 @@
+{
+ locals: {
+ url: "http://nodejs.org/"
+ }
+}
View
14 packages/haml-js/test/standard.haml
@@ -0,0 +1,14 @@
+!!! XML
+!!! strict
+%html{ xmlns: "http://www.w3.org/1999/xhtml" }
+ %head
+ %title
+ Sample haml template
+ %body
+ .profile
+ .left.column
+ #date= print_date()
+ #address= current_user.address
+ .right.column
+ #email= current_user.email
+ #bio= current_user.bio
View
3  packages/haml-js/test/standard.html
@@ -0,0 +1,3 @@
+<?xml version='1.0' encoding='utf-8' ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Sample haml template</title></head><body><div class="profile"><div class="left column"><div id="date">January 1, 2009</div><div id="address">Richardson, TX</div></div><div class="right column"><div id="email">tim@creationix.com</div><div id="bio">Experienced software professional...</div></div></div></body></html>
View
12 packages/haml-js/test/standard.js
@@ -0,0 +1,12 @@
+{
+ locals: {
+ print_date: function () {
+ return 'January 1, 2009';
+ },
+ current_user: {
+ address: "Richardson, TX",
+ email: "tim@creationix.com",
+ bio: "Experienced software professional..."
+ }
+ }
+}
View
23 packages/haml-js/test/test-commonjs.js
@@ -0,0 +1,23 @@
+var FILE = require("file");
+var ASSERT = require("assert");
+
+var Haml = require("../lib/haml");
+
+FILE.glob("test/*.haml").forEach(function(hamlFile) {
+ exports["test " + hamlFile] = function() {
+ var scopeFile = hamlFile.replace(/haml$/, "js");
+ var htmlFile = hamlFile.replace(/haml$/, "html");
+
+ var haml = FILE.read(hamlFile);
+ var expected = FILE.read(htmlFile);
+ var scope = FILE.exists(scopeFile) ? eval("("+FILE.read(scopeFile)+")") : {};
+
+ var js = Haml.compile(haml);
+ var js_opt = Haml.optimize(js);
+ var actual = Haml.execute(js_opt, scope.context, scope.locals);
+ ASSERT.equal(actual.trim(), expected.trim());
+ }
+});
+
+if (module == require.main)
+ require("os").exit(require("test").run(exports));
View
52 packages/haml-js/test/test.js
@@ -0,0 +1,52 @@
+var fs = require('fs');
+var assert = require('assert');
+var sys = require('sys');
+
+var Haml = require("../lib/haml");
+
+fs.readdir('.', function (err, files) {
+ files.forEach(function (haml_file) {
+ var m = haml_file.match(/^(.*)\.haml/),
+ base;
+ if (!m) {
+ return;
+ }
+ base = m[1];
+
+ function load_haml(scope) {
+ fs.readFile(haml_file, "utf8", function (err, haml) {
+ fs.readFile(base + ".html", "utf8", function (err, expected) {
+ try {
+ var js = Haml.compile(haml);
+ var js_opt = Haml.optimize(js);
+ var actual = Haml(haml).call(scope.context, scope.locals);
+ assert.equal(actual, expected);
+
+ sys.puts(haml_file + " Passed")
+ } catch (e) {
+ var message = e.name;
+ if (e.message) { message += ": " + e.message; }
+ sys.error(haml_file + " FAILED")
+ sys.error(message);
+ sys.error("\nJS:\n\n" + js);
+ sys.error("\nOptimized JS:\n\n" + js_opt);
+ sys.error("\nActual:\n\n" + actual);
+ sys.error("\nExpected:\n\n" + expected);
+ process.exit();
+ }
+ });
+ });
+ }
+
+ // Load scope
+ if (files.indexOf(base + ".js") >= 0) {
+ fs.readFile(base + ".js", "utf8", function (err, js) {
+ load_haml(eval("(" + js + ")"));
+ });
+ } else {
+ load_haml({});
+ }
+ });
+});
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.