JavaScript template language shootoff (v913)

Revision 913 of this benchmark created by Brandon Papworth on


Description

A brief comparison of some JavaScript templating engines on a short template: 6 header tags, and 10 list items.

Note: When adding a new test, please ensure that your test returns the same HTML string (or equivalent DOM fragment) as the others.

Preparation HTML

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>

<script src="http://documentcloud.github.com/underscore/underscore.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"></script>

<!-- Milk.js -->
<script>
  (function() {
  var Expand, Find, Milk, Parse, TemplateCache, key;
  var __slice = Array.prototype.slice;
  TemplateCache = {};
  Find = function(name, stack, value) {
    var ctx, i, part, parts, _i, _len, _ref, _ref2, _ref3;
    if (value == null) {
      value = null;
    }
    if (name === '.') {
      return stack[stack.length - 1];
    }
    _ref = name.split(/\./), name = _ref[0], parts = 2 <= _ref.length ? __slice.call(_ref, 1) : [];
    for (i = _ref2 = stack.length - 1, _ref3 = -1; (_ref2 <= _ref3 ? i < _ref3 : i > _ref3); (_ref2 <= _ref3 ? i += 1 : i -= 1)) {
      if (stack[i] == null) {
        continue;
      }
      if (!(typeof stack[i] === 'object' && name in (ctx = stack[i]))) {
        continue;
      }
      value = ctx[name];
      break;
    }
    for (_i = 0, _len = parts.length; _i < _len; _i++) {
      part = parts[_i];
      value = Find(part, [value]);
    }
    if (value instanceof Function) {
      value = (function(value) {
        return function() {
          var val;
          val = value.apply(ctx, arguments);
          return (val instanceof Function) && val.apply(null, arguments) || val;
        };
      })(value);
    }
    return value;
  };
  Expand = function() {
    var args, f, obj, tmpl;
    obj = arguments[0], tmpl = arguments[1], args = 3 <= arguments.length ? __slice.call(arguments, 2) : [];
    return ((function() {
      var _i, _len, _results;
      _results = [];
      for (_i = 0, _len = tmpl.length; _i < _len; _i++) {
        f = tmpl[_i];
        _results.push(f.call.apply(f, [obj].concat(__slice.call(args))));
      }
      return _results;
    })()).join('');
  };
  Parse = function(template, delimiters, section) {
    var BuildRegex, buffer, buildInterpolationTag, buildInvertedSectionTag, buildPartialTag, buildSectionTag, cache, content, contentEnd, d, error, escape, isStandalone, match, name, parseError, pos, sectionInfo, tag, tagPattern, tmpl, type, whitespace, _name, _ref, _ref2, _ref3;
    if (delimiters == null) {
      delimiters = ['{{', '}}'];
    }
    if (section == null) {
      section = null;
    }
    cache = (TemplateCache[_name = delimiters.join(' ')] || (TemplateCache[_name] = {}));
    if (template in cache) {
      return cache[template];
    }
    buffer = [];
    BuildRegex = function() {
      var tagClose, tagOpen;
      tagOpen = delimiters[0], tagClose = delimiters[1];
      return RegExp("([\\s\\S]*?)([" + ' ' + "\\t]*)(?:" + tagOpen + "\\s*(?:(!)\\s*([\\s\\S]+?)|(=)\\s*([\\s\\S]+?)\\s*=|({)\\s*(\\w[\\S]*?)\\s*}|([^0-9a-zA-Z._!={]?)\\s*([\\w.][\\S]*?))\\s*" + tagClose + ")", "gm");
    };
    tagPattern = BuildRegex();
    tagPattern.lastIndex = pos = (section || {
      start: 0
    }).start;
    parseError = function(pos, msg) {
      var carets, e, endOfLine, error, indent, key, lastLine, lastTag, lineNo, parsedLines, tagStart;
      (endOfLine = /$/gm).lastIndex = pos;
      endOfLine.exec(template);
      parsedLines = template.substr(0, pos).split('\n');
      lineNo = parsedLines.length;
      lastLine = parsedLines[lineNo - 1];
      tagStart = contentEnd + whitespace.length;
      lastTag = template.substr(tagStart + 1, pos - tagStart - 1);
      indent = new Array(lastLine.length - lastTag.length + 1).join(' ');
      carets = new Array(lastTag.length + 1).join('^');
      lastLine = lastLine + template.substr(pos, endOfLine.lastIndex - pos);
      error = new Error();
      for (key in e = {
        "message": "" + msg + "\n\nLine " + lineNo + ":\n" + lastLine + "\n" + indent + carets,
        "error": msg,
        "line": lineNo,
        "char": indent.length,
        "tag": lastTag
      }) {
        error[key] = e[key];
      }
      return error;
    };
    while (match = tagPattern.exec(template)) {
      _ref = match.slice(1, 3), content = _ref[0], whitespace = _ref[1];
      type = match[3] || match[5] || match[7] || match[9];
      tag = match[4] || match[6] || match[8] || match[10];
      contentEnd = (pos + content.length) - 1;
      pos = tagPattern.lastIndex;
      isStandalone = (contentEnd === -1 || template.charAt(contentEnd) === '\n') && ((_ref2 = template.charAt(pos)) === void 0 || _ref2 === '' || _ref2 === '\r' || _ref2 === '\n');
      if (content) {
        buffer.push((function(content) {
          return function() {
            return content;
          };
        })(content));
      }
      if (isStandalone && (type !== '' && type !== '&' && type !== '{')) {
        if (template.charAt(pos) === '\r') {
          pos += 1;
        }
        if (template.charAt(pos) === '\n') {
          pos += 1;
        }
      } else if (whitespace) {
        buffer.push((function(whitespace) {
          return function() {
            return whitespace;
          };
        })(whitespace));
        contentEnd += whitespace.length;
        whitespace = '';
      }
      switch (type) {
        case '!':
          break;
        case '':
        case '&':
        case '{':
          buildInterpolationTag = function(name, is_unescaped) {
            return function(context) {
              var value, _ref;
              if ((value = (_ref = Find(name, context)) != null ? _ref : '') instanceof Function) {
                value = Expand.apply(null, [this, Parse("" + (value()))].concat(__slice.call(arguments)));
              }
              if (!is_unescaped) {
                value = this.escape("" + value);
              }
              return "" + value;
            };
          };
          buffer.push(buildInterpolationTag(tag, type));
          break;
        case '>':
          buildPartialTag = function(name, indentation) {
            return function(context, partials) {
              var partial;
              partial = partials(name).toString();
              if (indentation) {
                partial = partial.replace(/^(?=.)/gm, indentation);
              }
              return Expand.apply(null, [this, Parse(partial)].concat(__slice.call(arguments)));
            };
          };
          buffer.push(buildPartialTag(tag, whitespace));
          break;
        case '#':
        case '^':
          sectionInfo = {
            name: tag,
            start: pos,
            error: parseError(tagPattern.lastIndex, "Unclosed section '" + tag + "'!")
          };
          _ref3 = Parse(template, delimiters, sectionInfo), tmpl = _ref3[0], pos = _ref3[1];
          sectionInfo['#'] = buildSectionTag = function(name, delims, raw) {
            return function(context) {
              var parsed, result, v, value;
              value = Find(name, context) || [];
              tmpl = value instanceof Function ? value(raw) : raw;
              if (!(value instanceof Array)) {
                value = [value];
              }
              parsed = Parse(tmpl || '', delims);
              context.push(value);
              result = (function() {
                var _i, _len, _results;
                _results = [];
                for (_i = 0, _len = value.length; _i < _len; _i++) {
                  v = value[_i];
                  context[context.length - 1] = v;
                  _results.push(Expand.apply(null, [this, parsed].concat(__slice.call(arguments))));
                }
                return _results;
              }).apply(this, arguments);
              context.pop();
              return result.join('');
            };
          };
          sectionInfo['^'] = buildInvertedSectionTag = function(name, delims, raw) {
            return function(context) {
              var value;
              value = Find(name, context) || [];
              if (!(value instanceof Array)) {
                value = [1];
              }
              value = value.length === 0 ? Parse(raw, delims) : [];
              return Expand.apply(null, [this, value].concat(__slice.call(arguments)));
            };
          };
          buffer.push(sectionInfo[type](tag, delimiters, tmpl));
          break;
        case '/':
          if (section == null) {
            error = "End Section tag '" + tag + "' found, but not in section!";
          } else if (tag !== (name = section.name)) {
            error = "End Section tag closes '" + tag + "'; expected '" + name + "'!";
          }
          if (error) {
            throw parseError(tagPattern.lastIndex, error);
          }
          template = template.slice(section.start, (contentEnd + 1) || 9e9);
          cache[template] = buffer;
          return [template, pos];
        case '=':
          if ((delimiters = tag.split(/\s+/)).length !== 2) {
            error = "Set Delimiters tags should have two and only two values!";
          }
          if (error) {
            throw parseError(tagPattern.lastIndex, error);
          }
          escape = /[-[\]{}()*+?.,\\^$|#]/g;
          delimiters = (function() {
            var _i, _len, _results;
            _results = [];
            for (_i = 0, _len = delimiters.length; _i < _len; _i++) {
              d = delimiters[_i];
              _results.push(d.replace(escape, "\\$&"));
            }
            return _results;
          })();
          tagPattern = BuildRegex();
          break;
        default:
          throw parseError(tagPattern.lastIndex, "Unknown tag type -- " + type);
      }
      tagPattern.lastIndex = pos != null ? pos : template.length;
    }
    if (section != null) {
      throw section.error;
    }
    if (template.length !== pos) {
      buffer.push(function() {
        return template.slice(pos);
      });
    }
    return cache[template] = buffer;
  };
  Milk = {
    VERSION: '1.2.0',
    helpers: [],
    partials: null,
    escape: function(value) {
      var entities;
      entities = {
        '&': 'amp',
        '"': 'quot',
        '<': 'lt',
        '>': 'gt'
      };
      return value.replace(/[&"<>]/g, function(ch) {
        return "&" + entities[ch] + ";";
      });
    },
    render: function(template, data, partials) {
      var context;
      if (partials == null) {
        partials = null;
      }
      if (!((partials || (partials = this.partials || {})) instanceof Function)) {
        partials = (function(partials) {
          return function(name) {
            if (!(name in partials)) {
              throw "Unknown partial '" + name + "'!";
            }
            return Find(name, [partials]);
          };
        })(partials);
      }
      context = this.helpers instanceof Array ? this.helpers : [this.helpers];
      return Expand(this, Parse(template), context.concat([data]), partials);
    }
  };
  if (typeof exports != "undefined" && exports !== null) {
    for (key in Milk) {
      exports[key] = Milk[key];
    }
  } else {
    this.Milk = Milk;
  }
}).call(this);
</script>

<script src="http://terrainformatica.com/kite/kite.js"></script>

<script src="http://github.com/downloads/wycats/handlebars.js/handlebars-0.9.0.pre.4.js"></script>

<!-- https://gist.githubusercontent.com/jashkenas/550881/raw/29bb186167079c0b33ab6e9d50d779f37860cfa4/micro.js -->
<script>
  (function() {
    var cache = {};

    this.tmpl = function tmpl(str, data) {
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :

        // Generate a reusable function that will serve as a template
        // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +

        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +

        // Convert the template into pure JavaScript
str.replace(/[\r\t\n]/g, " ")
   .replace(/'(?=[^%]*%>)/g,"\t")
   .split("'").join("\\'")
   .split("\t").join("'")
   .replace(/<%=(.+?)%>/g, "',$1,'")
   .split("<%").join("');")
   .split("%>").join("p.push('")
   + "');}return p.join('');");
        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };
})();
</script>

<!-- https://gist.githubusercontent.com/akorchev/860205/raw/8444586913ab249c619671b8f5054fc92dddf643/micro2.js -->
<script>
  (function() {
    var cache = {};

    this.tmpl2 = function tmpl(str, data) {
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :

        // Generate a reusable function that will serve as a template
        // generator (and which will be cached).
      new Function("data",
        "var p=[];" +

        // Introduce the data as local variables using with(){}
        "p.push('" +

        // Convert the template into pure JavaScript
str.replace(/[\r\t\n]/g, " ")
   .replace(/'(?=[^%]*%>)/g,"\t")
   .split("'").join("\\'")
   .split("\t").join("'")
   .replace(/<%=(.+?)%>/g, "',$1,'")
   .split("<%").join("');")
   .split("%>").join("p.push('")
   + "');return p.join('');");
        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };
})();
</script>

<!-- https://gist.githubusercontent.com/akorchev/860240/raw/cd98cacbdeee7eb2cfb2ca3ca76638dae2a5b1af/micro3.js -->
<script>
  (function() {
    this.tmpl3 = function tmpl(str, data) {
        var value = "var out = ''; out+=" + "'" +

        str.replace(/[\r\t\n]/g, " ")
           .replace(/'(?=[^%]*%>)/g,"\t")
           .split("'").join("\\'")
           .split("\t").join("'")
           .replace(/<%=(.+?)%>/g, "'; out += $1; out += '")
           .split("<%").join("';")
           .split("%>").join("out+='")
           + "'; return out;";
           
           return new Function("data", value);
   }
})();
</script>

<!-- http://github.com/creationix/haml-js/raw/master/lib/haml.js -->
<script>
  var Haml;
 
(function () {

  var matchers, self_close_tags, embedder, forceXML, escaperName, escapeHtmlByDefault;

  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 + '=\\"" + '+escaperName+'(' + 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; }
      if(match[1] === "#"){
        items.push(escaperName+"("+(match[2] || match[3])+")");
      }else{
        //unsafe!!!
        items.push(match[2] || match[3]);
      }
      
      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
    {
      name: "html tags",
      regexp: /^(\s*)((?:[.#%][a-z_\-][a-z0-9_:\-]*)+)(.*)$/i,
      process: function () {
        var line_beginning, tag, classes, ids, attribs, content, whitespaceSpecifier, whitespace={}, output;
        line_beginning = this.matches[2];
        classes = line_beginning.match(/\.([a-z_\-][a-z0-9_\-]*)/gi);
        ids = line_beginning.match(/\#([a-z_\-][a-z0-9_\-]*)/gi);
        tag = line_beginning.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) {
            var leader0 = attribs._content.charAt(0),
                leader1 = attribs._content.charAt(1),
                leaderLength = 0;
                
            if(leader0 == "<"){
              leaderLength++;
              whitespace.inside = true;
              if(leader1 == ">"){
                leaderLength++;
                whitespace.around = true;
              }
            }else if(leader0 == ">"){
              leaderLength++;
              whitespace.around = true;
              if(leader1 == "<"){
                leaderLength++;
                whitespace.inside = true;
              }
            }
            attribs._content = attribs._content.substr(leaderLength);
            //once we've identified the tag and its attributes, the rest is content.
            // this is currently trimmed for neatness.
            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(whitespace.inside){
          if(content.length==0){
            content='"  "'
          }else{
            try{ //remove quotes if they are there
              content = '" '+JSON.parse(content)+' "';
            }catch(e){
              content = '" "+\n'+content+'+\n" "';
            }            
          }
        }

        if (forceXML ? content.length > 0 : self_close_tags.indexOf(tag) == -1) {
          output = '"<' + tag + attribs + '>"' +
            (content.length > 0 ? ' + \n' + content : "") +
            ' + \n"</' + tag + '>"';
        } else {
          output = '"<' + tag + attribs + ' />"';
        }
        
        if(whitespace.around){
          //output now contains '"<b>hello</b>"'
          //we need to crack it open to insert whitespace.
          output = '" '+output.substr(1, output.length - 2)+' "';
        }

        return output;
      }
    },

    // each loops
    {
      name: "each loop",
      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
    {
      name: "if",
      regexp: /^(\s*):if\s+(.*)\s*$/i,
      process: function () {
        var condition = this.matches[2];
        this.pushIfCondition([condition]);
        return '(function () { ' +
          'if (' + condition + ') { ' +
          'return (\n' + (this.render_contents() || '') + '\n);' +
          '} else { return ""; } }).call(this)';
      }
    },
    
    // else if statements
    {
      name: "else if",
      regexp: /^(\s*):else if\s+(.*)\s*$/i,
      process: function () {
        var condition = this.matches[2],
          conditionsArray = this.getIfConditions()[this.getIfConditions().length - 1],
          ifArray = [],
          ifStatement;
        for (var i=0, l=conditionsArray.length; i<l; i++) {
          ifArray.push('! (' + conditionsArray[i]+')');
        }
        conditionsArray.push(condition);
        ifArray.push(condition);
        ifStatement = 'if (' + ifArray.join(' && ') + ') { ';
        return '(function () { ' +
          ifStatement +
          'return (\n' + (this.render_contents() || '') + '\n);' +
          '} else { return ""; } }).call(this)';
      }
    },
    
    // else statements
    {
      name: "else",
      regexp: /^(\s*):else\s*$/i,
      process: function () {
        var conditionsArray = this.popIfCondition(),
          ifArray = [],
          ifStatement;
        for (var i=0, l=conditionsArray.length; i<l; i++) {
          ifArray.push('! (' + conditionsArray[i]+')');
        }
        ifStatement = 'if (' + ifArray.join(' && ') + ') { ';
        return '(function () { ' +
          ifStatement +
          'return (\n' + (this.render_contents() || '') + '\n);' +
          '} else { return ""; } }).call(this)';
      }
    },
    
    // silent-comments
    {
      name: "silent-comments",
      regexp: /^(\s*)-#\s*(.*)\s*$/i,
      process: function () {
        return '""';
      }
    },
    
    //html-comments
    {
      name: "silent-comments",
      regexp: /^(\s*)\/\s*(.*)\s*$/i,
      process: function () {
        this.contents.unshift(this.matches[2]);
        
        return '"<!--'+this.contents.join('\\n').replace(/\"/g, '\\"')+'-->"';
      }
    },
    
    // raw js
    {
      name: "rawjs",
      regexp: /^(\s*)-\s*(.*)\s*$/i,
      process: function () {
        this.contents.unshift(this.matches[2]);
        return '"";' + this.contents.join("\n")+"; _$output = _$output ";
      }
    },

    // raw js
    {
      name: "pre",
      regexp: /^(\s*):pre(\s+(.*)|$)/i,
      process: function () {
        this.contents.unshift(this.matches[2]);
        return '"<pre>"+\n' + JSON.stringify(this.contents.join("\n"))+'+\n"</pre>"';
      }
    },
    
    // declarations
    {
      name: "doctype",
      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.
    {
      name: "markdown",
      regexp: /^(\s*):markdown\s*$/i,
      process: function () {
        return parse_interpol(exports.Markdown.encode(this.contents.join("\n")));
      }
    },

    // script blocks
    {
      name: "script",
      regexp: /^(\s*):(?:java)?script\s*$/,
      process: function () {
        return parse_interpol('\n<script>\n' +
          '//<![CDATA[\n' +
          this.contents.join("\n") +
          "\n//]]>\n</script>\n");
      }
    },

    // css blocks
    {
      name: "css",
      regexp: /^(\s*):css\s*$/,
      process: function () {
        return JSON.stringify('<style type="text/css">\n' +
          this.contents.join("\n") +
          "\n</style>");
      }
    }

  ];

  function compile(lines) {
    var block = false,
        output = [],
        ifConditions = [];

    // 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: [],
              indent_level: (match[1]),
              matches: match,
              check_indent: new RegExp("^(?:\\s*|" + match[1] + "  (.*))$"),
              process: matcher.process,
              getIfConditions: function() {
                return ifConditions;
              },
              pushIfCondition: function(condition) {
                ifConditions.push(condition);
              },
              popIfCondition: function() {
                return ifConditions.pop();
              },
              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));
          }


          function escapedLine(){
            try {
              return escaperName+'('+JSON.stringify(JSON.parse(line)) +')';
            } catch (e2) {
              return escaperName+'(' + line + ')';
            }
          }
          
          function unescapedLine(){
            try {
              return parse_interpol(JSON.parse(line));
            } catch (e) {
              return line;
            }
          }
          
          // always escaped
          if((line.substr(0, 2) === "&=")) {
            line = line.substr(2, line.length).trim();
            return escapedLine();
          }
          
          //never escaped
          if((line.substr(0, 2) === "!=")) {
            line = line.substr(2, line.length).trim();
            return unescapedLine();
          }
          
          // sometimes escaped
          if ( (line[0] === '=')) {
            line = line.substr(1, line.length).trim();
            if(escapeHtmlByDefault){
              return escapedLine();
            }else{
              return unescapedLine();
            }
          }

          // Plain text
          return parse_interpol(line);
        }());
      }

    });
    if (block) {
      output.push(block.process());
    }
    
    var txt = output.filter(function (part) { return part && part.length > 0}).join(" +\n");
    if(txt.length == 0){
      txt = '""';
    }
    return txt;
  };

  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, options);
    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 {
          var _$output;
          eval("_$output =" + js );
          return _$output; //set in eval
        } catch (e) {
          return "\n<pre class='error'>" + html_escape(e.stack) + "</pre>\n";
        }

      }
    }).call(self);
  };

  Haml = function Haml(haml, config) {
    if(typeof(config) != "object"){
      forceXML = config;
      config = {};
    }
    
    var escaper;
    if(config.customEscape){
      escaper = "";
      escaperName = config.customEscape;
    }else{
      escaper = html_escape.toString() + "\n";
      escaperName = "html_escape";
    }
    
    escapeHtmlByDefault = (config.escapeHtmlByDefault || config.escapeHTML || config.escape_html);
    
    var js = optimize(compile(haml));
    
    var str = "with(locals || {}) {\n" +
    "  try {\n" +
    "   var _$output=" + js + ";\n return _$output;" +
    "  } catch (e) {\n" +
    "    return \"\\n<pre class='error'>\" + "+escaperName+"(e.stack) + \"</pre>\\n\";\n" +
    "  }\n" +
    "}"

    try{
      var f = new Function("locals",  escaper + str );
      return f;
    }catch(e){
      if ( typeof(console) !== 'undefined' ) { console.error(str); }
      throw e;
    }
  }

  Haml.compile = compile;
  Haml.optimize = optimize;
  Haml.render = render;
  Haml.execute = execute;
  Haml.html_escape = html_escape;
}());

// Hook into module system
if (typeof module !== 'undefined') {
  module.exports = Haml;
}

</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.7.1/coffee-script.min.js"></script>

<!-- http://sstephenson.github.com/eco/dist/eco.js -->
<script>
  /**
 * Eco Compiler v1.1.0-pre
 * http://github.com/sstephenson/eco
 *
 * Copyright (c) 2010 Sam Stephenson
 * Released under the MIT License
 */
this.eco=function(j){return function g(h){var b,c={id:h,exports:{}};if(b=j[h]){b(c,g,c.exports);return c.exports}else throw"Cannot find module '"+h+"'";}}({eco:function(j,g){g("coffee-script");j.exports=g("eco/compiler")},"eco/compiler":function(j,g){(function(){var h,b,c,d,f;h=g("coffee-script");d=g("eco/util").indent;j.exports=c=function(a){(new Function("module",b(a)))(a={});return a.exports};c.preprocess=f=g("eco/preprocessor").preprocess;c.compile=b=function(a,e){var i,k;k=typeof(i=typeof e===
"undefined"||e===null?undefined:e.identifier)!=="undefined"&&i!==null?i:"module.exports";k.match(/\./)||(k="var "+k);i=h.compile(f(a),{noWrap:true});return""+k+" = function(__obj) {\n  if (!__obj) __obj = {};\n  var __out = [], __capture = function(callback) {\n    var out = __out, result;\n    __out = [];\n    callback.call(this);\n    result = __out.join('');\n    __out = out;\n    return __safe(result);\n  }, __sanitize = function(value) {\n    if (value && value.ecoSafe) {\n      return value;\n    } else if (typeof value !== 'undefined' && value != null) {\n      return __escape(value);\n    } else {\n      return '';\n    }\n  }, __safe, __objSafe = __obj.safe, __escape = __obj.escape;\n  __safe = __obj.safe = function(value) {\n    if (value && value.ecoSafe) {\n      return value;\n    } else {\n      if (!(typeof value !== 'undefined' && value != null)) value = '';\n      var result = new String(value);\n      result.ecoSafe = true;\n      return result;\n    }\n  };\n  if (!__escape) {\n    __escape = __obj.escape = function(value) {\n      return ('' + value)\n        .replace(/&/g, '&amp;')\n        .replace(/</g, '&lt;')\n        .replace(/>/g, '&gt;')\n        .replace(/\"/g, '&quot;');\n    };\n  }\n  (function() {\n"+
d(i,4)+"\n  }).call(__obj);\n  __obj.safe = __objSafe, __obj.escape = __escape;\n  return __out.join('');\n};"};c.render=function(a,e){return c(a)(e)};if(g.extensions)g.extensions[".eco"]=function(a,e){var i;i=g("fs").readFileSync(e,"utf-8");return a._compile(b(i),e)};else g.registerExtension&&g.registerExtension(".eco",b)}).call(this)},"eco/preprocessor":function(j,g,h){(function(){var b,c,d,f=function(a,e){return function(){return a.apply(e,arguments)}};c=g("eco/scanner").Scanner;d=g("eco/util");
h.preprocess=function(a){return(new b(a)).preprocess()};h.Preprocessor=function(){b=function(a){this.scanner=new c(a);this.output="";this.level=0;this.options={};this.captures=[];return this};b.prototype.preprocess=function(){for(;!this.scanner.done;)this.scanner.scan(f(function(a){return this[a[0]].apply(this,a.slice(1))},this));return this.output};b.prototype.record=function(a){this.output+=d.repeat("  ",this.level);return this.output+=a+"\n"};b.prototype.printString=function(a){return a.length?
this.record("__out.push "+d.inspectString(a)):null};b.prototype.beginCode=function(a){return this.options=a};b.prototype.recordCode=function(a){return a!=="end"?this.options.print?this.options.safe?this.record("__out.push "+a):this.record("__out.push __sanitize "+a):this.record(a):null};b.prototype.indent=function(a){this.level++;if(a){this.record("__capture "+a);this.captures.unshift(this.level);return this.indent()}};b.prototype.dedent=function(){this.level--;this.level<0&&this.fail("unexpected dedent");
if(this.captures[0]===this.level){this.captures.shift();return this.dedent()}};b.prototype.fail=function(a){throw"Parse error on line "+this.scanner.lineNo+": "+a;};return b}()}).call(this)},"eco/scanner":function(j,g,h){(function(){var b,c,d,f;d=g("strscan");c=d.StringScanner;d=g("eco/util");f=d.trim;h.scan=function(a){var e;e=[];for(a=new b(a);!a.done;)a.scan(function(i){return e.push(i)});return e};h.Scanner=function(){b=function(a){this.source=a.replace(/\r\n?/g,"\n");this.scanner=new c(this.source);
this.mode="data";this.buffer="";this.lineNo=1;this.done=false;return this};b.modePatterns={data:/(.*?)(<%(([=-])?)|\n|$)/,code:/(.*?)(((:|(->|=>))\s*)?%>|\n|$)/};b.dedentablePattern=/^(end|when|else|catch|finally)(?:\W|$)/;b.prototype.scan=function(a){if(this.done)return a();else if(this.scanner.hasTerminated()){this.done=true;switch(this.mode){case "data":return a(["printString",this.flush()]);case "code":return a(["fail","unexpected end of template"])}}else{this.advance();switch(this.mode){case "data":return this.scanData(a);
case "code":return this.scanCode(a)}}};b.prototype.advance=function(){this.scanner.scanUntil(b.modePatterns[this.mode]);this.buffer+=this.scanner.getCapture(0);this.tail=this.scanner.getCapture(1);this.directive=this.scanner.getCapture(3);return this.arrow=this.scanner.getCapture(4)};b.prototype.scanData=function(a){var e;if(this.tail==="\n"){this.buffer+=this.tail;this.lineNo++;return this.scan(a)}else if(this.tail){this.mode="code";a(["printString",this.flush()]);return a(["beginCode",{print:typeof(e=
this.directive)!=="undefined"&&e!==null,safe:this.directive==="-"}])}};b.prototype.scanCode=function(a){var e;if(this.tail==="\n")return a(["fail","unexpected newline in code block"]);else if(this.tail){this.mode="data";e=f(this.flush());if(this.arrow)e+=" "+this.arrow;this.isDedentable(e)&&a(["dedent"]);a(["recordCode",e]);if(this.directive)return a(["indent",this.arrow])}};b.prototype.flush=function(){var a;a=this.buffer;this.buffer="";return a};b.prototype.isDedentable=function(a){return a.match(b.dedentablePattern)};
return b}.call(this)}).call(this)},"eco/util":function(j,g,h){(function(){var b,c;h.repeat=b=function(d,f){return Array(f+1).join(d)};h.indent=function(d,f){var a,e,i,k,l,m;m=b(" ",f);k=[];i=d.split("\n");a=0;for(e=i.length;a<e;a++){l=i[a];k.push(m+l)}return k.join("\n")};h.trim=function(d){return d.replace(/^\s+/,"").replace(/\s+$/,"")};c={"\\":"\\\\","\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t"};h.inspectString=function(d){return"'"+d.replace(/[\x00-\x1f\\]/g,function(f){if(f in
c)return c[f];else{f=f.charCodeAt(0).toString(16);if(f.length===1)f="0"+f;return"\\u00"+f}}).replace(/'/g,"\\'")+"'"}}).call(this)},strscan:function(j,g,h){(function(){var b;(typeof h!=="undefined"&&h!==null?h:this).StringScanner=function(){b=function(c){this.source=c.toString();this.reset();return this};b.prototype.scan=function(c){var d;return(d=c.exec(this.getRemainder()))&&d.index===0?this.setState(d,{head:this.head+d[0].length,last:this.head}):this.setState([])};b.prototype.scanUntil=function(c){if(c=
c.exec(this.getRemainder())){this.setState(c,{head:this.head+c.index+c[0].length,last:this.head});return this.source.slice(this.last,this.head)}else return this.setState([])};b.prototype.scanChar=function(){return this.scan(/./)};b.prototype.skip=function(c){if(this.scan(c))return this.match.length};b.prototype.skipUntil=function(c){if(this.scanUntil(c))return this.head-this.last};b.prototype.check=function(c){var d;return(d=c.exec(this.getRemainder()))&&d.index===0?this.setState(d):this.setState([])};
b.prototype.checkUntil=function(c){if(c=c.exec(this.getRemainder())){this.setState(c);return this.source.slice(this.head,this.head+c.index+c[0].length)}else return this.setState([])};b.prototype.peek=function(c){return this.source.substr(this.head,typeof c!=="undefined"&&c!==null?c:1)};b.prototype.getSource=function(){return this.source};b.prototype.getRemainder=function(){return this.source.slice(this.head)};b.prototype.getPosition=function(){return this.head};b.prototype.hasTerminated=function(){return this.head===
this.source.length};b.prototype.getPreMatch=function(){if(this.match)return this.source.slice(0,this.head-this.match.length)};b.prototype.getMatch=function(){return this.match};b.prototype.getPostMatch=function(){if(this.match)return this.source.slice(this.head)};b.prototype.getCapture=function(c){return this.captures[c]};b.prototype.reset=function(){return this.setState([],{head:0,last:0})};b.prototype.terminate=function(){return this.setState([],{head:this.source.length,last:this.head})};b.prototype.concat=
function(c){return this.source+=c};b.prototype.unscan=function(){if(this.match)return this.setState([],{head:this.last,last:0});else throw"nothing to unscan";};b.prototype.setState=function(c,d){var f,a;this.head=typeof(f=typeof d==="undefined"||d===null?undefined:d.head)!=="undefined"&&f!==null?f:this.head;this.last=typeof(a=typeof d==="undefined"||d===null?undefined:d.last)!=="undefined"&&a!==null?a:this.last;this.captures=c.slice(1);return this.match=c[0]};return b}()})()},"coffee-script":function(j){if(typeof CoffeeScript!==
"undefined"&&CoffeeScript!=null)j.exports=CoffeeScript;else throw"Cannot require '"+j.id+"': CoffeeScript not found";}})("eco");
</script>

<!-- https://raw.github.com/BorisMoore/jquery-tmpl/master/jquery.tmpl.min.js -->
<script>
  /*
 * jQuery Templates Plugin 1.0.0pre
 * http://github.com/jquery/jquery-tmpl
 * Requires jQuery 1.4.2
 *
 * Copyright 2011, Software Freedom Conservancy, Inc.
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */
(function(a){var r=a.fn.domManip,d="_tmplitem",q=/^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,b={},f={},e,p={key:0,data:{}},i=0,c=0,l=[];function g(g,d,h,e){var c={data:e||(e===0||e===false)?e:d?d.data:{},_wrap:d?d._wrap:null,tmpl:null,parent:d||null,nodes:[],calls:u,nest:w,wrap:x,html:v,update:t};g&&a.extend(c,g,{nodes:[],parent:d});if(h){c.tmpl=h;c._ctnt=c._ctnt||c.tmpl(a,c);c.key=++i;(l.length?f:b)[i]=c}return c}a.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(f,d){a.fn[f]=function(n){var g=[],i=a(n),k,h,m,l,j=this.length===1&&this[0].parentNode;e=b||{};if(j&&j.nodeType===11&&j.childNodes.length===1&&i.length===1){i[d](this[0]);g=this}else{for(h=0,m=i.length;h<m;h++){c=h;k=(h>0?this.clone(true):this).get();a(i[h])[d](k);g=g.concat(k)}c=0;g=this.pushStack(g,f,i.selector)}l=e;e=null;a.tmpl.complete(l);return g}});a.fn.extend({tmpl:function(d,c,b){return a.tmpl(this[0],d,c,b)},tmplItem:function(){return a.tmplItem(this[0])},template:function(b){return a.template(b,this[0])},domManip:function(d,m,k){if(d[0]&&a.isArray(d[0])){var g=a.makeArray(arguments),h=d[0],j=h.length,i=0,f;while(i<j&&!(f=a.data(h[i++],"tmplItem")));if(f&&c)g[2]=function(b){a.tmpl.afterManip(this,b,k)};r.apply(this,g)}else r.apply(this,arguments);c=0;!e&&a.tmpl.complete(b);return this}});a.extend({tmpl:function(d,h,e,c){var i,k=!c;if(k){c=p;d=a.template[d]||a.template(null,d);f={}}else if(!d){d=c.tmpl;b[c.key]=c;c.nodes=[];c.wrapped&&n(c,c.wrapped);return a(j(c,null,c.tmpl(a,c)))}if(!d)return[];if(typeof h==="function")h=h.call(c||{});e&&e.wrapped&&n(e,e.wrapped);i=a.isArray(h)?a.map(h,function(a){return a?g(e,c,d,a):null}):[g(e,c,d,h)];return k?a(j(c,null,i)):i},tmplItem:function(b){var c;if(b instanceof a)b=b[0];while(b&&b.nodeType===1&&!(c=a.data(b,"tmplItem"))&&(b=b.parentNode));return c||p},template:function(c,b){if(b){if(typeof b==="string")b=o(b);else if(b instanceof a)b=b[0]||{};if(b.nodeType)b=a.data(b,"tmpl")||a.data(b,"tmpl",o(b.innerHTML));return typeof c==="string"?(a.template[c]=b):b}return c?typeof c!=="string"?a.template(null,c):a.template[c]||a.template(null,q.test(c)?c:a(c)):null},encode:function(a){return(""+a).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e<p;e++){if((k=o[e]).nodeType!==1)continue;j=k.getElementsByTagName("*");for(h=j.length-1;h>=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery);
</script>

<!-- https://github.com/olado/doT/raw/master/doT.js -->
<script>
// doT.js
// 2011, Laura Doktorova, https://github.com/olado/doT
// Licensed under the MIT license.

(function() {
  "use strict";

  var doT = {
    version: '1.0.1',
    templateSettings: {
      evaluate:    /\{\{([\s\S]+?(\}?)+)\}\}/g,
      interpolate: /\{\{=([\s\S]+?)\}\}/g,
      encode:      /\{\{!([\s\S]+?)\}\}/g,
      use:         /\{\{#([\s\S]+?)\}\}/g,
      useParams:   /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$\.]+|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
      define:      /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
      defineParams:/^\s*([\w$]+):([\s\S]+)/,
      conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
      iterate:     /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
      varname:  'it',
      strip:    true,
      append:   true,
      selfcontained: false
    },
    template: undefined, //fn, compile template
    compile:  undefined  //fn, for express
  }, global;

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = doT;
  } else if (typeof define === 'function' && define.amd) {
    define(function(){return doT;});
  } else {
    global = (function(){ return this || (0,eval)('this'); }());
    global.doT = doT;
  }

  function encodeHTMLSource() {
    var encodeHTMLRules = { "&": "&#38;", "<": "&#60;", ">": "&#62;", '"': '&#34;', "'": '&#39;', "/": '&#47;' },
      matchHTML = /&(?!#?\w+;)|<|>|"|'|\//g;
    return function() {
      return this ? this.replace(matchHTML, function(m) {return encodeHTMLRules[m] || m;}) : this;
    };
  }
  String.prototype.encodeHTML = encodeHTMLSource();

  var startend = {
    append: { start: "'+(",      end: ")+'",      endencode: "||'').toString().encodeHTML()+'" },
    split:  { start: "';out+=(", end: ");out+='", endencode: "||'').toString().encodeHTML();out+='"}
  }, skip = /$^/;

  function resolveDefs(c, block, def) {
    return ((typeof block === 'string') ? block : block.toString())
    .replace(c.define || skip, function(m, code, assign, value) {
      if (code.indexOf('def.') === 0) {
        code = code.substring(4);
      }
      if (!(code in def)) {
        if (assign === ':') {
          if (c.defineParams) value.replace(c.defineParams, function(m, param, v) {
            def[code] = {arg: param, text: v};
          });
          if (!(code in def)) def[code]= value;
        } else {
          new Function("def", "def['"+code+"']=" + value)(def);
        }
      }
      return '';
    })
    .replace(c.use || skip, function(m, code) {
      if (c.useParams) code = code.replace(c.useParams, function(m, s, d, param) {
        if (def[d] && def[d].arg && param) {
          var rw = (d+":"+param).replace(/'|\\/g, '_');
          def.__exp = def.__exp || {};
          def.__exp[rw] = def[d].text.replace(new RegExp("(^|[^\\w$])" + def[d].arg + "([^\\w$])", "g"), "$1" + param + "$2");
          return s + "def.__exp['"+rw+"']";
        }
      });
      var v = new Function("def", "return " + code)(def);
      return v ? resolveDefs(c, v, def) : v;
    });
  }

  function unescape(code) {
    return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, ' ');
  }

  doT.template = function(tmpl, c, def) {
    c = c || doT.templateSettings;
    var cse = c.append ? startend.append : startend.split, needhtmlencode, sid = 0, indv,
      str  = (c.use || c.define) ? resolveDefs(c, tmpl, def || {}) : tmpl;

    str = ("var out='" + (c.strip ? str.replace(/(^|\r|\n)\t* +| +\t*(\r|\n|$)/g,' ')
          .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g,''): str)
      .replace(/'|\\/g, '\\$&')
      .replace(c.interpolate || skip, function(m, code) {
        return cse.start + unescape(code) + cse.end;
      })
      .replace(c.encode || skip, function(m, code) {
        needhtmlencode = true;
        return cse.start + unescape(code) + cse.endencode;
      })
      .replace(c.conditional || skip, function(m, elsecase, code) {
        return elsecase ?
          (code ? "';}else if(" + unescape(code) + "){out+='" : "';}else{out+='") :
          (code ? "';if(" + unescape(code) + "){out+='" : "';}out+='");
      })
      .replace(c.iterate || skip, function(m, iterate, vname, iname) {
        if (!iterate) return "';} } out+='";
        sid+=1; indv=iname || "i"+sid; iterate=unescape(iterate);
        return "';var arr"+sid+"="+iterate+";if(arr"+sid+"){var "+vname+","+indv+"=-1,l"+sid+"=arr"+sid+".length-1;while("+indv+"<l"+sid+"){"
          +vname+"=arr"+sid+"["+indv+"+=1];out+='";
      })
      .replace(c.evaluate || skip, function(m, code) {
        return "';" + unescape(code) + "out+='";
      })
      + "';return out;")
      .replace(/\n/g, '\\n').replace(/\t/g, '\\t').replace(/\r/g, '\\r')
      .replace(/(\s|;|\}|^|\{)out\+='';/g, '$1').replace(/\+''/g, '')
      .replace(/(\s|;|\}|^|\{)out\+=''\+/g,'$1out+=');

    if (needhtmlencode && c.selfcontained) {
      str = "String.prototype.encodeHTML=(" + encodeHTMLSource.toString() + "());" + str;
    }
    try {
      return new Function(c.varname, str);
    } catch (e) {
      if (typeof console !== 'undefined') console.log("Could not create a template function: " + str);
      throw e;
    }
  };

  doT.compile = function(tmpl, def) {
    return doT.template(tmpl, null, def);
  };
}());
</script>

<!-- https://github.com/olado/doT/raw/master/doU.js -->
<script>
// doU.js
// (c) 2011, Laura Doktorova
// https://github.com/olado/doT
//
// doU is an extraction and slight modification of an excellent
// templating function from jQote2.js (jQuery plugin) by aefxx
// (http://aefxx.com/jquery-plugins/jqote2/).
//
// Modifications:
// 1. nodejs support
// 2. allow for custom template markers
// 3. only allow direct invocation of the compiled function
//
// Licensed under the MIT license.

(function() {
  var doU = { version : '0.1.2' };

  if (typeof module !== 'undefined' && module.exports) {
    module.exports = doU;
  } else {
    this.doU = doU;
  }

  doU.templateSettings = {
    begin : '{{',
    end : '}}',
    varname : 'it'
  };

  doU.template = function(tmpl, conf) {
    conf = conf || doU.templateSettings;
    var str = '', tb = conf.begin, te = conf.end, m, l,
      arr = tmpl.replace(/\s*<!\[CDATA\[\s*|\s*\]\]>\s*|[\r\n\t]|(\/\*[\s\S]*?\*\/)/g, '')
        .split(tb).join(te +'\x1b')
        .split(te);

    for (m=0,l=arr.length; m < l; m++) {
      str += arr[m].charAt(0) !== '\x1b' ?
      "out+='" + arr[m].replace(/(\\|["'])/g, '\\$1') + "'" : (arr[m].charAt(1) === '=' ?
      ';out+=(' + arr[m].substr(2) + ');' : (arr[m].charAt(1) === '!' ?
      ';out+=(' + arr[m].substr(2) + ").toString().replace(/&(?!\\w+;)/g, '&#38;').split('<').join('&#60;').split('>').join('&#62;').split('" + '"' + "').join('&#34;').split(" + '"' + "'" + '"' + ").join('&#39;').split('/').join('&#x2F;');" : ';' + arr[m].substr(1)));
    }

    str = ('var out="";'+str+';return out;')
      .split("out+='';").join('')
      .split('var out="";out+=').join('var out=');

    try {
      return new Function(conf.varname, str);
    } catch (e) {
      if (typeof console !== 'undefined') console.log("Could not create a template function: " + str);
      throw e;
    }
  };
}());
</script>

<!-- http://github.com/aefxx/jQote2/raw/69b2053a13f5f180e696de9b3dba856a3c00678c/jquery.jqote2.js -->
<script>
  /*
 * jQote2 - client-side Javascript templating engine
 * Copyright (C) 2010, aefxx
 * http://aefxx.com/
 *
 * Dual licensed under the WTFPL v2 or MIT (X11) licenses
 * WTFPL v2 Copyright (C) 2004, Sam Hocevar
 *
 * Date: Thu, Oct 21st, 2010
 * Version: 0.9.7
 */
(function($) {
    var JQOTE2_TMPL_UNDEF_ERROR = 'UndefinedTemplateError',
        JQOTE2_TMPL_COMP_ERROR  = 'TemplateCompilationError',
        JQOTE2_TMPL_EXEC_ERROR  = 'TemplateExecutionError';

    var ARR  = '[object Array]',
        STR  = '[object String]',
        FUNC = '[object Function]';

    var n = 1, tag = '%',
        qreg = /^[^<]*(<[\w\W]+>)[^>]*$/,
        type_of = Object.prototype.toString;

    function raise(error, ext) {
        throw ($.extend(error, ext), error);
    }

    function dotted_ns(fn) {
        var ns = [];

        if ( type_of.call(fn) !== ARR ) return false;

        for ( var i=0,l=fn.length; i < l; i++ )
            ns[i] = fn[i].jqote_id;

        return ns.length ?
            ns.sort().join('.').replace(/(\b\d+\b)\.(?:\1(\.|$))+/g, '$1$2') : false;
    }

    function lambda(tmpl, t) {
        var f, fn = [], t = t || tag,
            type = type_of.call(tmpl);

        if ( type === FUNC )
            return tmpl.jqote_id ? [tmpl] : false;

        if ( type !== ARR )
            return [$.jqotec(tmpl, t)];

        if ( type === ARR )
            for ( var i=0,l=tmpl.length; i < l; i++ )
                if ( f = lambda(tmpl[i], t) ) fn.push(f[0]);

        return fn.length ? fn : false;
    }

    $.fn.extend({
        jqote: function(data, t) {
            var data = type_of.call(data) === ARR ? data : [data],
                dom = '';

            this.each(function(i) {
                var fn = $.jqotec(this, t);

                for ( var j=0; j < data.length; j++ )
                    dom += fn.call(data[j], i, j, data, fn);
            });

            return dom;
        }
    });

    $.each({app: 'append', pre: 'prepend', sub: 'html'}, function(name, method) {
        $.fn['jqote'+name] = function(elem, data, t) {
            var ns, regexp, str = $.jqote(elem, data, t),
                $$ = !qreg.test(str) ?
                    function(str) {return $(document.createTextNode(str));} : $;

            if ( !!(ns = dotted_ns(lambda(elem))) )
                regexp = new RegExp('(^|\\.)'+ns.split('.').join('\\.(.*)?')+'(\\.|$)');

            return this.each(function() {
                var dom = $$(str);

                $(this)[method](dom);

                ( dom[0].nodeType === 3 ?
                    $(this) : dom ).trigger('jqote.'+name, [dom, regexp]);
            });
        };
    });

    $.extend({
        jqote: function(elem, data, t) {
            var str = '', t = t || tag,
                fn = lambda(elem);

            if ( fn === false )
                raise(new Error('Empty or undefined template passed to $.jqote'), {type: JQOTE2_TMPL_UNDEF_ERROR});

            data = type_of.call(data) !== ARR ?
                [data] : data;

            for ( var i=0,l=fn.length; i < l; i++ )
                for ( var j=0; j < data.length; j++ )
                    str += fn[i].call(data[j], i, j, data, fn[i]);

            return str;
        },

        jqotec: function(template, t) {
            var cache, elem, tmpl, t = t || tag,
                type = type_of.call(template);

            if ( type === STR && qreg.test(template) ) {
                elem = tmpl = template;

                if ( cache = $.jqotecache[template] ) return cache;
            } else {
                elem = type === STR || template.nodeType ?
                    $(template) : template instanceof jQuery ?
                        template : null;

                if ( !elem[0] || !(tmpl = elem[0].innerHTML) && !(tmpl = elem.text()) )
                    raise(new Error('Empty or undefined template passed to $.jqotec'), {type: JQOTE2_TMPL_UNDEF_ERROR});

                if ( cache = $.jqotecache[$.data(elem[0], 'jqote_id')] ) return cache;
            }

            var str = '', index,
                arr = tmpl.replace(/\s*<!\[CDATA\[\s*|\s*\]\]>\s*|[\r\n\t]/g, '')
                    .split('<'+t).join(t+'>\x1b')
                        .split(t+'>');

            for ( var m=0,l=arr.length; m < l; m++ )
                str += arr[m].charAt(0) !== '\x1b' ?
                    "out+='" + arr[m].replace(/(\\|["'])/g, '\\$1') + "'" : (arr[m].charAt(1) === '=' ?
                        ';out+=(' + arr[m].substr(2) + ');' : (arr[m].charAt(1) === '!' ?
                            ';out+=$.jqotenc((' + arr[m].substr(2) + '));' : ';' + arr[m].substr(1)));

            str = 'try{' +
                ('var out="";'+str+';return out;')
                    .split("out+='';").join('')
                        .split('var out="";out+=').join('var out=') +
                '}catch(e){e.type="'+JQOTE2_TMPL_EXEC_ERROR+'";e.args=arguments;e.template=arguments.callee.toString();throw e;}';

            try {
                var fn = new Function('i, j, data, fn', str);
            } catch ( e ) { raise(e, {type: JQOTE2_TMPL_COMP_ERROR}); }

            index = elem instanceof jQuery ?
                $.data(elem[0], 'jqote_id', n) : elem;

            return $.jqotecache[index] = (fn.jqote_id = n++, fn);
        },

        jqotefn: function(elem) {
            var type = type_of.call(elem),
                index = type === STR && qreg.test(elem) ?
                    elem : $.data($(elem)[0], 'jqote_id');

            return $.jqotecache[index] || false;
        },

        jqotetag: function(str) {
            if ( type_of.call(str) === STR ) tag = str;
        },

        jqotenc: function(str) {
            return s.toString()
                    .replace(/&(?!\w+;)/g, '&#38;')
                        .split('<').join('&#60;').split('>').join('&#62;')
                            .split('"').join('&#34;').split("'").join('&#39;');
        },

        jqotecache: {}
    });

    $.event.special.jqote = {
        add: function(obj) {
            var ns, handler = obj.handler,
                data = !obj.data ?
                    [] : type_of.call(obj.data) !== ARR ?
                        [obj.data] : obj.data;

            if ( !obj.namespace ) obj.namespace = 'app.pre.sub';
            if ( !data.length || !(ns = dotted_ns(lambda(data))) ) return;

            obj.handler = function(event, dom, regexp) {
                return !regexp || regexp.test(ns) ?
                    handler.apply(this, [event, dom]) : null;
            };
        }
    };
})(jQuery);

</script>
<script src="http://embeddedjavascript.googlecode.com/files/ejs_0.9_alpha_1_production.js"></script>

<script src="http://github.com/pure/pure/raw/master/libs/pure.js"></script>
<div class="pure">
        <h1 class='header'></h1>
        <h2 class='header2'></h2>
        <h3 class='header3'></h3>
        <h4 class='header4'></h4>
        <h5 class='header5'></h5>
        <h6 class='header6'></h6>
        <ul class='list'>
                <li class='item'></li>
        </ul>
</div>

<script src="http://github.com/douglascrockford/JSON-js/raw/master/json2.js"></script>

<script src="http://akdubya.github.com/dustjs/dist/dust-full-0.3.0.min.js"></script>


<script src="https://github.com/premasagar/tim/raw/master/tim.js"></script>

<script src="https://gist.github.com/raw/875670/d52752ead19a4eebc7237602438ae08a2541a5b5/tim-lite-cached-min.js"></script>

<script src="http://www.kuwata-lab.com/tenjin/shotenjin.js"></script>

<script src="https://github.com/QLeelulu/nTenjin/raw/master/nTenjin.js"></script>

<script>
  window.mustacheTemplate = "<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#list}}<li class='item'>{{.}}</li>{{/list}}</ul></div>";
  
  // note: exactly the same as the mustacheTemplate above.
  window.kiteTemplate = "<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#list}}<li class='item'>{{.}}</li>{{/list}}</ul></div>";
  window.kiteCompiledTemplate = kite(kiteTemplate); // seems like others are testing compiled versions.
  window.handlebarsTemplate = Handlebars.compile("<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#each list}}<li class='item'>{{this}}</li>{{/each}}</ul></div>");
  
  window.micro = "<div><h1 class='header'><%= header %></h1><h2 class='header2'><%= header2 %></h2><h3 class='header3'><%= header3 %></h3><h4 class='header4'><%= header4 %></h4><h5 class='header5'><%= header5 %></h5><h6 class='header6'><%= header6 %></h6><ul class='list'><% for (var i = 0, l = list.length; i < l; i++) { %><li class='item'><%= list[i] %></li><% } %></ul></div>";
  
  window.micro2 = "<div><h1 class='header'><%= data.header %></h1><h2 class='header2'><%= data.header2 %></h2><h3 class='header3'><%= data.header3 %></h3><h4 class='header4'><%= data.header4 %></h4><h5 class='header5'><%= data.header5 %></h5><h6 class='header6'><%= data.header6 %></h6><ul class='list'><% for (var i = 0, l = data.list.length; i < l; i++) { %><li class='item'><%= data.list[i] %></li><% } %></ul></div>";
  
  
  window.underscoreTemplate = _.template(micro);
  
  window.resigTemplate = tmpl(micro);
  
  window.resigTemplate2 = tmpl2(micro2);
  
  window.resigTemplate3 = tmpl3(micro2);
  
  window.sharedVariables = {
   header: "Header",
   header2: "Header2",
   header3: "Header3",
   header4: "Header4",
   header5: "Header5",
   header6: "Header6",
   list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
  };
  
  window.jadeTemplate = "div\n  h1.header!= header\n  h2.header2!= header2\n  h3.header3!= header3\n  h4.header4!= header4\n  h5.header5!= header5\n  h6.header6!= header6\n  ul.list\n    - each item in list\n      li.item!= item";
  
  window.hamlTemplate = Haml("%div\n  %h1.header= header\n  %h2.header2= header2\n  %h3.header3= header3\n  %h4.header4= header4\n  %h5.header5= header5\n  %h6.header6= header6\n  %ul.list\n    :each item in list\n      %li.item= item");
  
  window.ecoTemplate = eco("<div><h1 class='header'><%- @header %></h1><h2 class='header2'><%- @header2 %></h2><h3 class='header3'><%- @header3 %></h3><h4 class='header4'><%- @header4 %></h4><h5 class='header5'><%- @header5 %></h5><h6 class='header6'><%- @header6 %></h6><ul class='list'><% for item in @list: %><li class='item'><%- item %></li><% end %></ul></div>");
  
  window.jQueryTemplate = $.template(null, "<div><h1 class='header'>{{html header}}</h1><h2 class='header2'>{{html header2}}</h2><h3 class='header3'>{{html header3}}</h3><h4 class='header4'>{{html header4}}</h4><h5 class='header5'>{{html header5}}</h5><h6 class='header6'>{{html header6}}</h6><ul class='list'>{{each list}}<li class='item'>{{html $value}}</li>{{/each}}</ul></div>");
  
  window.doTtemplate = doT.template("<div><h1 class='header'>{{= it.header }}</h1><h2 class='header2'>{{= it.header2 }}</h2><h3 class='header3'>{{= it.header3 }}</h3><h4 class='header4'>{{= it.header4 }}</h4><h5 class='header5'>{{= it.header5 }}</h5><h6 class='header6'>{{= it.header6 }}</h6><ul class='list'>{{ for (var i = 0, l = it.list.length; i < l; i++) { }}<li class='item'>{{= it.list[i] }}</li>{{ } }}</ul></div>");
  
  window.doUtemplate = doU.template("<div><h1 class='header'>{{= it.header }}</h1><h2 class='header2'>{{= it.header2 }}</h2><h3 class='header3'>{{= it.header3 }}</h3><h4 class='header4'>{{= it.header4 }}</h4><h5 class='header5'>{{= it.header5 }}</h5><h6 class='header6'>{{= it.header6 }}</h6><ul class='list'>{{ for (var i = 0, l = it.list.length; i < l; i++) { }}<li class='item'>{{= it.list[i] }}</li>{{ } }}</ul></div>");
  
  window.jqote2 = $.jqotec(jqote_tmpl);
  
  window.ejs = new EJS({
   text: micro
  });
  
  window.baseHtml = "<div><h1 class='header'></h1><h2 class='header2'></h2><h3 class='header3'></h3><h4 class='header4'></h4><h5 class='header5'></h5><h6 class='header6'></h6><ul class='list'><li class='item'></li></ul></div>";
  
  window.pureTemplate = $p('div.pure').compile({
   h1: 'header',
   h2: 'header2',
   h3: 'header3',
   h4: 'header4',
   h5: 'header5',
   h6: 'header6',
   li: {
    'itm<-list': {
     '.': 'itm'
    }
   }
  });
  
  window.hamlTemplate_2 = "%div\n  %h1.header= header\n  %h2.header2= header2\n  %h3.header3= header3\n  %h4.header4= header4\n  %h5.header5= header5\n  %h6.header6= header6\n  %ul.list\n    :each item in list\n      %li.item= item";
  window.hamlCompiled = Haml.compile(hamlTemplate_2);
  window.hamlOptimized = Haml.optimize(hamlCompiled);
  
  dust.loadSource(dust.compile("<div><h1 class='header'>{header}</h1><h2 class='header2'>{header2}</h2><h3 class='header3'>{header3}</h3><h4 class='header4'>{header4}</h4><h5 class='header5'>{header5}</h5><h6 class='header6'>{header6}</h6><ul class='list'>{#list}<li class='item'>{.}</li>{/list}</ul></div>", "dustTemplate"));
  
  window.timTemplate = "<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{list}}<li class='item'>{{_content}}</li>{{/list}}</ul></div>";
  
  window.timLiteTemplate = "<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'><li class='item'>{{list.0}}</li><li class='item'>{{list.1}}</li><li class='item'>{{list.2}}</li><li class='item'>{{list.3}}</li><li class='item'>{{list.4}}</li><li class='item'>{{list.5}}</li><li class='item'>{{list.6}}</li><li class='item'>{{list.7}}</li><li class='item'>{{list.8}}</li><li class='item'>{{list.9}}</li></ul></div>";
  
  window.tenjinTemplate = "<div><h1 class='header'>#{ header }</h1><h2 class='header2'>#{ header2 }</h2><h3 class='header3'>#{ header3 }</h3><h4 class='header4'>#{ header4 }</h4><h5 class='header5'>#{ header5 }</h5><h6 class='header6'>#{ header6 }</h6><ul class='list'><?js for (var i = 0, l = list.length; i < l; i++) { ?><li class='item'>#{ list[i] }</li><?js } ?></ul></div>";
  
  window.convertedTenjinTemplate = new Shotenjin.Template();
  window.convertedTenjinTemplate.convert(tenjinTemplate);
  
  window.nTenjinTemplate = "<div><h1 class='header'>#{ it.header }</h1><h2 class='header2'>#{ it.header2 }</h2><h3 class='header3'>#{ it.header3 }</h3><h4 class='header4'>#{ it.header4 }</h4><h5 class='header5'>#{ it.header5 }</h5><h6 class='header6'>#{ it.header6 }</h6><ul class='list'><?js for (var i = 0, l = it.list.length; i < l; i++) { ?><li class='item'>#{ it.list[i] }</li><?js } ?></ul></div>";
  
  window.convertednTenjinTemplate = new nTenjin.Template();
  window.convertednTenjinTemplate.convert(nTenjinTemplate);
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
Mustache.js Template
Mustache.to_html(mustacheTemplate, sharedVariables);
ready
Resig Micro-Templating.
resigTemplate(sharedVariables);
ready
Creationix's Haml-js Template
hamlTemplate(sharedVariables);
ready
Jade Template
jade.render(jadeTemplate, {
 locals: sharedVariables,
 filename: 'test.js',
 cache: true
});
ready
Underscore.js Template
underscoreTemplate(sharedVariables);
ready
Eco Template
ecoTemplate(sharedVariables);
ready
jQuery Templates
jQueryTemplate($, {
 data: sharedVariables
}).join("");
ready
Handlebars.js
handlebarsTemplate(sharedVariables);
ready
doT.js
doTtemplate(sharedVariables);
ready
doU.js
doUtemplate(sharedVariables);
ready
jQote2 direct
jqote2.call(sharedVariables, 0, 0, [sharedVariables], jqote2);
ready
ejs
ejs.render(sharedVariables);
ready
Pure
pureTemplate(sharedVariables);
ready
Creationix's Haml-js Template (compiled & optimized)
Haml.execute(window.hamlOptimized, sharedVariables, sharedVariables);
ready
dust
var done = false;

while (!done) {
 // Need better way to benchmark asynchronous functions
 dust.render("dustTemplate", sharedVariables, function() {
  done = true;
 });
}
ready
KiTE
kiteCompiledTemplate(sharedVariables);
ready
Resig Micro-Templating without with(obj)
resigTemplate2(sharedVariables);
ready
Resig Micro-Templating with += instead of push and join
resigTemplate3(sharedVariables);
ready
Milk
Milk.render(mustacheTemplate, sharedVariables);
ready
Tim
tim(timTemplate, sharedVariables);
ready
Tim (lite, cached)
timLiteCached(timLiteTemplate, sharedVariables);
ready
jsTenjin(not converted)
Shotenjin.render(tenjinTemplate, sharedVariables);
ready
jsTenjin(converted)
convertedTenjinTemplate.render(sharedVariables);
ready
nTenjin(converted)
convertednTenjinTemplate.render(sharedVariables);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.