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

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

primeNG components

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