1 |
|
2 | import Async from 'neo-async';
|
3 | import fs from 'fs';
|
4 | import * as Handlebars from './handlebars';
|
5 | import {basename} from 'path';
|
6 | import {SourceMapConsumer, SourceNode} from 'source-map';
|
7 |
|
8 |
|
9 | 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 |
|
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) {
|
62 |
|
63 | let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-[\]]/g, function(arg) { return '\\' + arg; });
|
64 | extension = new RegExp('\\.' + extension + '$');
|
65 |
|
66 | let ret = [],
|
67 | queue = (opts.files || []).map((template) => ({template, root: opts.root}));
|
68 | Async.whilst(() => queue.length, function(callback) {
|
69 | let {template: path, root} = queue.shift();
|
70 |
|
71 | fs.stat(path, function(err, stat) {
|
72 | if (err) {
|
73 | return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
|
74 | }
|
75 |
|
76 | if (stat.isDirectory()) {
|
77 | opts.hasDirectory = true;
|
78 |
|
79 | fs.readdir(path, function(err, children) {
|
80 |
|
81 | if (err) {
|
82 | return callback(err);
|
83 | }
|
84 | children.forEach(function(file) {
|
85 | let childPath = path + '/' + file;
|
86 |
|
87 | if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
88 | queue.push({template: childPath, root: root || path});
|
89 | }
|
90 | });
|
91 |
|
92 | callback();
|
93 | });
|
94 | } else {
|
95 | fs.readFile(path, 'utf8', function(err, data) {
|
96 |
|
97 | if (err) {
|
98 | return callback(err);
|
99 | }
|
100 |
|
101 | if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
102 | data = data.substring(1);
|
103 | }
|
104 |
|
105 |
|
106 | let name = path;
|
107 | if (!root) {
|
108 | name = basename(name);
|
109 | } else if (name.indexOf(root) === 0) {
|
110 | name = name.substring(root.length + 1);
|
111 | }
|
112 | name = name.replace(extension, '');
|
113 |
|
114 | ret.push({
|
115 | path: path,
|
116 | name: name,
|
117 | source: data
|
118 | });
|
119 |
|
120 | callback();
|
121 | });
|
122 | }
|
123 | });
|
124 | },
|
125 | function(err) {
|
126 | if (err) {
|
127 | callback(err);
|
128 | } else {
|
129 | callback(undefined, ret);
|
130 | }
|
131 | });
|
132 | }
|
133 |
|
134 | module.exports.cli = function(opts) {
|
135 | if (opts.version) {
|
136 | console.log(Handlebars.VERSION);
|
137 | return;
|
138 | }
|
139 |
|
140 | if (!opts.templates.length && !opts.hasDirectory) {
|
141 | throw new Handlebars.Exception('Must define at least one template or directory.');
|
142 | }
|
143 |
|
144 | if (opts.simple && opts.min) {
|
145 | throw new Handlebars.Exception('Unable to minimize simple output');
|
146 | }
|
147 |
|
148 | const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
149 | if (opts.simple && multiple) {
|
150 | throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
151 | }
|
152 |
|
153 |
|
154 | if (!opts.amd && !opts.commonjs && opts.templates.length === 1
|
155 | && !opts.templates[0].name) {
|
156 | opts.simple = true;
|
157 | }
|
158 |
|
159 |
|
160 | let known = {};
|
161 | if (opts.known && !Array.isArray(opts.known)) {
|
162 | opts.known = [opts.known];
|
163 | }
|
164 | if (opts.known) {
|
165 | for (let i = 0, len = opts.known.length; i < len; i++) {
|
166 | known[opts.known[i]] = true;
|
167 | }
|
168 | }
|
169 |
|
170 | const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
171 |
|
172 | let output = new SourceNode();
|
173 | if (!opts.simple) {
|
174 | if (opts.amd) {
|
175 | output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
176 | } else if (opts.commonjs) {
|
177 | output.add('var Handlebars = require("' + opts.commonjs + '");');
|
178 | } else {
|
179 | output.add('(function() {\n');
|
180 | }
|
181 | output.add(' var template = Handlebars.template, templates = ');
|
182 | if (opts.namespace) {
|
183 | output.add(opts.namespace);
|
184 | output.add(' = ');
|
185 | output.add(opts.namespace);
|
186 | output.add(' || ');
|
187 | }
|
188 | output.add('{};\n');
|
189 | }
|
190 |
|
191 | opts.templates.forEach(function(template) {
|
192 | let options = {
|
193 | knownHelpers: known,
|
194 | knownHelpersOnly: opts.o
|
195 | };
|
196 |
|
197 | if (opts.map) {
|
198 | options.srcName = template.path;
|
199 | }
|
200 | if (opts.data) {
|
201 | options.data = true;
|
202 | }
|
203 |
|
204 | let precompiled = Handlebars.precompile(template.source, options);
|
205 |
|
206 |
|
207 | if (opts.map) {
|
208 | let consumer = new SourceMapConsumer(precompiled.map);
|
209 | precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
210 | }
|
211 |
|
212 | if (opts.simple) {
|
213 | output.add([precompiled, '\n']);
|
214 | } else {
|
215 | if (!template.name) {
|
216 | throw new Handlebars.Exception('Name missing for template');
|
217 | }
|
218 |
|
219 | if (opts.amd && !multiple) {
|
220 | output.add('return ');
|
221 | }
|
222 | output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
223 | }
|
224 | });
|
225 |
|
226 |
|
227 | if (!opts.simple) {
|
228 | if (opts.amd) {
|
229 | if (multiple) {
|
230 | output.add(['return ', objectName, ';\n']);
|
231 | }
|
232 | output.add('});');
|
233 | } else if (!opts.commonjs) {
|
234 | output.add('})();');
|
235 | }
|
236 | }
|
237 |
|
238 | if (opts.map) {
|
239 | output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
240 | }
|
241 |
|
242 | output = output.toStringWithSourceMap();
|
243 | output.map = output.map + '';
|
244 |
|
245 | if (opts.min) {
|
246 | output = minify(output, opts.map);
|
247 | }
|
248 |
|
249 | if (opts.map) {
|
250 | fs.writeFileSync(opts.map, output.map, 'utf8');
|
251 | }
|
252 | output = output.code;
|
253 |
|
254 | if (opts.output) {
|
255 | fs.writeFileSync(opts.output, output, 'utf8');
|
256 | } else {
|
257 | console.log(output);
|
258 | }
|
259 | };
|
260 |
|
261 | function arrayCast(value) {
|
262 | value = value != null ? value : [];
|
263 | if (!Array.isArray(value)) {
|
264 | value = [value];
|
265 | }
|
266 | return value;
|
267 | }
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | function minify(output, sourceMapFile) {
|
280 | try {
|
281 |
|
282 | require.resolve('uglify-js');
|
283 | } catch (e) {
|
284 | if (e.code !== 'MODULE_NOT_FOUND') {
|
285 |
|
286 | throw e;
|
287 | }
|
288 |
|
289 | console.error('Code minimization is disabled due to missing uglify-js dependency');
|
290 | return output;
|
291 | }
|
292 | return require('uglify-js').minify(output.code, {
|
293 | sourceMap: {
|
294 | content: output.map,
|
295 | url: sourceMapFile
|
296 | }
|
297 | });
|
298 | }
|