diff --git a/.gitignore b/.gitignore index 2d7dda8..a6aede8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ node_modules/ coverage/ npm-debug.log +*.swp +test/fixtures/tmp/ +.nyc_output/ +tags diff --git a/README.md b/README.md index db04a49..4776b19 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,93 @@ -engine-munger +engine-munger ============= -Lead Maintainer: [Aria Stewart](https://github.com/aredridel) +A replacement Express view class that provides asynchronous resolution, allows +engines to use the lookup method to locate partials, and extends the lookup +method to be configurable based on i18n locale and a template specialization +rule map. + +This is a backport of [work for Express 5](https://github.com/strongloop/express/pull/2653) + +Lead Maintainer: [Aria Stewart](https://github.com/aredridel) [![Build Status](https://travis-ci.org/krakenjs/engine-munger.svg?branch=master)](https://travis-ci.org/krakenjs/engine-munger) -A template engine munging library. -It looks for appropriate template consolidation library for specified view engine and includes i18n and specialization in the workflow. +What does i18n mean? +-------------------- + +i18n means "internationalization". Given a `locale` property in the render options, `engine-munger` will look for content in a locale-specific directory (or in a fallback locale if that is not a match) for templates and partials. This is particularly useful with template engines that pre-localize their compiled forms, such as with [`localizr`](https://github.com/krakenjs/localizr) and [`dustjs-linkedin`](http://dustjs.com/) together. -Note: If you use specialization features, you must use dustjs before 2.6.0. +What does specialization mean? +------------------------------ -###### What does i18n mean ? -Localization of included content tags for a specified locale. Currently supported only for dust templating engine and internally uses the module [localizr](https://github.com/krakenjs/localizr) for translating content tags included in the templates +Ability to switch a specific template with another based on a rule set specified in the app config. The actual rule parsing is done using the module [`karka`](https://github.com/krakenjs/karka). -###### What does specialization mean ? -Ability to switch a specific template with another based on a rule set specified in the app config. The actual rule parsing is done using the module [karka](https://github.com/krakenjs/karka) and can be extended and used in any templating engine and not dust. All engine-munger does is includes a specialization map with the list of key value pairs using the karka module. + ```javascript { - _specialization : { - ... - originalTemplate : - ... + specialization : { + 'jekyll': [ + { + is: 'hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] } } ``` -##### Currently supported template engines out of the box: - -* Dust: Engine types 'js' and 'dust' +The above will switch the template from `jekyll` to `hyde` if the render options contain `"whoAmI": "badGuy"`. Rules can be as complex as you need for your application and are particularly good for running A/B tests. +Using engine-munger in an application +===================================== -Simple Usage: +This example uses the [`adaro`](https://github.com/krakenjs/adaro) template engine, which wraps dust up as an express view engine, and uses engine-munger's more sophisticated lookup method to find partials, allowing them to be localized and specialized based on the render options. ```javascript -var engine-munger = require('engine-munger'), - app = require('express')(); +var munger = require('engine-munger'); +var adaro = require('adaro'); +var app = require('express')(); + +var specialization = { + 'jekyll': [ + { + is: 'hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] +}; +app.set("view", munger.makeViewClass({ + "dust": { + specialization: specialization + }, + "js": { + specialization: specialization, + i18n: { + fallback: 'en-US', + contentPath: 'locales' + } + } +}); -app.engine('dust', engine-munger['dust'](settings, config)); -app.engine('js', engine-munger['js'](settings, config)); -``` +var engineConfig = {}; // Configuration for your view engine -* settings : [JSON] Arguments you want passed to the templating engine, -* config: [JSON] used to specify whether you need i18n/specialization enabled. It also compulsarily requires the 'view' and 'view engine' settings passed into express app. - - If you are using kraken-js 1.0 along with engine-munger, the kraken generator will automatically set this all up for you. - But if you want to use this with a stand alone express app with dust as templating engine, you can specify as follows: - - Example params: - - ```javascript - var settings = {cache: false}, - config = { - 'views': 'public/templates', - 'view engine': 'dust', - 'i18n': { - 'fallback': 'en-US', - 'contentPath': 'locales' - }, - specialization: { - 'jekyll': [ - { - is: 'hyde', - when: { - 'whoAmI': 'badGuy' - } - } - ] - - } - }; - ``` +app.engine('dust', adaro.dust(engineConfig)); +app.engine('js', adaro.js(engineConfig)); +``` Running Tests: +```shell +npm test ``` -To run tests: -$ npm test -To run coverage -$ npm run-script cover +To run coverage: -To run lint -$ npm run-script lint +```shell +npm run cover ``` - - diff --git a/index.js b/index.js index 4f77492..b709e95 100644 --- a/index.js +++ b/index.js @@ -15,51 +15,205 @@ │ See the License for the specific language governing permissions and │ │ limitations under the License. │ \*───────────────────────────────────────────────────────────────────────────*/ +"use strict"; +var path = require('path'); +var debug = require('debuglog')('engine-munger'); +var fs = require('fs'); +var permutron = require('permutron'); +var oldView = require('express/lib/view'); +var karka = require('karka'); +var aproba = require('aproba'); +var bcp47 = require('bcp47'); +var bcp47stringify = require('bcp47-stringify'); +var VError = require('verror'); -'use strict'; -var engine = require('adaro'), - munger = require('./lib/munger'); +/** + * Make a View class that uses our configuration, set far in advance of + * instantiation because Express passes very little to the actual constructor. + */ +function makeViewClass(config) { + aproba('O', arguments); + var conf = normalizeConfigs(config); -exports.dust = function (setting, config) { - var settings = (arguments.length > 1) ? setting : {}, - configs = (arguments.length > 1) ? config : setting, - renderer; + var proto = Object.create(oldView.prototype); - if (!configs || !(configs.specialization || configs.i18n)) { - return engine.dust(settings); - } + // Unfortunately, since we don't know the actual file to resolve until + // we get request context (in `render`), we can't say whether it exists or not. + // + // Express checks that this is truthy to see if it should return an error or + // run the render, so we hard code it to true. + proto.path = true; + + proto.lookup = function lookup(name, options, cb) { + if (arguments.length === 1) { + // This is the unoverriden constructor calling us. Ignore the call. + return true; + } + + var ext = path.extname(name); + + if (conf[ext] && conf[ext].specialization) { + var nameNoExt = name.slice(0, -ext.length); + var newName = conf[ext].specialization.resolve(nameNoExt, options) + ext; + debug("specialization mapped '%s' to '%s'", name, newName); + name = newName; + } + + var search = []; + search.push([].concat(conf[ext] && conf[ext].root ? conf[ext].root : this.root)); + + if (conf[ext] && conf[ext].i18n) { + var i18n = conf[ext].i18n; + var locales = []; + if (options.locale) { + locales.push(i18n.formatPath(typeof options.locale === 'object' ? options.locale : bcp47.parse(options.locale.replace(/_/g, '-')))); + } + if (i18n.fallback) { + locales.push(i18n.formatPath(i18n.fallback)); + } + debug("trying locales %j", locales); + search.push(locales); + } + + search.push([name, path.join(path.basename(name), 'index' + ext)]); + + debug('lookup "%s"', name); + + var view = this; - if (configs['view engine'] === 'dust') { - munger.wrapDustOnLoad('dust', configs, settings.cache); + permutron.raw(search, function (candidate, next) { + var resolved = path.resolve.apply(null, candidate); + limitStat(resolved, function (err, stat) { + if (!err && stat.isFile()) { + debug('found "%s"', resolved); + cb(null, resolved); + } else if ((!err && stat.isDirectory()) || (err && err.code === 'ENOENT')) { + next(); + } else { + cb(err); + } + }); + }, function (err) { + if (err) { + cb(err); + } else { + var dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"'; + var viewError = new VError('Failed to lookup view "%s" in views %s', name, dirs); + viewError.view = view; + cb(viewError); + } + }); + }; + + /** + * Render with the given `options` and callback `fn(err, str)`. + * + * @param {Object} options + * @param {Function} fn + * @api private + */ + proto.render = function render(options, fn) { + aproba('OF', arguments); + var view = this; + view.lookupMain(options, function (err) { + if (err) { + fn(err); + } else { + debug('render "%s"', view.path); + view.engine(view.path, options, fn); + } + }); + }; + + /** Resolve the main template for this view + * + * @param {function} cb + * @private + */ + proto.lookupMain = function lookupMain(options, cb) { + if (this.path && this.path !== true) { + return cb(); + } + var view = this; + var name = path.extname(this.name) === this.ext ? this.name : this.name + this.ext; + this.lookup(name, options, function (err, path) { + if (err) { + return cb(err); + } else { + view.path = path; + cb(); + } + }); + }; + + function View(name, options) { + oldView.call(this, name, options); } - // Disabling cache - // since we add our own caching layer below. (Clone it first so we don't muck with the original object.) - settings.cache = false; + View.prototype = proto; + View.prototype.constructor = View; + return View; +} - // For i18n we silently switch to the JS engine for all requests, passing config - renderer = configs.i18n ? engine.js(settings): engine.dust(settings); - return munger.wrapEngine(configs, renderer); -}; +module.exports = makeViewClass; -exports.js = function (setting, config) { - var settings = (arguments.length > 1) ? setting : {}, - configs = (arguments.length > 1) ? config : setting, - renderer; +/** + * an fs.stat call that limits the number of outstanding requests to 10. + * + * @param {String} path + * @param {Function} cb + */ +var pendingStats = []; +var numPendingStats = 0; + +function limitStat(path, cb) { + debug('stat "%s"', path); + if (++numPendingStats > 10) { + pendingStats.push([path, cb]); + } else { + fs.stat(path, dequeue(cb)); + } - if (!configs || !(configs.specialization || configs.i18n)) { - return engine.js(settings); + function dequeue(cb) { + return function (err, stat) { + cb(err, stat); + var next = pendingStats.shift(); + if (next) { + fs.stat(next[0], dequeue(next[1])); + } else { + numPendingStats--; + } + }; } +} - if (configs['view engine'] === 'js') { - munger.wrapDustOnLoad('js', configs, settings.cache); +function normalizeConfigs(config) { + var out = {}; + for (var ext in config) { + if (ext[0] === '.') { + out[ext] = normalizeConfig(config[ext]); + } else { + out['.' + ext] = normalizeConfig(config[ext]); + } } - // Disabling cache - // since we add our own caching layer below. (Clone it first so we don't muck with the original object.) - settings.cache = false; - renderer = engine.js(settings); - return (configs.specialization) ? munger.wrapEngine(configs, renderer) : renderer; -}; + return out; +} + +function normalizeConfig(config) { + var out = {}; + if (config.i18n) { + out.i18n = { + fallback: config.i18n.fallback && bcp47.parse(config.i18n.fallback.replace(/_/g, '-')), + formatPath: config.i18n.formatPath || bcp47stringify, + contentPath: config.i18n.contentPath + }; + } + + if (config.specialization) { + out.specialization = karka.create(config.specialization); + } + return out; +} diff --git a/lib/cache.js b/lib/cache.js deleted file mode 100644 index 40f1153..0000000 --- a/lib/cache.js +++ /dev/null @@ -1,136 +0,0 @@ - /*───────────────────────────────────────────────────────────────────────────*\ -│ Copyright (C) 2014 eBay Software Foundation │ -│ │ -│hh ,'""`. │ -│ / _ _ \ 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. │ -\*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; - -var vm = require('vm'), - util = require('./util'), - assert = require('assert'); - - - -var cache = { - - get dataProvider() { - return this._dataProvider; - }, - - - set dataProvider(value) { - this._dataProvider = value; - }, - - - get fallback() { - return this._fallback; - }, - - - set fallback(value) { - this._fallback = value; - }, - - - get: function (name, context, callback) { - var that, subContext, locality, value; - that = this; - subContext = (typeof context.get === 'function' && context.get('context')) || context; - locality = util.parseLangTag(subContext.locality) || this._fallback; - value = this._get(locality.country || '*', locality.language || '*', name); - if (value !== undefined) { - callback(null, value); - return; - } - - this._dataProvider(name, context, function (err, data) { - if (err) { - callback(err); - return; - } - data = that._set(locality.country || '*', locality.language || '*', name, data); - callback(null, data); - }); - }, - - - reset: function () { - this._sandbox.dust.cache = Object.create(null); - }, - - - _get: function (country, lang, name) { - var hash = util.md5.apply(undefined, arguments); - return this._sandbox.dust.cache[hash]; - }, - - - _set: function (country, lang, name, data) { - - var args, value, hash, sandbox, cache; - //console.info('country:', country, ',lang:', lang,',name:',name, 'data:', data); - - args = Array.prototype.slice.call(arguments); - value = args.pop(); - hash = util.md5.apply(undefined, args); - - sandbox = this._sandbox; - cache = sandbox.dust.cache; - - if (typeof data === 'string') { - cache[hash] = data; - return cache[hash]; - } - // Execute the template in the sandbox and then move it. - vm.runInContext(value, sandbox, 'cache.vm'); - cache[hash] = cache[name]; - delete cache[name]; - return cache[hash]; - } -}; - - -exports.create = function (provider, fallback) { - assert(provider, 'No data provider implementation found.'); - - var sandbox = { - dust: { - cache: {}, - register: function (name, fn) { - this.cache[name] = fn; - } - } - }; - - return Object.create(cache, { - - _sandbox: { - writable: true, - value: vm.createContext(sandbox) - }, - - _dataProvider: { - writable: true, - value: provider - }, - - _fallback: { - writable: true, - value: util.parseLangTag(fallback) - } - - }); -}; diff --git a/lib/expressView.js b/lib/expressView.js deleted file mode 100644 index 06d7277..0000000 --- a/lib/expressView.js +++ /dev/null @@ -1,81 +0,0 @@ -/*───────────────────────────────────────────────────────────────────────────*\ - │ Copyright (C) 2014 eBay Software Foundation │ - │ │ - │hh ,'""`. │ - │ / _ _ \ 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. │ - \*───────────────────────────────────────────────────────────────────────────*/ -"use strict"; -var resolver = require('file-resolver'); -var util = require('./util'); - -var proto = { - - get path() { - // Unfortunately, since we don't know the actual file to resolve until - // we get request context (in `render`), we can't say whether it exists or not. - return true; - }, - - render: function (options, callback) { - var locals, view, engine; - - locals = options && options.context; - view = this.resolver.resolve(this.name, util.localityFromLocals(locals)); - - // This is a bit of a hack to ensure we override `views` for the duration - // of the rendering lifecycle. Unfortunately, `adaro` and `consolidate` - // (https://github.com/visionmedia/consolidate.js/blob/407266806f3a713240db2285527de934be7a8019/lib/consolidate.js#L214) - // check `options.views` but override with `options.settings.views` if available. - // So, for this rendering task we need to override with the more specific root directory. - options.settings = Object.create(options.settings); - options.views = options.settings.views = view.root; - - engine = this.engines['.' + this.defaultEngine]; - engine(view.file, options, callback); - } - -}; - - -function buildCtor(fallback) { - - function View(name, options) { - this.name = name; - this.root = options.root; - this.defaultEngine = options.defaultEngine; - this.engines = options.engines; - this.resolver = resolver.create({ root: options.root, ext: this.defaultEngine, fallback: fallback }); - } - - View.prototype = proto; - View.prototype.constructor = View; - return View; -} - -module.exports = function () { - var view; - return function (req, res, next) { - var config = req.app.kraken; - - //if the view engine is 'js and if it has not been overridden already - if (config.get('express:view engine') === 'js' && !view) { - view = buildCtor(config.get('i18n:fallback')); - req.app.set('view', view); - } - next(); - }; -}; - - - diff --git a/lib/munger.js b/lib/munger.js deleted file mode 100644 index 9cdd55b..0000000 --- a/lib/munger.js +++ /dev/null @@ -1,88 +0,0 @@ -/*───────────────────────────────────────────────────────────────────────────*\ - │ Copyright (C) 2014 eBay Software Foundation │ - │ │ - │hh ,'""`. │ - │ / _ _ \ 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. │ - \*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; -var views = require('../view'), - util = require('./util'), - dustjs = require('dustjs-linkedin'), - cache = require('./cache'), - fs = require('fs'), - path = require('path'); - -//wrapEngine helps populate the context -//with the specialization map before -//dust.load is called -//this helps load the right specialized templates -//down the render work flow - -exports.wrapEngine = function (config, engine) { - var spclizr, module; - - if (config.specialization) { - module = util.tryRequire('karka'); - spclizr = module && module.create(config.specialization); - return function (file, options, callback) { - //generate the specialization map - options._specialization = spclizr && spclizr.resolveAll(options); - engine.apply(null, arguments); - }; - } else { - return engine; - } -}; - -//wrapDustOnLoad makes sure every dust partial that is loaded -// has the right specialization/localization applied on it - -exports.wrapDustOnLoad = function (ext, config, needCache, app) { - var conf = {}, - viewCache, - i18n = config.i18n; - - var onLoad = (i18n) ? views[ext].create(config, app) : function load(name, context, cb) { - var views, file; - - views = config.views; - file = path.join(views, name + '.' + ext); - fs.readFile(file, 'utf8', function (err, data) { - cb.apply(undefined, arguments); - }); - }; - - //custom cache for all specialized or localized templates - if (needCache) { - viewCache = cache.create(onLoad, i18n ? i18n.fallback : '*'); - onLoad = viewCache.get.bind(viewCache); - } - - dustjs.onLoad = function spclOnLoad(name, context, cb) { - var specialization = (typeof context.get === 'function' && context.get('_specialization')) || context._specialization; - var mappedName = (specialization && specialization[name] || name); - onLoad(mappedName, context, function (err, data) { - if (err) { - return cb(err); - } - - if (mappedName !== name && typeof data === 'string') { - //this is a workaround, since adaro is not aware of the mapped name up the chain - //we find the dust.register line and replace the mappedName of template with original name - data = data.replace(mappedName, name).replace(dustjs.escapeJs(mappedName), dustjs.escapeJs(name)); - } - cb(null, data); - }); - }; -}; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index b20ba24..0000000 --- a/lib/util.js +++ /dev/null @@ -1,76 +0,0 @@ - /*───────────────────────────────────────────────────────────────────────────*\ -│ Copyright (C) 2014 eBay Software Foundation │ -│ │ -│hh ,'""`. │ -│ / _ _ \ 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. │ -\*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; - -var path = require('path'), - crypto = require('crypto'), - assert = require('assert'); - - -/** - * Converts a lang tag (en-US, en, fr-CA) into an object with properties `country` and `locale` - * @param str String a language tag in the format `en-US`, `en_US`, `en`, etc. - * @returns {{language: string, country: string}} - */ -exports.parseLangTag = function (str) { - var pair, tuple; - - if (typeof str === 'object') { - return str; - } - - pair = { - language: '', - country: '' - }; - - if (str) { - tuple = str.split(/[-_]/); - pair.language = (tuple[0] || pair.language).toLowerCase(); - pair.country = (tuple[1] || pair.country).toUpperCase(); - } - - return pair; -}; - - -exports.md5 = function () { - var hash; - - hash = crypto.createHash('md5'); - Array.prototype.slice.call(arguments).forEach(function (arg) { - hash.update(String(arg), 'utf8'); - }); - - return hash.digest('hex'); -}; - - -exports.tryRequire = function tryRequire(moduleName, fallback) { - var result; - try { - result = moduleName && require(moduleName); - } catch (err) { - // noop - } - return result || fallback; -}; - -exports.localityFromLocals = function localityFromLocals(locals) { - return locals && (locals.contentLocality || locals.locality); -}; diff --git a/package.json b/package.json index 7688aa7..d3d66f3 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,67 @@ { "name": "engine-munger", - "version": "0.2.6", - "description": "An engine munger for express apps.", + "version": "1.0.0-3", + "description": "A replacement Express view class that provides asynchronous resolution, internationalization and specialization support", "main": "index.js", "author": { "name": "Poornima Venkatakrishnan", "email": "pvenkatakrishnan@paypal.com" }, + "contributors": { + "name": "Aria Stewart", + "email": "ariastewart@paypal.com" + }, "repository": { "type": "git", "url": "https://github.com/krakenjs/engine-munger.git" }, "dependencies": { + "aproba": "^1.0.1", + "bcp47": "^1.1.2", + "bcp47-stringify": "^1.0.0", "bl": "^0.9.0", + "debuglog": "^1.0.1", + "express": "^4.12.4", "file-resolver": "^1.0.0", "graceful-fs": "~2.0.1", "karka": "~0.0.1", - "localizr": "~1.0.0", + "permutron": "^1.3.1", "verror": "^1.6.0" }, "devDependencies": { - "tape": "~2.4.2", - "adaro": "~0.1.5", - "dustjs-linkedin": "~2.6.2", - "dustjs-helpers": "~1.6.3", - "istanbul": "~0.2.4", + "adaro": "1.0.0-10", + "dustjs-linkedin": "^2.7.1", "jshint": "~2.4.3", - "mockery": "~1.4.0", - "freshy": "0.0.2" - }, - "peerDependencies": { - "dustjs-linkedin": "^2.0.0", - "adaro": "~0.1.5" + "localizr": "^1.1.0", + "nyc": "^2.0.5", + "tap": "^1.0.8" }, "scripts": { - "test": "tape test/*.js && npm run lint", - "cover": "istanbul cover tape -- test/*.js", - "lint": "jshint -c .jshintrc index.js lib/ view/" - } + "test": "nyc tap test/*.js && npm run lint", + "cover": "nyc report", + "lint": "jshint -c .jshintrc index.js", + "regenerate-test-data": "./test/fixtures/compile-test-data.sh" + }, + "bugs": { + "url": "https://github.com/krakenjs/engine-munger/issues" + }, + "homepage": "https://github.com/krakenjs/engine-munger", + "maintainers": [ + { + "name": "pvenkatakrishnan", + "email": "pvenkatakrishnan@paypal.com" + }, + { + "name": "totherik", + "email": "totherik@gmail.com" + }, + { + "name": "jeffharrell", + "email": "jeff@juxtadesign.com" + }, + { + "name": "grawk", + "email": "mattedelman@gmail.com" + } + ] } diff --git a/test/cache.js b/test/cache.js deleted file mode 100644 index 198475a..0000000 --- a/test/cache.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; -var cash = require('../lib/cache'), - test = require('tape'); - -test('cache', function (t) { - var cache, - invoked = false; - - t.test('create cache success', function(t) { - var provider = function(name, context, cb) { - }; - cache = cash.create(provider, 'en_US'); - t.equal(typeof cache, 'object'); - t.equal(typeof cache.dataProvider, 'function'); - t.deepEqual(cache.fallback, {"language":"en","country":"US"}); - cache.dataProvider = provider; - cache.fallback = {"language":"de","country":"DE"}; - t.equal(typeof cache.dataProvider, 'function'); - t.deepEqual(cache.fallback, {"language":"de","country":"DE"}); - t.end(); - }); - - - t.test('cache get hits the provider function', function (t) { - var provider = function(name, context, cb) { - invoked = true; - cb(null, 'Test'); - }; - cache = cash.create(provider, 'en_US'); - cache.get('test', {locality: {country: 'US', language: 'es'}}, function (err, data) { - t.equal(err, null); - t.equal(data, 'Test'); - t.equal(invoked, true); - t.end(); - }); - }); - - t.test('cache get does not hit the provider function', function (t) { - invoked = false; - cache.get('test', {locality: {country: 'US', language: 'es'}}, function (err, data) { - t.equal(err, null); - t.equal(data, 'Test'); - t.equal(invoked, false); - t.end(); - }); - }); - - t.test('cache reset', function (t) { - cache.reset(); - cache.get('test', {locality: {country: 'US', language: 'es'}}, function (err, data) { - t.equal(err, null); - t.equal(data, 'Test'); - t.equal(invoked, true); - t.end(); - }); - }); -}); \ No newline at end of file diff --git a/test/fixtures/.build/US/en/hyde.js b/test/fixtures/.build/US/en/hyde.js new file mode 100644 index 0000000..b6ab0b2 --- /dev/null +++ b/test/fixtures/.build/US/en/hyde.js @@ -0,0 +1 @@ +(function(dust){dust.register("hyde",body_0);function body_0(chk,ctx){return chk.w("

Hello Mister Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/en/jekyll.js b/test/fixtures/.build/US/en/jekyll.js new file mode 100644 index 0000000..8942772 --- /dev/null +++ b/test/fixtures/.build/US/en/jekyll.js @@ -0,0 +1 @@ +(function(dust){dust.register("jekyll",body_0);function body_0(chk,ctx){return chk.w("

Hello Doctor Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/en/spcl/hyde.js b/test/fixtures/.build/US/en/spcl/hyde.js new file mode 100644 index 0000000..862c451 --- /dev/null +++ b/test/fixtures/.build/US/en/spcl/hyde.js @@ -0,0 +1 @@ +(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hello Mister Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/en/spcl/jekyll.js b/test/fixtures/.build/US/en/spcl/jekyll.js new file mode 100644 index 0000000..a75f755 --- /dev/null +++ b/test/fixtures/.build/US/en/spcl/jekyll.js @@ -0,0 +1 @@ +(function(dust){dust.register("spcl\/jekyll",body_0);function body_0(chk,ctx){return chk.w("

Hello Doctor Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/en/test.js b/test/fixtures/.build/US/en/test.js new file mode 100644 index 0000000..1fd1b7a --- /dev/null +++ b/test/fixtures/.build/US/en/test.js @@ -0,0 +1 @@ +(function(dust){dust.register("test",body_0);function body_0(chk,ctx){return chk.w("

Hey Test

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/es/hyde.js b/test/fixtures/.build/US/es/hyde.js new file mode 100644 index 0000000..974e9a7 --- /dev/null +++ b/test/fixtures/.build/US/es/hyde.js @@ -0,0 +1 @@ +(function(dust){dust.register("hyde",body_0);function body_0(chk,ctx){return chk.w("

Hola Señor Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/es/jekyll.js b/test/fixtures/.build/US/es/jekyll.js index 9a76311..bef350a 100644 --- a/test/fixtures/.build/US/es/jekyll.js +++ b/test/fixtures/.build/US/es/jekyll.js @@ -1 +1 @@ -(function(){dust.register("jekyll", body_0);function body_0(chk,ctx){return chk.write("

Hola Jekyll

");}return body_0;})(); \ No newline at end of file +(function(dust){dust.register("jekyll",body_0);function body_0(chk,ctx){return chk.w("

Hola Don Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/es/spcl/hyde.js b/test/fixtures/.build/US/es/spcl/hyde.js index fdbaac3..35089f2 100644 --- a/test/fixtures/.build/US/es/spcl/hyde.js +++ b/test/fixtures/.build/US/es/spcl/hyde.js @@ -1 +1 @@ -(function(){dust.register("spcl/hyde",body_0);function body_0(chk,ctx){return chk.write("

Hola Hyde

");}return body_0;})() \ No newline at end of file +(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hola Señor Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/es/spcl/jekyll.js b/test/fixtures/.build/US/es/spcl/jekyll.js new file mode 100644 index 0000000..b5165b7 --- /dev/null +++ b/test/fixtures/.build/US/es/spcl/jekyll.js @@ -0,0 +1 @@ +(function(dust){dust.register("spcl\/jekyll",body_0);function body_0(chk,ctx){return chk.w("

Hola Don Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/US/es/test.js b/test/fixtures/.build/US/es/test.js new file mode 100644 index 0000000..1fd1b7a --- /dev/null +++ b/test/fixtures/.build/US/es/test.js @@ -0,0 +1 @@ +(function(dust){dust.register("test",body_0);function body_0(chk,ctx){return chk.w("

Hey Test

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/hyde.js b/test/fixtures/.build/hyde.js new file mode 100644 index 0000000..c24b4d4 --- /dev/null +++ b/test/fixtures/.build/hyde.js @@ -0,0 +1 @@ +(function(dust){dust.register("hyde",body_0);function body_0(chk,ctx){return chk.w("

☃greeting☃ Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/jekyll.js b/test/fixtures/.build/jekyll.js new file mode 100644 index 0000000..c4d569b --- /dev/null +++ b/test/fixtures/.build/jekyll.js @@ -0,0 +1 @@ +(function(dust){dust.register("jekyll",body_0);function body_0(chk,ctx){return chk.w("

☃greeting☃ Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/spcl/hyde.js b/test/fixtures/.build/spcl/hyde.js index fdbaac3..a4a2793 100644 --- a/test/fixtures/.build/spcl/hyde.js +++ b/test/fixtures/.build/spcl/hyde.js @@ -1 +1 @@ -(function(){dust.register("spcl/hyde",body_0);function body_0(chk,ctx){return chk.write("

Hola Hyde

");}return body_0;})() \ No newline at end of file +(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

☃greeting☃ Hyde

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/spcl/jekyll.js b/test/fixtures/.build/spcl/jekyll.js new file mode 100644 index 0000000..f36144a --- /dev/null +++ b/test/fixtures/.build/spcl/jekyll.js @@ -0,0 +1 @@ +(function(dust){dust.register("spcl\/jekyll",body_0);function body_0(chk,ctx){return chk.w("

☃greeting☃ Jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/.build/test.js b/test/fixtures/.build/test.js index 47bb2e8..1fd1b7a 100644 --- a/test/fixtures/.build/test.js +++ b/test/fixtures/.build/test.js @@ -1 +1 @@ -(function(){dust.register("test",body_0);function body_0(chk,ctx){return chk.write("

Hey Test

");}return body_0;})() \ No newline at end of file +(function(dust){dust.register("test",body_0);function body_0(chk,ctx){return chk.w("

Hey Test

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/compile-test-data.sh b/test/fixtures/compile-test-data.sh new file mode 100755 index 0000000..91ce51b --- /dev/null +++ b/test/fixtures/compile-test-data.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +for tmpl in $( (cd test/fixtures/templates; find . -type f | grep -v invalid | sed -e 's@[.]/@@; s@.dust@@') ); do + for lang in US/es US/en; do + echo $lang $tmpl + TMPF="test/fixtures/.build/$lang/$(echo $tmpl)" + mkdir -p test/fixtures/{tmp,.build}/$lang/$(dirname $tmpl) && + localizr --props test/fixtures/properties/$lang/$(basename $tmpl).properties --pwd test/fixtures/templates --out test/fixtures/tmp/$lang test/fixtures/templates/$tmpl.dust && + dustc --pwd test/fixtures/tmp/$lang test/fixtures/tmp/$lang/$tmpl.dust > $TMPF.js.tmp && + mv $TMPF.js.tmp $TMPF.js + rm -f $TMPF.js.tmp + done + + TMPF="test/fixtures/.build/$(echo $tmpl)" + localizr --props test/fixtures/properties/null.properties --pwd test/fixtures/templates --out test/fixtures/tmp/ test/fixtures/templates/$tmpl.dust && + dustc --pwd test/fixtures/tmp/ test/fixtures/tmp/$tmpl.dust > $TMPF.js.tmp && + mv $TMPF.js.tmp $TMPF.js + rm -f $TMPF.js.tmp +done diff --git a/test/fixtures/properties/US/en/hyde.properties b/test/fixtures/properties/US/en/hyde.properties index 8e3fc85..62043a7 100644 --- a/test/fixtures/properties/US/en/hyde.properties +++ b/test/fixtures/properties/US/en/hyde.properties @@ -1 +1 @@ -title=Hello Hyde \ No newline at end of file +greeting=Hello Mister diff --git a/test/fixtures/properties/US/en/jekyll.properties b/test/fixtures/properties/US/en/jekyll.properties index 4ae9761..16f074c 100644 --- a/test/fixtures/properties/US/en/jekyll.properties +++ b/test/fixtures/properties/US/en/jekyll.properties @@ -1 +1 @@ -title=Hello Jekyll \ No newline at end of file +greeting=Hello Doctor diff --git a/test/fixtures/properties/US/en/spcl/hyde.properties b/test/fixtures/properties/US/en/spcl/hyde.properties new file mode 100644 index 0000000..62043a7 --- /dev/null +++ b/test/fixtures/properties/US/en/spcl/hyde.properties @@ -0,0 +1 @@ +greeting=Hello Mister diff --git a/test/fixtures/properties/US/en/spcl/jekyll.properties b/test/fixtures/properties/US/en/spcl/jekyll.properties new file mode 100644 index 0000000..138b51f --- /dev/null +++ b/test/fixtures/properties/US/en/spcl/jekyll.properties @@ -0,0 +1 @@ +greeting=Hello Sir diff --git a/test/fixtures/properties/US/es/hyde.properties b/test/fixtures/properties/US/es/hyde.properties index b58368a..735b4dd 100644 --- a/test/fixtures/properties/US/es/hyde.properties +++ b/test/fixtures/properties/US/es/hyde.properties @@ -1 +1 @@ -title=Hola Hyde \ No newline at end of file +greeting=Hola Señor diff --git a/test/fixtures/properties/US/es/jekyll.properties b/test/fixtures/properties/US/es/jekyll.properties index 9879164..694d578 100644 --- a/test/fixtures/properties/US/es/jekyll.properties +++ b/test/fixtures/properties/US/es/jekyll.properties @@ -1 +1 @@ -title=Hola Jekyll \ No newline at end of file +greeting=Hola Don diff --git a/test/fixtures/properties/US/es/spcl/hyde.properties b/test/fixtures/properties/US/es/spcl/hyde.properties new file mode 100644 index 0000000..735b4dd --- /dev/null +++ b/test/fixtures/properties/US/es/spcl/hyde.properties @@ -0,0 +1 @@ +greeting=Hola Señor diff --git a/test/fixtures/properties/US/es/spcl/jekyll.properties b/test/fixtures/properties/US/es/spcl/jekyll.properties new file mode 100644 index 0000000..694d578 --- /dev/null +++ b/test/fixtures/properties/US/es/spcl/jekyll.properties @@ -0,0 +1 @@ +greeting=Hola Don diff --git a/test/fixtures/properties/null.properties b/test/fixtures/properties/null.properties new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/templates/hyde.dust b/test/fixtures/templates/hyde.dust index 276d096..4a6f4b6 100644 --- a/test/fixtures/templates/hyde.dust +++ b/test/fixtures/templates/hyde.dust @@ -1 +1 @@ -

{@pre type="content" key="title" /}

\ No newline at end of file +

{@pre type="content" key="greeting" /} Hyde

diff --git a/test/fixtures/templates/invalidPre.dust b/test/fixtures/templates/invalidPre.dust index 9789b7f..a58385d 100644 --- a/test/fixtures/templates/invalidPre.dust +++ b/test/fixtures/templates/invalidPre.dust @@ -1 +1 @@ -

{@pre type="content" key="title"}

\ No newline at end of file +

{@pre type="content" key="greeting"}

diff --git a/test/fixtures/templates/jekyll.dust b/test/fixtures/templates/jekyll.dust index 276d096..b0c79c5 100644 --- a/test/fixtures/templates/jekyll.dust +++ b/test/fixtures/templates/jekyll.dust @@ -1 +1 @@ -

{@pre type="content" key="title" /}

\ No newline at end of file +

{@pre type="content" key="greeting" /} Jekyll

diff --git a/test/fixtures/templates/spcl/hyde.dust b/test/fixtures/templates/spcl/hyde.dust index e7b787e..4a6f4b6 100644 --- a/test/fixtures/templates/spcl/hyde.dust +++ b/test/fixtures/templates/spcl/hyde.dust @@ -1 +1 @@ -

Hello from hyde

\ No newline at end of file +

{@pre type="content" key="greeting" /} Hyde

diff --git a/test/fixtures/templates/spcl/jekyll.dust b/test/fixtures/templates/spcl/jekyll.dust index 9c73959..b0c79c5 100644 --- a/test/fixtures/templates/spcl/jekyll.dust +++ b/test/fixtures/templates/spcl/jekyll.dust @@ -1 +1 @@ -

hello from jekyll

\ No newline at end of file +

{@pre type="content" key="greeting" /} Jekyll

diff --git a/test/fixtures/testConfig.js b/test/fixtures/testConfig.js deleted file mode 100644 index b92c3d0..0000000 --- a/test/fixtures/testConfig.js +++ /dev/null @@ -1,142 +0,0 @@ -module.exports = { - 'none-js': { - config: { - 'views': 'test/fixtures/.build/', - 'view engine': 'js' - } - }, - 'onlySpcl-js': { - 'config': { - 'views': 'test/fixtures/.build/', - 'view engine': 'js', - 'specialization': { - 'spcl/jekyll': [ - { - 'is': 'spcl/hyde', - 'when': { - 'whoAmI': 'badGuy' - } - } - ] - } - }, - 'context': { - 'whoAmI': 'badGuy', - views: 'test/fixtures/.build' - } - }, - 'onlyIntl-js': { - 'config': { - 'views': 'test/fixtures/.build/', - 'view engine': 'js', - 'i18n': { - 'fallback': 'en-US', - 'contentPath': 'test/fixtures/properties' - } - }, - 'context': { - views: 'test/fixtures/.build', - context: { - locality: 'es_US' - } - } - }, - 'spclAndIntl-js': { - 'config': { - 'views': 'test/fixtures/.build/', - 'view engine': 'js', - 'i18n': { - 'fallback': 'en-US', - 'contentPath': 'test/fixtures/properties' - }, - specialization: { - 'spcl/jekyll': [ - { - is: 'spcl/hyde', - when: { - 'whoAmI': 'badGuy' - } - } - ] - - } - }, - 'context': { - views: 'test/fixtures/.build', - whoAmI: 'badGuy', - context: { - locality: 'es_US' - } - } - }, - 'none-dust': { - config: { - 'views': 'test/fixtures/templates' - } - }, - 'onlySpcl-dust': { - 'config': { - 'views': 'test/fixtures/templates', - 'view engine': 'dust', - 'specialization': { - 'spcl/jekyll': [ - { - 'is': 'spcl/hyde', - 'when': { - 'whoAmI': 'badGuy' - } - } - ] - } - }, - 'context': { - 'whoAmI': 'badGuy', - 'views': 'test/fixtures/templates' - } - }, - 'onlyIntl-dust': { - 'config': { - 'views': 'test/fixtures/templates', - 'view engine': 'dust', - 'i18n': { - 'fallback': 'en-US', - 'contentPath': 'test/fixtures/properties' - } - }, - 'context': { - views: 'test/fixtures/templates', - context: { - locality: 'es_US' - } - } - }, - 'spclAndIntl-dust': { - 'config': { - 'i18n': { - 'fallback': 'en-US', - 'contentPath': 'test/fixtures/properties' - }, - specialization: { - 'spcl/jekyll': [ - { - is: 'spcl/hyde', - when: { - 'whoAmI': 'badGuy' - } - } - ] - - }, - 'views' : 'test/fixtures/templates', - 'view engine': 'dust' - }, - 'context': { - 'views' : 'test/fixtures/templates', - whoAmI: 'badGuy', - context: { - locality: 'es_US' - } - } - - } -}; \ No newline at end of file diff --git a/test/index.js b/test/index.js index b41f088..aa4627c 100644 --- a/test/index.js +++ b/test/index.js @@ -1,20 +1,13 @@ 'use strict'; -var dustjs = require('dustjs-linkedin'), - test = require('tape'), - engineMunger = require('../index'), - testData = require('./fixtures/testConfig'), - freshy = require('freshy'); - +var test = require('tap').test; +var makeViewClass = require('../'); +var path = require('path'); +var adaro = require('adaro'); test('engine-munger', function (t) { - var settings = {cache: false}; t.test('when no specialization or internationalization enabled for js engine', function (t) { - var settings = {cache: false}, - config = clone(testData['none-js'].config); - - resetDust(); - engineMunger['js'](settings, config)('test', {views: 'test/fixtures/.build'}, function(err, data) { + makeView('js', 'test', {}).render({}, function(err, data) { t.equal(err, null); t.equal(data, '

Hey Test

'); t.end(); @@ -22,46 +15,122 @@ test('engine-munger', function (t) { }); t.test('when only specialization enabled for js engine', function (t) { - var config = clone(testData['onlySpcl-js'].config), - context = clone(testData['onlySpcl-js'].context); - resetDust(); - engineMunger['js'](settings, config)('spcl/jekyll', context, function(err, data) { + var conf = { + specialization: { + 'spcl/jekyll': [ + { + is: 'spcl/hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] + } + } + makeView('js', 'spcl/jekyll', conf).render({ whoAmI: 'badGuy' }, function(err, data) { t.equal(err, null); - t.equal(data, '

Hola Hyde

'); - t.end(); + t.equal(data, '

☃greeting☃ Hyde

'); + makeView('js', 'spcl/jekyll', conf).render({ whoAmI: 'goodGuy' }, function(err, data) { + t.equal(err, null); + t.equal(data, '

☃greeting☃ Jekyll

'); + t.end(); + }); }); }); t.test('when only internationalization enabled for js engine', function (t) { - var config = clone(testData['onlyIntl-js'].config), - context = clone(testData['onlyIntl-js'].context); - resetDust(); - engineMunger['js'](settings, config)('jekyll', context, function(err, data) { + var config = { + i18n: { + fallback: 'en-US', + formatPath: krakenFormatPath, + contentPath: 'test/fixtures/properties' + } + }; + var context = { + locale: 'es_US' + }; + + makeView('js', 'jekyll', config).render(context, function(err, data) { t.equal(err, null); - t.equal(data, '

Hola Jekyll

'); + t.equal(data, '

Hola Don Jekyll

'); t.end(); }); }); t.test('when specialization and internationalization enabled for js engine', function (t) { - var config = clone(testData['spclAndIntl-js'].config), - context = clone(testData['spclAndIntl-js'].context); - resetDust(); - engineMunger['js'](settings, config)('spcl/jekyll', context, function(err, data) { + var config = { + i18n: { + fallback: 'en-US', + formatPath: krakenFormatPath, + contentPath: 'test/fixtures/properties' + }, + specialization: { + 'spcl/jekyll': [ + { + is: 'spcl/hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] + + } + }; + + var context1 = { + whoAmI: 'badGuy', + locale: 'es_US' + }; + + var context2 = { + whoAmI: 'goodGuy', + locale: 'es_US' + }; + + var context3 = { + whoAmI: 'badGuy', + locale: 'en_US' + }; + + var context4 = { + whoAmI: 'goodGuy', + locale: 'en_US' + }; + + makeView('js', 'spcl/jekyll', config).render(context1, checkContext1); + + function checkContext1(err, data) { + t.equal(err, null); + t.equal(data, '

Hola Señor Hyde

'); + makeView('js', 'spcl/jekyll', config).render(context2, checkContext2); + } + + function checkContext2(err, data) { + t.equal(err, null); + t.equal(data, '

Hola Don Jekyll

'); + makeView('js', 'spcl/jekyll', config).render(context3, checkContext3); + } + + function checkContext3(err, data) { t.equal(err, null); - t.equal(data, '

Hola Hyde

'); + t.equal(data, '

Hello Mister Hyde

'); + makeView('js', 'spcl/jekyll', config).render(context4, checkContext4); + }; + + function checkContext4(err, data) { + t.equal(err, null); + t.equal(data, '

Hello Doctor Jekyll

'); t.end(); - }); + } }); t.test('when no specialization or internationalization enabled for dust engine', function (t) { - var config = clone(testData['none-dust'].config); + var config = {}; - resetDust(); - engineMunger['dust'](settings, config)('test', {views: 'test/fixtures/templates'}, function(err, data) { + makeView('dust', 'test', {}).render({}, function(err, data) { t.equal(err, null); t.equal(data, '

Hey Test

'); t.end(); @@ -71,153 +140,137 @@ test('engine-munger', function (t) { t.test('using munger when only specialization enabled for dust engine', function (t) { - var config = clone(testData['onlySpcl-dust'].config), - context = clone(testData['onlySpcl-dust'].context); - - resetDust(); - engineMunger['dust'](settings, config)('spcl/jekyll', context, function (err, data) { + var config = { + specialization: { + 'spcl/jekyll': [ + { + is: 'spcl/hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] + } + }; + + var context = { 'whoAmI': 'badGuy' }; + + makeView('dust', 'spcl/jekyll', config).render(context, function (err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hyde

'); t.end(); }); }); - t.test('using munger when only specialization enabled for dust engine with cache', function (t) { - - var config = clone(testData['onlySpcl-dust'].config), - context = clone(testData['onlySpcl-dust'].context), - setting = {cache: true}; - - resetDust(); - engineMunger['dust'](setting, config)('spcl/jekyll', context, function (err, data) { - t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + //error cases + t.test('i18n with js engine- template not found case', function(t) { + var config = { + i18n: { + fallback: 'en-US', + formatPath: krakenFormatPath, + contentPath: 'test/fixtures/properties' + } + }; + makeView('js', 'peekaboo', config).render({}, function(err, data) { + t.match(err.message, /Failed to lookup view "peekaboo.js" in views directory/); + t.equal(data, undefined); t.end(); }); - }); - t.test('when only internationalization is enabled for dust engine', function (t) { - var config = clone(testData['onlyIntl-dust'].config), - context = clone(testData['onlyIntl-dust'].context); - resetDust(); - engineMunger['dust'](settings, config)('jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Jekyll

'); - t.end(); - }); - - }); + t.test('dust engine - catch error while compiling invalid dust and report name of broken template', function(t) { + var config = { + }; - t.test('when specialization/internationalization is enabled for dust engine', function(t) { - var config = clone(testData['spclAndIntl-dust'].config), - context = clone(testData['spclAndIntl-dust'].context); - resetDust(); + var context = { + locale: 'es_US' + }; - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + makeView('dust', 'invalidTemp', config).render(context, function(err, data) { + t.match(err.message, /Problem rendering dust template ".*": Expected end tag for elements but it was not found. At line : 5, column : 11/); + t.equal(data, undefined); t.end(); }); - }); - - t.test('when specialization/internationalization is enabled for dust engine with cache', function(t) { - var config = clone(testData['spclAndIntl-dust'].config), - context = clone(testData['spclAndIntl-dust'].context), - settings = {cache: true}; - resetDust(); - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hello from hyde

'); - t.end(); - }); }); - - t.test('i18n using view.render for js engine', function(t) { - var config = clone(testData['onlyIntl-js'].config), - context = clone(testData['onlyIntl-js'].context); - resetDust(); - var engine = engineMunger['js'](settings, config); - - engineMunger['js'](settings, config)('jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Jekyll

'); - t.end(); + t.test('early lookupMain and cached behavior', function (t) { + var view = makeView('js', 'test', {}); + view.lookupMain({}, function (err) { + t.error(err); + t.equal(view.path, path.resolve(__dirname, 'fixtures', '.build', 'test.js')); + view.lookupMain({}, function(err) { + t.error(err); + t.end(); + }); }); }); - t.test('i18n using view.render for js engine with caching', function(t) { - var config = clone(testData['onlyIntl-js'].config), - context = clone(testData['onlyIntl-js'].context), - settings = {cache: true}; - resetDust(); - var engine = engineMunger['js'](settings, config); - - engineMunger['js'](settings, config)('jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Jekyll

'); - t.end(); + t.test('multiple roots - found', function (t) { + var view = makeView('dust', 'test', { + root: [ + path.resolve(__dirname, 'fixtures', 'not-here'), + path.resolve(__dirname, 'fixtures', 'templates') + ] }); - }); - - t.test('when specialization/internationalization is enabled for dust engine with cache', function(t) { - var config = clone(testData['spclAndIntl-dust'].config), - context = clone(testData['spclAndIntl-dust'].context), - settings = {cache: true}; - resetDust(); - - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + view.lookupMain({}, function (err) { + t.error(err); + t.equal(view.path, path.resolve(__dirname, 'fixtures', 'templates', 'test.dust')); t.end(); }); }); - //error cases - t.test('i18n with js engine- template not found case', function(t) { - var config = clone(testData['onlyIntl-js'].config), - context = clone(testData['onlyIntl-js'].context); - resetDust(); - engineMunger.js(settings, config)('peekaboo', context, function(err, data) { - t.equal(err.message, 'Could not load template peekaboo'); - t.equal(data, undefined); - t.end(); + t.test('multiple roots - not found', function (t) { + var path1 = path.resolve(__dirname, 'fixtures', 'not-here'); + var path2 = path.resolve(__dirname, 'fixtures', 'nor-there') + var view = makeView('dust', 'test', { + root: [ + path1, + path2 + ] }); - - }); - - - t.test('i18n dust engine- catch error while compiling invalid dust and report name of broken template', function(err, data) { - - var config = clone(testData['onlyIntl-dust'].config), - context = clone(testData['onlyIntl-dust'].context); - resetDust(); - engineMunger.dust(settings, config)('invalidTemp', context, function(err, data) { - t.equal(err.message, 'Problem rendering dust template named invalidTemp: Expected end tag for elements but it was not found. At line : 5, column : 11'); - t.equal(data, undefined); + view.lookupMain({}, function (err) { + t.match(err.message, /Failed to lookup view "test.dust"/); + t.match(err.message, /not-here/); + t.match(err.message, /nor-there/); + t.same(view.path, true); t.end(); }); + }); + t.test('multiple roots - deferred stat', function (t) { + var view = makeView('dust', 'test', { }); + var pending = 0; + for (var i = 0; i < 11; i++) { + pending++; + view.lookup('test.dust', {}, function (err) { + t.error(err); + if (--pending === 0) { + t.end(); + } + }); + } }); }); -function resetDust() { - var freshDust = freshy.freshy('dustjs-linkedin'); - dustjs.onLoad = freshDust.onLoad; - dustjs.load = freshDust.load; - dustjs.cache = freshDust.cache; +function makeView(ext, tmpl, config) { + var viewConf = {}; + viewConf[ext] = config; + var View = makeViewClass(viewConf); + var engines = {}; + engines['.' + ext] = adaro[ext](); + tmpl += '.' + ext; + + return new View(tmpl, { + root: config.root ? config.root : ext == 'js' ? path.resolve(__dirname, 'fixtures/.build') : path.resolve(__dirname, 'fixtures/templates'), + defaultEngine: '.' + ext, + engines: engines + }); } -function clone(obj) { - var out = {}; - for (var i in obj) { - out[i] = obj[i]; - } - - return out; +function krakenFormatPath(locale) { + return locale.langtag.region + '/' + locale.langtag.language.language; } diff --git a/view/dust.js b/view/dust.js deleted file mode 100644 index 1cda5cb..0000000 --- a/view/dust.js +++ /dev/null @@ -1,67 +0,0 @@ -/*───────────────────────────────────────────────────────────────────────────*\ - │ Copyright (C) 2014 eBay Software Foundation │ - │ │ - │hh ,'""`. │ - │ / _ _ \ 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. │ - \*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; - -var localizr = require('localizr'), - util = require('../lib/util'), - dustjs = require('dustjs-linkedin'), - resolver = require('file-resolver'), - path = require('path'), - bl = require('bl'), - VError = require('verror'); - - -//config has -//fallbackLocale -//baseTemplatePath - -exports.create = function (config) { - var i18n = config.i18n, - res = resolver.create({ root: i18n.contentPath, ext: 'properties', fallback: i18n.fallback}); - return function onLoad(name, context, callback) { - - var out, options, global, locals, locality, props; - - global = context.global; - locals = context.get('context'); - locality = util.localityFromLocals(locals); - props = res.resolve(name, locality).file || i18n.contentPath; - - options = { - src: path.join(config.views, name + '.dust'), - props: props, - enableMetadata: config.enableMetadata - }; - - out = bl(function (err, data) { - if (err) { - return callback(err); - } - - try { - var compiledDust = dustjs.compile(data.toString('utf-8'), name); - callback(null, compiledDust); - } catch (e) { - callback(new VError(e, 'Problem rendering dust template named %s', name)); - } - }); - - localizr.createReadStream(options).pipe(out); - }; -}; - diff --git a/view/index.js b/view/index.js deleted file mode 100644 index 2ac17be..0000000 --- a/view/index.js +++ /dev/null @@ -1,24 +0,0 @@ - /*───────────────────────────────────────────────────────────────────────────*\ -│ Copyright (C) 2014 eBay Software Foundation │ -│ │ -│hh ,'""`. │ -│ / _ _ \ 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. │ -\*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; - - -module.exports = { - dust: require('./dust'), - js: require('./js') -}; \ No newline at end of file diff --git a/view/js.js b/view/js.js deleted file mode 100644 index e4421f3..0000000 --- a/view/js.js +++ /dev/null @@ -1,45 +0,0 @@ -/*───────────────────────────────────────────────────────────────────────────*\ - │ Copyright (C) 2014 eBay Software Foundation │ - │ │ - │hh ,'""`. │ - │ / _ _ \ 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. │ - \*───────────────────────────────────────────────────────────────────────────*/ -'use strict'; - -var fs = require('graceful-fs'); -var util = require('../lib/util'); -var resolver = require('file-resolver'); - -exports.create = function (config) { - - var res, - defaultLocale = config.i18n.fallback; - - res = resolver.create({ root: config.views, ext: 'js', fallback: defaultLocale }); - - return function onLoad(name, context, callback) { - var locals, view; - - locals = context.get('context'); - - view = res.resolve(name, util.localityFromLocals(locals)); - if (!view.file) { - callback(new Error('Could not load template ' + name)); - return; - } - fs.readFile(view.file, 'utf8', callback); - }; - -}; -