UNPKG

iopa-template-handlebars

Version:

Handlebars Template Engine for the IOPA Stack for Node.js

329 lines (265 loc) 9.62 kB
/* * Copyright (c) 2016 Internet of Protocols Alliance (IOPA) * Portions copyright (c) 2014, Yahoo Inc. All rights reserved. * * 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 Handlebars = require('./lib/handlebars-v4.0.5.js'); var fs = require('fs'); var path = require('path'); var utils = require('./utils'); module.exports = IopaHandlebars; function IopaHandlebars(config) { utils.assign(this, { handlebars : Handlebars, extname : '.hbs', views : 'views', defaultLayout : undefined, helpers : undefined, compilerOptions: undefined, }, config); utils.assign(this, { layouts : this.views + '/layouts/', partials : this.views + '/partials/', }, config); this.engine = this.renderView.bind(this); if (this.extname.charAt(0) !== '.') { this.extname = '.' + this.extname; } this.compiled = Object.create(null); this.precompiled = Object.create(null); this._fsCache = Object.create(null); } IopaHandlebars.prototype.getPartials = function (options) { var partials = Array.isArray(this.partials) ? this.partials : [this.partials]; partials = partials.map(function (dir) { var dirPath; var dirTemplates; var dirNamespace; if (typeof dir === 'string') { dirPath = path.resolve(dir); } else if (typeof dir === 'object') { dirTemplates = dir.templates; dirNamespace = dir.namespace; dirPath = dir.dir; } if (!(dirPath || dirTemplates)) { throw new Error('A partials dir must be a string or config object'); } var templatesPromise = dirTemplates ? Promise.resolve(dirTemplates) : this.getTemplates(dirPath, options); return templatesPromise.then(function (templates) { return { templates: templates, namespace: dirNamespace, }; }); }, this); return Promise.all(partials).then(function (dirs) { var getTemplateName = this._getTemplateName.bind(this); return dirs.reduce(function (partials, dir) { var templates = dir.templates; var namespace = dir.namespace; var filePaths = Object.keys(templates); filePaths.forEach(function (filePath) { var partialName = getTemplateName(filePath, namespace); partials[partialName] = templates[filePath]; }); return partials; }, {}); }.bind(this)); }; IopaHandlebars.prototype.getTemplate = function (filePath, options) { filePath = path.resolve(filePath); options || (options = {}); var precompiled = options.precompiled; var cache = precompiled ? this.precompiled : this.compiled; var template = options.cache && cache[filePath]; if (template) { return template; } template = cache[filePath] = this._getFile(filePath, {cache: options.cache}) .then(function (file) { if (precompiled) { return this._precompileTemplate(file, this.compilerOptions); } return this._compileTemplate(file, this.compilerOptions); }.bind(this)); return template.catch(function (err) { delete cache[filePath]; throw err; }); }; IopaHandlebars.prototype.getTemplates = function (dirPath, options) { options || (options = {}); var cache = options.cache; return this._getDir(dirPath, {cache: cache}).then(function (filePaths) { var templates = filePaths.map(function (filePath) { var x = this.getTemplate(filePath, options); return x; }, this); return Promise.all(templates).then(function (templates) { return filePaths.reduce(function (hash, filePath, i) { hash[path.relative(dirPath, filePath)] = templates[i]; return hash; }, {}); }); }.bind(this)); }; IopaHandlebars.prototype.render = function (filePath, context, options) { options || (options = {}); return Promise.all([ this.getTemplate(filePath, {cache: options.cache}), options.partials || this.getPartials({cache: options.cache}), ]).then(function (templates) { var template = templates[0]; var partials = templates[1]; var helpers = options.helpers || this.helpers; var data = utils.assign({}, options.data, { "iopa.Templates": utils.assign({}, options, { filePath: filePath, helpers : helpers, partials: partials, }), }); return this._renderTemplate(template, context, { data : data, helpers : helpers, partials: partials, }); }.bind(this)); }; IopaHandlebars.prototype.renderView = function (view, options, callback) { options || (options = {}); options.partials = options.partials || null; var viewname, viewPath; var viewRoot = ( options.settings && options.settings.views) || this.views; var basePath = path.resolve(viewRoot); if (path.resolve( view ) == path.normalize( view )) { // absolute path viewPath = view; view = path.relative(basePath, viewPath) } else { // relative path viewPath = path.join(basePath, view ); } viewname = this._getTemplateName(view); var context = options; var helpers = utils.assign({}, this.helpers, options.helpers); var partials = Promise.all([ this.getPartials({cache: options.cache}), Promise.resolve(options.partials), ]).then(function (partials) { return utils.assign.apply(null, [{}].concat(partials)); }); options = { cache : options.cache, view : viewname, layout: 'layout' in options ? options.layout : this.defaultLayout, data : options.data, helpers : helpers, partials: partials, }; this.render(viewPath, context, options) .then(function (body) { var layoutPath = this._resolveLayoutPath(options.layout); if (layoutPath) { return this.render( layoutPath, utils.assign({}, context, {body: body}), utils.assign({}, options, {layout: undefined}) ); } return body; }.bind(this)) .then(utils.passValue(callback)) .catch(utils.passError(callback)); }; IopaHandlebars.prototype._compileTemplate = function (template, options) { return this.handlebars.compile(template, options); }; IopaHandlebars.prototype._precompileTemplate = function (template, options) { return this.handlebars.precompile(template, options); }; IopaHandlebars.prototype._renderTemplate = function (template, context, options) { return template(context, options); }; IopaHandlebars.prototype._getDir = function (dirPath, options) { dirPath = path.resolve(dirPath); options || (options = {}); var cache = this._fsCache; var dir = options.cache && cache[dirPath]; if (dir) { return dir.then(function (dir) { return dir.concat(); }); } var self = this; dir = cache[dirPath] = new Promise(function (resolve, reject) { utils.walkSubFolders(dirPath, self.extname, function (err, dir) { if (err) { reject(err); } else { resolve(dir); } }); }); return dir.then(function (dir) { return dir.concat(); }).catch(function (err) { delete cache[dirPath]; return []; }); }; IopaHandlebars.prototype._getFile = function (filePath, options) { filePath = path.resolve(filePath); options || (options = {}); var cache = this._fsCache; var file = options.cache && cache[filePath]; if (file) { return file; } file = cache[filePath] = new Promise(function (resolve, reject) { fs.readFile(filePath, 'utf8', function (err, file) { if (err) { reject(err); } else { resolve(file); } }); }); return file.catch(function (err) { delete cache[filePath]; throw err; }); }; IopaHandlebars.prototype._getTemplateName = function (filePath, namespace) { var extRegex = new RegExp(this.extname + '$'); var name = filePath.replace(extRegex, ''); if (namespace) { name = namespace + '/' + name; } return name; }; IopaHandlebars.prototype._resolveLayoutPath = function (layoutPath) { if (!layoutPath) { return null; } if (!path.extname(layoutPath)) { layoutPath += this.extname; } return path.resolve(this.layouts, layoutPath); };