New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

hoganyam

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hoganyam - npm Package Compare versions

Comparing version 0.0.3 to 0.1.0

test/data/p1.template

436

index.js
//
// (pre)compile/render mustache templates with hogan
// Can be used as middleware or broadway plugin
// (pre)compile/render mustache templates with hogan.js
// can be used as middleware or broadway plugin
//
// common options {
// debug:Boolean - check files for updates
// cache:Object - use your own cache object
// hoganOptions:Object - options for hoganjs
// }
//

@@ -21,45 +15,87 @@ var hogan = require('hogan.js'),

//
// cache for compiled templates
// exports
//
var objCache = {};
// for compiled template src strings
var srcCache = {};
//
// exports
// broadway plugin
// attach options:
// @dir templates dir
// @ext templates ext
// @recompile recompile template on every call to render
//
// broadway plugin
exports.plugin = {
name: 'hoganyam',
attach: attach
attach: function(options) {
options.cache = options.cache || {};
this.render = function(res, name, context) {
context = context || {};
var file = path.join(options.dir, name + options.ext);
render(file, context, options, function(err, str) {
if (err) {
winston.error(err);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(err.toString());
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(str);
}
});
};
}
};
// connect-style middleware function
// provide templates from a directory individually as connect-style middleware
exports.provide = provide;
// bundle templates form a directory into one js-file as connect-style middleware
exports.bundle = bundle;
// render a template
exports.render = render;
//
// return a connect-style middleware function that writes the source
// of the precompiled template to the response object
// provide compiled templates individually through url
// connect-style middleware function
//
// @srcDir absolute path to templates
// @srcUrl base url for request
// @options {
// namespace:String - namespace for precompiled templates, default is 'templates'
// prefixpath:String - virtual base path to request templates from
// ext:String - template extension, default is '.html'
// @namespace namespace for precompiled templates, default is this
// @ext template extension, default is '.html'
// @recompile do not use cache and recompile on every request
// @hoganOptions options for the hogan.js template engine
// }
//
function provide(srcDir, options) {
options = options || {};
options.cache = options.cache || srcCache;
options.namespace = options.namespace || 'templates';
options.ext = options.ext || '.html';
options.mount = options.mount || '';
options.namespace = options.namespace || 'this';
options.hoganOptions = options.hoganOptions || {};
options.hoganOptions.asString = true;
options.processTemplate = createTemplateSource;
options.ext = options.ext || '.html';
var dstExt = /\.js$/,
srcExt = options.ext;
srcExt = options.ext,
cache = options.cache || {},
jsTemplate,
jst = '';
return function compileAndSend(req, res, next) {
jst += ';(function(root) {\n';
jst += ' var template = new Hogan.Template({{{template}}});\n';
jst += ' var partials = {\n';
jst += ' {{#partials}}';
jst += ' "{{name}}": new Hogan.Template({{{template}}}),\n';
jst += ' {{/partials}}';
jst += ' };\n';
jst += ' root.templates = root.templates || {};\n';
jst += ' root.templates["{{name}}"] = function(context) {\n';
jst += ' return template.render(context, partials);\n';
jst += ' };\n';
jst += '})({{namespace}});\n';
jsTemplate = hogan.compile(jst);
return function(req, res, next) {
if (req.method !== 'GET') return next();

@@ -69,24 +105,103 @@

var pathname = url.parse(req.url).pathname,
opts = utile.clone(options), // clone to avoid async race
parts, srcFile;
srcFile, src, ux;
if (!pathname.match(dstExt)) return next();
// remove the prefixpath if there is one
parts = pathname.split('/');
if (opts.prefixpath) {
if (parts[1] !== opts.prefixpath) return next();
pathname = '/' + parts.slice(2, parts.length).join('/');
if (options.mount) {
ux = new RegExp('^' + options.mount);
if (!pathname.match(ux)) return next();
// remove prefix url and leading slashes
pathname = pathname.replace(ux, '').replace(/^\/*/, '');
}
srcFile = path.join(srcDir, pathname).replace(dstExt, srcExt);
opts.cacheKey = srcFile;
winston.info('setting cachekey to: ' + srcFile);
getTemplate(srcFile, opts, function(err,t) {
if (!options.recompile && cache[srcFile]) {
winston.verbose('providing template from cache: ', srcFile);
sendResponse(res, cache[srcFile].source, cache[srcFile].mtime.toUTCString());
}
else {
getTemplate(srcFile, options, function(err,t) {
if (err) return next(err);
var name = createTemplateName(srcDir, srcFile, options.ext),
context = {
name: name,
template: t.template,
partials: [],
namespace: options.namespace
};
utile.each(t.partials, function(v, k) {
context.partials.push({
name: k,
template: v
});
});
src = jsTemplate.render(context);
if (!options.recompile) {
cache[srcFile] = { source: src, mtime: t.mtime };
}
sendResponse(res, src, t.mtime.toUTCString());
});
}
};
}
//
// bundle all compiled templates into one js file
//
// @srcDir directory with templates
// @options {
// @namespace namespace for precompiled templates, default is this
// @ext template extension, default is '.html'
// @recompile do not use cache and recompile on every request
// @hoganOptions options for the hogan.js template engine
// }
//
function bundle(srcDir, options) {
options = options || {};
options.mount = options.mount || '/templates.js';
options.ext = options.ext || '.html';
options.namespace = options.namespace || 'this';
options.hoganOptions = options.hoganOptions || {};
options.hoganOptions.asString = true;
var jsTemplate = createTemplate();
function createTemplate() {
var jst = '';
jst += '// autogenerated file\n';
jst += ';(function(root){\n';
jst += ' root = root || {};\n';
jst += ' var templates = {\n';
jst += ' {{#templates}}\n';
jst += ' "{{name}}": new Hogan.Template({{{template}}}),\n';
jst += ' {{/templates}}\n';
jst += ' };\n';
jst += ' var renderers = {\n';
jst += ' {{#templates}}\n';
jst += ' "{{name}}": function(context) {\n';
jst += ' return templates["{{name}}"].render(context, templates)\n';
jst += ' },\n';
jst += ' {{/templates}}\n';
jst += ' };\n';
jst += ' root.templates = renderers;\n';
jst += '})({{namespace}});\n';
return hogan.compile(jst);
}
return function(req, res, next) {
if (req.method !== 'GET') return next();
var reqUrl = url.parse(req.url).pathname,
src = '';
// only answer correct url
if (reqUrl !== options.mount) return next();
compileDir(srcDir, options, function(err, templates) {
winston.verbose("compiling template dir: ", srcDir);
if (err) return next(err);
res.setHeader('Date', new Date().toUTCString());
res.setHeader('Last-Modified', t.mtime.toUTCString());
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('Content-Length', t.template.length);
res.end(t.template);
resolvePartialNames(templates);
src = jsTemplate.render({ templates: templates, namespace: options.namespace});
sendResponse(res, src);
});

@@ -98,23 +213,34 @@ };

//
// plugin attach function flatiron.broadway-style
// @options see file header
// resolve partial name like '../header' to qualified template name
// @templates dict with templates
//
function attach(options) {
this.render = function(res, name, context) {
context = context || {};
var file = path.join(options.dir, name + options.ext);
render(file, context, options, function(err, str) {
if (err) {
winston.error(err);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(err.toString());
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(str);
}
function resolvePartialNames(templates) {
templates.forEach(function(template) {
var parts = template.name.split(path.sep),
basePath = parts.length === 1 ? '' : parts.slice(0, parts.length - 1).join(path.sep);
template.partials = template.partials.map(function(partialName) {
return path.join(basePath, partialName);
});
};
});
}
function createTemplateName(basePath, filePath, ext) {
var len = basePath.split(path.sep).length,
relPath = filePath.split(path.sep).slice(len).join(path.sep),
name = relPath.replace(new RegExp(ext + '$'), '');
return name;
}
function sendResponse(res, str, mtime) {
res.setHeader('Date', new Date().toUTCString());
res.setHeader('Last-Modified', mtime || (new Date).toUTCString());
res.setHeader('Content-Type', 'application/javascript');
res.setHeader('Content-Length', str.length);
res.end(str);
}
//

@@ -125,3 +251,7 @@ // render a template file with context mapping

// @context vars to map
// @options see file header
// @options {
// @cache cache object to use
// @recompile do not use cache and recompile on every request
// @hoganOptions options for the hogan.js template engine
// }
// @callback is called with (err, str)

@@ -132,9 +262,18 @@ //

options = options || {};
options.cache = options.cache || objCache;
options.cacheKey = file;
options.cache = options.cache || {};
options.hoganOptions = options.hoganOptions || {};
getTemplate(file, options, function(err, t) {
callback(err, t ? t.template.render(context, t.partials) : err.message);
});
var key = file,
ct = options.cache[key];
if (!options.recompile && ct) {
winston.verbose('render template from cache: ' + file);
callback(null, ct.template.render(context, ct.partials));
}
else {
getTemplate(file, options, function(err, t) {
if (!options.recompile) options.cache[key] = t;
callback(err, t ? t.template.render(context, t.partials) : err.message);
});
}
}

@@ -144,9 +283,6 @@

//
// get the template from file or cache
// get the template file and compile it
//
function getTemplate(file, options, callback) {
if (!options.debug && options.cache && options.cache[options.cacheKey]) {
winston.verbose('get template from cache: ' + options.cacheKey);
return callback(null, options.cache[options.cacheKey]);
}
winston.verbose('getting template: ' + file);
findfilep(file, function(err, foundfile) {

@@ -156,17 +292,7 @@ if (err) return callback(err);

if (err) return callback(err);
// use the cached version if it exists and is recent enough
var c = options.cache[options.cacheKey],
ext, dir;
if (c && stats.mtime.getTime() <= c.mtime.getTime()) {
winston.verbose('get template from cache: ' + options.cacheKey);
return callback(null, c);
}
winston.verbose('compile template: ' + file);
var ext, dir;
compile(foundfile, options, function(err, t) {
if (err) return callback(err);
if (options.processTemplate) options.processTemplate(t, options);
// if (options.processTemplate) options.processTemplate(t, options);
t.mtime = stats.mtime;
options.cache[options.cacheKey] = t;
callback(null, t);

@@ -180,8 +306,4 @@ });

//
// compile template - passes a template object to the callback
// compile template and partials
//
// {
// template:Function,
// partials:Dict of partial Functions
// }
function compile(file, options, callback) {

@@ -191,8 +313,11 @@ var ext = path.extname(file),

// compile the template file and all partials recusively
hoganCompile(file, options, function(err, tmpl, partialNames) {
// compile the template
hoganCompile(file, options.hoganOptions, function(err, template, partialNames) {
if (err) return callback(err);
tmpl.name = path.basename(file, ext);
var tmpl = {
template: template,
name: path.basename(file, ext),
partials: {}
};
// compile the partials
async.forEach(partialNames,

@@ -202,3 +327,2 @@ function(name, cb) {

poptions = utile.clone(options);
poptions.cacheKey = pfile;

@@ -208,3 +332,2 @@ getTemplate(pfile, poptions, function(err, t) {

tmpl.partials[name] = t.template;
// _.extend(tmpl.partials, t.partials);
tmpl.partials = utile.mixin(tmpl.partials, t.partials);

@@ -221,3 +344,42 @@ cb();

// compiles the template and extracts names of the partials
//
// compile template files in the directory and all subdirectories asynchronously
// @basePath base path
// @options
// @callback call when finished
//
function compileDir(basePath, options, callback) {
var templates = [],
compileIterator = function(filePath, callback) {
if (!filePath.match(new RegExp(options.ext + '$'))) {
return callback();
}
hoganCompile(filePath, options.hoganOptions, function(err, template, partialNames) {
if (err) return callback(err);
// var len = basePath.split(path.sep).length,
// relPath = filePath.split(path.sep).slice(len).join(path.sep),
// name = relPath.replace(/.html$/, '');
templates.push({
name: createTemplateName(basePath, filePath, options.ext),
template: template,
partials: partialNames
});
callback();
});
};
eachFileInDir(basePath,
compileIterator,
function(err) {
callback(err, templates);
});
}
//
// compiles the template and extracts partial names
// @file the template file
// @options hogan options
// @callback is called when finished
//
function hoganCompile(file, options, callback) {

@@ -229,18 +391,7 @@ fs.readFile(file, 'utf8', function(err, str) {

var tokens = hogan.scan(str),
// partialTokens = _.filter(tokens, function(t) {
// return t.tag === '>';
// }),
// partialNames = _.map(partialTokens, function(t) {
// return t.n;
// }),
partialNames = tokens
.filter(function(t) { return t.tag === '>'; })
.map(function(t) { return t.n; }),
hgopts = options.hoganOptions,
tmpl = {};
// compile the tokens
tmpl.template = hogan.generate(hogan.parse(tokens, str, hgopts), str, hgopts);
tmpl.partials = {};
callback(err, tmpl, partialNames);
template = hogan.generate(hogan.parse(tokens, str, options), str, options);
callback(err, template, partialNames);
});

@@ -251,43 +402,2 @@ }

//
// transform template property of template object to proper js source
// client can render template by calling
// [namespace].[name].render(context);
//
function createTemplateSource(t, options) {
var str = '',
p;
str += ';(function(root) {\n';
str += '\troot.' + t.name + ' = {\n';
str += '\t\ttemplate: new Hogan.Template(' + t.template + '),\n';
str += '\t\tpartials: {\n';
for (p in t.partials) {
str += '\t\t\t' + p + ': new Hogan.Template(' + t.partials[p] + '),\n';
}
str += '\t\t},\n';
str += '\t\trender: function(context){\n';
str += '\t\t\treturn this.template.render(context, this.partials);\n';
str += '\t\t}\n';
str += '\t};\n';
str += '})( this.' + options.namespace + ' || this);\n';
if (options.debug) str += 'console.log("template: ' + t.name + ' loaded");\n';
if (options && options.compress) {
var jsp = require("uglify-js").parser,
pro = require("uglify-js").uglify,
ast = jsp.parse(str); // parse code and get the initial AST
ast = pro.ast_mangle(ast); // get a new AST with mangled names
ast = pro.ast_squeeze(ast); // get an AST with compression optimizations
str = pro.gen_code(ast); // compressed code here
}
t.template = str;
return t;
}
//
// find a file by walking up the path

@@ -312,1 +422,29 @@ //

}
//
// call iterator on all files in the path and subpaths asynchronously
// @startPath base path
// @iterator iterator function(filePath, callback) {}
// @callback call when finished
//
function eachFileInDir(startPath, iterator, callback) {
(function rec(basePath, callback) {
fs.stat(basePath, function(err, stats) {
if (err) return callback(err);
if (stats.isFile()) {
iterator(basePath, callback);
}
else if (stats.isDirectory()) {
fs.readdir(basePath, function(err, nodes) {
if (err) return callback(err);
utile.async.forEach(nodes,
function(node, callback) {
rec(path.join(basePath, node), callback);
},
callback);
});
}
});
})(startPath, callback);
}

@@ -5,3 +5,3 @@ {

"description": "hogan-js template middleware and render function",
"version": "0.0.3",
"version": "0.1.0",
"homepage": "https://github.com/skoni/hoganyam",

@@ -8,0 +8,0 @@ "repository": {

# hoganyam
Yet another hogan.js(moustache templates) middleware. Can render templates with partials serverside or precompile them for use on the client. The templates are compiled, cached and updated when the file changes.
Yet another hogan.js(moustache templates) middleware. Can render templates with partials serverside or precompile them for use on the client.

@@ -11,6 +11,12 @@ ## Usage

Makes the templates available individually.
``` js
app.use(hoganyam.provide(templatesDir, options))
app.use(hoganyam.provide(templatesDir, options));
```
Bundle all templates in directory into one js file
``` js
app.use(hoganyam.bundle(templatesDir, options));
```
### Serverside rendering

@@ -26,3 +32,3 @@

``` js
app.use(hoganyam.plugin, {dir: viewsDir, ext: '.html'});
app.use(hoganyam.plugin, {dir: templatesDir, ext: '.html'});
// now you can render directly to the response

@@ -29,0 +35,0 @@ app.render(res, 'templatename', { title: 'Hello Hogan'});

@@ -8,9 +8,37 @@ var vows = require('vows'),

fs = require('fs'),
srcDir = path.join(__dirname, 'data'),
testFile = path.join(srcDir, 'test.template'),
dataDir = path.join(__dirname, 'data'),
testFile = path.join(dataDir, 'test.template'),
options = {},
data = { title: "a little test", name: "Hogan"},
resultStr = "This is " + data.title + " for Mr. " + data.name + '.';
correctResult = fs.readFileSync(path.join(dataDir, 'result.txt')).toString();
function puts(str) {
process.stderr.write(str + '\n');
}
function mockReq(url) {
return {
method: 'GET',
url: url
};
}
function mockRes(callback) {
return {
setHeader: function() {},
end: function(str) { callback(null, str); }
};
}
function evalTemplate(templateStr, callStr, data) {
var str = templateStr + 'result = ' + callStr + '(' + JSON.stringify(data) + ');\n',
sandbox = {
Hogan: hogan,
result: null
},
context = vm.createContext(sandbox);
vm.runInContext(str, context);
return context.result;
}
vows.describe('hoganyam').addBatch({

@@ -22,2 +50,3 @@ "The hoganyam module": {

assert.isFunction(hoganyam.provide);
assert.isFunction(hoganyam.bundle);
assert.isFunction(hoganyam.render);

@@ -30,33 +59,38 @@ assert.isObject(hoganyam.plugin);

},
"is showing the correct output": function(str) {
assert.equal(str, resultStr);
"should show the correct output": function(str) {
assert.equal(str, correctResult);
}
},
"used as middleware": {
"used as middleware for single template": {
topic: function() {
var self = this,
next = self.callback,
req = {
method: 'GET',
url: 'test.js'
},
res = {
setHeader: function() {},
end: function(str) { self.callback(null, str); }
};
hoganyam.provide(srcDir, {ext: '.template'})(req, res, next);
var f = hoganyam.provide(dataDir, {ext: '.template'}),
that = this;
f(mockReq('test.js'), mockRes(this.callback), function(err) {
that.callback(err || new Error('request failed'));
});
},
"provides the correct source js template function ": function(str) {
var templates = {},
sandbox = {
Hogan: hogan,
result: null
},
context = vm.createContext(sandbox);
str += 'result = test.render(' + JSON.stringify(data) + ');\n';
vm.runInContext(str, context);
assert.equal(context.result, resultStr);
"sould provide the compiled template and render the correct result": function(err, str) {
assert.isNull(err);
var result = evalTemplate(str, 'templates.test', data);
assert.equal(result, correctResult);
}
},
"used as middleware for bundled templates": {
topic: function() {
var that = this,
f = hoganyam.bundle(dataDir, {ext: '.template'});
f(mockReq('/templates.js'), mockRes(this.callback), function(err) {
that.callback(err || new Error('request failed'));
});
},
"should bundle the compiled templates and render the correct result": function(err, str) {
assert.isNull(err);
// puts(str);
var result = evalTemplate(str, 'templates.test', data);
assert.equal(result, correctResult);
}
}
}
}).export(module);

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc