summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkpdecker <kpdecker@gmail.com>2015-08-04 09:26:19 -0500
committerkpdecker <kpdecker@gmail.com>2015-08-04 09:26:19 -0500
commita62cbad95acdf544dc9daae9834588453ee1f835 (patch)
treef83f5e1f353d63b3c4fb887e6755319d73184099
parentba31ef8ae4964f2cc9cb6bedd44d7dbf829693ab (diff)
downloadhandlebars.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-xbin/handlebars14
-rw-r--r--lib/precompiler.js154
-rw-r--r--spec/precompiler.js98
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');
+ });
+ });
});