From c50c6f91bc9d43d6e99235c9456107d79f4df8e2 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Wed, 6 May 2015 14:32:03 -0400 Subject: [PATCH 01/42] Directly depend on adaro, and let it bring in dustjs --- index.js | 18 ++++++++++-------- lib/munger.js | 3 +-- package.json | 8 +------- test/index.js | 22 ---------------------- view/dust.js | 1 - 5 files changed, 12 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 4f77492..40ada32 100644 --- a/index.js +++ b/index.js @@ -30,16 +30,17 @@ exports.dust = function (setting, config) { return engine.dust(settings); } - if (configs['view engine'] === 'dust') { - munger.wrapDustOnLoad('dust', configs, settings.cache); - } - // 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; // For i18n we silently switch to the JS engine for all requests, passing config renderer = configs.i18n ? engine.js(settings): engine.dust(settings); + + if (configs['view engine'] === 'dust') { + munger.wrapDustOnLoad('dust', renderer.dust, configs, settings.cache); + } + return munger.wrapEngine(configs, renderer); }; @@ -52,14 +53,15 @@ exports.js = function (setting, config) { return engine.js(settings); } - if (configs['view engine'] === 'js') { - munger.wrapDustOnLoad('js', configs, settings.cache); - } - // 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); + + if (configs['view engine'] === 'js') { + munger.wrapDustOnLoad('js', renderer.dust, configs, settings.cache); + } + return (configs.specialization) ? munger.wrapEngine(configs, renderer) : renderer; }; diff --git a/lib/munger.js b/lib/munger.js index 9cdd55b..8975d47 100644 --- a/lib/munger.js +++ b/lib/munger.js @@ -18,7 +18,6 @@ 'use strict'; var views = require('../view'), util = require('./util'), - dustjs = require('dustjs-linkedin'), cache = require('./cache'), fs = require('fs'), path = require('path'); @@ -48,7 +47,7 @@ exports.wrapEngine = function (config, 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) { +exports.wrapDustOnLoad = function (ext, dustjs, config, needCache, app) { var conf = {}, viewCache, i18n = config.i18n; diff --git a/package.json b/package.json index 7688aa7..b3b43ae 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "url": "https://github.com/krakenjs/engine-munger.git" }, "dependencies": { + "adaro": "1.0.0-0", "bl": "^0.9.0", "file-resolver": "^1.0.0", "graceful-fs": "~2.0.1", @@ -21,18 +22,11 @@ }, "devDependencies": { "tape": "~2.4.2", - "adaro": "~0.1.5", - "dustjs-linkedin": "~2.6.2", - "dustjs-helpers": "~1.6.3", "istanbul": "~0.2.4", "jshint": "~2.4.3", "mockery": "~1.4.0", "freshy": "0.0.2" }, - "peerDependencies": { - "dustjs-linkedin": "^2.0.0", - "adaro": "~0.1.5" - }, "scripts": { "test": "tape test/*.js && npm run lint", "cover": "istanbul cover tape -- test/*.js", diff --git a/test/index.js b/test/index.js index b41f088..c59dc46 100644 --- a/test/index.js +++ b/test/index.js @@ -13,7 +13,6 @@ test('engine-munger', 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) { t.equal(err, null); t.equal(data, '

Hey Test

'); @@ -24,7 +23,6 @@ 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) { t.equal(err, null); t.equal(data, '

Hola Hyde

'); @@ -35,7 +33,6 @@ test('engine-munger', function (t) { 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) { t.equal(err, null); t.equal(data, '

Hola Jekyll

'); @@ -47,7 +44,6 @@ test('engine-munger', function (t) { 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) { t.equal(err, null); t.equal(data, '

Hola Hyde

'); @@ -60,7 +56,6 @@ test('engine-munger', function (t) { var config = clone(testData['none-dust'].config); - resetDust(); engineMunger['dust'](settings, config)('test', {views: 'test/fixtures/templates'}, function(err, data) { t.equal(err, null); t.equal(data, '

Hey Test

'); @@ -74,7 +69,6 @@ test('engine-munger', 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) { t.equal(err, null); t.equal(data, '

Hello from hyde

'); @@ -89,7 +83,6 @@ test('engine-munger', function (t) { 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

'); @@ -101,7 +94,6 @@ test('engine-munger', function (t) { 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

'); @@ -114,7 +106,6 @@ test('engine-munger', function (t) { 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(); engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); @@ -127,7 +118,6 @@ test('engine-munger', 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); @@ -140,7 +130,6 @@ test('engine-munger', function (t) { 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) { @@ -154,7 +143,6 @@ test('engine-munger', 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) { @@ -168,7 +156,6 @@ test('engine-munger', 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); @@ -181,7 +168,6 @@ test('engine-munger', function (t) { 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); @@ -195,7 +181,6 @@ test('engine-munger', function (t) { 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); @@ -206,13 +191,6 @@ test('engine-munger', function (t) { }); -function resetDust() { - var freshDust = freshy.freshy('dustjs-linkedin'); - dustjs.onLoad = freshDust.onLoad; - dustjs.load = freshDust.load; - dustjs.cache = freshDust.cache; -} - function clone(obj) { var out = {}; for (var i in obj) { diff --git a/view/dust.js b/view/dust.js index 1cda5cb..9ed3e27 100644 --- a/view/dust.js +++ b/view/dust.js @@ -19,7 +19,6 @@ var localizr = require('localizr'), util = require('../lib/util'), - dustjs = require('dustjs-linkedin'), resolver = require('file-resolver'), path = require('path'), bl = require('bl'), From 62624e3597802e1082e49d8a01603ddd3a0a2527 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Wed, 6 May 2015 16:05:44 -0400 Subject: [PATCH 02/42] Update built fixtures to use dust 2.7 format --- test/fixtures/.build/US/es/jekyll.js | 2 +- test/fixtures/.build/US/es/spcl/hyde.js | 2 +- test/fixtures/.build/spcl/hyde.js | 2 +- test/fixtures/.build/test.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/fixtures/.build/US/es/jekyll.js b/test/fixtures/.build/US/es/jekyll.js index 9a76311..10f2e1f 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 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..5ed1a87 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 Hyde

");}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..5ed1a87 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("

Hola Hyde

");}body_0.__dustBody=!0;return body_0}(dust)); diff --git a/test/fixtures/.build/test.js b/test/fixtures/.build/test.js index 47bb2e8..e7253c0 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)); From 68aae66bab6481a5199baccf15cc877c8e645037 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Wed, 6 May 2015 14:33:17 -0400 Subject: [PATCH 03/42] Pass specialization via requestOptions --- lib/munger.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/munger.js b/lib/munger.js index 8975d47..fdfb273 100644 --- a/lib/munger.js +++ b/lib/munger.js @@ -22,7 +22,7 @@ var views = require('../view'), fs = require('fs'), path = require('path'); -//wrapEngine helps populate the context +//wrapEngine helps populate the options //with the specialization map before //dust.load is called //this helps load the right specialized templates @@ -36,7 +36,13 @@ exports.wrapEngine = function (config, engine) { spclizr = module && module.create(config.specialization); return function (file, options, callback) { //generate the specialization map - options._specialization = spclizr && spclizr.resolveAll(options); + + if (!options.renderOptions) { + options.renderOptions = {}; + } + + options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); + engine.apply(null, arguments); }; } else { @@ -52,7 +58,7 @@ exports.wrapDustOnLoad = function (ext, dustjs, config, needCache, app) { viewCache, i18n = config.i18n; - var onLoad = (i18n) ? views[ext].create(config, app) : function load(name, context, cb) { + var onLoad = (i18n) ? views[ext].create(config, app) : function load(name, options, cb) { var views, file; views = config.views; @@ -68,10 +74,10 @@ exports.wrapDustOnLoad = function (ext, dustjs, config, needCache, app) { onLoad = viewCache.get.bind(viewCache); } - dustjs.onLoad = function spclOnLoad(name, context, cb) { - var specialization = (typeof context.get === 'function' && context.get('_specialization')) || context._specialization; + dustjs.onLoad = function spclOnLoad(name, options, cb) { + var specialization = options && options.specialization; var mappedName = (specialization && specialization[name] || name); - onLoad(mappedName, context, function (err, data) { + onLoad(mappedName, options, function (err, data) { if (err) { return cb(err); } From 0b18d7d0ad97378c913a314eedc42b7c54322d20 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 7 May 2015 16:29:06 -0400 Subject: [PATCH 04/42] Fix specialization test and test both paths --- test/fixtures/.build/spcl/hyde.js | 2 +- test/fixtures/.build/spcl/jekyll.js | 1 + test/fixtures/testConfig.js | 6 +++++- test/index.js | 16 +++++++++++----- 4 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/.build/spcl/jekyll.js diff --git a/test/fixtures/.build/spcl/hyde.js b/test/fixtures/.build/spcl/hyde.js index 5ed1a87..4fce573 100644 --- a/test/fixtures/.build/spcl/hyde.js +++ b/test/fixtures/.build/spcl/hyde.js @@ -1 +1 @@ -(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hola Hyde

");}body_0.__dustBody=!0;return body_0}(dust)); +(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hello from 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..4abe359 --- /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("

hello from jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); diff --git a/test/fixtures/testConfig.js b/test/fixtures/testConfig.js index b92c3d0..5c29333 100644 --- a/test/fixtures/testConfig.js +++ b/test/fixtures/testConfig.js @@ -20,9 +20,13 @@ module.exports = { ] } }, - 'context': { + 'context1': { 'whoAmI': 'badGuy', views: 'test/fixtures/.build' + }, + 'context2': { + 'whoAmI': 'goodGuy', + views: 'test/fixtures/.build' } }, 'onlyIntl-js': { diff --git a/test/index.js b/test/index.js index c59dc46..e353144 100644 --- a/test/index.js +++ b/test/index.js @@ -21,12 +21,18 @@ 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); - engineMunger['js'](settings, config)('spcl/jekyll', context, function(err, data) { + var config = clone(testData['onlySpcl-js'].config); + var context1 = clone(testData['onlySpcl-js'].context1); + var context2 = clone(testData['onlySpcl-js'].context2); + var engine = engineMunger['js'](settings, config); + engine('spcl/jekyll', context1, function(err, data) { t.equal(err, null); - t.equal(data, '

Hola Hyde

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

Hello from hyde

'); + engine('spcl/jekyll', context2, function(err, data) { + t.equal(err, null); + t.equal(data, '

hello from jekyll

'); + t.end(); + }); }); }); From 400efff99835b1fcc37df0ae92800fb1d72f307f Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 7 May 2015 12:07:45 -0400 Subject: [PATCH 05/42] Simplify and make unconditional the onLoad wrappers --- .gitignore | 1 + index.js | 18 ++----- lib/cache.js | 136 -------------------------------------------------- lib/munger.js | 84 +++++++++++++++---------------- package.json | 2 +- test/cache.js | 57 --------------------- test/index.js | 3 +- view/dust.js | 14 +++--- view/js.js | 16 ++++-- 9 files changed, 63 insertions(+), 268 deletions(-) delete mode 100644 lib/cache.js delete mode 100644 test/cache.js diff --git a/.gitignore b/.gitignore index 2d7dda8..286f5e9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ coverage/ npm-debug.log +*.swp diff --git a/index.js b/index.js index 40ada32..080239e 100644 --- a/index.js +++ b/index.js @@ -30,16 +30,10 @@ exports.dust = function (setting, config) { return engine.dust(settings); } - // 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; - // For i18n we silently switch to the JS engine for all requests, passing config renderer = configs.i18n ? engine.js(settings): engine.dust(settings); - if (configs['view engine'] === 'dust') { - munger.wrapDustOnLoad('dust', renderer.dust, configs, settings.cache); - } + munger.wrapDustOnLoad('dust', renderer.dust, configs); return munger.wrapEngine(configs, renderer); }; @@ -53,15 +47,9 @@ exports.js = function (setting, config) { return engine.js(settings); } - // 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); - if (configs['view engine'] === 'js') { - munger.wrapDustOnLoad('js', renderer.dust, configs, settings.cache); - } + munger.wrapDustOnLoad('js', renderer.dust, configs); - return (configs.specialization) ? munger.wrapEngine(configs, renderer) : renderer; + return munger.wrapEngine(configs, renderer); }; - 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/munger.js b/lib/munger.js index fdfb273..f1904f9 100644 --- a/lib/munger.js +++ b/lib/munger.js @@ -18,7 +18,6 @@ 'use strict'; var views = require('../view'), util = require('./util'), - cache = require('./cache'), fs = require('fs'), path = require('path'); @@ -29,65 +28,62 @@ var views = require('../view'), //down the render work flow exports.wrapEngine = function (config, engine) { - var spclizr, module; + if (config.i18n) { + engine = wrapForI18n(config, engine); + } if (config.specialization) { - module = util.tryRequire('karka'); - spclizr = module && module.create(config.specialization); - return function (file, options, callback) { - //generate the specialization map + engine = wrapForSpecialization(config, engine); + } + + return engine; +}; - if (!options.renderOptions) { - options.renderOptions = {}; - } +function wrapForI18n(config, engine) { + return function (file, options, callback) { + if (!options.renderOptions) { + options.renderOptions = {}; + } - options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); + if (options.context) { + options.renderOptions.locality = options.context.locality; + options.renderOptions.contentLocality = options.context.contentLocality; + } - engine.apply(null, arguments); - }; - } else { - return engine; + engine(file, options, callback); } -}; +} + +function wrapForSpecialization(config, engine) { + var spclizr, module; + + module = util.tryRequire('karka'); + spclizr = module && module.create(config.specialization); + return function (file, options, callback) { + //generate the specialization map + + if (!options.renderOptions) { + options.renderOptions = {}; + } + + options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); + + engine(file, options, callback); + }; +} //wrapDustOnLoad makes sure every dust partial that is loaded // has the right specialization/localization applied on it -exports.wrapDustOnLoad = function (ext, dustjs, config, needCache, app) { +exports.wrapDustOnLoad = function (ext, dustjs, config) { var conf = {}, - viewCache, i18n = config.i18n; - var onLoad = (i18n) ? views[ext].create(config, app) : function load(name, options, 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); - } + var onLoad = (i18n) ? views[ext].create(config, dustjs) : dustjs.onLoad; dustjs.onLoad = function spclOnLoad(name, options, cb) { var specialization = options && options.specialization; var mappedName = (specialization && specialization[name] || name); - onLoad(mappedName, options, 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); - }); + onLoad(mappedName, options, cb); }; }; diff --git a/package.json b/package.json index b3b43ae..569ad5e 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "url": "https://github.com/krakenjs/engine-munger.git" }, "dependencies": { - "adaro": "1.0.0-0", + "adaro": "1.0.0-2", "bl": "^0.9.0", "file-resolver": "^1.0.0", "graceful-fs": "~2.0.1", 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/index.js b/test/index.js index e353144..9022a01 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,5 @@ 'use strict'; -var dustjs = require('dustjs-linkedin'), - test = require('tape'), +var test = require('tape'), engineMunger = require('../index'), testData = require('./fixtures/testConfig'), freshy = require('freshy'); diff --git a/view/dust.js b/view/dust.js index 9ed3e27..2fdefa6 100644 --- a/view/dust.js +++ b/view/dust.js @@ -29,19 +29,17 @@ var localizr = require('localizr'), //fallbackLocale //baseTemplatePath -exports.create = function (config) { +exports.create = function (config, dustjs) { var i18n = config.i18n, res = resolver.create({ root: i18n.contentPath, ext: 'properties', fallback: i18n.fallback}); - return function onLoad(name, context, callback) { + return function onLoad(name, options, callback) { - var out, options, global, locals, locality, props; + var out, localizrOptions, global, locals, locality, props; - global = context.global; - locals = context.get('context'); - locality = util.localityFromLocals(locals); + locality = util.localityFromLocals(options); props = res.resolve(name, locality).file || i18n.contentPath; - options = { + localizrOptions = { src: path.join(config.views, name + '.dust'), props: props, enableMetadata: config.enableMetadata @@ -60,7 +58,7 @@ exports.create = function (config) { } }); - localizr.createReadStream(options).pipe(out); + localizr.createReadStream(localizrOptions).pipe(out); }; }; diff --git a/view/js.js b/view/js.js index e4421f3..f634d0f 100644 --- a/view/js.js +++ b/view/js.js @@ -21,24 +21,30 @@ var fs = require('graceful-fs'); var util = require('../lib/util'); var resolver = require('file-resolver'); -exports.create = function (config) { +exports.create = function (config, dustjs) { var res, defaultLocale = config.i18n.fallback; res = resolver.create({ root: config.views, ext: 'js', fallback: defaultLocale }); - return function onLoad(name, context, callback) { + return function onLoad(name, options, callback) { var locals, view; - locals = context.get('context'); - view = res.resolve(name, util.localityFromLocals(locals)); + view = res.resolve(name, util.localityFromLocals(options)); if (!view.file) { callback(new Error('Could not load template ' + name)); return; } - fs.readFile(view.file, 'utf8', callback); + fs.readFile(view.file, 'utf8', function (err, data) { + if (err) { + return callback(err); + } else { + var tmpl = dustjs.loadSource(data); + return callback(null, tmpl); + } + }); }; }; From 1b92a95d8638cf7163ab87b461c30557b12901f1 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 7 May 2015 16:43:44 -0400 Subject: [PATCH 06/42] Syntactic cleanups --- lib/munger.js | 2 +- test/fixtures/testConfig.js | 2 +- view/js.js | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/munger.js b/lib/munger.js index f1904f9..0cca430 100644 --- a/lib/munger.js +++ b/lib/munger.js @@ -51,7 +51,7 @@ function wrapForI18n(config, engine) { } engine(file, options, callback); - } + }; } function wrapForSpecialization(config, engine) { diff --git a/test/fixtures/testConfig.js b/test/fixtures/testConfig.js index 5c29333..d491630 100644 --- a/test/fixtures/testConfig.js +++ b/test/fixtures/testConfig.js @@ -143,4 +143,4 @@ module.exports = { } } -}; \ No newline at end of file +}; diff --git a/view/js.js b/view/js.js index f634d0f..7bb7e6d 100644 --- a/view/js.js +++ b/view/js.js @@ -29,14 +29,12 @@ exports.create = function (config, dustjs) { res = resolver.create({ root: config.views, ext: 'js', fallback: defaultLocale }); return function onLoad(name, options, callback) { - var locals, view; + var view = res.resolve(name, util.localityFromLocals(options)); - - view = res.resolve(name, util.localityFromLocals(options)); if (!view.file) { - callback(new Error('Could not load template ' + name)); - return; + return callback(new Error('Could not load template ' + name)); } + fs.readFile(view.file, 'utf8', function (err, data) { if (err) { return callback(err); From 0f440b16c76b15673b187c13492d3a7ee4930125 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 7 May 2015 16:39:21 -0400 Subject: [PATCH 07/42] Use dustjs.loadSource and return template instead of source --- view/dust.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/view/dust.js b/view/dust.js index 2fdefa6..b7249dd 100644 --- a/view/dust.js +++ b/view/dust.js @@ -51,8 +51,9 @@ exports.create = function (config, dustjs) { } try { - var compiledDust = dustjs.compile(data.toString('utf-8'), name); - callback(null, compiledDust); + var tmpl = dustjs.loadSource(dustjs.compile(data.toString('utf-8'), name)); + // FIXME: update template name to include locale, test caching, etc. + callback(null, tmpl); } catch (e) { callback(new VError(e, 'Problem rendering dust template named %s', name)); } From efa64d098fbde928b1637075fddcc23671b28be5 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 8 May 2015 16:23:59 -0400 Subject: [PATCH 08/42] Rework test suite --- .gitignore | 1 + package.json | 4 +- test/fixtures/.build/US/en/hyde.js | 1 + test/fixtures/.build/US/en/jekyll.js | 1 + test/fixtures/.build/US/en/spcl/hyde.js | 1 + test/fixtures/.build/US/en/spcl/jekyll.js | 1 + test/fixtures/.build/US/en/test.js | 1 + test/fixtures/.build/US/es/hyde.js | 1 + test/fixtures/.build/US/es/jekyll.js | 2 +- test/fixtures/.build/US/es/spcl/hyde.js | 2 +- test/fixtures/.build/US/es/spcl/jekyll.js | 1 + test/fixtures/.build/US/es/test.js | 1 + test/fixtures/.build/hyde.js | 1 + test/fixtures/.build/jekyll.js | 1 + test/fixtures/.build/spcl/hyde.js | 2 +- test/fixtures/.build/spcl/jekyll.js | 2 +- test/fixtures/.build/test.js | 2 +- test/fixtures/compile-test-data.sh | 19 ++++ .../fixtures/properties/US/en/hyde.properties | 2 +- .../properties/US/en/jekyll.properties | 2 +- .../properties/US/en/spcl/hyde.properties | 1 + .../properties/US/en/spcl/jekyll.properties | 1 + .../fixtures/properties/US/es/hyde.properties | 2 +- .../properties/US/es/jekyll.properties | 2 +- .../properties/US/es/spcl/hyde.properties | 1 + .../properties/US/es/spcl/jekyll.properties | 1 + test/fixtures/properties/null.properties | 0 test/fixtures/templates/hyde.dust | 2 +- test/fixtures/templates/invalidPre.dust | 2 +- test/fixtures/templates/jekyll.dust | 2 +- test/fixtures/templates/spcl/hyde.dust | 2 +- test/fixtures/templates/spcl/jekyll.dust | 2 +- test/fixtures/testConfig.js | 9 +- test/index.js | 101 +++++++++--------- 34 files changed, 107 insertions(+), 69 deletions(-) create mode 100644 test/fixtures/.build/US/en/hyde.js create mode 100644 test/fixtures/.build/US/en/jekyll.js create mode 100644 test/fixtures/.build/US/en/spcl/hyde.js create mode 100644 test/fixtures/.build/US/en/spcl/jekyll.js create mode 100644 test/fixtures/.build/US/en/test.js create mode 100644 test/fixtures/.build/US/es/hyde.js create mode 100644 test/fixtures/.build/US/es/spcl/jekyll.js create mode 100644 test/fixtures/.build/US/es/test.js create mode 100644 test/fixtures/.build/hyde.js create mode 100644 test/fixtures/.build/jekyll.js create mode 100644 test/fixtures/compile-test-data.sh create mode 100644 test/fixtures/properties/US/en/spcl/hyde.properties create mode 100644 test/fixtures/properties/US/en/spcl/jekyll.properties create mode 100644 test/fixtures/properties/US/es/spcl/hyde.properties create mode 100644 test/fixtures/properties/US/es/spcl/jekyll.properties create mode 100644 test/fixtures/properties/null.properties diff --git a/.gitignore b/.gitignore index 286f5e9..ff7ae44 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ coverage/ npm-debug.log *.swp +test/fixtures/tmp/ diff --git a/package.json b/package.json index 569ad5e..b5b780f 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "file-resolver": "^1.0.0", "graceful-fs": "~2.0.1", "karka": "~0.0.1", - "localizr": "~1.0.0", - "verror": "^1.6.0" + "verror": "^1.6.0", + "localizr": "^1.0.0" }, "devDependencies": { "tape": "~2.4.2", 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 10f2e1f..bef350a 100644 --- a/test/fixtures/.build/US/es/jekyll.js +++ b/test/fixtures/.build/US/es/jekyll.js @@ -1 +1 @@ -(function(dust){dust.register("jekyll",body_0);function body_0(chk,ctx){return chk.w("

Hola Jekyll

");}body_0.__dustBody=!0;return body_0}(dust)); +(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 5ed1a87..35089f2 100644 --- a/test/fixtures/.build/US/es/spcl/hyde.js +++ b/test/fixtures/.build/US/es/spcl/hyde.js @@ -1 +1 @@ -(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hola Hyde

");}body_0.__dustBody=!0;return body_0}(dust)); +(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 4fce573..a4a2793 100644 --- a/test/fixtures/.build/spcl/hyde.js +++ b/test/fixtures/.build/spcl/hyde.js @@ -1 +1 @@ -(function(dust){dust.register("spcl\/hyde",body_0);function body_0(chk,ctx){return chk.w("

Hello from hyde

");}body_0.__dustBody=!0;return body_0;})(dust); +(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 index 4abe359..f36144a 100644 --- a/test/fixtures/.build/spcl/jekyll.js +++ b/test/fixtures/.build/spcl/jekyll.js @@ -1 +1 @@ -(function(dust){dust.register("spcl\/jekyll",body_0);function body_0(chk,ctx){return chk.w("

hello from jekyll

");}body_0.__dustBody=!0;return body_0;})(dust); +(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 e7253c0..1fd1b7a 100644 --- a/test/fixtures/.build/test.js +++ b/test/fixtures/.build/test.js @@ -1 +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)); +(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 100644 index 0000000..b483435 --- /dev/null +++ b/test/fixtures/compile-test-data.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +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 index d491630..e7ab0d0 100644 --- a/test/fixtures/testConfig.js +++ b/test/fixtures/testConfig.js @@ -65,12 +65,19 @@ module.exports = { } }, - 'context': { + 'context1': { views: 'test/fixtures/.build', whoAmI: 'badGuy', context: { locality: 'es_US' } + }, + 'context2': { + views: 'test/fixtures/.build', + whoAmI: 'goodGuy', + context: { + locality: 'es_US' + } } }, 'none-dust': { diff --git a/test/index.js b/test/index.js index 9022a01..3f6e653 100644 --- a/test/index.js +++ b/test/index.js @@ -10,7 +10,7 @@ test('engine-munger', function (t) { t.test('when no specialization or internationalization enabled for js engine', function (t) { var settings = {cache: false}, - config = clone(testData['none-js'].config); + config = testData['none-js'].config; engineMunger['js'](settings, config)('test', {views: 'test/fixtures/.build'}, function(err, data) { t.equal(err, null); @@ -20,46 +20,52 @@ test('engine-munger', function (t) { }); t.test('when only specialization enabled for js engine', function (t) { - var config = clone(testData['onlySpcl-js'].config); - var context1 = clone(testData['onlySpcl-js'].context1); - var context2 = clone(testData['onlySpcl-js'].context2); + var config = testData['onlySpcl-js'].config; + var context1 = testData['onlySpcl-js'].context1; + var context2 = testData['onlySpcl-js'].context2; var engine = engineMunger['js'](settings, config); engine('spcl/jekyll', context1, function(err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

☃greeting☃ Hyde

'); engine('spcl/jekyll', context2, function(err, data) { t.equal(err, null); - t.equal(data, '

hello from jekyll

'); + 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); + var config = testData['onlyIntl-js'].config, + context = testData['onlyIntl-js'].context; engineMunger['js'](settings, config)('jekyll', 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); - engineMunger['js'](settings, config)('spcl/jekyll', context, function(err, data) { + var config = testData['spclAndIntl-js'].config; + var context1 = testData['spclAndIntl-js'].context1; + var context2 = testData['spclAndIntl-js'].context2; + var engine = engineMunger['js'](settings, config); + engine('spcl/jekyll', context1, function(err, data) { t.equal(err, null); - t.equal(data, '

Hola Hyde

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

Hola Señor Hyde

'); + engine('spcl/jekyll', context2, function(err, data) { + t.equal(err, null); + t.equal(data, '

Hola Don 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 = testData['none-dust'].config; engineMunger['dust'](settings, config)('test', {views: 'test/fixtures/templates'}, function(err, data) { t.equal(err, null); @@ -71,12 +77,12 @@ 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); + var config = testData['onlySpcl-dust'].config, + context = testData['onlySpcl-dust'].context; engineMunger['dust'](settings, config)('spcl/jekyll', context, function (err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hyde

'); t.end(); }); @@ -84,24 +90,24 @@ test('engine-munger', function (t) { 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), + var config = testData['onlySpcl-dust'].config, + context = testData['onlySpcl-dust'].context, setting = {cache: true}; engineMunger['dust'](setting, config)('spcl/jekyll', context, function (err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hyde

'); 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); + var config = testData['onlyIntl-dust'].config, + context = testData['onlyIntl-dust'].context; engineMunger['dust'](settings, config)('jekyll', context, function(err, data) { t.equal(err, null); - t.equal(data, '

Hola Jekyll

'); + t.equal(data, '

Hola Don Jekyll

'); t.end(); }); @@ -109,70 +115,70 @@ test('engine-munger', function (t) { 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); + var config = testData['spclAndIntl-dust'].config, + context = testData['spclAndIntl-dust'].context; engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hola Señor Hyde

'); 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), + var config = testData['spclAndIntl-dust'].config, + context = testData['spclAndIntl-dust'].context, settings = {cache: true}; engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hola Señor 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); + var config = testData['onlyIntl-js'].config, + context = testData['onlyIntl-js'].context; 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.equal(data, '

Hola Don Jekyll

'); 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), + var config = testData['onlyIntl-js'].config, + context = testData['onlyIntl-js'].context, settings = {cache: true}; 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.equal(data, '

Hola Don Jekyll

'); 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), + var config = testData['spclAndIntl-dust'].config, + context = testData['spclAndIntl-dust'].context, settings = {cache: true}; engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); - t.equal(data, '

Hello from hyde

'); + t.equal(data, '

Hola Señor Hyde

'); 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); + var config = testData['onlyIntl-js'].config, + context = testData['onlyIntl-js'].context; engineMunger.js(settings, config)('peekaboo', context, function(err, data) { t.equal(err.message, 'Could not load template peekaboo'); t.equal(data, undefined); @@ -184,8 +190,8 @@ test('engine-munger', function (t) { 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); + var config = testData['onlyIntl-dust'].config, + context = testData['onlyIntl-dust'].context; 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); @@ -195,12 +201,3 @@ test('engine-munger', function (t) { }); }); - -function clone(obj) { - var out = {}; - for (var i in obj) { - out[i] = obj[i]; - } - - return out; -} From 71e879a13c54f615a7597ea91365c47ccc44f5ec Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 8 May 2015 17:27:02 -0400 Subject: [PATCH 09/42] Check all i18n and specialization combinations in spclAndIntl-js test --- test/fixtures/testConfig.js | 14 ++++++++++++++ test/index.js | 32 +++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/test/fixtures/testConfig.js b/test/fixtures/testConfig.js index e7ab0d0..978fa8e 100644 --- a/test/fixtures/testConfig.js +++ b/test/fixtures/testConfig.js @@ -78,6 +78,20 @@ module.exports = { context: { locality: 'es_US' } + }, + 'context3': { + views: 'test/fixtures/.build', + whoAmI: 'badGuy', + context: { + locality: 'en_US' + } + }, + 'context4': { + views: 'test/fixtures/.build', + whoAmI: 'goodGuy', + context: { + locality: 'en_US' + } } }, 'none-dust': { diff --git a/test/index.js b/test/index.js index 3f6e653..ee37376 100644 --- a/test/index.js +++ b/test/index.js @@ -50,16 +50,34 @@ test('engine-munger', function (t) { var config = testData['spclAndIntl-js'].config; var context1 = testData['spclAndIntl-js'].context1; var context2 = testData['spclAndIntl-js'].context2; + var context3 = testData['spclAndIntl-js'].context3; + var context4 = testData['spclAndIntl-js'].context4; var engine = engineMunger['js'](settings, config); - engine('spcl/jekyll', context1, function(err, data) { + engine('spcl/jekyll', context1, checkContext1); + + function checkContext1(err, data) { t.equal(err, null); t.equal(data, '

Hola Señor Hyde

'); - engine('spcl/jekyll', context2, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Don Jekyll

'); - t.end(); - }); - }); + engine('spcl/jekyll', context2, checkContext2); + } + + function checkContext2(err, data) { + t.equal(err, null); + t.equal(data, '

Hola Don Jekyll

'); + engine('spcl/jekyll', context3, checkContext3); + } + + function checkContext3(err, data) { + t.equal(err, null); + t.equal(data, '

Hello Mister Hyde

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

Hello Doctor Jekyll

'); + t.end(); + } }); From e30dfead14093d05d8b2a79656ce79a76e178829 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 11 Jul 2014 14:16:21 -0400 Subject: [PATCH 10/42] Add option `enableMetadata` to allow hooking by in-place content editing systems --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index db04a49..d973003 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ app.engine('js', engine-munger['js'](settings, config)); 'view engine': 'dust', 'i18n': { 'fallback': 'en-US', - 'contentPath': 'locales' + 'contentPath': 'locales', + 'enableMetadata': true }, specialization: { 'jekyll': [ From 6a8d8362c848508e82e45001e944ebe36dd9c1e8 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 15 Jul 2014 12:30:59 -0400 Subject: [PATCH 11/42] Appease the linter --- test/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/index.js b/test/index.js index ee37376..2fb9c64 100644 --- a/test/index.js +++ b/test/index.js @@ -12,7 +12,7 @@ test('engine-munger', function (t) { var settings = {cache: false}, config = testData['none-js'].config; - engineMunger['js'](settings, config)('test', {views: 'test/fixtures/.build'}, function(err, data) { + engineMunger.js(settings, config)('test', {views: 'test/fixtures/.build'}, function(err, data) { t.equal(err, null); t.equal(data, '

Hey Test

'); t.end(); @@ -23,7 +23,7 @@ test('engine-munger', function (t) { var config = testData['onlySpcl-js'].config; var context1 = testData['onlySpcl-js'].context1; var context2 = testData['onlySpcl-js'].context2; - var engine = engineMunger['js'](settings, config); + var engine = engineMunger.js(settings, config); engine('spcl/jekyll', context1, function(err, data) { t.equal(err, null); t.equal(data, '

☃greeting☃ Hyde

'); @@ -38,7 +38,7 @@ test('engine-munger', function (t) { t.test('when only internationalization enabled for js engine', function (t) { var config = testData['onlyIntl-js'].config, context = testData['onlyIntl-js'].context; - engineMunger['js'](settings, config)('jekyll', context, function(err, data) { + engineMunger.js(settings, config)('jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Don Jekyll

'); t.end(); @@ -52,7 +52,7 @@ test('engine-munger', function (t) { var context2 = testData['spclAndIntl-js'].context2; var context3 = testData['spclAndIntl-js'].context3; var context4 = testData['spclAndIntl-js'].context4; - var engine = engineMunger['js'](settings, config); + var engine = engineMunger.js(settings, config); engine('spcl/jekyll', context1, checkContext1); function checkContext1(err, data) { @@ -85,7 +85,7 @@ test('engine-munger', function (t) { var config = testData['none-dust'].config; - engineMunger['dust'](settings, config)('test', {views: 'test/fixtures/templates'}, function(err, data) { + engineMunger.dust(settings, config)('test', {views: 'test/fixtures/templates'}, function(err, data) { t.equal(err, null); t.equal(data, '

Hey Test

'); t.end(); @@ -98,7 +98,7 @@ test('engine-munger', function (t) { var config = testData['onlySpcl-dust'].config, context = testData['onlySpcl-dust'].context; - engineMunger['dust'](settings, config)('spcl/jekyll', context, function (err, data) { + engineMunger.dust(settings, config)('spcl/jekyll', context, function (err, data) { t.equal(err, null); t.equal(data, '

Hyde

'); t.end(); @@ -112,7 +112,7 @@ test('engine-munger', function (t) { context = testData['onlySpcl-dust'].context, setting = {cache: true}; - engineMunger['dust'](setting, config)('spcl/jekyll', context, function (err, data) { + engineMunger.dust(setting, config)('spcl/jekyll', context, function (err, data) { t.equal(err, null); t.equal(data, '

Hyde

'); t.end(); @@ -123,7 +123,7 @@ test('engine-munger', function (t) { t.test('when only internationalization is enabled for dust engine', function (t) { var config = testData['onlyIntl-dust'].config, context = testData['onlyIntl-dust'].context; - engineMunger['dust'](settings, config)('jekyll', context, function(err, data) { + engineMunger.dust(settings, config)('jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Don Jekyll

'); t.end(); @@ -136,7 +136,7 @@ test('engine-munger', function (t) { var config = testData['spclAndIntl-dust'].config, context = testData['spclAndIntl-dust'].context; - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { + engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Señor Hyde

'); t.end(); @@ -148,7 +148,7 @@ test('engine-munger', function (t) { context = testData['spclAndIntl-dust'].context, settings = {cache: true}; - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { + engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Señor Hyde

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

Hola Don Jekyll

'); t.end(); @@ -172,9 +172,9 @@ test('engine-munger', function (t) { var config = testData['onlyIntl-js'].config, context = testData['onlyIntl-js'].context, settings = {cache: true}; - var engine = engineMunger['js'](settings, config); + var engine = engineMunger.js(settings, config); - engineMunger['js'](settings, config)('jekyll', context, function(err, data) { + engineMunger.js(settings, config)('jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Don Jekyll

'); t.end(); @@ -186,7 +186,7 @@ test('engine-munger', function (t) { context = testData['spclAndIntl-dust'].context, settings = {cache: true}; - engineMunger['dust'](settings, config)('spcl/jekyll', context, function(err, data) { + engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { t.equal(err, null); t.equal(data, '

Hola Señor Hyde

'); t.end(); From a35d02bf66d4097b58d1b6f6be32ca582fe72735 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 11 May 2015 13:34:08 -0400 Subject: [PATCH 12/42] Improve package.json metadata --- package.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b5b780f..d6330be 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,27 @@ "test": "tape test/*.js && npm run lint", "cover": "istanbul cover tape -- test/*.js", "lint": "jshint -c .jshintrc index.js lib/ view/" - } + }, + "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" + } + ] } From 5e125f718d814bda0031e370676d5506c9fa8ab2 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 12 May 2015 16:59:20 -0400 Subject: [PATCH 13/42] Rework expressView to mimic the original better and work independently of the engine it modifies --- index.js | 2 ++ lib/expressView.js | 66 ++++++++++++++++++++++++++++++++-------------- package.json | 5 ++-- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/index.js b/index.js index 080239e..e89fa36 100644 --- a/index.js +++ b/index.js @@ -53,3 +53,5 @@ exports.js = function (setting, config) { return munger.wrapEngine(configs, renderer); }; + +exports.setupViewClass = require('./lib/expressView'); diff --git a/lib/expressView.js b/lib/expressView.js index 06d7277..69072a3 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -18,14 +18,17 @@ "use strict"; var resolver = require('file-resolver'); var util = require('./util'); +var path = require('path'); +var debug = require('debuglog')('engine-munger'); 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; - }, + // 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. + path: true, render: function (options, callback) { var locals, view, engine; @@ -41,21 +44,46 @@ var proto = { options.settings = Object.create(options.settings); options.views = options.settings.views = view.root; - engine = this.engines['.' + this.defaultEngine]; - engine(view.file, options, callback); + this.engine(this.name, options, callback); } }; -function buildCtor(fallback) { +function buildCtor(fallback, enginesToMunge, OriginalView) { + var mungeable = [].concat(enginesToMunge).map(function (e) { + return e[0] !== '.' ? '.' + e : e; + }); function View(name, options) { + options = 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 }); + var engines = options.engines; + this.defaultEngine = options.defaultEngine; + var ext = this.ext = path.extname(name); + if (!ext && !this.defaultEngine) { + throw new Error('No default engine was specified and no extension was provided.'); + } + + if (!ext) { + name += (ext = this.ext = ('.' !== this.defaultEngine[0] ? '.' : '') + this.defaultEngine); + } + + this.engine = engines[ext] || (engines[ext] = require(ext.slice(1)).__express); + + if (~mungeable.indexOf(ext)) { + this.resolver = resolver.create({ + root: options.root, + ext: this.ext.slice(1), + fallback: fallback + }); + } else { + debug("No specialization or i18n munging to do, punting to original Express View class"); + return new OriginalView(name, options); + } } View.prototype = proto; @@ -63,19 +91,17 @@ function buildCtor(fallback) { return View; } -module.exports = function () { - var view; - return function (req, res, next) { - var config = req.app.kraken; +module.exports = function setupViewClass(options) { + if (!options || !options.fallback || !options.engines) { + throw new Error("setupViewClass must be configured with a fallback locale and engines to munge"); + } - //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); + var hasConfiguredApp = false; + return function (req, res, next) { + if (!hasConfiguredApp) { + req.app.set('view', buildCtor(options.fallback, options.engines, req.app.get('view'))); + hasConfiguredApp = true; } next(); }; }; - - - diff --git a/package.json b/package.json index d6330be..070d48d 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,12 @@ "dependencies": { "adaro": "1.0.0-2", "bl": "^0.9.0", + "debuglog": "^1.0.1", "file-resolver": "^1.0.0", "graceful-fs": "~2.0.1", "karka": "~0.0.1", - "verror": "^1.6.0", - "localizr": "^1.0.0" + "localizr": "^1.0.0", + "verror": "^1.6.0" }, "devDependencies": { "tape": "~2.4.2", From 312c0993a404ef996aa52d4e01001d61c43d4794 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 19 May 2015 13:17:21 -0400 Subject: [PATCH 14/42] Switch from tape to tap --- package.json | 8 ++++---- test/index.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 070d48d..57dc50c 100644 --- a/package.json +++ b/package.json @@ -22,15 +22,15 @@ "verror": "^1.6.0" }, "devDependencies": { - "tape": "~2.4.2", "istanbul": "~0.2.4", "jshint": "~2.4.3", "mockery": "~1.4.0", - "freshy": "0.0.2" + "freshy": "0.0.2", + "tap": "^1.0.8" }, "scripts": { - "test": "tape test/*.js && npm run lint", - "cover": "istanbul cover tape -- test/*.js", + "test": "tap test/*.js && npm run lint", + "cover": "istanbul cover tap -- test/*.js", "lint": "jshint -c .jshintrc index.js lib/ view/" }, "bugs": { diff --git a/test/index.js b/test/index.js index 2fb9c64..20b9334 100644 --- a/test/index.js +++ b/test/index.js @@ -1,5 +1,5 @@ 'use strict'; -var test = require('tape'), +var test = require('tap').test, engineMunger = require('../index'), testData = require('./fixtures/testConfig'), freshy = require('freshy'); From 055be1869d910e27ade8c9f1e75efd04eaf9145e Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Wed, 20 May 2015 10:57:23 -0400 Subject: [PATCH 15/42] Add nyc for coverage tracking in tests --- .gitignore | 1 + package.json | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ff7ae44..5661071 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ coverage/ npm-debug.log *.swp test/fixtures/tmp/ +nyc_output/ diff --git a/package.json b/package.json index 57dc50c..53cf83e 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,12 @@ "jshint": "~2.4.3", "mockery": "~1.4.0", "freshy": "0.0.2", + "nyc": "^2.0.5", "tap": "^1.0.8" }, "scripts": { - "test": "tap test/*.js && npm run lint", - "cover": "istanbul cover tap -- test/*.js", + "test": "nyc tap test/*.js && npm run lint", + "cover": "nyc report", "lint": "jshint -c .jshintrc index.js lib/ view/" }, "bugs": { From 8b666b58929331e586432141da154f54b5f4089a Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 19 May 2015 13:00:03 -0400 Subject: [PATCH 16/42] Replace expressView with one with async lookup This exposes the (potential) express 5 style asynchronous locate, and by blessing the view.locate() function, engines can use this to look up resources. --- lib/expressView.js | 220 ++++++++++++++++++++++++++++++++++----------- package.json | 4 +- test/viewClass.js | 70 +++++++++++++++ 3 files changed, 239 insertions(+), 55 deletions(-) create mode 100644 test/viewClass.js diff --git a/lib/expressView.js b/lib/expressView.js index 69072a3..dde5dcd 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -20,70 +20,115 @@ var resolver = require('file-resolver'); var util = require('./util'); 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 proto = { +/** + * 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) { + var proto = Object.create(oldView.prototype); // 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. - path: true, + proto.path = 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; - - this.engine(this.name, options, callback); - } - -}; - - -function buildCtor(fallback, enginesToMunge, OriginalView) { - var mungeable = [].concat(enginesToMunge).map(function (e) { - return e[0] !== '.' ? '.' + e : e; - }); - - function View(name, options) { - options = options || {}; - this.name = name; - this.root = options.root; - this.defaultEngine = options.defaultEngine; - this.engines = options.engines; - var engines = options.engines; - this.defaultEngine = options.defaultEngine; - var ext = this.ext = path.extname(name); - if (!ext && !this.defaultEngine) { - throw new Error('No default engine was specified and no extension was provided.'); + proto.lookup = function lookup(name, options, cb) { + if (arguments.length == 1) { + // This is the unoverriden constructor calling us. Ignore the call. + return true; } - if (!ext) { - name += (ext = this.ext = ('.' !== this.defaultEngine[0] ? '.' : '') + this.defaultEngine); + var ext = path.extname(name); + + var roots = [].concat(this.root); + + debug('lookup "%s"', name); + + function lookup(roots, callback) { + var root = roots.shift(); + if (!root) { + return callback(null, null); + } + debug("looking up '%s' in '%s' with ext '%s'", name, root, ext); + + // resolve the path + var loc = path.resolve(root, name); + var dir = path.dirname(loc); + var file = path.basename(loc); + + // resolve the file + resolveView(dir, file, ext, function (err, resolved) { + if (err) { + return callback(err); + } else if (resolved) { + return callback(null, resolved); + } else { + return lookup(roots, callback); + } + }); + } - this.engine = engines[ext] || (engines[ext] = require(ext.slice(1)).__express); + return lookup(roots, cb); + }; - if (~mungeable.indexOf(ext)) { - this.resolver = resolver.create({ - root: options.root, - ext: this.ext.slice(1), - fallback: fallback + /** + * Render with the given `options` and callback `fn(err, str)`. + * + * @param {Object} options + * @param {Function} fn + * @api private + */ + proto.render = function render(options, fn) { + debug('render "%s"', this.path); + if (!this.path || this.path === true) { + this.lookupMain(options, function (err) { + if (err) { + fn(err); + } else { + this.engine(this.path, options, fn); + } }); - } else { - debug("No specialization or i18n munging to do, punting to original Express View class"); - return new OriginalView(name, options); } + this.engine(this.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 if (!path) { + 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 Error('Failed to lookup view "' + view.name + '" in views ' + dirs); + viewError.view = view; + return cb(viewError); + } else { + view.path = path; + cb(); + } + }); + }; + + function View(name, options) { + oldView.call(this, name, options); } View.prototype = proto; @@ -92,16 +137,83 @@ function buildCtor(fallback, enginesToMunge, OriginalView) { } module.exports = function setupViewClass(options) { - if (!options || !options.fallback || !options.engines) { - throw new Error("setupViewClass must be configured with a fallback locale and engines to munge"); - } - var hasConfiguredApp = false; return function (req, res, next) { if (!hasConfiguredApp) { - req.app.set('view', buildCtor(options.fallback, options.engines, req.app.get('view'))); + req.app.set('view', makeViewClass(options)); hasConfiguredApp = true; } next(); }; }; + +module.exports.makeViewClass = makeViewClass; + +function enabledForExt(config, ext) { + var extNoDot = ext.slice(1); + return config.engines && (config.engines.indexOf(ext) !== -1 || config.engines.indexOf(extNoDot) != -1); +} + + +/** + * 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)); + } + + 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--; + } + }; + } +} + + +/** + * Resolve the file within the given directory. + * + * @param {string} dir + * @param {string} file + * @param {string} ext + * @param {function} cb + * @private + */ +function resolveView(dir, file, ext, cb) { + var resolved = path.join(dir, file); + limitStat(resolved, function (err, stat) { + if (err && err.code == 'ENOENT') { + // Skip down + } else if (!err && stat && stat.isFile()) { + return cb(null, resolved); + } + + // /index. + resolved = path.join(dir, path.basename(file, ext), 'index' + ext); + limitStat(resolved, function (err, stat) { + if (err && err.code == 'ENOENT') { + return cb(null, null); + } else if (!err && stat && stat.isFile()) { + return cb(null, resolved); + } else { + return cb(err || new Error("error looking up '" + resolved + "'")); + } + }); + }); +} diff --git a/package.json b/package.json index 53cf83e..3f33ed1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "adaro": "1.0.0-2", "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", @@ -22,11 +23,12 @@ "verror": "^1.6.0" }, "devDependencies": { + "freshy": "0.0.2", "istanbul": "~0.2.4", "jshint": "~2.4.3", "mockery": "~1.4.0", - "freshy": "0.0.2", "nyc": "^2.0.5", + "supertest": "^1.0.1", "tap": "^1.0.8" }, "scripts": { diff --git a/test/viewClass.js b/test/viewClass.js new file mode 100644 index 0000000..bd3ab7b --- /dev/null +++ b/test/viewClass.js @@ -0,0 +1,70 @@ +var test = require('tap').test; +var MungedView = require('../lib/expressView').makeViewClass({}); +var setupViewClass = require('../lib/expressView'); +var express = require('express'); +var supertest = require('supertest'); +var path = require('path'); + +test('view class lookup', function (t) { + var v = new MungedView('test', { + root: path.resolve(__dirname, 'fixtures/templates'), + defaultEngine: '.dust', + engines: { + '.dust': fakeEngine + } + }); + v.lookup('test.dust', {}, function (err, result) { + t.error(err); + t.equal(result, path.resolve(__dirname, 'fixtures/templates/test.dust')); + t.end(); + }); +}); + +test('view class lookupMain', function (t) { + var v = new MungedView('test', { + root: path.resolve(__dirname, 'fixtures/templates'), + defaultEngine: '.dust', + engines: { + '.dust': fakeEngine + } + }); + v.lookupMain({}, function (err) { + t.error(err); + t.equal(v.path, path.resolve(__dirname, 'fixtures/templates/test.dust')); + t.end(); + }); +}); + +test('first-run middleware', function (t) { + var app = express(); + var view, afterView; + + app.set('view engine', 'fake'); + app.engine('fake', fakeEngine); + app.use(function (req, res, next) { + view = app.get('view'); + t.ok(view); + next(); + }); + app.use(setupViewClass({stuff: true})); + app.use(function (req, res, next) { + afterView = app.get('view'); + t.ok(afterView); + next(); + }); + app.get('/', function (req, res) { + res.end('got it'); + }); + + supertest(app).get('/').end(function (err, res) { + t.error(err); + t.ok(res); + t.notEqual(afterView, view); + t.end(); + }); + +}); + +function fakeEngine() { + console.log('fake engine called', arguments); +} From 491847b8e808dbf17b31fe8759f3a3a867c38d1c Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Wed, 20 May 2015 11:53:47 -0400 Subject: [PATCH 17/42] Make karka unconditional --- lib/munger.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/munger.js b/lib/munger.js index 0cca430..bb7d4e9 100644 --- a/lib/munger.js +++ b/lib/munger.js @@ -19,6 +19,7 @@ var views = require('../view'), util = require('./util'), fs = require('fs'), + karka = require('karka'), path = require('path'); //wrapEngine helps populate the options @@ -55,10 +56,7 @@ function wrapForI18n(config, engine) { } function wrapForSpecialization(config, engine) { - var spclizr, module; - - module = util.tryRequire('karka'); - spclizr = module && module.create(config.specialization); + var spclizr = karka.create(config.specialization.rules); return function (file, options, callback) { //generate the specialization map @@ -66,7 +64,11 @@ function wrapForSpecialization(config, engine) { options.renderOptions = {}; } - options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); + try { + options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); + } catch (e) { + return callback(e); + } engine(file, options, callback); }; From c34d1d12ca1aa764927dbc3e252acc4ad28173c2 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 21 May 2015 12:08:41 -0400 Subject: [PATCH 18/42] Rework to use permutron --- lib/expressView.js | 126 ++++++++++++++++++++++++++------------------- package.json | 2 + test/viewClass.js | 65 ++++++++++------------- 3 files changed, 103 insertions(+), 90 deletions(-) diff --git a/lib/expressView.js b/lib/expressView.js index dde5dcd..5768aed 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -23,12 +23,20 @@ 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'); /** * 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 exts = prefixDots(config); + + var specialization = setupSpecialization(exts); + var proto = Object.create(oldView.prototype); // Unfortunately, since we don't know the actual file to resolve until @@ -46,36 +54,36 @@ function makeViewClass(config) { var ext = path.extname(name); - var roots = [].concat(this.root); + if (specialization[ext]) { + name = specialization[ext].resolve(name, options); + } - debug('lookup "%s"', name); + var search = []; + search.push([].concat(exts[ext] && exts[ext].root ? exts[ext].root : this.root)); - function lookup(roots, callback) { - var root = roots.shift(); - if (!root) { - return callback(null, null); - } - debug("looking up '%s' in '%s' with ext '%s'", name, root, ext); + if (exts[ext] && exts[ext].i18n) { + var locales = []; + search.push([options.locale, exts[ext].i18n.fallback].filter(function (e) { return e })); + } - // resolve the path - var loc = path.resolve(root, name); - var dir = path.dirname(loc); - var file = path.basename(loc); + search.push([name, path.join(path.basename(name), 'index' + ext)]); - // resolve the file - resolveView(dir, file, ext, function (err, resolved) { - if (err) { - return callback(err); - } else if (resolved) { - return callback(null, resolved); + debug('lookup "%s"', name); + + permutron.raw(search, function (candidate, next) { + debug("candidate %j", candidate); + 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 { - return lookup(roots, callback); + cb(err); } }); - - } - - return lookup(roots, cb); + }, cb); }; /** @@ -137,10 +145,32 @@ function makeViewClass(config) { } module.exports = function setupViewClass(options) { + var opts = {}; + opts['.properties'] = {}; + opts['.js'] = {}; + opts['.dust'] = {}; + + if (options.i18n) { + opts['.properties'].root = [].concat(options.i18n.contentPath); + opts['.properties'].i18n = { + fallback: options.i18n.fallback + }; + + opts['.js'].i18n = { + fallback: options.i18n.fallback + }; + } + + if (options.specialization) { + opts['.properties'].specialization = options.specialization; + opts['.js'].specialization = options.specialization; + opts['.dust'].specialization = options.specialization; + } + var hasConfiguredApp = false; return function (req, res, next) { if (!hasConfiguredApp) { - req.app.set('view', makeViewClass(options)); + req.app.set('view', makeViewClass(opts)); hasConfiguredApp = true; } next(); @@ -185,35 +215,25 @@ function limitStat(path, cb) { } } - -/** - * Resolve the file within the given directory. - * - * @param {string} dir - * @param {string} file - * @param {string} ext - * @param {function} cb - * @private - */ -function resolveView(dir, file, ext, cb) { - var resolved = path.join(dir, file); - limitStat(resolved, function (err, stat) { - if (err && err.code == 'ENOENT') { - // Skip down - } else if (!err && stat && stat.isFile()) { - return cb(null, resolved); +function prefixDots(config) { + var out = {}; + for (var ext in config) { + if (ext[0] == '.') { + out[ext] = config[ext]; + } else { + out['.' + ext] = config[ext]; } + } - // /index. - resolved = path.join(dir, path.basename(file, ext), 'index' + ext); - limitStat(resolved, function (err, stat) { - if (err && err.code == 'ENOENT') { - return cb(null, null); - } else if (!err && stat && stat.isFile()) { - return cb(null, resolved); - } else { - return cb(err || new Error("error looking up '" + resolved + "'")); - } - }); - }); + return out; +} + +function setupSpecialization(config) { + var out = {}; + + for (var ext in config) { + out[ext] = karka.create(config[ext]); + } + + return out; } diff --git a/package.json b/package.json index 3f33ed1..ccc7ae7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "adaro": "1.0.0-2", + "aproba": "^1.0.1", "bl": "^0.9.0", "debuglog": "^1.0.1", "express": "^4.12.4", @@ -20,6 +21,7 @@ "graceful-fs": "~2.0.1", "karka": "~0.0.1", "localizr": "^1.0.0", + "permutron": "^1.3.1", "verror": "^1.6.0" }, "devDependencies": { diff --git a/test/viewClass.js b/test/viewClass.js index bd3ab7b..46fcd5e 100644 --- a/test/viewClass.js +++ b/test/viewClass.js @@ -1,12 +1,10 @@ var test = require('tap').test; -var MungedView = require('../lib/expressView').makeViewClass({}); -var setupViewClass = require('../lib/expressView'); -var express = require('express'); -var supertest = require('supertest'); +var makeViewClass = require('../lib/expressView').makeViewClass; var path = require('path'); -test('view class lookup', function (t) { - var v = new MungedView('test', { +test('view class lookup plain', function (t) { + var View = makeViewClass({}); + var v = new View('test', { root: path.resolve(__dirname, 'fixtures/templates'), defaultEngine: '.dust', engines: { @@ -20,49 +18,42 @@ test('view class lookup', function (t) { }); }); -test('view class lookupMain', function (t) { - var v = new MungedView('test', { - root: path.resolve(__dirname, 'fixtures/templates'), - defaultEngine: '.dust', +test('view class lookup i18n', function (t) { + var View = makeViewClass({ + ".js": { + "i18n": { + "fallback": "en-US" + } + } + }); + var v = new View('test', { + root: path.resolve(__dirname, 'fixtures/.build'), + defaultEngine: '.js', engines: { - '.dust': fakeEngine + '.js': fakeEngine } }); - v.lookupMain({}, function (err) { + v.lookup('test.js', {}, function (err, result) { t.error(err); - t.equal(v.path, path.resolve(__dirname, 'fixtures/templates/test.dust')); + t.equal(result, path.resolve(__dirname, 'fixtures/templates/US/en/test.js')); t.end(); }); }); -test('first-run middleware', function (t) { - var app = express(); - var view, afterView; - - app.set('view engine', 'fake'); - app.engine('fake', fakeEngine); - app.use(function (req, res, next) { - view = app.get('view'); - t.ok(view); - next(); - }); - app.use(setupViewClass({stuff: true})); - app.use(function (req, res, next) { - afterView = app.get('view'); - t.ok(afterView); - next(); - }); - app.get('/', function (req, res) { - res.end('got it'); +test('view class lookupMain', function (t) { + var View = makeViewClass({}); + var v = new View('test', { + root: path.resolve(__dirname, 'fixtures/templates'), + defaultEngine: '.dust', + engines: { + '.dust': fakeEngine + } }); - - supertest(app).get('/').end(function (err, res) { + v.lookupMain({}, function (err) { t.error(err); - t.ok(res); - t.notEqual(afterView, view); + t.equal(v.path, path.resolve(__dirname, 'fixtures/templates/test.dust')); t.end(); }); - }); function fakeEngine() { From fa385dddd016faca137db46af7b404a5bcf04aa8 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Thu, 21 May 2015 14:58:38 -0400 Subject: [PATCH 19/42] Implement i18n lookups cleanly --- lib/expressView.js | 81 ++++++++++++++++++++++++++++------------------ package.json | 2 ++ test/viewClass.js | 7 ++-- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/lib/expressView.js b/lib/expressView.js index 5768aed..6b98c03 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -25,6 +25,8 @@ 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'); /** * Make a View class that uses our configuration, set far in advance of @@ -33,9 +35,7 @@ var aproba = require('aproba'); function makeViewClass(config) { aproba('O', arguments); - var exts = prefixDots(config); - - var specialization = setupSpecialization(exts); + var conf = normalizeConfigs(config); var proto = Object.create(oldView.prototype); @@ -47,23 +47,34 @@ function makeViewClass(config) { proto.path = true; proto.lookup = function lookup(name, options, cb) { - if (arguments.length == 1) { + if (arguments.length === 1) { // This is the unoverriden constructor calling us. Ignore the call. return true; } var ext = path.extname(name); - if (specialization[ext]) { - name = specialization[ext].resolve(name, options); + 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(exts[ext] && exts[ext].root ? exts[ext].root : this.root)); + search.push([].concat(conf[ext] && conf[ext].root ? conf[ext].root : this.root)); - if (exts[ext] && exts[ext].i18n) { + if (conf[ext] && conf[ext].i18n) { + var i18n = conf[ext].i18n; var locales = []; - search.push([options.locale, exts[ext].i18n.fallback].filter(function (e) { return e })); + 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)]); @@ -71,13 +82,12 @@ function makeViewClass(config) { debug('lookup "%s"', name); permutron.raw(search, function (candidate, next) { - debug("candidate %j", candidate); 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')) { + } else if ((!err && stat.isDirectory()) || (err && err.code === 'ENOENT')) { next(); } else { cb(err); @@ -94,17 +104,21 @@ function makeViewClass(config) { * @api private */ proto.render = function render(options, fn) { - debug('render "%s"', this.path); - if (!this.path || this.path === true) { - this.lookupMain(options, function (err) { + aproba('OF', arguments); + var view = this; + if (!view.path || view.path === true) { + view.lookupMain(options, function (err) { if (err) { fn(err); } else { - this.engine(this.path, options, fn); + debug('render "%s"', view.path); + view.engine(view.path, options, fn); } }); + } else { + debug('render "%s"', view.path); + view.engine(view.path, options, fn); } - this.engine(this.path, options, fn); }; /** Resolve the main template for this view @@ -113,18 +127,16 @@ function makeViewClass(config) { * @private */ proto.lookupMain = function lookupMain(options, cb) { - if (this.path && this.path !== true) return 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; + 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 if (!path) { - 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 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 Error('Failed to lookup view "' + view.name + '" in views ' + dirs); viewError.view = view; return cb(viewError); @@ -181,7 +193,7 @@ module.exports.makeViewClass = makeViewClass; function enabledForExt(config, ext) { var extNoDot = ext.slice(1); - return config.engines && (config.engines.indexOf(ext) !== -1 || config.engines.indexOf(extNoDot) != -1); + return config.engines && (config.engines.indexOf(ext) !== -1 || config.engines.indexOf(extNoDot) !== -1); } @@ -215,24 +227,31 @@ function limitStat(path, cb) { } } -function prefixDots(config) { +function normalizeConfigs(config) { var out = {}; for (var ext in config) { - if (ext[0] == '.') { - out[ext] = config[ext]; + if (ext[0] === '.') { + out[ext] = normalizeConfig(config[ext]); } else { - out['.' + ext] = config[ext]; + out['.' + ext] = normalizeConfig(config[ext]); } } return out; } -function setupSpecialization(config) { +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 + }; + } - for (var ext in config) { - out[ext] = karka.create(config[ext]); + if (config.specialization) { + out.specialization = karka.create(config.specialization); } return out; diff --git a/package.json b/package.json index ccc7ae7..d1f09e7 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "dependencies": { "adaro": "1.0.0-2", "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", diff --git a/test/viewClass.js b/test/viewClass.js index 46fcd5e..c51e890 100644 --- a/test/viewClass.js +++ b/test/viewClass.js @@ -22,7 +22,10 @@ test('view class lookup i18n', function (t) { var View = makeViewClass({ ".js": { "i18n": { - "fallback": "en-US" + "fallback": "en-US", + "formatPath": function (locale) { + return path.join(locale.langtag.region, locale.langtag.language.language); + } } } }); @@ -35,7 +38,7 @@ test('view class lookup i18n', function (t) { }); v.lookup('test.js', {}, function (err, result) { t.error(err); - t.equal(result, path.resolve(__dirname, 'fixtures/templates/US/en/test.js')); + t.equal(result, path.resolve(__dirname, 'fixtures/.build/US/en/test.js')); t.end(); }); }); From 036e3b0854e603f15f3bf6693220793ef3f5e4fc Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 29 May 2015 23:19:13 -0400 Subject: [PATCH 20/42] Remove dead code --- index.js | 37 ++----------------- lib/expressView.js | 2 - lib/munger.js | 91 ---------------------------------------------- lib/util.js | 76 -------------------------------------- package.json | 2 +- view/dust.js | 65 --------------------------------- view/index.js | 24 ------------ view/js.js | 49 ------------------------- 8 files changed, 4 insertions(+), 342 deletions(-) delete mode 100644 lib/munger.js delete mode 100644 lib/util.js delete mode 100644 view/dust.js delete mode 100644 view/index.js delete mode 100644 view/js.js diff --git a/index.js b/index.js index e89fa36..87d2fd3 100644 --- a/index.js +++ b/index.js @@ -17,41 +17,10 @@ \*───────────────────────────────────────────────────────────────────────────*/ 'use strict'; -var engine = require('adaro'), - munger = require('./lib/munger'); +var adaro = require('adaro'); +exports.dust = adaro.dust; -exports.dust = function (setting, config) { - var settings = (arguments.length > 1) ? setting : {}, - configs = (arguments.length > 1) ? config : setting, - renderer; - - if (!configs || !(configs.specialization || configs.i18n)) { - return engine.dust(settings); - } - - // For i18n we silently switch to the JS engine for all requests, passing config - renderer = configs.i18n ? engine.js(settings): engine.dust(settings); - - munger.wrapDustOnLoad('dust', renderer.dust, configs); - - return munger.wrapEngine(configs, renderer); -}; - -exports.js = function (setting, config) { - var settings = (arguments.length > 1) ? setting : {}, - configs = (arguments.length > 1) ? config : setting, - renderer; - - if (!configs || !(configs.specialization || configs.i18n)) { - return engine.js(settings); - } - - renderer = engine.js(settings); - - munger.wrapDustOnLoad('js', renderer.dust, configs); - - return munger.wrapEngine(configs, renderer); -}; +exports.js = adaro.js; exports.setupViewClass = require('./lib/expressView'); diff --git a/lib/expressView.js b/lib/expressView.js index 6b98c03..af86036 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -16,8 +16,6 @@ │ limitations under the License. │ \*───────────────────────────────────────────────────────────────────────────*/ "use strict"; -var resolver = require('file-resolver'); -var util = require('./util'); var path = require('path'); var debug = require('debuglog')('engine-munger'); var fs = require('fs'); diff --git a/lib/munger.js b/lib/munger.js deleted file mode 100644 index bb7d4e9..0000000 --- a/lib/munger.js +++ /dev/null @@ -1,91 +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'), - fs = require('fs'), - karka = require('karka'), - path = require('path'); - -//wrapEngine helps populate the options -//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) { - if (config.i18n) { - engine = wrapForI18n(config, engine); - } - - if (config.specialization) { - engine = wrapForSpecialization(config, engine); - } - - return engine; -}; - -function wrapForI18n(config, engine) { - return function (file, options, callback) { - if (!options.renderOptions) { - options.renderOptions = {}; - } - - if (options.context) { - options.renderOptions.locality = options.context.locality; - options.renderOptions.contentLocality = options.context.contentLocality; - } - - engine(file, options, callback); - }; -} - -function wrapForSpecialization(config, engine) { - var spclizr = karka.create(config.specialization.rules); - return function (file, options, callback) { - //generate the specialization map - - if (!options.renderOptions) { - options.renderOptions = {}; - } - - try { - options.renderOptions.specialization = spclizr && spclizr.resolveAll(options); - } catch (e) { - return callback(e); - } - - engine(file, options, callback); - }; -} - -//wrapDustOnLoad makes sure every dust partial that is loaded -// has the right specialization/localization applied on it - -exports.wrapDustOnLoad = function (ext, dustjs, config) { - var conf = {}, - i18n = config.i18n; - - var onLoad = (i18n) ? views[ext].create(config, dustjs) : dustjs.onLoad; - - dustjs.onLoad = function spclOnLoad(name, options, cb) { - var specialization = options && options.specialization; - var mappedName = (specialization && specialization[name] || name); - onLoad(mappedName, options, cb); - }; -}; 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 d1f09e7..0ed8f5d 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "scripts": { "test": "nyc tap test/*.js && npm run lint", "cover": "nyc report", - "lint": "jshint -c .jshintrc index.js lib/ view/" + "lint": "jshint -c .jshintrc index.js lib/" }, "bugs": { "url": "https://github.com/krakenjs/engine-munger/issues" diff --git a/view/dust.js b/view/dust.js deleted file mode 100644 index b7249dd..0000000 --- a/view/dust.js +++ /dev/null @@ -1,65 +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'), - resolver = require('file-resolver'), - path = require('path'), - bl = require('bl'), - VError = require('verror'); - - -//config has -//fallbackLocale -//baseTemplatePath - -exports.create = function (config, dustjs) { - var i18n = config.i18n, - res = resolver.create({ root: i18n.contentPath, ext: 'properties', fallback: i18n.fallback}); - return function onLoad(name, options, callback) { - - var out, localizrOptions, global, locals, locality, props; - - locality = util.localityFromLocals(options); - props = res.resolve(name, locality).file || i18n.contentPath; - - localizrOptions = { - src: path.join(config.views, name + '.dust'), - props: props, - enableMetadata: config.enableMetadata - }; - - out = bl(function (err, data) { - if (err) { - return callback(err); - } - - try { - var tmpl = dustjs.loadSource(dustjs.compile(data.toString('utf-8'), name)); - // FIXME: update template name to include locale, test caching, etc. - callback(null, tmpl); - } catch (e) { - callback(new VError(e, 'Problem rendering dust template named %s', name)); - } - }); - - localizr.createReadStream(localizrOptions).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 7bb7e6d..0000000 --- a/view/js.js +++ /dev/null @@ -1,49 +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, dustjs) { - - var res, - defaultLocale = config.i18n.fallback; - - res = resolver.create({ root: config.views, ext: 'js', fallback: defaultLocale }); - - return function onLoad(name, options, callback) { - var view = res.resolve(name, util.localityFromLocals(options)); - - if (!view.file) { - return callback(new Error('Could not load template ' + name)); - } - - fs.readFile(view.file, 'utf8', function (err, data) { - if (err) { - return callback(err); - } else { - var tmpl = dustjs.loadSource(data); - return callback(null, tmpl); - } - }); - }; - -}; - From c0ec0b69bd3698c74c3172f6c50085f500be0dc3 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Fri, 29 May 2015 23:20:55 -0400 Subject: [PATCH 21/42] Update tests to use expressView, rather than the engine directly --- test/fixtures/testConfig.js | 167 ----------------------- test/index.js | 256 ++++++++++++++++++------------------ 2 files changed, 129 insertions(+), 294 deletions(-) delete mode 100644 test/fixtures/testConfig.js diff --git a/test/fixtures/testConfig.js b/test/fixtures/testConfig.js deleted file mode 100644 index 978fa8e..0000000 --- a/test/fixtures/testConfig.js +++ /dev/null @@ -1,167 +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' - } - } - ] - } - }, - 'context1': { - 'whoAmI': 'badGuy', - views: 'test/fixtures/.build' - }, - 'context2': { - 'whoAmI': 'goodGuy', - 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' - } - } - ] - - } - }, - 'context1': { - views: 'test/fixtures/.build', - whoAmI: 'badGuy', - context: { - locality: 'es_US' - } - }, - 'context2': { - views: 'test/fixtures/.build', - whoAmI: 'goodGuy', - context: { - locality: 'es_US' - } - }, - 'context3': { - views: 'test/fixtures/.build', - whoAmI: 'badGuy', - context: { - locality: 'en_US' - } - }, - 'context4': { - views: 'test/fixtures/.build', - whoAmI: 'goodGuy', - context: { - locality: 'en_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' - } - } - - } -}; diff --git a/test/index.js b/test/index.js index 20b9334..65864d3 100644 --- a/test/index.js +++ b/test/index.js @@ -1,18 +1,15 @@ 'use strict'; -var test = require('tap').test, - engineMunger = require('../index'), - testData = require('./fixtures/testConfig'), - freshy = require('freshy'); - +var test = require('tap').test; +var engineMunger = require('../index'); +var freshy = require('freshy'); +var makeViewClass = require('../lib/expressView').makeViewClass; +var path = require('path'); 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 = testData['none-js'].config; - - 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(); @@ -20,14 +17,22 @@ test('engine-munger', function (t) { }); t.test('when only specialization enabled for js engine', function (t) { - var config = testData['onlySpcl-js'].config; - var context1 = testData['onlySpcl-js'].context1; - var context2 = testData['onlySpcl-js'].context2; - var engine = engineMunger.js(settings, config); - engine('spcl/jekyll', context1, 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, '

☃greeting☃ Hyde

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

☃greeting☃ Jekyll

'); t.end(); @@ -36,9 +41,18 @@ test('engine-munger', function (t) { }); t.test('when only internationalization enabled for js engine', function (t) { - var config = testData['onlyIntl-js'].config, - context = testData['onlyIntl-js'].context; - 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 Don Jekyll

'); t.end(); @@ -47,30 +61,63 @@ test('engine-munger', function (t) { }); t.test('when specialization and internationalization enabled for js engine', function (t) { - var config = testData['spclAndIntl-js'].config; - var context1 = testData['spclAndIntl-js'].context1; - var context2 = testData['spclAndIntl-js'].context2; - var context3 = testData['spclAndIntl-js'].context3; - var context4 = testData['spclAndIntl-js'].context4; - var engine = engineMunger.js(settings, config); - engine('spcl/jekyll', context1, checkContext1); + 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

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

Hola Don Jekyll

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

Hello Mister Hyde

'); - engine('spcl/jekyll', context4, checkContext4); + makeView('js', 'spcl/jekyll', config).render(context4, checkContext4); }; function checkContext4(err, data) { @@ -83,9 +130,9 @@ test('engine-munger', function (t) { t.test('when no specialization or internationalization enabled for dust engine', function (t) { - var config = testData['none-dust'].config; + var config = {}; - 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(); @@ -95,24 +142,22 @@ test('engine-munger', function (t) { t.test('using munger when only specialization enabled for dust engine', function (t) { - var config = testData['onlySpcl-dust'].config, - context = testData['onlySpcl-dust'].context; - - engineMunger.dust(settings, config)('spcl/jekyll', context, function (err, data) { - t.equal(err, null); - t.equal(data, '

Hyde

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

Hyde

'); t.end(); @@ -120,97 +165,37 @@ test('engine-munger', function (t) { }); - t.test('when only internationalization is enabled for dust engine', function (t) { - var config = testData['onlyIntl-dust'].config, - context = testData['onlyIntl-dust'].context; - engineMunger.dust(settings, config)('jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Don Jekyll

'); - t.end(); - }); - - }); - - - t.test('when specialization/internationalization is enabled for dust engine', function(t) { - var config = testData['spclAndIntl-dust'].config, - context = testData['spclAndIntl-dust'].context; - - engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Señor Hyde

'); - t.end(); - }); - }); - - t.test('when specialization/internationalization is enabled for dust engine with cache', function(t) { - var config = testData['spclAndIntl-dust'].config, - context = testData['spclAndIntl-dust'].context, - settings = {cache: true}; - - engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Señor Hyde

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

Hola Don Jekyll

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

Hola Don Jekyll

'); - t.end(); - }); - }); - - t.test('when specialization/internationalization is enabled for dust engine with cache', function(t) { - var config = testData['spclAndIntl-dust'].config, - context = testData['spclAndIntl-dust'].context, - settings = {cache: true}; - - engineMunger.dust(settings, config)('spcl/jekyll', context, function(err, data) { - t.equal(err, null); - t.equal(data, '

Hola Señor Hyde

'); - t.end(); - }); - }); - //error cases t.test('i18n with js engine- template not found case', function(t) { - var config = testData['onlyIntl-js'].config, - context = testData['onlyIntl-js'].context; - engineMunger.js(settings, config)('peekaboo', context, function(err, data) { - t.equal(err.message, 'Could not load template peekaboo'); + var config = { + i18n: { + fallback: 'en-US', + formatPath: krakenFormatPath, + contentPath: 'test/fixtures/properties' + } + }; + makeView('js', 'peekaboo', config).render({}, function(err, data) { + t.ok(/Failed to lookup view "peekaboo" in views directory/.test(err.message)); t.equal(data, undefined); t.end(); }); - }); t.test('i18n dust engine- catch error while compiling invalid dust and report name of broken template', function(err, data) { + var config = { + i18n: { + fallback: 'en-US', + formatPath: krakenFormatPath, + contentPath: 'test/fixtures/properties' + } + }; - var config = testData['onlyIntl-dust'].config, - context = testData['onlyIntl-dust'].context; - engineMunger.dust(settings, config)('invalidTemp', context, function(err, data) { + var context = { + locale: 'es_US' + }; + + makeView('dust', 'invalidTemp', config).render(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); t.end(); @@ -219,3 +204,20 @@ test('engine-munger', function (t) { }); }); + +function makeView(ext, tmpl, config) { + var viewConf = {}; + viewConf[ext] = config; + var View = makeViewClass(viewConf); + var engines = {}; + engines['.' + ext] = engineMunger[ext](); + return new View(tmpl, { + root: ext == 'js' ? path.resolve(__dirname, 'fixtures/.build') : path.resolve(__dirname, 'fixtures/templates'), + defaultEngine: '.' + ext, + engines: engines + }); +} + +function krakenFormatPath(locale) { + return locale.langtag.region + '/' + locale.langtag.language.language; +} From eb6b5cb2cc94451bc3a5a7ee09e1fe8024a7125b Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Sat, 30 May 2015 07:43:24 -0400 Subject: [PATCH 22/42] Make adaro a dev dependency --- index.js | 5 ----- package.json | 2 +- test/index.js | 3 ++- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 87d2fd3..da1f79a 100644 --- a/index.js +++ b/index.js @@ -17,10 +17,5 @@ \*───────────────────────────────────────────────────────────────────────────*/ 'use strict'; -var adaro = require('adaro'); - -exports.dust = adaro.dust; - -exports.js = adaro.js; exports.setupViewClass = require('./lib/expressView'); diff --git a/package.json b/package.json index 0ed8f5d..4d1d3cd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "url": "https://github.com/krakenjs/engine-munger.git" }, "dependencies": { - "adaro": "1.0.0-2", "aproba": "^1.0.1", "bcp47": "^1.1.2", "bcp47-stringify": "^1.0.0", @@ -27,6 +26,7 @@ "verror": "^1.6.0" }, "devDependencies": { + "adaro": "1.0.0-6", "freshy": "0.0.2", "istanbul": "~0.2.4", "jshint": "~2.4.3", diff --git a/test/index.js b/test/index.js index 65864d3..22fb96d 100644 --- a/test/index.js +++ b/test/index.js @@ -4,6 +4,7 @@ var engineMunger = require('../index'); var freshy = require('freshy'); var makeViewClass = require('../lib/expressView').makeViewClass; var path = require('path'); +var adaro = require('adaro'); test('engine-munger', function (t) { var settings = {cache: false}; @@ -210,7 +211,7 @@ function makeView(ext, tmpl, config) { viewConf[ext] = config; var View = makeViewClass(viewConf); var engines = {}; - engines['.' + ext] = engineMunger[ext](); + engines['.' + ext] = adaro[ext](); return new View(tmpl, { root: ext == 'js' ? path.resolve(__dirname, 'fixtures/.build') : path.resolve(__dirname, 'fixtures/templates'), defaultEngine: '.' + ext, From 11dc7f0dadd32b8598c454ae151f14041242fcbc Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 09:21:09 -0400 Subject: [PATCH 23/42] Update the README --- README.md | 121 +++++++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index d973003..ddf8f29 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,88 @@ -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. + +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: +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. -* Dust: Engine types 'js' and 'dust' +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', - 'enableMetadata': true - }, - 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 ``` - - From 565712919138991dfb3b9ec2c5c030403df36de1 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 09:46:22 -0400 Subject: [PATCH 24/42] Remove dead code --- lib/expressView.js | 5 ----- test/index.js | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/expressView.js b/lib/expressView.js index af86036..9446365 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -189,11 +189,6 @@ module.exports = function setupViewClass(options) { module.exports.makeViewClass = makeViewClass; -function enabledForExt(config, ext) { - var extNoDot = ext.slice(1); - return config.engines && (config.engines.indexOf(ext) !== -1 || config.engines.indexOf(extNoDot) !== -1); -} - /** * an fs.stat call that limits the number of outstanding requests to 10. diff --git a/test/index.js b/test/index.js index 22fb96d..b7af31e 100644 --- a/test/index.js +++ b/test/index.js @@ -7,7 +7,6 @@ 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) { makeView('js', 'test', {}).render({}, function(err, data) { From 082ea49110d4559da06d59fb2f14778f4cb23523 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 10:20:31 -0400 Subject: [PATCH 25/42] Make error message for not found template slightly more useful --- lib/expressView.js | 2 +- test/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/expressView.js b/lib/expressView.js index 9446365..a984010 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -135,7 +135,7 @@ function makeViewClass(config) { return cb(err); } else if (!path) { 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 Error('Failed to lookup view "' + view.name + '" in views ' + dirs); + var viewError = new Error('Failed to lookup view "' + name + '" in views ' + dirs); viewError.view = view; return cb(viewError); } else { diff --git a/test/index.js b/test/index.js index b7af31e..9a0ef28 100644 --- a/test/index.js +++ b/test/index.js @@ -175,7 +175,7 @@ test('engine-munger', function (t) { } }; makeView('js', 'peekaboo', config).render({}, function(err, data) { - t.ok(/Failed to lookup view "peekaboo" in views directory/.test(err.message)); + t.match(err.message, /Failed to lookup view "peekaboo.js" in views directory/); t.equal(data, undefined); t.end(); }); From d41cfab43de024fdd90906d9fb3de40211ac98e7 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 10:21:07 -0400 Subject: [PATCH 26/42] Stop checking for i18n on invalid parse check since i18n for a dust engine makes no sense as Kraken uses it, this test can be much simpler. --- package.json | 2 +- test/index.js | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 4d1d3cd..30bc89d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "verror": "^1.6.0" }, "devDependencies": { - "adaro": "1.0.0-6", + "adaro": "1.0.0-9", "freshy": "0.0.2", "istanbul": "~0.2.4", "jshint": "~2.4.3", diff --git a/test/index.js b/test/index.js index 9a0ef28..8a1909e 100644 --- a/test/index.js +++ b/test/index.js @@ -182,13 +182,8 @@ test('engine-munger', function (t) { }); - t.test('i18n dust engine- catch error while compiling invalid dust and report name of broken template', function(err, data) { + t.test('dust engine - catch error while compiling invalid dust and report name of broken template', function(t) { var config = { - i18n: { - fallback: 'en-US', - formatPath: krakenFormatPath, - contentPath: 'test/fixtures/properties' - } }; var context = { From 422073f7eb4986e6a68da6a48db8c7a01c502cf1 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 10:39:56 -0400 Subject: [PATCH 27/42] Check new, better error message --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 8a1909e..bdb896c 100644 --- a/test/index.js +++ b/test/index.js @@ -191,7 +191,7 @@ test('engine-munger', function (t) { }; makeView('dust', 'invalidTemp', config).render(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.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(); }); From 5c5cb941d399a4966189186d37be7c1f7a1667d8 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 10:40:22 -0400 Subject: [PATCH 28/42] simplify lookupMain and test it --- lib/expressView.js | 21 ++++++++------------- test/index.js | 12 ++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/expressView.js b/lib/expressView.js index a984010..3e5daaa 100644 --- a/lib/expressView.js +++ b/lib/expressView.js @@ -104,19 +104,14 @@ function makeViewClass(config) { proto.render = function render(options, fn) { aproba('OF', arguments); var view = this; - if (!view.path || view.path === true) { - view.lookupMain(options, function (err) { - if (err) { - fn(err); - } else { - debug('render "%s"', view.path); - view.engine(view.path, options, fn); - } - }); - } else { - debug('render "%s"', view.path); - view.engine(view.path, options, fn); - } + 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 diff --git a/test/index.js b/test/index.js index bdb896c..1f09830 100644 --- a/test/index.js +++ b/test/index.js @@ -198,6 +198,18 @@ test('engine-munger', function (t) { }); + 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(); + }); + }); + }); + }); function makeView(ext, tmpl, config) { From f224899496e3fcbdecc19b776d8a8fdbf251ca2b Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 11:17:07 -0400 Subject: [PATCH 29/42] Test having multiple roots to search --- test/index.js | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 1f09830..f8c1a46 100644 --- a/test/index.js +++ b/test/index.js @@ -210,6 +210,52 @@ test('engine-munger', function (t) { }); }); + t.test('multiple roots - found', function (t) { + var view = makeView('dust', 'test', { + root: [ + path.resolve(__dirname, 'fixtures', 'not-here'), + path.resolve(__dirname, 'fixtures', 'templates') + ] + }); + view.lookupMain({}, function (err) { + t.error(err); + t.equal(view.path, path.resolve(__dirname, 'fixtures', 'templates', 'test.dust')); + 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 + ] + }); + 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', {}, function (err) { + t.error(err); + if (--pending === 0) { + t.end(); + } + }); + } + }); + }); function makeView(ext, tmpl, config) { @@ -219,7 +265,7 @@ function makeView(ext, tmpl, config) { var engines = {}; engines['.' + ext] = adaro[ext](); return new View(tmpl, { - root: ext == 'js' ? path.resolve(__dirname, 'fixtures/.build') : path.resolve(__dirname, 'fixtures/templates'), + root: config.root ? config.root : ext == 'js' ? path.resolve(__dirname, 'fixtures/.build') : path.resolve(__dirname, 'fixtures/templates'), defaultEngine: '.' + ext, engines: engines }); From a03afac571dbab5cbf710583c86b6181c0c05828 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 11:25:39 -0400 Subject: [PATCH 30/42] Rearrange what is exported by what This gets ready to remove the middleware part into a kraken-specific package. --- index.js | 195 ++++++++++++++++++++++++++++++++++- lib/expressView.js | 246 --------------------------------------------- middleware.js | 54 ++++++++++ package.json | 2 +- test/index.js | 3 +- test/middleware.js | 54 ++++++++++ test/viewClass.js | 64 ------------ 7 files changed, 303 insertions(+), 315 deletions(-) delete mode 100644 lib/expressView.js create mode 100644 middleware.js create mode 100644 test/middleware.js delete mode 100644 test/viewClass.js diff --git a/index.js b/index.js index da1f79a..a7f44db 100644 --- a/index.js +++ b/index.js @@ -15,7 +15,198 @@ │ 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'); -'use strict'; +/** + * 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); -exports.setupViewClass = require('./lib/expressView'); + var conf = normalizeConfigs(config); + + var proto = Object.create(oldView.prototype); + + // 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); + + 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); + } + }); + }, cb); + }; + + /** + * 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 if (!path) { + 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 Error('Failed to lookup view "' + name + '" in views ' + dirs); + viewError.view = view; + return cb(viewError); + } else { + view.path = path; + cb(); + } + }); + }; + + function View(name, options) { + oldView.call(this, name, options); + } + + View.prototype = proto; + View.prototype.constructor = View; + return View; +} + +module.exports = makeViewClass; + +/** + * 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)); + } + + 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--; + } + }; + } +} + +function normalizeConfigs(config) { + var out = {}; + for (var ext in config) { + if (ext[0] === '.') { + out[ext] = normalizeConfig(config[ext]); + } else { + out['.' + ext] = normalizeConfig(config[ext]); + } + } + + 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/expressView.js b/lib/expressView.js deleted file mode 100644 index 3e5daaa..0000000 --- a/lib/expressView.js +++ /dev/null @@ -1,246 +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'); -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'); - -/** - * 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); - - var proto = Object.create(oldView.prototype); - - // 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); - - 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); - } - }); - }, cb); - }; - - /** - * 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 if (!path) { - 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 Error('Failed to lookup view "' + name + '" in views ' + dirs); - viewError.view = view; - return cb(viewError); - } else { - view.path = path; - cb(); - } - }); - }; - - function View(name, options) { - oldView.call(this, name, options); - } - - View.prototype = proto; - View.prototype.constructor = View; - return View; -} - -module.exports = function setupViewClass(options) { - var opts = {}; - opts['.properties'] = {}; - opts['.js'] = {}; - opts['.dust'] = {}; - - if (options.i18n) { - opts['.properties'].root = [].concat(options.i18n.contentPath); - opts['.properties'].i18n = { - fallback: options.i18n.fallback - }; - - opts['.js'].i18n = { - fallback: options.i18n.fallback - }; - } - - if (options.specialization) { - opts['.properties'].specialization = options.specialization; - opts['.js'].specialization = options.specialization; - opts['.dust'].specialization = options.specialization; - } - - var hasConfiguredApp = false; - return function (req, res, next) { - if (!hasConfiguredApp) { - req.app.set('view', makeViewClass(opts)); - hasConfiguredApp = true; - } - next(); - }; -}; - -module.exports.makeViewClass = makeViewClass; - - -/** - * 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)); - } - - 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--; - } - }; - } -} - -function normalizeConfigs(config) { - var out = {}; - for (var ext in config) { - if (ext[0] === '.') { - out[ext] = normalizeConfig(config[ext]); - } else { - out['.' + ext] = normalizeConfig(config[ext]); - } - } - - 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/middleware.js b/middleware.js new file mode 100644 index 0000000..9f0a972 --- /dev/null +++ b/middleware.js @@ -0,0 +1,54 @@ +/*───────────────────────────────────────────────────────────────────────────*\ + │ 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 makeViewClass = require('./'); + +module.exports = function setupViewClass(options) { + var opts = {}; + opts['.properties'] = {}; + opts['.js'] = {}; + opts['.dust'] = {}; + + if (options.i18n) { + opts['.properties'].root = [].concat(options.i18n.contentPath); + opts['.properties'].i18n = { + fallback: options.i18n.fallback + }; + + opts['.js'].i18n = { + fallback: options.i18n.fallback + }; + } + + if (options.specialization) { + opts['.properties'].specialization = options.specialization; + opts['.js'].specialization = options.specialization; + opts['.dust'].specialization = options.specialization; + } + + var hasConfiguredApp = false; + return function (req, res, next) { + if (!hasConfiguredApp) { + req.app.set('view', makeViewClass(opts)); + hasConfiguredApp = true; + } + next(); + }; +}; + diff --git a/package.json b/package.json index 30bc89d..17b1efd 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "scripts": { "test": "nyc tap test/*.js && npm run lint", "cover": "nyc report", - "lint": "jshint -c .jshintrc index.js lib/" + "lint": "jshint -c .jshintrc index.js" }, "bugs": { "url": "https://github.com/krakenjs/engine-munger/issues" diff --git a/test/index.js b/test/index.js index f8c1a46..ca306e8 100644 --- a/test/index.js +++ b/test/index.js @@ -1,8 +1,7 @@ 'use strict'; var test = require('tap').test; -var engineMunger = require('../index'); var freshy = require('freshy'); -var makeViewClass = require('../lib/expressView').makeViewClass; +var makeViewClass = require('../'); var path = require('path'); var adaro = require('adaro'); diff --git a/test/middleware.js b/test/middleware.js new file mode 100644 index 0000000..32b98ad --- /dev/null +++ b/test/middleware.js @@ -0,0 +1,54 @@ +var test = require('tap').test; +var View = require('../')({}); +var setupViewClass = require('../middleware'); +var express = require('express'); +var supertest = require('supertest'); +var path = require('path'); + +test('first-run middleware', function (t) { + var app = express(); + var view, afterView; + + app.set('view engine', 'fake'); + app.engine('fake', fakeEngine); + app.use(function (req, res, next) { + view = app.get('view'); + t.ok(view); + next(); + }); + app.use(setupViewClass({ + i18n: { + contentPath: path.resolve(__dirname, 'fixtures', 'properties') + }, + specialization: { + 'spcl/jekyll': [ + { + is: 'spcl/hyde', + when: { + 'whoAmI': 'badGuy' + } + } + ] + } + })); + app.use(function (req, res, next) { + afterView = app.get('view'); + t.ok(afterView); + next(); + }); + app.get('/', function (req, res) { + res.end('got it'); + }); + + supertest(app).get('/').end(function (err, res) { + t.error(err); + t.ok(res); + t.notEqual(afterView, view); + t.end(); + }); + +}); + +function fakeEngine() { + console.log('fake engine called', arguments); +} diff --git a/test/viewClass.js b/test/viewClass.js deleted file mode 100644 index c51e890..0000000 --- a/test/viewClass.js +++ /dev/null @@ -1,64 +0,0 @@ -var test = require('tap').test; -var makeViewClass = require('../lib/expressView').makeViewClass; -var path = require('path'); - -test('view class lookup plain', function (t) { - var View = makeViewClass({}); - var v = new View('test', { - root: path.resolve(__dirname, 'fixtures/templates'), - defaultEngine: '.dust', - engines: { - '.dust': fakeEngine - } - }); - v.lookup('test.dust', {}, function (err, result) { - t.error(err); - t.equal(result, path.resolve(__dirname, 'fixtures/templates/test.dust')); - t.end(); - }); -}); - -test('view class lookup i18n', function (t) { - var View = makeViewClass({ - ".js": { - "i18n": { - "fallback": "en-US", - "formatPath": function (locale) { - return path.join(locale.langtag.region, locale.langtag.language.language); - } - } - } - }); - var v = new View('test', { - root: path.resolve(__dirname, 'fixtures/.build'), - defaultEngine: '.js', - engines: { - '.js': fakeEngine - } - }); - v.lookup('test.js', {}, function (err, result) { - t.error(err); - t.equal(result, path.resolve(__dirname, 'fixtures/.build/US/en/test.js')); - t.end(); - }); -}); - -test('view class lookupMain', function (t) { - var View = makeViewClass({}); - var v = new View('test', { - root: path.resolve(__dirname, 'fixtures/templates'), - defaultEngine: '.dust', - engines: { - '.dust': fakeEngine - } - }); - v.lookupMain({}, function (err) { - t.error(err); - t.equal(v.path, path.resolve(__dirname, 'fixtures/templates/test.dust')); - t.end(); - }); -}); - -function fakeEngine() { - console.log('fake engine called', arguments); -} From 985f29710e8b127640bf759f2dd126e31d025065 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 14:49:34 -0400 Subject: [PATCH 31/42] 1.0.0-2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17b1efd..ff9312a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine-munger", - "version": "0.2.6", + "version": "1.0.0-2", "description": "An engine munger for express apps.", "main": "index.js", "author": { From 0d1f0a1491d2324e18e111ce6d83373889b8e2e8 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 14:54:59 -0400 Subject: [PATCH 32/42] Improve package.json description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff9312a..6c87de9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "engine-munger", "version": "1.0.0-2", - "description": "An engine munger for express apps.", + "description": "A replacement Express view class that provides asynchronous resolution, internationalization and specialization support", "main": "index.js", "author": { "name": "Poornima Venkatakrishnan", From 528b2c193f59fe260ca1db223a38b37401f6f6e9 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 14:55:06 -0400 Subject: [PATCH 33/42] Add myself as a contributor --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 6c87de9..c256b3c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,10 @@ "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" From 7c77448c2c92a539673795b4636d3d4f0ed25f3b Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 15:27:54 -0400 Subject: [PATCH 34/42] Remove middleware, moved to makara --- middleware.js | 54 ---------------------------------------------- package.json | 2 -- test/middleware.js | 54 ---------------------------------------------- 3 files changed, 110 deletions(-) delete mode 100644 middleware.js delete mode 100644 test/middleware.js diff --git a/middleware.js b/middleware.js deleted file mode 100644 index 9f0a972..0000000 --- a/middleware.js +++ /dev/null @@ -1,54 +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 makeViewClass = require('./'); - -module.exports = function setupViewClass(options) { - var opts = {}; - opts['.properties'] = {}; - opts['.js'] = {}; - opts['.dust'] = {}; - - if (options.i18n) { - opts['.properties'].root = [].concat(options.i18n.contentPath); - opts['.properties'].i18n = { - fallback: options.i18n.fallback - }; - - opts['.js'].i18n = { - fallback: options.i18n.fallback - }; - } - - if (options.specialization) { - opts['.properties'].specialization = options.specialization; - opts['.js'].specialization = options.specialization; - opts['.dust'].specialization = options.specialization; - } - - var hasConfiguredApp = false; - return function (req, res, next) { - if (!hasConfiguredApp) { - req.app.set('view', makeViewClass(opts)); - hasConfiguredApp = true; - } - next(); - }; -}; - diff --git a/package.json b/package.json index c256b3c..347de7e 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,7 @@ "freshy": "0.0.2", "istanbul": "~0.2.4", "jshint": "~2.4.3", - "mockery": "~1.4.0", "nyc": "^2.0.5", - "supertest": "^1.0.1", "tap": "^1.0.8" }, "scripts": { diff --git a/test/middleware.js b/test/middleware.js deleted file mode 100644 index 32b98ad..0000000 --- a/test/middleware.js +++ /dev/null @@ -1,54 +0,0 @@ -var test = require('tap').test; -var View = require('../')({}); -var setupViewClass = require('../middleware'); -var express = require('express'); -var supertest = require('supertest'); -var path = require('path'); - -test('first-run middleware', function (t) { - var app = express(); - var view, afterView; - - app.set('view engine', 'fake'); - app.engine('fake', fakeEngine); - app.use(function (req, res, next) { - view = app.get('view'); - t.ok(view); - next(); - }); - app.use(setupViewClass({ - i18n: { - contentPath: path.resolve(__dirname, 'fixtures', 'properties') - }, - specialization: { - 'spcl/jekyll': [ - { - is: 'spcl/hyde', - when: { - 'whoAmI': 'badGuy' - } - } - ] - } - })); - app.use(function (req, res, next) { - afterView = app.get('view'); - t.ok(afterView); - next(); - }); - app.get('/', function (req, res) { - res.end('got it'); - }); - - supertest(app).get('/').end(function (err, res) { - t.error(err); - t.ok(res); - t.notEqual(afterView, view); - t.end(); - }); - -}); - -function fakeEngine() { - console.log('fake engine called', arguments); -} From 206295ab86d119b5877bbebfa1e955802bad8013 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Mon, 1 Jun 2015 15:31:42 -0400 Subject: [PATCH 35/42] Remove direct devDependency on istanbul, we have nyc for that --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 347de7e..89a8495 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "devDependencies": { "adaro": "1.0.0-9", "freshy": "0.0.2", - "istanbul": "~0.2.4", "jshint": "~2.4.3", "nyc": "^2.0.5", "tap": "^1.0.8" From 7e8422cfa331e13d8de8e66779af4abbbc8aeee6 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 13:18:07 -0400 Subject: [PATCH 36/42] Add reference to Express 5 change --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ddf8f29..4776b19 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ engine-munger ============= -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. +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) From c600fda060c3da3ffc9e75f06c22b4375041f6c6 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 14:35:06 -0400 Subject: [PATCH 37/42] Expose regnerate-test-data script --- package.json | 6 ++++-- test/fixtures/compile-test-data.sh | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) mode change 100644 => 100755 test/fixtures/compile-test-data.sh diff --git a/package.json b/package.json index 89a8495..051e7a3 100644 --- a/package.json +++ b/package.json @@ -25,21 +25,23 @@ "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": { "adaro": "1.0.0-9", + "dustjs-linkedin": "^2.7.1", "freshy": "0.0.2", "jshint": "~2.4.3", + "localizr": "^1.1.0", "nyc": "^2.0.5", "tap": "^1.0.8" }, "scripts": { "test": "nyc tap test/*.js && npm run lint", "cover": "nyc report", - "lint": "jshint -c .jshintrc index.js" + "lint": "jshint -c .jshintrc index.js", + "regenerate-test-data": "./test/fixtures/compile-test-data.sh" }, "bugs": { "url": "https://github.com/krakenjs/engine-munger/issues" diff --git a/test/fixtures/compile-test-data.sh b/test/fixtures/compile-test-data.sh old mode 100644 new mode 100755 index b483435..91ce51b --- a/test/fixtures/compile-test-data.sh +++ b/test/fixtures/compile-test-data.sh @@ -1,5 +1,7 @@ #!/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 From 457e310ea2f7e9d920aaed17b63504e8ac2a64e4 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 15:30:02 -0400 Subject: [PATCH 38/42] Improve .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5661071..a6aede8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ coverage/ npm-debug.log *.swp test/fixtures/tmp/ -nyc_output/ +.nyc_output/ +tags From 9c8b2991f17b013c6c76bda8a630591ef5a9dc04 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 16:15:40 -0400 Subject: [PATCH 39/42] Move error handling into lookup rather than lookupMain to improve ergonomics of lookup --- index.js | 19 +++++++++++++------ test/index.js | 4 +++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index a7f44db..b709e95 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,7 @@ var karka = require('karka'); var aproba = require('aproba'); var bcp47 = require('bcp47'); var bcp47stringify = require('bcp47-stringify'); +var VError = require('verror'); /** * Make a View class that uses our configuration, set far in advance of @@ -79,6 +80,8 @@ function makeViewClass(config) { debug('lookup "%s"', name); + var view = this; + permutron.raw(search, function (candidate, next) { var resolved = path.resolve.apply(null, candidate); limitStat(resolved, function (err, stat) { @@ -91,7 +94,16 @@ function makeViewClass(config) { cb(err); } }); - }, cb); + }, 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); + } + }); }; /** @@ -128,11 +140,6 @@ function makeViewClass(config) { this.lookup(name, options, function (err, path) { if (err) { return cb(err); - } else if (!path) { - 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 Error('Failed to lookup view "' + name + '" in views ' + dirs); - viewError.view = view; - return cb(viewError); } else { view.path = path; cb(); diff --git a/test/index.js b/test/index.js index ca306e8..8414eb2 100644 --- a/test/index.js +++ b/test/index.js @@ -246,7 +246,7 @@ test('engine-munger', function (t) { var pending = 0; for (var i = 0; i < 11; i++) { pending++; - view.lookup('test', {}, function (err) { + view.lookup('test.dust', {}, function (err) { t.error(err); if (--pending === 0) { t.end(); @@ -263,6 +263,8 @@ function makeView(ext, tmpl, 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, From 5af265aaa3b018266f5ba5bc5bfc386225c5f8cf Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 16:51:23 -0400 Subject: [PATCH 40/42] Bump adaro to get version that always passes the extension to lookup --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 051e7a3..19837d4 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "verror": "^1.6.0" }, "devDependencies": { - "adaro": "1.0.0-9", + "adaro": "1.0.0-10", "dustjs-linkedin": "^2.7.1", "freshy": "0.0.2", "jshint": "~2.4.3", From 6f993104a64f58b7e704360b89655cd7b16df778 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 16:51:27 -0400 Subject: [PATCH 41/42] 1.0.0-3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 19837d4..e688eae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "engine-munger", - "version": "1.0.0-2", + "version": "1.0.0-3", "description": "A replacement Express view class that provides asynchronous resolution, internationalization and specialization support", "main": "index.js", "author": { From 0a7d7c27c1326ac732055c1d360181398e90e198 Mon Sep 17 00:00:00 2001 From: Aria Stewart Date: Tue, 2 Jun 2015 23:10:16 -0400 Subject: [PATCH 42/42] Remove useless freshy dependency --- package.json | 1 - test/index.js | 1 - 2 files changed, 2 deletions(-) diff --git a/package.json b/package.json index e688eae..d3d66f3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "devDependencies": { "adaro": "1.0.0-10", "dustjs-linkedin": "^2.7.1", - "freshy": "0.0.2", "jshint": "~2.4.3", "localizr": "^1.1.0", "nyc": "^2.0.5", diff --git a/test/index.js b/test/index.js index 8414eb2..aa4627c 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,5 @@ 'use strict'; var test = require('tap').test; -var freshy = require('freshy'); var makeViewClass = require('../'); var path = require('path'); var adaro = require('adaro');