Skip to content

Commit 0de8dac

Browse files
committed
Add support for string and stdin precompilation
Fixes #1071
1 parent d716fd0 commit 0de8dac

File tree

4 files changed

+130
-4
lines changed

4 files changed

+130
-4
lines changed

bin/handlebars

+11
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ var optimist = require('optimist')
5454
'description': 'Output template function only.',
5555
'alias': 'simple'
5656
},
57+
'N': {
58+
'type': 'string',
59+
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
60+
'alias': 'name'
61+
},
62+
'i': {
63+
'type': 'string',
64+
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
65+
'alias': 'string'
66+
},
5767
'r': {
5868
'type': 'string',
5969
'description': 'Template root. Base value that will be stripped from template names.',
@@ -92,6 +102,7 @@ var optimist = require('optimist')
92102
}
93103
})
94104

105+
.wrap(120)
95106
.check(function(argv) {
96107
if (argv.version) {
97108
return;

lib/precompiler.js

+72-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,64 @@ import {SourceMapConsumer, SourceNode} from 'source-map';
77
import uglify from 'uglify-js';
88

99
module.exports.loadTemplates = function(opts, callback) {
10+
loadStrings(opts, function(err, strings) {
11+
if (err) {
12+
callback(err);
13+
} else {
14+
loadFiles(opts, function(err, files) {
15+
if (err) {
16+
callback(err);
17+
} else {
18+
opts.templates = strings.concat(files);
19+
callback(undefined, opts);
20+
}
21+
});
22+
}
23+
});
24+
};
25+
26+
function loadStrings(opts, callback) {
27+
let strings = arrayCast(opts.string),
28+
names = arrayCast(opts.name);
29+
30+
if (names.length !== strings.length
31+
&& strings.length > 1) {
32+
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
33+
}
34+
35+
Async.map(strings, function(string, callback) {
36+
if (string !== '-') {
37+
callback(undefined, string);
38+
} else {
39+
// Load from stdin
40+
let buffer = '';
41+
process.stdin.setEncoding('utf8');
42+
43+
process.stdin.on('data', function(chunk) {
44+
buffer += chunk;
45+
});
46+
process.stdin.on('end', function() {
47+
callback(undefined, buffer);
48+
});
49+
}
50+
},
51+
function(err, strings) {
52+
strings = strings.map((string, index) => ({
53+
name: names[index],
54+
path: names[index],
55+
source: string
56+
}));
57+
callback(err, strings);
58+
});
59+
}
60+
61+
function loadFiles(opts, callback) {
1062
// Build file extension pattern
1163
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
1264
extension = new RegExp('\\.' + extension + '$');
1365

1466
let ret = [],
15-
queue = opts.files.map((template) => ({template, root: opts.root}));
67+
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
1668
Async.whilst(() => queue.length, function(callback) {
1769
let {template: path, root} = queue.shift();
1870

@@ -74,9 +126,7 @@ module.exports.loadTemplates = function(opts, callback) {
74126
if (err) {
75127
callback(err);
76128
} else {
77-
opts.templates = ret;
78-
79-
callback(undefined, opts);
129+
callback(undefined, ret);
80130
}
81131
});
82132
}
@@ -100,6 +150,12 @@ module.exports.cli = function(opts) {
100150
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
101151
}
102152

153+
// Force simple mode if we have only one template and it's unnamed.
154+
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
155+
&& !opts.templates[0].name) {
156+
opts.simple = true;
157+
}
158+
103159
// Convert the known list into a hash
104160
let known = {};
105161
if (opts.known && !Array.isArray(opts.known)) {
@@ -156,6 +212,10 @@ module.exports.cli = function(opts) {
156212
if (opts.simple) {
157213
output.add([precompiled, '\n']);
158214
} else {
215+
if (!template.name) {
216+
throw new Handlebars.Exception('Name missing for template');
217+
}
218+
159219
if (opts.amd && !multiple) {
160220
output.add('return ');
161221
}
@@ -206,3 +266,11 @@ module.exports.cli = function(opts) {
206266
console.log(output);
207267
}
208268
};
269+
270+
function arrayCast(value) {
271+
value = value != null ? value : [];
272+
if (!Array.isArray(value)) {
273+
value = [value];
274+
}
275+
return value;
276+
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"jison": "~0.3.0",
5454
"keen.io": "0.0.3",
5555
"mocha": "~1.20.0",
56+
"mock-stdin": "^0.3.0",
5657
"mustache": "0.x",
5758
"semver": "^4.0.0",
5859
"underscore": "^1.5.1"

spec/precompiler.js

+46
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ describe('precompiler', function() {
7171
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
7272
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
7373
});
74+
it('should throw when missing name', function() {
75+
shouldThrow(function() {
76+
Precompiler.cli({templates: [{source: ''}], amd: true});
77+
}, Handlebars.Exception, 'Name missing for template');
78+
});
7479
it('should throw when combining simple and directories', function() {
7580
shouldThrow(function() {
7681
Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
@@ -82,6 +87,11 @@ describe('precompiler', function() {
8287
Precompiler.cli({templates: [emptyTemplate], simple: true});
8388
equal(log, 'simple\n');
8489
});
90+
it('should default to simple templates', function() {
91+
Handlebars.precompile = function() { return 'simple'; };
92+
Precompiler.cli({templates: [{source: ''}]});
93+
equal(log, 'simple\n');
94+
});
8595
it('should output amd templates', function() {
8696
Handlebars.precompile = function() { return 'amd'; };
8797
Precompiler.cli({templates: [emptyTemplate], amd: true});
@@ -197,6 +207,42 @@ describe('precompiler', function() {
197207
});
198208
});
199209

210+
it('should accept string inputs', function(done) {
211+
var opts = {string: ''};
212+
Precompiler.loadTemplates(opts, function(err, opts) {
213+
equal(opts.templates[0].name, undefined);
214+
equal(opts.templates[0].source, '');
215+
done(err);
216+
});
217+
});
218+
it('should accept string array inputs', function(done) {
219+
var opts = {string: ['', 'bar'], name: ['beep', 'boop']};
220+
Precompiler.loadTemplates(opts, function(err, opts) {
221+
equal(opts.templates[0].name, 'beep');
222+
equal(opts.templates[0].source, '');
223+
equal(opts.templates[1].name, 'boop');
224+
equal(opts.templates[1].source, 'bar');
225+
done(err);
226+
});
227+
});
228+
it('should accept stdin input', function(done) {
229+
var stdin = require('mock-stdin').stdin();
230+
Precompiler.loadTemplates({string: '-'}, function(err, opts) {
231+
equal(opts.templates[0].source, 'foo');
232+
done(err);
233+
});
234+
stdin.send('fo');
235+
stdin.send('o');
236+
stdin.end();
237+
});
238+
it('error on name missing', function(done) {
239+
var opts = {string: ['', 'bar']};
240+
Precompiler.loadTemplates(opts, function(err) {
241+
equal(err.message, 'Number of names did not match the number of string inputs');
242+
done();
243+
});
244+
});
245+
200246
it('should complete when no args are passed', function(done) {
201247
Precompiler.loadTemplates({}, function(err, opts) {
202248
equal(opts.templates.length, 0);

0 commit comments

Comments
 (0)