Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 2 commits
  • 9 files changed
  • 0 commit comments
  • 1 contributor
View
2  README.md
@@ -135,6 +135,7 @@ Nap currently only supports the following pre-processors by default. But please
* [Coffeescript](http://jashkenas.github.com/coffee-script/) (.coffee)
* [Stylus](https://github.com/LearnBoost/stylus) (.styl)
+ * [Less](https://github.com/cloudhead/less.git) (.less)
### Adding your own preprocessors
@@ -157,6 +158,7 @@ e.g. The template *app/templates/artwork/detail.jade* will be parsed using jade
Nap currently only supports the following template parsers by default. But please feel free to contribute more.
* [Jade](https://github.com/visionmedia/jade) (.jade)
+ * [Mustache (using Hogan.js)](https://github.com/twitter/hogan.js.git) (.mustache)
### Adding your own template parsers
View
575 deps/hogan.js
@@ -0,0 +1,575 @@
+/*
+ * Copyright 2011 Twitter, Inc.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+var Hogan = {};
+
+(function (Hogan, useArrayBuffer) {
+ Hogan.Template = function (renderFunc, text, compiler, options) {
+ this.r = renderFunc || this.r;
+ this.c = compiler;
+ this.options = options;
+ this.text = text || '';
+ this.buf = (useArrayBuffer) ? [] : '';
+ }
+
+ Hogan.Template.prototype = {
+ // render: replaced by generated code.
+ r: function (context, partials, indent) { return ''; },
+
+ // variable escaping
+ v: hoganEscape,
+
+ // triple stache
+ t: coerceToString,
+
+ render: function render(context, partials, indent) {
+ return this.ri([context], partials || {}, indent);
+ },
+
+ // render internal -- a hook for overrides that catches partials too
+ ri: function (context, partials, indent) {
+ return this.r(context, partials, indent);
+ },
+
+ // tries to find a partial in the curent scope and render it
+ rp: function(name, context, partials, indent) {
+ var partial = partials[name];
+
+ if (!partial) {
+ return '';
+ }
+
+ if (this.c && typeof partial == 'string') {
+ partial = this.c.compile(partial, this.options);
+ }
+
+ return partial.ri(context, partials, indent);
+ },
+
+ // render a section
+ rs: function(context, partials, section) {
+ var tail = context[context.length - 1];
+
+ if (!isArray(tail)) {
+ section(context, partials, this);
+ return;
+ }
+
+ for (var i = 0; i < tail.length; i++) {
+ context.push(tail[i]);
+ section(context, partials, this);
+ context.pop();
+ }
+ },
+
+ // maybe start a section
+ s: function(val, ctx, partials, inverted, start, end, tags) {
+ var pass;
+
+ if (isArray(val) && val.length === 0) {
+ return false;
+ }
+
+ if (typeof val == 'function') {
+ val = this.ls(val, ctx, partials, inverted, start, end, tags);
+ }
+
+ pass = (val === '') || !!val;
+
+ if (!inverted && pass && ctx) {
+ ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
+ }
+
+ return pass;
+ },
+
+ // find values with dotted names
+ d: function(key, ctx, partials, returnFound) {
+ var names = key.split('.'),
+ val = this.f(names[0], ctx, partials, returnFound),
+ cx = null;
+
+ if (key === '.' && isArray(ctx[ctx.length - 2])) {
+ return ctx[ctx.length - 1];
+ }
+
+ for (var i = 1; i < names.length; i++) {
+ if (val && typeof val == 'object' && names[i] in val) {
+ cx = val;
+ val = val[names[i]];
+ } else {
+ val = '';
+ }
+ }
+
+ if (returnFound && !val) {
+ return false;
+ }
+
+ if (!returnFound && typeof val == 'function') {
+ ctx.push(cx);
+ val = this.lv(val, ctx, partials);
+ ctx.pop();
+ }
+
+ return val;
+ },
+
+ // find values with normal names
+ f: function(key, ctx, partials, returnFound) {
+ var val = false,
+ v = null,
+ found = false;
+
+ for (var i = ctx.length - 1; i >= 0; i--) {
+ v = ctx[i];
+ if (v && typeof v == 'object' && key in v) {
+ val = v[key];
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return (returnFound) ? false : "";
+ }
+
+ if (!returnFound && typeof val == 'function') {
+ val = this.lv(val, ctx, partials);
+ }
+
+ return val;
+ },
+
+ // higher order templates
+ ho: function(val, cx, partials, text, tags) {
+ var compiler = this.c;
+ var options = this.options;
+ options.delimiters = tags;
+ var text = val.call(cx, text);
+ text = (text == null) ? String(text) : text.toString();
+ this.b(compiler.compile(text, options).render(cx, partials));
+ return false;
+ },
+
+ // template result buffering
+ b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
+ function(s) { this.buf += s; },
+ fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
+ function() { var r = this.buf; this.buf = ''; return r; },
+
+ // lambda replace section
+ ls: function(val, ctx, partials, inverted, start, end, tags) {
+ var cx = ctx[ctx.length - 1],
+ t = null;
+
+ if (!inverted && this.c && val.length > 0) {
+ return this.ho(val, cx, partials, this.text.substring(start, end), tags);
+ }
+
+ t = val.call(cx);
+
+ if (typeof t == 'function') {
+ if (inverted) {
+ return true;
+ } else if (this.c) {
+ return this.ho(t, cx, partials, this.text.substring(start, end), tags);
+ }
+ }
+
+ return t;
+ },
+
+ // lambda replace variable
+ lv: function(val, ctx, partials) {
+ var cx = ctx[ctx.length - 1];
+ var result = val.call(cx);
+
+ if (typeof result == 'function') {
+ result = coerceToString(result.call(cx));
+ if (this.c && ~result.indexOf("{\u007B")) {
+ return this.c.compile(result, this.options).render(cx, partials);
+ }
+ }
+
+ return coerceToString(result);
+ }
+
+ };
+
+ var rAmp = /&/g,
+ rLt = /</g,
+ rGt = />/g,
+ rApos =/\'/g,
+ rQuot = /\"/g,
+ hChars =/[&<>\"\']/;
+
+
+ function coerceToString(val) {
+ return String((val === null || val === undefined) ? '' : val);
+ }
+
+ function hoganEscape(str) {
+ str = coerceToString(str);
+ return hChars.test(str) ?
+ str
+ .replace(rAmp,'&amp;')
+ .replace(rLt,'&lt;')
+ .replace(rGt,'&gt;')
+ .replace(rApos,'&#39;')
+ .replace(rQuot, '&quot;') :
+ str;
+ }
+
+ var isArray = Array.isArray || function(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+ };
+
+})(typeof exports !== 'undefined' ? exports : Hogan);
+
+
+
+
+(function (Hogan) {
+ // Setup regex assignments
+ // remove whitespace according to Mustache spec
+ var rIsWhitespace = /\S/,
+ rQuot = /\"/g,
+ rNewline = /\n/g,
+ rCr = /\r/g,
+ rSlash = /\\/g,
+ tagTypes = {
+ '#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
+ '<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
+ };
+
+ Hogan.scan = function scan(text, delimiters) {
+ var len = text.length,
+ IN_TEXT = 0,
+ IN_TAG_TYPE = 1,
+ IN_TAG = 2,
+ state = IN_TEXT,
+ tagType = null,
+ tag = null,
+ buf = '',
+ tokens = [],
+ seenTag = false,
+ i = 0,
+ lineStart = 0,
+ otag = '{{',
+ ctag = '}}';
+
+ function addBuf() {
+ if (buf.length > 0) {
+ tokens.push(new String(buf));
+ buf = '';
+ }
+ }
+
+ function lineIsWhitespace() {
+ var isAllWhitespace = true;
+ for (var j = lineStart; j < tokens.length; j++) {
+ isAllWhitespace =
+ (tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
+ (!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
+ if (!isAllWhitespace) {
+ return false;
+ }
+ }
+
+ return isAllWhitespace;
+ }
+
+ function filterLine(haveSeenTag, noNewLine) {
+ addBuf();
+
+ if (haveSeenTag && lineIsWhitespace()) {
+ for (var j = lineStart, next; j < tokens.length; j++) {
+ if (!tokens[j].tag) {
+ if ((next = tokens[j+1]) && next.tag == '>') {
+ // set indent to token value
+ next.indent = tokens[j].toString()
+ }
+ tokens.splice(j, 1);
+ }
+ }
+ } else if (!noNewLine) {
+ tokens.push({tag:'\n'});
+ }
+
+ seenTag = false;
+ lineStart = tokens.length;
+ }
+
+ function changeDelimiters(text, index) {
+ var close = '=' + ctag,
+ closeIndex = text.indexOf(close, index),
+ delimiters = trim(
+ text.substring(text.indexOf('=', index) + 1, closeIndex)
+ ).split(' ');
+
+ otag = delimiters[0];
+ ctag = delimiters[1];
+
+ return closeIndex + close.length - 1;
+ }
+
+ if (delimiters) {
+ delimiters = delimiters.split(' ');
+ otag = delimiters[0];
+ ctag = delimiters[1];
+ }
+
+ for (i = 0; i < len; i++) {
+ if (state == IN_TEXT) {
+ if (tagChange(otag, text, i)) {
+ --i;
+ addBuf();
+ state = IN_TAG_TYPE;
+ } else {
+ if (text.charAt(i) == '\n') {
+ filterLine(seenTag);
+ } else {
+ buf += text.charAt(i);
+ }
+ }
+ } else if (state == IN_TAG_TYPE) {
+ i += otag.length - 1;
+ tag = tagTypes[text.charAt(i + 1)];
+ tagType = tag ? text.charAt(i + 1) : '_v';
+ if (tagType == '=') {
+ i = changeDelimiters(text, i);
+ state = IN_TEXT;
+ } else {
+ if (tag) {
+ i++;
+ }
+ state = IN_TAG;
+ }
+ seenTag = i;
+ } else {
+ if (tagChange(ctag, text, i)) {
+ tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
+ i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
+ buf = '';
+ i += ctag.length - 1;
+ state = IN_TEXT;
+ if (tagType == '{') {
+ if (ctag == '}}') {
+ i++;
+ } else {
+ cleanTripleStache(tokens[tokens.length - 1]);
+ }
+ }
+ } else {
+ buf += text.charAt(i);
+ }
+ }
+ }
+
+ filterLine(seenTag, true);
+
+ return tokens;
+ }
+
+ function cleanTripleStache(token) {
+ if (token.n.substr(token.n.length - 1) === '}') {
+ token.n = token.n.substring(0, token.n.length - 1);
+ }
+ }
+
+ function trim(s) {
+ if (s.trim) {
+ return s.trim();
+ }
+
+ return s.replace(/^\s*|\s*$/g, '');
+ }
+
+ function tagChange(tag, text, index) {
+ if (text.charAt(index) != tag.charAt(0)) {
+ return false;
+ }
+
+ for (var i = 1, l = tag.length; i < l; i++) {
+ if (text.charAt(index + i) != tag.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function buildTree(tokens, kind, stack, customTags) {
+ var instructions = [],
+ opener = null,
+ token = null;
+
+ while (tokens.length > 0) {
+ token = tokens.shift();
+ if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
+ stack.push(token);
+ token.nodes = buildTree(tokens, token.tag, stack, customTags);
+ instructions.push(token);
+ } else if (token.tag == '/') {
+ if (stack.length === 0) {
+ throw new Error('Closing tag without opener: /' + token.n);
+ }
+ opener = stack.pop();
+ if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
+ throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
+ }
+ opener.end = token.i;
+ return instructions;
+ } else {
+ instructions.push(token);
+ }
+ }
+
+ if (stack.length > 0) {
+ throw new Error('missing closing tag: ' + stack.pop().n);
+ }
+
+ return instructions;
+ }
+
+ function isOpener(token, tags) {
+ for (var i = 0, l = tags.length; i < l; i++) {
+ if (tags[i].o == token.n) {
+ token.tag = '#';
+ return true;
+ }
+ }
+ }
+
+ function isCloser(close, open, tags) {
+ for (var i = 0, l = tags.length; i < l; i++) {
+ if (tags[i].c == close && tags[i].o == open) {
+ return true;
+ }
+ }
+ }
+
+ Hogan.generate = function (tree, text, options) {
+ var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
+ if (options.asString) {
+ return 'function(c,p,i){' + code + ';}';
+ }
+
+ return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
+ }
+
+ function esc(s) {
+ return s.replace(rSlash, '\\\\')
+ .replace(rQuot, '\\\"')
+ .replace(rNewline, '\\n')
+ .replace(rCr, '\\r');
+ }
+
+ function chooseMethod(s) {
+ return (~s.indexOf('.')) ? 'd' : 'f';
+ }
+
+ function walk(tree) {
+ var code = '';
+ for (var i = 0, l = tree.length; i < l; i++) {
+ var tag = tree[i].tag;
+ if (tag == '#') {
+ code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
+ tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
+ } else if (tag == '^') {
+ code += invertedSection(tree[i].nodes, tree[i].n,
+ chooseMethod(tree[i].n));
+ } else if (tag == '<' || tag == '>') {
+ code += partial(tree[i]);
+ } else if (tag == '{' || tag == '&') {
+ code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
+ } else if (tag == '\n') {
+ code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
+ } else if (tag == '_v') {
+ code += variable(tree[i].n, chooseMethod(tree[i].n));
+ } else if (tag === undefined) {
+ code += text('"' + esc(tree[i]) + '"');
+ }
+ }
+ return code;
+ }
+
+ function section(nodes, id, method, start, end, tags) {
+ return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
+ 'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
+ '_.rs(c,p,' +
+ 'function(c,p,_){' +
+ walk(nodes) +
+ '});c.pop();}';
+ }
+
+ function invertedSection(nodes, id, method) {
+ return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
+ walk(nodes) +
+ '};';
+ }
+
+ function partial(tok) {
+ return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
+ }
+
+ function tripleStache(id, method) {
+ return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
+ }
+
+ function variable(id, method) {
+ return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
+ }
+
+ function text(id) {
+ return '_.b(' + id + ');';
+ }
+
+ Hogan.parse = function(tokens, text, options) {
+ options = options || {};
+ return buildTree(tokens, '', [], options.sectionTags || []);
+ },
+
+ Hogan.cache = {};
+
+ Hogan.compile = function(text, options) {
+ // options
+ //
+ // asString: false (default)
+ //
+ // sectionTags: [{o: '_foo', c: 'foo'}]
+ // An array of object with o and c fields that indicate names for custom
+ // section tags. The example above allows parsing of {{_foo}}{{/foo}}.
+ //
+ // delimiters: A string that overrides the default delimiters.
+ // Example: "<% %>"
+ //
+ options = options || {};
+
+ var key = text + '||' + !!options.asString;
+
+ var t = this.cache[key];
+
+ if (t) {
+ return t;
+ }
+
+ t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
+ return this.cache[key] = t;
+ };
+})(typeof exports !== 'undefined' ? exports : Hogan);
View
2  examples/dev/app.coffee
@@ -4,7 +4,7 @@ global.nap = require('../../lib/nap')
nap
assets:
js:
- default: ['/app/scripts/jquery.js', '/app/scripts/foo.js', '/app/scripts/hogan.js']
+ default: ['/app/scripts/jquery.js', '/app/scripts/foo.js']
css:
default: ['/app/stylesheets/bar.styl']
jst:
View
4 examples/dev/package.json
@@ -3,7 +3,7 @@
, "version": "0.0.1"
, "private": true
, "dependencies": {
- "express": "2.4.4"
- , "jade": ">= 0.0.1"
+ "express": "*"
+ , "jade": "*"
}
}
View
1  examples/dev/public/assets/default.jst.js
@@ -1 +0,0 @@
-JST['hello'] = function(c,p,i){var _=this;_.b(i=i||"");_.b("h1 Hello ");_.b(_.v(_.f("world",c,p,0)));return _.fl();;};
View
1  examples/dev/public/assets/nap-templates-prefix.js
@@ -1 +0,0 @@
-window.JST = {};
View
5 lib/nap.coffee
@@ -8,6 +8,7 @@ nib = require 'nib'
jade = require 'jade'
jadeRuntime = fs.readFileSync(path.resolve __dirname, '../deps/jade.runtime.js').toString()
hogan = require 'hogan'
+hoganPrefix = fs.readFileSync(path.resolve __dirname, '../deps/hogan.js').toString()
sqwish = require 'sqwish'
uglifyjs = require "uglify-js"
_ = require 'underscore'
@@ -72,7 +73,9 @@ module.exports = (options = {}) =>
switch ext
when '.jade' then @_tmplPrefix = jadeRuntime + '\n' + @_tmplPrefix
-
+
+ when '.mustache' then @_tmplPrefix = hoganPrefix + '\n' + @_tmplPrefix
+
@
# Run js pre-processors & output the packages in dev.
View
2  package.json
@@ -1,6 +1,6 @@
{
"name": "nap",
- "version": "0.3.4",
+ "version": "0.3.5",
"author": "Craig Spaeth <craigspaeth@gmail.com> (http://craigspaeth.com)",
"description": "Compile and package your assets including stylesheets, javascripts, and client-side javascript templates for node.js",
"keywords": [
View
19 test/nap_spec.coffee
@@ -431,6 +431,15 @@ describe '#jst', ->
fs.readFileSync(process.cwd() + '/public/assets/nap-templates-prefix.js').toString()
.should.include "var jade"
+ it 'adds the hogan prefix', ->
+ nap
+ assets:
+ jst:
+ foo: ['/test/fixtures/1/foo.mustache']
+ nap.jst('foo')
+ fs.readFileSync(process.cwd() + '/public/assets/nap-templates-prefix.js').toString()
+ .should.include "var Hogan = {};"
+
it 'compiles jade templates into JST functions', ->
nap
assets:
@@ -593,6 +602,16 @@ describe '#package', ->
fs.readFileSync(process.cwd() + '/public/assets/templates.jst.js').toString()
.should.include "var jade="
+ it 'adds the hogan prefix', ->
+ nap
+ mode: 'production'
+ assets:
+ jst:
+ templates: ['/test/fixtures/1/foo.mustache']
+ nap.package()
+ fs.readFileSync(process.cwd() + '/public/assets/templates.jst.js').toString()
+ .should.include "var Hogan={};"
+
it 'is able to generate a package and reference it with a fingerprint when specified', ->
nap
mode: 'production'

No commit comments for this range

Something went wrong with that request. Please try again.