toffee
Version:
A NodeJs and browser-side templating language based on CoffeeScript with slicker tokens and syntax.
417 lines (379 loc) • 15.3 kB
JavaScript
// Generated by CoffeeScript 1.12.7
(function() {
var LockTable, MAX_CACHED_SANDBOXES, Pool, engine, fs, path, ref, sandboxCons, states, tweakables, util, utils, view, vm,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
view = require('./view').view;
ref = require('./consts'), states = ref.states, tweakables = ref.tweakables;
Pool = require('./pool').Pool;
utils = require('./utils');
fs = require('fs');
path = require('path');
util = require('util');
vm = require('vm');
LockTable = require('iced-lock').Table;
MAX_CACHED_SANDBOXES = 100;
sandboxCons = function() {
return vm.createContext({});
};
engine = (function() {
function engine(options) {
this._fn_partial = bind(this._fn_partial, this);
this._fn_snippet = bind(this._fn_snippet, this);
this._fn_load = bind(this._fn_load, this);
this._inlineInclude = bind(this._inlineInclude, this);
this.run = bind(this.run, this);
this.render = bind(this.render, this);
options = options || {};
this.verbose = options.verbose || false;
this.pool = new Pool(sandboxCons, options.poolSize || MAX_CACHED_SANDBOXES);
this.prettyPrintErrors = options.prettyPrintErrors != null ? options.prettyPrintErrors : true;
this.prettyLogErrors = options.prettyLogErrors != null ? options.prettyLogErrors : true;
this.autoEscape = options.autoEscape != null ? options.autoEscape : true;
this.cache = options.cache != null ? options.cache : true;
this.additionalErrorHandler = options.additionalErrorHandler || null;
this.viewCache = {};
this.fsErrorCache = {};
this.filenameCache = {};
this.fileLockTable = new LockTable();
}
engine.prototype._log = function(o) {
var ref1;
if (this.verbose) {
if ((ref1 = typeof o) === "string" || ref1 === "number" || ref1 === "boolean") {
return console.log("toffee: " + o);
} else {
return console.log("toffee: " + (util.inspect(o)));
}
}
};
engine.prototype.normalizeFilename = function(dir, filename) {
var cache, normalized;
cache = this.filenameCache[dir];
if (cache == null) {
this.filenameCache[dir] = {};
cache = {};
}
normalized = cache[filename];
if (normalized == null) {
normalized = path.normalize(path.resolve(dir, filename));
this.filenameCache[dir][filename] = normalized;
}
return normalized;
};
engine.prototype.render = function(filename, options, cb) {
return this.run(filename, options, cb);
};
engine.prototype.run = function(filename, options, cb) {
/*
"options" contains the pub vars and may contain special items:
layout: path to a template expecting a body var (express 2.x style, but for use with express 3.x)
postProcess: a function which takes the string of output and post processes it (returning new string)
__toffee.dir: path to look relative to
__toffee.parent: parent file
__toffee.noInheritance: if true, don't pass variables through unless explicitly passed
__toffee.repress if true, don't output anything; useful with including definition files with passback of vars
__toffee.autoEscape: if set as false, don't escape output of #{} vars by default
*/
var err, k, layout_options, post_process, ref1, ref2, ref3, ref4, ref5, res, v;
if (options.prettyPrintErrors == null) {
options.prettyPrintErrors = this.prettyPrintErrors;
}
if (options.prettyLogErrors == null) {
options.prettyLogErrors = this.prettyLogErrors;
}
if (options.additionalErrorHandler == null) {
options.additionalErrorHandler = this.additionalErrorHandler;
}
if (options.autoEscape == null) {
options.autoEscape = this.autoEscape;
}
post_process = options.postProcess;
options.postProcess = null;
if (options != null ? options.layout : void 0) {
layout_options = {};
for (k in options) {
v = options[k];
if (k !== "layout") {
layout_options[k] = v;
}
}
}
ref1 = this.runSync(filename, options), err = ref1[0], res = ref1[1];
if (err && this.prettyPrintErrors) {
ref2 = [null, err], err = ref2[0], res = ref2[1];
}
if ((!err) && (layout_options != null)) {
layout_options.body = res;
ref3 = this.runSync(options.layout, layout_options), err = ref3[0], res = ref3[1];
if (err && this.prettyPrintErrors) {
ref4 = [null, err], err = ref4[0], res = ref4[1];
}
}
if ((!err) && (typeof post_process === "function")) {
ref5 = this.postProcess(post_process, res), err = ref5[0], res = ref5[1];
}
return cb(err, res);
};
engine.prototype.postProcess = function(fn, res) {
var e, err;
err = null;
try {
res = fn(res);
} catch (error) {
e = error;
err = e;
}
return [err, res];
};
engine.prototype.runSync = function(filename, options) {
/*
"options" the same as run() above
*/
var ctx, err, realpath, ref1, ref2, ref3, res, start_time, v;
start_time = Date.now();
options = options || {};
options.__toffee = options.__toffee || {};
options.__toffee.dir = options.__toffee.dir || process.cwd();
realpath = this.normalizeFilename(options.__toffee.dir, filename);
if (this.cache) {
v = (this._viewCacheGet(realpath)) || (this._loadCacheAndMonitor(realpath, options));
} else {
v = this._loadWithoutCache(realpath, options);
}
if (v) {
if (this.fsErrorCache[realpath]) {
ref1 = [new Error("Couldn't load " + realpath), null], err = ref1[0], res = ref1[1];
} else {
options.__toffee.parent = realpath;
options.partial = options.partial || (function(_this) {
return function(fname, lvars) {
return _this._fn_partial(fname, lvars, realpath, options);
};
})(this);
options.snippet = options.snippet || (function(_this) {
return function(fname, lvars) {
return _this._fn_snippet(fname, lvars, realpath, options);
};
})(this);
options.load = options.load || (function(_this) {
return function(fname, lvars) {
return _this._fn_load(fname, lvars, realpath, options);
};
})(this);
options.print = options.print || (function(_this) {
return function(txt) {
return _this._fn_print(txt, options);
};
})(this);
if (options.console == null) {
options.console = {
log: console.log
};
}
ctx = this.pool.get();
ref2 = v.run(options, ctx), err = ref2[0], res = ref2[1];
this.pool.release(ctx);
}
} else {
ref3 = [new Error("Couldn't load " + realpath), null], err = ref3[0], res = ref3[1];
}
this._log(realpath + " run in " + (Date.now() - start_time) + "ms");
return [err, res];
};
engine.prototype._viewCacheGet = function(filename) {
if (this.viewCache[filename] == null) {
return null;
} else if (this.fsErrorCache[filename] == null) {
return this.viewCache[filename];
} else if ((Date.now() - this.fsErrorCache[filename]) < tweakables.MISSING_FILE_RECHECK) {
return this.viewCache[filename];
} else {
return null;
}
};
engine.prototype._inlineInclude = function(filename, local_vars, parent_realpath, parent_options) {
var err, i, k, len, noInheritance, options, ref1, ref2, ref3, repress, res, reserved, v;
options = local_vars || {};
options.passback = {};
options.__toffee = options.__toffee || {};
options.__toffee.dir = path.dirname(parent_realpath);
options.__toffee.parent = parent_realpath;
noInheritance = options.__toffee.noInheritance;
repress = options.__toffee.repress;
reserved = {};
ref1 = ["passback", "load", "print", "partial", "snippet", "layout", "__toffee", "postProcess"];
for (i = 0, len = ref1.length; i < len; i++) {
k = ref1[i];
reserved[k] = true;
}
if (!noInheritance) {
for (k in parent_options) {
v = parent_options[k];
if ((local_vars != null ? local_vars[k] : void 0) == null) {
if (reserved[k] == null) {
options[k] = v;
}
}
}
}
ref2 = this.runSync(filename, options), err = ref2[0], res = ref2[1];
ref3 = options.passback;
for (k in ref3) {
v = ref3[k];
parent_options[k] = v;
}
return err || res;
};
engine.prototype._fn_load = function(fname, lvars, realpath, options) {
lvars = lvars != null ? lvars : {};
lvars.__toffee = lvars.__toffee || {};
lvars.__toffee.repress = true;
return this._inlineInclude(fname, lvars, realpath, options);
};
engine.prototype._fn_snippet = function(fname, lvars, realpath, options) {
lvars = lvars != null ? lvars : {};
lvars.__toffee = lvars.__toffee || {};
lvars.__toffee.noInheritance = true;
return this._inlineInclude(fname, lvars, realpath, options);
};
engine.prototype._fn_partial = function(fname, lvars, realpath, options) {
return this._inlineInclude(fname, lvars, realpath, options);
};
engine.prototype._fn_print = function(txt, options) {
if (options.__toffee.state === states.COFFEE) {
options.__toffee.out.push(txt);
return '';
} else {
return txt;
}
};
engine.prototype._loadWithoutCache = function(filename, options) {
var e, ref1, txt, v, view_options;
try {
txt = fs.readFileSync(filename, 'utf8');
} catch (error) {
e = error;
txt = "Error: Could not read " + filename;
if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) {
txt += " first requested in " + options.__toffee.parent;
}
}
view_options = this._generateViewOptions(filename);
v = new view(txt, view_options);
return v;
};
engine.prototype._loadCacheAndMonitor = function(filename, options) {
var e, previous_fs_err, ref1, txt, v, view_options;
previous_fs_err = this.fsErrorCache[filename] != null;
try {
txt = fs.readFileSync(filename, 'utf8');
if (this.fsErrorCache[filename] != null) {
delete this.fsErrorCache[filename];
}
} catch (error) {
e = error;
txt = "Error: Could not read " + filename;
if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) {
txt += " first requested in " + options.__toffee.parent;
}
this.fsErrorCache[filename] = Date.now();
}
if (this.fsErrorCache[filename] && previous_fs_err && this.viewCache[filename]) {
return this.viewCache[filename];
} else {
view_options = this._generateViewOptions(filename);
v = new view(txt, view_options);
this.viewCache[filename] = v;
this._monitorForChanges(filename, options);
return v;
}
};
engine.prototype._reloadFileInBkg = function(filename, options) {
this._log(filename + " acquiring lock to read");
return this.fileLockTable.acquire2({
name: filename
}, (function(_this) {
return function(lock) {
return fs.readFile(filename, 'utf8', function(err, txt) {
var ctx, ref1, v, view_options, waiting_for_view;
if (!err) {
_this._log((Date.now()) + " - " + filename + " changed to " + (txt != null ? txt.length : void 0) + " bytes. " + (txt != null ? typeof txt.replace === "function" ? txt.replace(/\n/g, '').slice(0, 80) : void 0 : void 0));
}
waiting_for_view = false;
if (err || (txt !== _this.viewCache[filename].txt)) {
if (err) {
_this.fsErrorCache[filename] = Date.now();
txt = "Error: Could not read " + filename;
if (((ref1 = options.__toffee) != null ? ref1.parent : void 0) != null) {
txt += " requested in " + options.__toffee.parent;
}
}
if (!(err && _this.viewCache[filename].fsError)) {
view_options = _this._generateViewOptions(filename);
ctx = _this.pool.get();
view_options.ctx = ctx;
view_options.cb = function(v) {
_this._log(filename + " updated and ready");
_this.viewCache[filename] = v;
_this.pool.release(ctx);
_this._log(filename + " lock releasing (view_options.cb)");
return lock.release();
};
waiting_for_view = true;
if (err) {
view_options.fsError = true;
}
v = new view(txt, view_options);
}
}
if (!waiting_for_view) {
_this._log(filename + " lock releasing (not waiting for view)");
return lock.release();
}
});
};
})(this));
};
engine.prototype._generateViewOptions = function(filename) {
return {
fileName: filename,
verbose: this.verbose,
prettyPrintErrors: this.prettyPrintErrors,
prettyLogErrors: this.prettyLogErrors,
autoEscape: this.autoEscape,
additionalErrorHandler: this.additionalErrorHandler
};
};
engine.prototype._monitorForChanges = function(filename, options) {
/*
we must continuously unwatch/rewatch because some editors/systems invoke a "rename"
event and we'll end up following the wrong, old 'file' as a new one
is dropped in its place.
Files that are missing are ignored here because they get picked up by new calls to _loadCacheAndMonitor
*/
var e, fsw;
if (this.fsErrorCache[filename] == null) {
fsw = null;
try {
this._log(filename + " starting fs.watch()");
return fsw = fs.watch(filename, {
persistent: true
}, (function(_this) {
return function(change) {
_this._log(filename + " closing fs.watch()");
fsw.close();
_this._monitorForChanges(filename, options);
return _this._reloadFileInBkg(filename, options);
};
})(this));
} catch (error) {
e = error;
this._log("fs.watch() failed for " + filename + "; settings fsErrorCache = true");
return this.fsErrorCache[filename] = Date.now();
}
}
};
return engine;
})();
exports.engine = engine;
}).call(this);