diff options
author | kpdecker <kpdecker@gmail.com> | 2015-08-04 09:26:19 -0500 |
---|---|---|
committer | kpdecker <kpdecker@gmail.com> | 2015-08-04 09:26:19 -0500 |
commit | a62cbad95acdf544dc9daae9834588453ee1f835 (patch) | |
tree | f83f5e1f353d63b3c4fb887e6755319d73184099 | |
parent | ba31ef8ae4964f2cc9cb6bedd44d7dbf829693ab (diff) | |
download | handlebars.js-a62cbad95acdf544dc9daae9834588453ee1f835.zip handlebars.js-a62cbad95acdf544dc9daae9834588453ee1f835.tar.gz handlebars.js-a62cbad95acdf544dc9daae9834588453ee1f835.tar.bz2 |
Refactor precompiler API into two phase
Load templates and then parse them in a distinct operation. This will allow us to use other input sources such as stdin and strings.
-rwxr-xr-x | bin/handlebars | 14 | ||||
-rw-r--r-- | lib/precompiler.js | 154 | ||||
-rw-r--r-- | spec/precompiler.js | 98 |
3 files changed, 149 insertions, 117 deletions
diff --git a/bin/handlebars b/bin/handlebars index 4ed98b3..10cc6c5 100755 --- a/bin/handlebars +++ b/bin/handlebars @@ -26,7 +26,7 @@ var optimist = require('optimist') 'type': 'string', 'description': 'Path to handlebar.js (only valid for amd-style)', 'alias': 'handlebarPath', - 'default': '' + 'default': '' }, 'k': { 'type': 'string', @@ -59,12 +59,12 @@ var optimist = require('optimist') 'description': 'Template root. Base value that will be stripped from template names.', 'alias': 'root' }, - 'p' : { + 'p': { 'type': 'boolean', 'description': 'Compiling a partial template', 'alias': 'partial' }, - 'd' : { + 'd': { 'type': 'boolean', 'description': 'Include data when compiling', 'alias': 'data' @@ -103,9 +103,11 @@ var argv = optimist.argv; argv.templates = argv._; delete argv._; +var Precompiler = require('../dist/cjs/precompiler'); +Precompiler.loadTemplates(argv); + if (argv.help || (!argv.templates.length && !argv.version)) { optimist.showHelp(); - return; +} else { + Precompiler.cli(argv); } - -return require('../dist/cjs/precompiler').cli(argv); diff --git a/lib/precompiler.js b/lib/precompiler.js index 48cfebd..2809b1b 100644 --- a/lib/precompiler.js +++ b/lib/precompiler.js @@ -5,28 +5,73 @@ import {basename} from 'path'; import {SourceMapConsumer, SourceNode} from 'source-map'; import uglify from 'uglify-js'; +module.exports.loadTemplates = function(opts) { + // Build file extension pattern + let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; }); + extension = new RegExp('\\.' + extension + '$'); + + let ret = []; + function processTemplate(template, root) { + let path = template, + stat; + try { + stat = fs.statSync(template); + } catch (err) { + throw new Handlebars.Exception(`Unable to open template file "${template}"`); + } + + if (stat.isDirectory()) { + opts.hasDirectory = true; + + fs.readdirSync(template).map(function(file) { + let childPath = template + '/' + file; + + if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) { + processTemplate(childPath, root || template); + } + }); + } else { + let data = fs.readFileSync(path, 'utf8'); + + if (opts.bom && data.indexOf('\uFEFF') === 0) { + data = data.substring(1); + } + + // Clean the template name + if (!root) { + template = basename(template); + } else if (template.indexOf(root) === 0) { + template = template.substring(root.length + 1); + } + template = template.replace(extension, ''); + + ret.push({ + path: path, + name: template, + source: data + }); + } + } + opts.templates.forEach(function(template) { + processTemplate(template, opts.root); + }); + opts.templates = ret; +}; + module.exports.cli = function(opts) { if (opts.version) { console.log(Handlebars.VERSION); return; } - if (!opts.templates.length) { + if (!opts.templates.length && !opts.hasDirectory) { throw new Handlebars.Exception('Must define at least one template or directory.'); } - opts.templates.forEach(function(template) { - try { - fs.statSync(template); - } catch (err) { - throw new Handlebars.Exception(`Unable to open template file "${template}"`); - } - }); - if (opts.simple && opts.min) { throw new Handlebars.Exception('Unable to minimize simple output'); } - if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) { + if (opts.simple && (opts.templates.length !== 1 || opts.hasDirectory)) { throw new Handlebars.Exception('Unable to output multiple templates in simple mode'); } @@ -41,10 +86,6 @@ module.exports.cli = function(opts) { } } - // Build file extension pattern - let extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; }); - extension = new RegExp('\\.' + extension + '$'); - let output = new SourceNode(); if (!opts.simple) { if (opts.amd) { @@ -63,76 +104,47 @@ module.exports.cli = function(opts) { } output.add('{};\n'); } - function processTemplate(template, root) { - let path = template, - stat = fs.statSync(path); - if (stat.isDirectory()) { - fs.readdirSync(template).map(function(file) { - let childPath = template + '/' + file; - if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) { - processTemplate(childPath, root || template); - } - }); - } else { - let data = fs.readFileSync(path, 'utf8'); - - if (opts.bom && data.indexOf('\uFEFF') === 0) { - data = data.substring(1); - } - - let options = { - knownHelpers: known, - knownHelpersOnly: opts.o - }; + opts.templates.forEach(function(template) { + let options = { + knownHelpers: known, + knownHelpersOnly: opts.o + }; - if (opts.map) { - options.srcName = path; - } - if (opts.data) { - options.data = true; - } + if (opts.map) { + options.srcName = template.path; + } + if (opts.data) { + options.data = true; + } - // Clean the template name - if (!root) { - template = basename(template); - } else if (template.indexOf(root) === 0) { - template = template.substring(root.length + 1); - } - template = template.replace(extension, ''); + let precompiled = Handlebars.precompile(template.source, options); - let precompiled = Handlebars.precompile(data, options); + // If we are generating a source map, we have to reconstruct the SourceNode object + if (opts.map) { + let consumer = new SourceMapConsumer(precompiled.map); + precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer); + } - // If we are generating a source map, we have to reconstruct the SourceNode object - if (opts.map) { - let consumer = new SourceMapConsumer(precompiled.map); - precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer); + if (opts.simple) { + output.add([precompiled, '\n']); + } else if (opts.partial) { + if (opts.amd && (opts.templates.length == 1 && !opts.hasDirectory)) { + output.add('return '); } - - if (opts.simple) { - output.add([precompiled, '\n']); - } else if (opts.partial) { - if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { - output.add('return '); - } - output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']); - } else { - if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) { - output.add('return '); - } - output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']); + output.add(['Handlebars.partials[\'', template.name, '\'] = template(', precompiled, ');\n']); + } else { + if (opts.amd && (opts.templates.length == 1 && !opts.hasDirectory)) { + output.add('return '); } + output.add(['templates[\'', template.name, '\'] = template(', precompiled, ');\n']); } - } - - opts.templates.forEach(function(template) { - processTemplate(template, opts.root); }); // Output the content if (!opts.simple) { if (opts.amd) { - if (opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) { + if (opts.templates.length > 1 || (opts.templates.length == 1 && opts.hasDirectory)) { if (opts.partial) { output.add('return Handlebars.partials;\n'); } else { diff --git a/spec/precompiler.js b/spec/precompiler.js index 21e25b0..f9cc8fd 100644 --- a/spec/precompiler.js +++ b/spec/precompiler.js @@ -16,6 +16,12 @@ describe('precompiler', function() { precompile, minify, + emptyTemplate = { + path: __dirname + '/artifacts/empty.handlebars', + name: 'empty', + source: '' + }, + file, content, writeFileSync; @@ -51,10 +57,9 @@ describe('precompiler', function() { Precompiler.cli({templates: []}); }, Handlebars.Exception, 'Must define at least one template or directory.'); }); - it('should throw on missing template', function() { - shouldThrow(function() { - Precompiler.cli({templates: ['foo']}); - }, Handlebars.Exception, 'Unable to open template file "foo"'); + it('should handle empty/filtered directories', function() { + Precompiler.cli({hasDirectory: true, templates: []}); + // Success is not throwing }); it('should throw when combining simple and minimized', function() { shouldThrow(function() { @@ -68,105 +73,118 @@ describe('precompiler', function() { }); it('should throw when combining simple and directories', function() { shouldThrow(function() { - Precompiler.cli({templates: [__dirname], simple: true}); + Precompiler.cli({hasDirectory: true, templates: [1], simple: true}); }, Handlebars.Exception, 'Unable to output multiple templates in simple mode'); }); - it('should enumerate directories by extension', function() { - Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hbs'}); - equal(/'example_2'/.test(log), true); - log = ''; - Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'handlebars'}); - equal(/'empty'/.test(log), true); - equal(/'example_1'/.test(log), true); - }); - it('should protect from regexp patterns', function() { - Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hb(s'}); - // Success is not throwing - }); it('should output simple templates', function() { Handlebars.precompile = function() { return 'simple'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], simple: true}); equal(log, 'simple\n'); }); it('should output amd templates', function() { Handlebars.precompile = function() { return 'amd'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], amd: true}); equal(/template\(amd\)/.test(log), true); }); it('should output multiple amd', function() { Handlebars.precompile = function() { return 'amd'; }; - Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'}); + Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'}); equal(/templates = foo = foo \|\|/.test(log), true); equal(/return templates/.test(log), true); equal(/template\(amd\)/.test(log), true); }); it('should output amd partials', function() { Handlebars.precompile = function() { return 'amd'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, partial: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true}); equal(/return Handlebars\.partials\['empty'\]/.test(log), true); equal(/template\(amd\)/.test(log), true); }); it('should output multiple amd partials', function() { Handlebars.precompile = function() { return 'amd'; }; - Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, partial: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true}); equal(/return Handlebars\.partials\[/.test(log), false); equal(/template\(amd\)/.test(log), true); }); it('should output commonjs templates', function() { Handlebars.precompile = function() { return 'commonjs'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], commonjs: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], commonjs: true}); equal(/template\(commonjs\)/.test(log), true); }); it('should set data flag', function() { Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', data: true}); + Precompiler.cli({templates: [emptyTemplate], simple: true, data: true}); equal(log, 'simple\n'); }); it('should set known helpers', function() { Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'}); - equal(log, 'simple\n'); - }); - - it('should handle different root', function() { - Handlebars.precompile = function() { return 'simple'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', root: 'foo/'}); + Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'}); equal(log, 'simple\n'); }); it('should output to file system', function() { Handlebars.precompile = function() { return 'simple'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', output: 'file!'}); + Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'}); equal(file, 'file!'); equal(content, 'simple\n'); equal(log, ''); }); - it('should handle BOM', function() { - Handlebars.precompile = function(template) { return template === 'a' ? 'simple' : 'fail'; }; - Precompiler.cli({templates: [__dirname + '/artifacts/bom.handlebars'], simple: true, extension: 'handlebars', bom: true}); - equal(log, 'simple\n'); - }); it('should output minimized templates', function() { Handlebars.precompile = function() { return 'amd'; }; uglify.minify = function() { return {code: 'min'}; }; - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], min: true}); equal(log, 'min'); }); it('should output map', function() { - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'}); equal(file, 'foo.js.map'); equal(/sourceMappingURL=/.test(log), true); }); it('should output map', function() { - Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, map: 'foo.js.map', extension: 'handlebars'}); + Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'}); equal(file, 'foo.js.map'); equal(/sourceMappingURL=/.test(log), true); }); + + describe('#loadTemplates', function() { + it('should throw on missing template', function() { + shouldThrow(function() { + Precompiler.loadTemplates({templates: ['foo']}); + }, Handlebars.Exception, 'Unable to open template file "foo"'); + }); + it('should enumerate directories by extension', function() { + var opts = {templates: [__dirname + '/artifacts'], extension: 'hbs'}; + Precompiler.loadTemplates(opts); + equal(opts.templates.length, 1); + equal(opts.templates[0].name, 'example_2'); + + opts = {templates: [__dirname + '/artifacts'], extension: 'handlebars'}; + Precompiler.loadTemplates(opts); + equal(opts.templates.length, 3); + equal(opts.templates[0].name, 'bom'); + equal(opts.templates[1].name, 'empty'); + equal(opts.templates[2].name, 'example_1'); + }); + it('should handle regular expression characters in extensions', function() { + Precompiler.loadTemplates({templates: [__dirname + '/artifacts'], extension: 'hb(s'}); + // Success is not throwing + }); + it('should handle BOM', function() { + var opts = {templates: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true}; + Precompiler.loadTemplates(opts); + equal(opts.templates[0].source, 'a'); + }); + + it('should handle different root', function() { + var opts = {templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'}; + Precompiler.loadTemplates(opts); + equal(opts.templates[0].name, __dirname + '/artifacts/empty'); + }); + }); }); |