Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

version 0.21 with snippets

  • Loading branch information...
commit fb6e54dd48518a1a6df0f34248cf373e158171ec 1 parent 8e9693c
@malgorithms authored
View
2  index.coffee
@@ -1,6 +1,6 @@
# expose the render function
eclass = require('./lib/engine').engine
-e = new eclass { maxCacheAge: Infinity }
+e = new eclass { maxCacheAge: 2000 }
exports.expressEngine = e
exports.render = e.run
View
2  index.js
@@ -5,7 +5,7 @@
eclass = require('./lib/engine').engine;
e = new eclass({
- maxCacheAge: Infinity
+ maxCacheAge: 2000
});
exports.expressEngine = e;
View
48 lib/engine.js
@@ -16,6 +16,10 @@
engine = (function() {
function engine(options) {
+ this._fn_partial = __bind(this._fn_partial, this);
+
+ this._fn_snippet = __bind(this._fn_snippet, this);
+
this._inlineInclude = __bind(this._inlineInclude, this);
this.run = __bind(this.run, this);
@@ -31,15 +35,22 @@
"options" contains the pub vars
may also contain special items:
__dir: path to look relative to
+ layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x)
*/
- var err, res, _ref;
+ var err, res, _ref, _ref1, _ref2, _ref3;
_ref = this.runSync(filename, options), err = _ref[0], res = _ref[1];
if (err && this.prettyPrintErrors) {
- return cb(null, err);
- } else {
- return cb(err, res);
+ _ref1 = [null, err], err = _ref1[0], res = _ref1[1];
+ }
+ if ((!err) && (options != null ? options.layout : void 0)) {
+ options.body = res;
+ _ref2 = this.runSync(options.layout, options), err = _ref2[0], res = _ref2[1];
+ if (err && this.prettyPrintErrors) {
+ _ref3 = [null, err], err = _ref3[0], res = _ref3[1];
+ }
}
+ return cb(err, res);
};
engine.prototype.runSync = function(filename, options) {
@@ -52,7 +63,7 @@
_this = this;
options = options || {};
options.__dir = options.__dir || process.cwd();
- if (filename.charAt(0) !== "/") {
+ if (filename[0] !== "/") {
filename = "" + options.__dir + "/" + filename;
}
realpath = filename;
@@ -60,15 +71,18 @@
if (Date.now() - this.lastCacheReset > this.maxCacheAge) {
this._resetCache();
}
- v = this.viewCache[filename] || this._loadAndCache(filename, options);
+ v = this.viewCache[realpath] || this._loadAndCache(realpath, options);
if (v) {
- options.__parent = filename;
+ options.__parent = realpath;
options.include = options.include || function(fname, lvars) {
return _this._fn_include(fname, lvars, realpath, options);
};
options.partial = options.partial || function(fname, lvars) {
return _this._fn_partial(fname, lvars, realpath, options);
};
+ options.snippet = options.snippet || function(fname, lvars) {
+ return _this._fn_snippet(fname, lvars, realpath, options);
+ };
options.print = options.print || function(txt) {
return _this._fn_print(txt, options);
};
@@ -88,10 +102,16 @@
options = local_vars || {};
options.__dir = path.dirname(parent_realpath);
options.__parent = parent_realpath;
- for (k in parent_options) {
- v = parent_options[k];
- if ((k.slice(0, 2) !== "__") && !(local_keys[k] != null)) {
- options[k] = v;
+ if (!options.__no_inheritance) {
+ for (k in parent_options) {
+ v = parent_options[k];
+ if (!(local_keys[k] != null)) {
+ if (k.slice(0, 2) !== "__") {
+ if (!(k === "print" || k === "partial" || k === "include" || k === "snippet")) {
+ options[k] = v;
+ }
+ }
+ }
}
}
_ref = this.runSync(filename, options), err = _ref[0], res = _ref[1];
@@ -112,6 +132,12 @@
}
};
+ engine.prototype._fn_snippet = function(fname, lvars, realpath, options) {
+ lvars = lvars != null ? lvars : {};
+ lvars.__no_inheritance = true;
+ return this._inlineInclude(fname, lvars, realpath, options);
+ };
+
engine.prototype._fn_partial = function(fname, lvars, realpath, options) {
return this._inlineInclude(fname, lvars, realpath, options);
};
View
49 lib/view.js
@@ -168,8 +168,6 @@
ind = indent_level;
res += "\n" + (this._space(ind)) + "__toffee.lineno = " + obj[2];
res += "\n" + (this._space(ind)) + "__toffee.state = states.TOFFEE";
- res += "\n" + (this._space(ind)) + "__toffee.indent_baseline = " + indent_baseline;
- res += "\n" + (this._space(ind)) + "__toffee.indent_level = " + indent_level;
lines = obj[1].split("\n");
for (i = _l = 0, _len3 = lines.length; _l < _len3; i = ++_l) {
line = lines[i];
@@ -178,9 +176,9 @@
res += "\n" + (this._space(ind)) + "__toffee.lineno = " + (obj[2] + i);
}
lbreak = i !== lines.length - 1 ? "\n" : "";
- res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + '"""' + this._escapeForStr(line + lbreak) + '"""';
+ res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + this._quoteStr(line + lbreak);
} else {
- res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + '"""' + this._escapeForStr(lines.slice(i).join("\n")) + '"""';
+ res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + this._quoteStr(lines.slice(i).join("\n"));
break;
}
}
@@ -199,6 +197,35 @@
return [res, i_delta];
};
+ view.prototype._quoteStr = function(s) {
+ /*
+ returns a triple-quoted string, dividing into single quoted
+ start and stops, if the string begins with double quotes, since
+ coffee doesn't want to let us escape those.
+ */
+
+ var follow, lead, res;
+ lead = "";
+ follow = "";
+ while (s.length && (s[0] === '"')) {
+ s = s.slice(1);
+ lead += '"';
+ }
+ while (s.length && (s.slice(-1) === '"')) {
+ s = s.slice(0, -1);
+ follow += '"';
+ }
+ res = '';
+ if (lead.length) {
+ res += "\'" + lead + "\' + ";
+ }
+ res += '"""' + this._escapeForStr(s) + '"""';
+ if (follow.length) {
+ res += "+ \'" + follow + "\'";
+ }
+ return res;
+ };
+
view.prototype._escapeForStr = function(s) {
/*
escapes a string so it can make it into coffeescript
@@ -224,20 +251,20 @@
};
view.prototype._getIndentationBaseline = function(coffee) {
- var line, lines, res, _i, _len;
+ var i, line, lines, res, _i, _len;
res = null;
lines = coffee.split("\n");
- if (lines.length !== 0) {
- for (_i = 0, _len = lines.length; _i < _len; _i++) {
- line = lines[_i];
- if (!line.match(/^[ ]*$/)) {
+ if (lines.length) {
+ for (i = _i = 0, _len = lines.length; _i < _len; i = ++_i) {
+ line = lines[i];
+ if ((!line.match(/^[ ]*$/)) || i === (lines.length - 1)) {
res = line.match(/[ ]*/)[0].length;
break;
}
}
}
- if (!res) {
- res = coffee.length - 1;
+ if (!(res != null)) {
+ res = coffee.length;
}
return res;
};
View
2  package.coffee
@@ -3,7 +3,7 @@ fs = require 'fs'
obj =
name: "toffee"
description: """An Express 3.x and 2.x templating language based on CoffeeScript with slicker tokens and syntax. Built at OkCupid."""
- version: "0.0.18"
+ version: "0.0.21"
directories: {"lib" : "./lib"}
main: "index.js"
author: "Chris Coyne <ccoyne77@gmail.com>"
View
2  package.json
@@ -1,7 +1,7 @@
{
"name": "toffee",
"description": "An Express 3.x and 2.x templating language based on CoffeeScript with slicker tokens and syntax. Built at OkCupid.",
- "version": "0.0.18",
+ "version": "0.0.21",
"directories": {
"lib": "./lib"
},
View
41 src/engine.coffee
@@ -19,13 +19,25 @@ class engine
"options" contains the pub vars
may also contain special items:
__dir: path to look relative to
+ layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x)
###
+
[err, res] = @runSync filename, options
+
+ # if we got an error but want to pretty-print by faking ok result
if err and @prettyPrintErrors
- cb null, err
- else
- cb err, res
-
+ [err, res] = [null, err]
+
+ # if we're using a layout, pub into that
+ if (not err) and options?.layout
+ options.body = res
+ [err, res] = @runSync options.layout, options
+ if err and @prettyPrintErrors
+ [err, res] = [null, err]
+
+ cb err, res
+
+
runSync: (filename, options) ->
###
returns [err, res];
@@ -33,16 +45,17 @@ class engine
###
options = options or {}
options.__dir = options.__dir or process.cwd()
- filename = "#{options.__dir}/#{filename}" if filename.charAt(0) isnt "/"
+ filename = "#{options.__dir}/#{filename}" if filename[0] isnt "/"
realpath = filename
pwd = path.dirname realpath
@_resetCache() if Date.now() - @lastCacheReset > @maxCacheAge
- v = @viewCache[filename] or @_loadAndCache filename, options
+ v = @viewCache[realpath] or @_loadAndCache realpath, options
if v
- options.__parent = filename
+ options.__parent = realpath
options.include = options.include or (fname, lvars) => @_fn_include fname, lvars, realpath, options
options.partial = options.partial or (fname, lvars) => @_fn_partial fname, lvars, realpath, options
+ options.snippet = options.snippet or (fname, lvars) => @_fn_snippet fname, lvars, realpath, options
options.print = options.print or (txt) => @_fn_print txt, options
[err, res] = v.run options
return [err, res]
@@ -57,8 +70,11 @@ class engine
options.__parent = parent_realpath
# we need to make a shallow copy of parent variables
- for k,v of parent_options when (k[0...2] isnt "__") and not local_keys[k]?
- options[k] = v
+ if not options.__no_inheritance
+ for k,v of parent_options when not local_keys[k]?
+ if k[0...2] isnt "__"
+ if not (k in ["print", "partial", "include", "snippet"])
+ options[k] = v
[err, res] = @runSync filename, options
@@ -94,7 +110,12 @@ class engine
else
res
- _fn_partial: (fname, lvars, realpath, options) ->
+ _fn_snippet: (fname, lvars, realpath, options) =>
+ lvars = if lvars? then lvars else {}
+ lvars.__no_inheritance = true
+ @_inlineInclude fname, lvars, realpath, options
+
+ _fn_partial: (fname, lvars, realpath, options) =>
@_inlineInclude fname, lvars, realpath, options
_fn_print: (txt, options) ->
View
41 src/view.coffee
@@ -123,8 +123,8 @@ class view
ind = indent_level# - indent_baseline
res += "\n#{@_space ind}__toffee.lineno = #{obj[2]}"
res += "\n#{@_space ind}__toffee.state = states.TOFFEE"
- res += "\n#{@_space ind}__toffee.indent_baseline = #{indent_baseline}"
- res += "\n#{@_space ind}__toffee.indent_level = #{indent_level}"
+ # res += "\n#{@_space ind}__toffee.indent_baseline = #{indent_baseline}"
+ # res += "\n#{@_space ind}__toffee.indent_level = #{indent_level}"
lines = obj[1].split "\n"
for line, i in lines
@@ -132,9 +132,9 @@ class view
if i
res += "\n#{@_space ind}__toffee.lineno = #{obj[2]+i}"
lbreak = if i isnt lines.length - 1 then "\n" else ""
- res += "\n#{@_space ind}__toffee.out.push " + '"""' + @_escapeForStr(line + lbreak) + '"""'
+ res += "\n#{@_space ind}__toffee.out.push " + @_quoteStr(line + lbreak)
else
- res += "\n#{@_space ind}__toffee.out.push " + '"""' + @_escapeForStr(lines[(i)...].join "\n") + '"""'
+ res += "\n#{@_space ind}__toffee.out.push " + @_quoteStr(lines[(i)...].join "\n")
break
#res += "\n#{@_space indent_level}__toffee.out.push " + '"""' + @_escapeForStr(obj[1]) + '"""'
@@ -152,6 +152,26 @@ class view
return [res, i_delta]
+ _quoteStr: (s) ->
+ ###
+ returns a triple-quoted string, dividing into single quoted
+ start and stops, if the string begins with double quotes, since
+ coffee doesn't want to let us escape those.
+ ###
+ lead = ""
+ follow = ""
+ while s.length and (s[0] is '"')
+ s = s[1...]
+ lead += '"'
+ while s.length and (s[-1...] is '"')
+ s = s[...-1]
+ follow += '"'
+ res = ''
+ if lead.length then res += "\'#{lead}\' + "
+ res += '"""' + @_escapeForStr(s) + '"""'
+ if follow.length then res += "+ \'#{follow}\'"
+ res
+
_escapeForStr: (s) ->
###
escapes a string so it can make it into coffeescript
@@ -173,14 +193,15 @@ class view
# or null, if the region doesn't have any real code in it
res = null
lines = coffee.split "\n"
- if lines.length isnt 0
- for line in lines
- if not line.match /^[ ]*$/
+ if lines.length
+ for line, i in lines
+ # if it has characters or it's the last chance, use it
+ if (not line.match /^[ ]*$/) or i is (lines.length - 1)
res = line.match(/[ ]*/)[0].length
break
- if not res
- res = coffee.length - 1
- #console.log "====\n#{coffee}\n#{res}\n---"
+ if not res?
+ res = coffee.length
+ #console.log "====\n?#{coffee.replace /[ ]/g, '.'}?\n#{res}\n---"
return res
_getIndentationDelta: (coffee, baseline) ->
View
5 test/cases/include_order/child.toffee
@@ -0,0 +1,5 @@
+a
+{#
+ say_hi()
+#}
+b
View
9 test/cases/include_order/input.toffee
@@ -0,0 +1,9 @@
+{#
+ say_hi = ->
+ -{:hi:}
+#}
+1
+2
+#{partial "child.toffee", say_hi: say_hi}
+3
+4
View
8 test/cases/include_order/output.toffee
@@ -0,0 +1,8 @@
+
+1
+2
+hia
+
+b
+3
+4
View
3  test/cases/include_order/vars.json
@@ -0,0 +1,3 @@
+{
+ "greeting": "Hello"
+}
View
1  test/cases/indent_attack/input.toffee
@@ -56,5 +56,6 @@
#}
{#
+
-{:...passed with flying colors.:}
#}
View
16 test/cases/lambda_fns/input.toffee
@@ -0,0 +1,16 @@
+{#
+ print_it = (msg) -> {:#{msg}:}
+
+ print_it_twice = (msg) ->
+ -{:#{msg}:}
+ m = msg
+ -{:#{m}:}
+
+ echo_it = (msg) ->
+ v = msg
+ v
+
+ print_it "Pass"
+ print_it_twice "Pass"
+ print echo_it "Pass"
+#}
View
1  test/cases/lambda_fns/output.toffee
@@ -0,0 +1 @@
+PassPassPassPass
View
4 test/cases/snippets/foo/bar/body.toffee
@@ -0,0 +1,4 @@
+{#
+ msg = msg or "Unknown message"
+ print msg
+#}
View
8 test/cases/snippets/foo/message.toffee
@@ -0,0 +1,8 @@
+{#
+ from = from or "Unknown sender"
+ msg = msg or "Unknown message."
+ print """
+ From: #{from}
+ #{snippet './bar/body.toffee', msg: msg}
+ """
+#}
View
4 test/cases/snippets/input.toffee
@@ -0,0 +1,4 @@
+#{ partial "./foo/message.toffee"}
+#{ snippet "./foo/message.toffee"}
+#{ partial "./foo/message.toffee", from: "Sam"}
+#{ snippet "./foo/message.toffee", from: "Max"}
View
8 test/cases/snippets/output.toffee
@@ -0,0 +1,8 @@
+From: Preloaded sender
+Preloaded message.
+From: Unknown sender
+Unknown message.
+From: Sam
+Preloaded message.
+From: Max
+Unknown message.
View
4 test/cases/snippets/vars.json
@@ -0,0 +1,4 @@
+{
+ "from": "Preloaded sender",
+ "msg" : "Preloaded message."
+}
View
7 test/cases/special_cases/input.toffee
@@ -0,0 +1,7 @@
+{##
+
+ Make sure leading a trailing quotes don't mess with string printing.
+
+##}{#
+ -{:"PASSED":}
+#}
View
1  test/cases/special_cases/output.toffee
@@ -0,0 +1 @@
+"PASSED"
View
97 toffee.js
@@ -119,6 +119,10 @@
engine = (function() {
function engine(options) {
+ this._fn_partial = __bind(this._fn_partial, this);
+
+ this._fn_snippet = __bind(this._fn_snippet, this);
+
this._inlineInclude = __bind(this._inlineInclude, this);
this.run = __bind(this.run, this);
@@ -134,15 +138,22 @@
"options" contains the pub vars
may also contain special items:
__dir: path to look relative to
+ layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x)
*/
- var err, res, _ref;
+ var err, res, _ref, _ref1, _ref2, _ref3;
_ref = this.runSync(filename, options), err = _ref[0], res = _ref[1];
if (err && this.prettyPrintErrors) {
- return cb(null, err);
- } else {
- return cb(err, res);
+ _ref1 = [null, err], err = _ref1[0], res = _ref1[1];
}
+ if ((!err) && (options != null ? options.layout : void 0)) {
+ options.body = res;
+ _ref2 = this.runSync(options.layout, options), err = _ref2[0], res = _ref2[1];
+ if (err && this.prettyPrintErrors) {
+ _ref3 = [null, err], err = _ref3[0], res = _ref3[1];
+ }
+ }
+ return cb(err, res);
};
engine.prototype.runSync = function(filename, options) {
@@ -155,7 +166,7 @@
_this = this;
options = options || {};
options.__dir = options.__dir || process.cwd();
- if (filename.charAt(0) !== "/") {
+ if (filename[0] !== "/") {
filename = "" + options.__dir + "/" + filename;
}
realpath = filename;
@@ -163,15 +174,18 @@
if (Date.now() - this.lastCacheReset > this.maxCacheAge) {
this._resetCache();
}
- v = this.viewCache[filename] || this._loadAndCache(filename, options);
+ v = this.viewCache[realpath] || this._loadAndCache(realpath, options);
if (v) {
- options.__parent = filename;
+ options.__parent = realpath;
options.include = options.include || function(fname, lvars) {
return _this._fn_include(fname, lvars, realpath, options);
};
options.partial = options.partial || function(fname, lvars) {
return _this._fn_partial(fname, lvars, realpath, options);
};
+ options.snippet = options.snippet || function(fname, lvars) {
+ return _this._fn_snippet(fname, lvars, realpath, options);
+ };
options.print = options.print || function(txt) {
return _this._fn_print(txt, options);
};
@@ -191,10 +205,16 @@
options = local_vars || {};
options.__dir = path.dirname(parent_realpath);
options.__parent = parent_realpath;
- for (k in parent_options) {
- v = parent_options[k];
- if ((k.slice(0, 2) !== "__") && !(local_keys[k] != null)) {
- options[k] = v;
+ if (!options.__no_inheritance) {
+ for (k in parent_options) {
+ v = parent_options[k];
+ if (!(local_keys[k] != null)) {
+ if (k.slice(0, 2) !== "__") {
+ if (!(k === "print" || k === "partial" || k === "include" || k === "snippet")) {
+ options[k] = v;
+ }
+ }
+ }
}
}
_ref = this.runSync(filename, options), err = _ref[0], res = _ref[1];
@@ -215,6 +235,12 @@
}
};
+ engine.prototype._fn_snippet = function(fname, lvars, realpath, options) {
+ lvars = lvars != null ? lvars : {};
+ lvars.__no_inheritance = true;
+ return this._inlineInclude(fname, lvars, realpath, options);
+ };
+
engine.prototype._fn_partial = function(fname, lvars, realpath, options) {
return this._inlineInclude(fname, lvars, realpath, options);
};
@@ -1015,8 +1041,6 @@ if (typeof module !== 'undefined' && require.main === module) {
ind = indent_level;
res += "\n" + (this._space(ind)) + "__toffee.lineno = " + obj[2];
res += "\n" + (this._space(ind)) + "__toffee.state = states.TOFFEE";
- res += "\n" + (this._space(ind)) + "__toffee.indent_baseline = " + indent_baseline;
- res += "\n" + (this._space(ind)) + "__toffee.indent_level = " + indent_level;
lines = obj[1].split("\n");
for (i = _l = 0, _len3 = lines.length; _l < _len3; i = ++_l) {
line = lines[i];
@@ -1025,9 +1049,9 @@ if (typeof module !== 'undefined' && require.main === module) {
res += "\n" + (this._space(ind)) + "__toffee.lineno = " + (obj[2] + i);
}
lbreak = i !== lines.length - 1 ? "\n" : "";
- res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + '"""' + this._escapeForStr(line + lbreak) + '"""';
+ res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + this._quoteStr(line + lbreak);
} else {
- res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + '"""' + this._escapeForStr(lines.slice(i).join("\n")) + '"""';
+ res += ("\n" + (this._space(ind)) + "__toffee.out.push ") + this._quoteStr(lines.slice(i).join("\n"));
break;
}
}
@@ -1046,6 +1070,35 @@ if (typeof module !== 'undefined' && require.main === module) {
return [res, i_delta];
};
+ view.prototype._quoteStr = function(s) {
+ /*
+ returns a triple-quoted string, dividing into single quoted
+ start and stops, if the string begins with double quotes, since
+ coffee doesn't want to let us escape those.
+ */
+
+ var follow, lead, res;
+ lead = "";
+ follow = "";
+ while (s.length && (s[0] === '"')) {
+ s = s.slice(1);
+ lead += '"';
+ }
+ while (s.length && (s.slice(-1) === '"')) {
+ s = s.slice(0, -1);
+ follow += '"';
+ }
+ res = '';
+ if (lead.length) {
+ res += "\'" + lead + "\' + ";
+ }
+ res += '"""' + this._escapeForStr(s) + '"""';
+ if (follow.length) {
+ res += "+ \'" + follow + "\'";
+ }
+ return res;
+ };
+
view.prototype._escapeForStr = function(s) {
/*
escapes a string so it can make it into coffeescript
@@ -1071,20 +1124,20 @@ if (typeof module !== 'undefined' && require.main === module) {
};
view.prototype._getIndentationBaseline = function(coffee) {
- var line, lines, res, _i, _len;
+ var i, line, lines, res, _i, _len;
res = null;
lines = coffee.split("\n");
- if (lines.length !== 0) {
- for (_i = 0, _len = lines.length; _i < _len; _i++) {
- line = lines[_i];
- if (!line.match(/^[ ]*$/)) {
+ if (lines.length) {
+ for (i = _i = 0, _len = lines.length; _i < _len; i = ++_i) {
+ line = lines[i];
+ if ((!line.match(/^[ ]*$/)) || i === (lines.length - 1)) {
res = line.match(/[ ]*/)[0].length;
break;
}
}
}
- if (!res) {
- res = coffee.length - 1;
+ if (!(res != null)) {
+ res = coffee.length;
}
return res;
};
Please sign in to comment.
Something went wrong with that request. Please try again.