source: trip-planner-front/node_modules/svgo/lib/svgo/coa.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 13.4 KB
Line 
1'use strict';
2
3const FS = require('fs');
4const PATH = require('path');
5const { green, red } = require('nanocolors');
6const { loadConfig, optimize } = require('../svgo-node.js');
7const pluginsMap = require('../../plugins/plugins.js');
8const PKG = require('../../package.json');
9const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');
10
11const regSVGFile = /\.svg$/i;
12
13/**
14 * Synchronously check if path is a directory. Tolerant to errors like ENOENT.
15 * @param {string} path
16 */
17function checkIsDir(path) {
18 try {
19 return FS.lstatSync(path).isDirectory();
20 } catch (e) {
21 return false;
22 }
23}
24
25module.exports = function makeProgram(program) {
26 program
27 .name(PKG.name)
28 .description(PKG.description, {
29 INPUT: 'Alias to --input',
30 })
31 .version(PKG.version, '-v, --version')
32 .arguments('[INPUT...]')
33 .option('-i, --input <INPUT...>', 'Input files, "-" for STDIN')
34 .option('-s, --string <STRING>', 'Input SVG data string')
35 .option(
36 '-f, --folder <FOLDER>',
37 'Input folder, optimize and rewrite all *.svg files'
38 )
39 .option(
40 '-o, --output <OUTPUT...>',
41 'Output file or folder (by default the same as the input), "-" for STDOUT'
42 )
43 .option(
44 '-p, --precision <INTEGER>',
45 'Set number of digits in the fractional part, overrides plugins params'
46 )
47 .option('--config <CONFIG>', 'Custom config file, only .js is supported')
48 .option(
49 '--datauri <FORMAT>',
50 'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)'
51 )
52 .option(
53 '--multipass',
54 'Pass over SVGs multiple times to ensure all optimizations are applied'
55 )
56 .option('--pretty', 'Make SVG pretty printed')
57 .option('--indent <INTEGER>', 'Indent number when pretty printing SVGs')
58 .option(
59 '--eol <EOL>',
60 'Line break to use when outputting SVG: lf, crlf. If unspecified, uses platform default.'
61 )
62 .option('--final-newline', 'Ensure SVG ends with a line break')
63 .option(
64 '-r, --recursive',
65 "Use with '--folder'. Optimizes *.svg files in folders recursively."
66 )
67 .option(
68 '--exclude <PATTERN...>',
69 "Use with '--folder'. Exclude files matching regular expression pattern."
70 )
71 .option(
72 '-q, --quiet',
73 'Only output error messages, not regular status messages'
74 )
75 .option('--show-plugins', 'Show available plugins and exit')
76 .action(action);
77};
78
79async function action(args, opts, command) {
80 var input = opts.input || args;
81 var output = opts.output;
82 var config = {};
83
84 if (opts.precision != null) {
85 const number = Number.parseInt(opts.precision, 10);
86 if (Number.isNaN(number)) {
87 console.error(
88 "error: option '-p, --precision' argument must be an integer number"
89 );
90 process.exit(1);
91 } else {
92 opts.precision = number;
93 }
94 }
95
96 if (opts.datauri != null) {
97 if (
98 opts.datauri !== 'base64' &&
99 opts.datauri !== 'enc' &&
100 opts.datauri !== 'unenc'
101 ) {
102 console.error(
103 "error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'"
104 );
105 process.exit(1);
106 }
107 }
108
109 if (opts.indent != null) {
110 const number = Number.parseInt(opts.indent, 10);
111 if (Number.isNaN(number)) {
112 console.error(
113 "error: option '--indent' argument must be an integer number"
114 );
115 process.exit(1);
116 } else {
117 opts.indent = number;
118 }
119 }
120
121 if (opts.eol != null && opts.eol !== 'lf' && opts.eol !== 'crlf') {
122 console.error(
123 "error: option '--eol' must have one of the following values: 'lf' or 'crlf'"
124 );
125 process.exit(1);
126 }
127
128 // --show-plugins
129 if (opts.showPlugins) {
130 showAvailablePlugins();
131 return;
132 }
133
134 // w/o anything
135 if (
136 (input.length === 0 || input[0] === '-') &&
137 !opts.string &&
138 !opts.stdin &&
139 !opts.folder &&
140 process.stdin.isTTY === true
141 ) {
142 return command.help();
143 }
144
145 if (
146 typeof process == 'object' &&
147 process.versions &&
148 process.versions.node &&
149 PKG &&
150 PKG.engines.node
151 ) {
152 var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0];
153 if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) {
154 throw Error(
155 `${PKG.name} requires Node.js version ${nodeVersion} or higher.`
156 );
157 }
158 }
159
160 // --config
161 const loadedConfig = await loadConfig(opts.config);
162 if (loadedConfig != null) {
163 config = loadedConfig;
164 }
165
166 // --quiet
167 if (opts.quiet) {
168 config.quiet = opts.quiet;
169 }
170
171 // --recursive
172 if (opts.recursive) {
173 config.recursive = opts.recursive;
174 }
175
176 // --exclude
177 config.exclude = opts.exclude
178 ? opts.exclude.map((pattern) => RegExp(pattern))
179 : [];
180
181 // --precision
182 if (opts.precision != null) {
183 var precision = Math.min(Math.max(0, opts.precision), 20);
184 config.floatPrecision = precision;
185 }
186
187 // --multipass
188 if (opts.multipass) {
189 config.multipass = true;
190 }
191
192 // --pretty
193 if (opts.pretty) {
194 config.js2svg = config.js2svg || {};
195 config.js2svg.pretty = true;
196 if (opts.indent != null) {
197 config.js2svg.indent = opts.indent;
198 }
199 }
200
201 // --eol
202 if (opts.eol) {
203 config.js2svg = config.js2svg || {};
204 config.js2svg.eol = opts.eol;
205 }
206
207 // --final-newline
208 if (opts.finalNewline) {
209 config.js2svg = config.js2svg || {};
210 config.js2svg.finalNewline = true;
211 }
212
213 // --output
214 if (output) {
215 if (input.length && input[0] != '-') {
216 if (output.length == 1 && checkIsDir(output[0])) {
217 var dir = output[0];
218 for (var i = 0; i < input.length; i++) {
219 output[i] = checkIsDir(input[i])
220 ? input[i]
221 : PATH.resolve(dir, PATH.basename(input[i]));
222 }
223 } else if (output.length < input.length) {
224 output = output.concat(input.slice(output.length));
225 }
226 }
227 } else if (input.length) {
228 output = input;
229 } else if (opts.string) {
230 output = '-';
231 }
232
233 if (opts.datauri) {
234 config.datauri = opts.datauri;
235 }
236
237 // --folder
238 if (opts.folder) {
239 var ouputFolder = (output && output[0]) || opts.folder;
240 await optimizeFolder(config, opts.folder, ouputFolder);
241 }
242
243 // --input
244 if (input.length !== 0) {
245 // STDIN
246 if (input[0] === '-') {
247 return new Promise((resolve, reject) => {
248 var data = '',
249 file = output[0];
250
251 process.stdin
252 .on('data', (chunk) => (data += chunk))
253 .once('end', () =>
254 processSVGData(config, { input: 'string' }, data, file).then(
255 resolve,
256 reject
257 )
258 );
259 });
260 // file
261 } else {
262 await Promise.all(
263 input.map((file, n) => optimizeFile(config, file, output[n]))
264 );
265 }
266
267 // --string
268 } else if (opts.string) {
269 var data = decodeSVGDatauri(opts.string);
270
271 return processSVGData(config, { input: 'string' }, data, output[0]);
272 }
273}
274
275/**
276 * Optimize SVG files in a directory.
277 * @param {Object} config options
278 * @param {string} dir input directory
279 * @param {string} output output directory
280 * @return {Promise}
281 */
282function optimizeFolder(config, dir, output) {
283 if (!config.quiet) {
284 console.log(`Processing directory '${dir}':\n`);
285 }
286 return FS.promises
287 .readdir(dir)
288 .then((files) => processDirectory(config, dir, files, output));
289}
290
291/**
292 * Process given files, take only SVG.
293 * @param {Object} config options
294 * @param {string} dir input directory
295 * @param {Array} files list of file names in the directory
296 * @param {string} output output directory
297 * @return {Promise}
298 */
299function processDirectory(config, dir, files, output) {
300 // take only *.svg files, recursively if necessary
301 var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output);
302
303 return svgFilesDescriptions.length
304 ? Promise.all(
305 svgFilesDescriptions.map((fileDescription) =>
306 optimizeFile(
307 config,
308 fileDescription.inputPath,
309 fileDescription.outputPath
310 )
311 )
312 )
313 : Promise.reject(
314 new Error(`No SVG files have been found in '${dir}' directory.`)
315 );
316}
317
318/**
319 * Get svg files descriptions
320 * @param {Object} config options
321 * @param {string} dir input directory
322 * @param {Array} files list of file names in the directory
323 * @param {string} output output directory
324 * @return {Array}
325 */
326function getFilesDescriptions(config, dir, files, output) {
327 const filesInThisFolder = files
328 .filter(
329 (name) =>
330 regSVGFile.test(name) &&
331 !config.exclude.some((regExclude) => regExclude.test(name))
332 )
333 .map((name) => ({
334 inputPath: PATH.resolve(dir, name),
335 outputPath: PATH.resolve(output, name),
336 }));
337
338 return config.recursive
339 ? [].concat(
340 filesInThisFolder,
341 files
342 .filter((name) => checkIsDir(PATH.resolve(dir, name)))
343 .map((subFolderName) => {
344 const subFolderPath = PATH.resolve(dir, subFolderName);
345 const subFolderFiles = FS.readdirSync(subFolderPath);
346 const subFolderOutput = PATH.resolve(output, subFolderName);
347 return getFilesDescriptions(
348 config,
349 subFolderPath,
350 subFolderFiles,
351 subFolderOutput
352 );
353 })
354 .reduce((a, b) => [].concat(a, b), [])
355 )
356 : filesInThisFolder;
357}
358
359/**
360 * Read SVG file and pass to processing.
361 * @param {Object} config options
362 * @param {string} file
363 * @param {string} output
364 * @return {Promise}
365 */
366function optimizeFile(config, file, output) {
367 return FS.promises.readFile(file, 'utf8').then(
368 (data) =>
369 processSVGData(config, { input: 'file', path: file }, data, output, file),
370 (error) => checkOptimizeFileError(config, file, output, error)
371 );
372}
373
374/**
375 * Optimize SVG data.
376 * @param {Object} config options
377 * @param {string} data SVG content to optimize
378 * @param {string} output where to write optimized file
379 * @param {string} [input] input file name (being used if output is a directory)
380 * @return {Promise}
381 */
382function processSVGData(config, info, data, output, input) {
383 var startTime = Date.now(),
384 prevFileSize = Buffer.byteLength(data, 'utf8');
385
386 const result = optimize(data, { ...config, ...info });
387 if (result.modernError) {
388 console.error(red(result.modernError.toString()));
389 process.exit(1);
390 }
391 if (config.datauri) {
392 result.data = encodeSVGDatauri(result.data, config.datauri);
393 }
394 var resultFileSize = Buffer.byteLength(result.data, 'utf8'),
395 processingTime = Date.now() - startTime;
396
397 return writeOutput(input, output, result.data).then(
398 function () {
399 if (!config.quiet && output != '-') {
400 if (input) {
401 console.log(`\n${PATH.basename(input)}:`);
402 }
403 printTimeInfo(processingTime);
404 printProfitInfo(prevFileSize, resultFileSize);
405 }
406 },
407 (error) =>
408 Promise.reject(
409 new Error(
410 error.code === 'ENOTDIR'
411 ? `Error: output '${output}' is not a directory.`
412 : error
413 )
414 )
415 );
416}
417
418/**
419 * Write result of an optimization.
420 * @param {string} input
421 * @param {string} output output file name. '-' for stdout
422 * @param {string} data data to write
423 * @return {Promise}
424 */
425function writeOutput(input, output, data) {
426 if (output == '-') {
427 console.log(data);
428 return Promise.resolve();
429 }
430
431 FS.mkdirSync(PATH.dirname(output), { recursive: true });
432
433 return FS.promises
434 .writeFile(output, data, 'utf8')
435 .catch((error) => checkWriteFileError(input, output, data, error));
436}
437
438/**
439 * Write a time taken by optimization.
440 * @param {number} time time in milliseconds.
441 */
442function printTimeInfo(time) {
443 console.log(`Done in ${time} ms!`);
444}
445
446/**
447 * Write optimizing information in human readable format.
448 * @param {number} inBytes size before optimization.
449 * @param {number} outBytes size after optimization.
450 */
451function printProfitInfo(inBytes, outBytes) {
452 var profitPercents = 100 - (outBytes * 100) / inBytes;
453
454 console.log(
455 Math.round((inBytes / 1024) * 1000) / 1000 +
456 ' KiB' +
457 (profitPercents < 0 ? ' + ' : ' - ') +
458 green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') +
459 ' = ' +
460 Math.round((outBytes / 1024) * 1000) / 1000 +
461 ' KiB'
462 );
463}
464
465/**
466 * Check for errors, if it's a dir optimize the dir.
467 * @param {Object} config
468 * @param {string} input
469 * @param {string} output
470 * @param {Error} error
471 * @return {Promise}
472 */
473function checkOptimizeFileError(config, input, output, error) {
474 if (error.code == 'EISDIR') {
475 return optimizeFolder(config, input, output);
476 } else if (error.code == 'ENOENT') {
477 return Promise.reject(
478 new Error(`Error: no such file or directory '${error.path}'.`)
479 );
480 }
481 return Promise.reject(error);
482}
483
484/**
485 * Check for saving file error. If the output is a dir, then write file there.
486 * @param {string} input
487 * @param {string} output
488 * @param {string} data
489 * @param {Error} error
490 * @return {Promise}
491 */
492function checkWriteFileError(input, output, data, error) {
493 if (error.code == 'EISDIR' && input) {
494 return FS.promises.writeFile(
495 PATH.resolve(output, PATH.basename(input)),
496 data,
497 'utf8'
498 );
499 } else {
500 return Promise.reject(error);
501 }
502}
503
504/**
505 * Show list of available plugins with short description.
506 */
507function showAvailablePlugins() {
508 const list = Object.entries(pluginsMap)
509 .sort(([a], [b]) => a.localeCompare(b))
510 .map(([name, plugin]) => ` [ ${green(name)} ] ${plugin.description}`)
511 .join('\n');
512 console.log('Currently available plugins:\n' + list);
513}
514
515module.exports.checkIsDir = checkIsDir;
Note: See TracBrowser for help on using the repository browser.