123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /* jshint quotmark: false */
- 'use strict';
- require('colors');
- var FS = require('fs'),
- PATH = require('path'),
- SVGO = require('../svgo.js'),
- YAML = require('js-yaml'),
- PKG = require('../../package.json'),
- mkdirp = require('mkdirp'),
- encodeSVGDatauri = require('./tools.js').encodeSVGDatauri,
- decodeSVGDatauri = require('./tools.js').decodeSVGDatauri,
- regSVGFile = /\.svg$/;
- /**
- * Command-Option-Argument.
- *
- * @see https://github.com/veged/coa
- */
- module.exports = require('coa').Cmd()
- .helpful()
- .name(PKG.name)
- .title(PKG.description)
- .opt()
- .name('version').title('Version')
- .short('v').long('version')
- .only()
- .flag()
- .act(function() {
- // output the version to stdout instead of stderr if returned
- process.stdout.write(PKG.version + '\n');
- // coa will run `.toString` on the returned value and send it to stderr
- return '';
- })
- .end()
- .opt()
- .name('input').title('Input file, "-" for STDIN')
- .short('i').long('input')
- .val(function(val) {
- return val || this.reject("Option '--input' must have a value.");
- })
- .end()
- .opt()
- .name('string').title('Input SVG data string')
- .short('s').long('string')
- .end()
- .opt()
- .name('folder').title('Input folder, optimize and rewrite all *.svg files')
- .short('f').long('folder')
- .val(function(val) {
- return val || this.reject("Option '--folder' must have a value.");
- })
- .end()
- .opt()
- .name('output').title('Output file or folder (by default the same as the input), "-" for STDOUT')
- .short('o').long('output')
- .val(function(val) {
- return val || this.reject("Option '--output' must have a value.");
- })
- .end()
- .opt()
- .name('precision').title('Set number of digits in the fractional part, overrides plugins params')
- .short('p').long('precision')
- .val(function(val) {
- return !isNaN(val) ? val : this.reject("Option '--precision' must be an integer number");
- })
- .end()
- .opt()
- .name('config').title('Config file or JSON string to extend or replace default')
- .long('config')
- .val(function(val) {
- return val || this.reject("Option '--config' must have a value.");
- })
- .end()
- .opt()
- .name('disable').title('Disable plugin by name')
- .long('disable')
- .arr()
- .val(function(val) {
- return val || this.reject("Option '--disable' must have a value.");
- })
- .end()
- .opt()
- .name('enable').title('Enable plugin by name')
- .long('enable')
- .arr()
- .val(function(val) {
- return val || this.reject("Option '--enable' must have a value.");
- })
- .end()
- .opt()
- .name('datauri').title('Output as Data URI string (base64, URI encoded or unencoded)')
- .long('datauri')
- .val(function(val) {
- return val || this.reject("Option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'");
- })
- .end()
- .opt()
- .name('multipass').title('Enable multipass')
- .long('multipass')
- .flag()
- .end()
- .opt()
- .name('pretty').title('Make SVG pretty printed')
- .long('pretty')
- .flag()
- .end()
- .opt()
- .name('indent').title('Indent number when pretty printing SVGs')
- .long('indent')
- .val(function(val) {
- return !isNaN(val) ? val : this.reject("Option '--indent' must be an integer number");
- })
- .end()
- .opt()
- .name('quiet').title('Only output error messages, not regular status messages')
- .short('q').long('quiet')
- .flag()
- .end()
- .opt()
- .name('show-plugins').title('Show available plugins and exit')
- .long('show-plugins')
- .flag()
- .end()
- .arg()
- .name('input').title('Alias to --input')
- .end()
- .arg()
- .name('output').title('Alias to --output')
- .end()
- .act(function(opts, args) {
- var input = args && args.input ? args.input : opts.input,
- output = args && args.output ? args.output : opts.output,
- config = {};
- // --show-plugins
- if (opts['show-plugins']) {
- showAvailablePlugins();
- process.exit(0);
- }
- // w/o anything
- if (
- (!input || input === '-') &&
- !opts.string &&
- !opts.stdin &&
- !opts.folder &&
- process.stdin.isTTY
- ) return this.usage();
- // --config
- if (opts.config) {
- // string
- if (opts.config.charAt(0) === '{') {
- try {
- config = JSON.parse(opts.config);
- } catch (e) {
- console.error("Error: Couldn't parse config JSON.");
- console.error(String(e));
- return;
- }
- // external file
- } else {
- var configPath = PATH.resolve(opts.config);
- try {
- // require() adds some weird output on YML files
- config = JSON.parse(FS.readFileSync(configPath, 'utf8'));
- } catch (err) {
- if (err.code === 'ENOENT') {
- console.error('Error: couldn\'t find config file \'' + opts.config + '\'.');
- return;
- } else if (err.code === 'EISDIR') {
- console.error('Error: directory \'' + opts.config + '\' is not a config file.');
- return;
- }
- config = YAML.safeLoad(FS.readFileSync(configPath, 'utf8'));
- if (!config || Array.isArray(config)) {
- console.error('Error: invalid config file \'' + opts.config + '\'.');
- return;
- }
- }
- }
- }
- // --quiet
- if (opts.quiet) {
- config.quiet = opts.quiet;
- }
- // --precision
- if (opts.precision) {
- config.floatPrecision = Math.max(0, parseInt(opts.precision));
- }
- // --disable
- if (opts.disable) {
- changePluginsState(opts.disable, false, config);
- }
- // --enable
- if (opts.enable) {
- changePluginsState(opts.enable, true, config);
- }
- // --multipass
- if (opts.multipass) {
- config.multipass = true;
- }
- // --pretty
- if (opts.pretty) {
- config.js2svg = config.js2svg || {};
- config.js2svg.pretty = true;
- if (opts.indent) {
- config.js2svg.indent = parseInt(opts.indent, 10);
- }
- }
- // --output
- if (opts.output) {
- config.output = opts.output;
- }
- // --folder
- if (opts.folder) {
- optimizeFolder(opts.folder, config, output);
- return;
- }
- // --input
- if (input) {
- // STDIN
- if (input === '-') {
- var data = '';
- process.stdin.pause();
- process.stdin
- .on('data', function(chunk) {
- data += chunk;
- })
- .once('end', function() {
- optimizeFromString(data, config, opts.datauri, input, output);
- })
- .resume();
- // file
- } else {
- FS.readFile(input, 'utf8', function(err, data) {
- if (err) {
- if (err.code === 'EISDIR')
- optimizeFolder(input, config, output);
- else if (err.code === 'ENOENT')
- console.error('Error: no such file or directory \'' + input + '\'.');
- else
- console.error(err);
- return;
- }
- optimizeFromString(data, config, opts.datauri, input, output);
- });
- }
- // --string
- } else if (opts.string) {
- opts.string = decodeSVGDatauri(opts.string);
- optimizeFromString(opts.string, config, opts.datauri, input, output);
- }
- });
- function optimizeFromString(svgstr, config, datauri, input, output) {
- var startTime = Date.now(config),
- time,
- inBytes = Buffer.byteLength(svgstr, 'utf8'),
- outBytes,
- svgo = new SVGO(config);
- svgo.optimize(svgstr, function(result) {
- if (result.error) {
- console.error(result.error);
- return;
- }
- if (datauri) {
- result.data = encodeSVGDatauri(result.data, datauri);
- }
- outBytes = Buffer.byteLength(result.data, 'utf8');
- time = Date.now() - startTime;
- // stdout
- if (output === '-' || (!input || input === '-') && !output) {
- process.stdout.write(result.data + '\n');
- // file
- } else {
- // overwrite input file if there is no output
- if (!output && input) {
- output = input;
- }
- if (!config.quiet) {
- console.log('\r');
- }
- saveFileAndPrintInfo(config, result.data, output, inBytes, outBytes, time);
- }
- });
- }
- function saveFileAndPrintInfo(config, data, path, inBytes, outBytes, time) {
- FS.writeFile(path, data, 'utf8', function() {
- if (config.quiet) {
- return;
- }
- // print time info
- printTimeInfo(time);
- // print optimization profit info
- printProfitInfo(inBytes, outBytes);
- });
- }
- function printTimeInfo(time) {
- console.log('Done in ' + time + ' ms!');
- }
- function printProfitInfo(inBytes, outBytes) {
- var profitPercents = 100 - outBytes * 100 / inBytes;
- console.log(
- (Math.round((inBytes / 1024) * 1000) / 1000) + ' KiB' +
- (profitPercents < 0 ? ' + ' : ' - ') +
- String(Math.abs((Math.round(profitPercents * 10) / 10)) + '%').green + ' = ' +
- (Math.round((outBytes / 1024) * 1000) / 1000) + ' KiB\n'
- );
- }
- /**
- * Change plugins state by names array.
- *
- * @param {Array} names plugins names
- * @param {Boolean} state active state
- * @param {Object} config original config
- * @return {Object} changed config
- */
- function changePluginsState(names, state, config) {
- // extend config
- if (config.plugins) {
- names.forEach(function(name) {
- var matched,
- key;
- config.plugins.forEach(function(plugin) {
- // get plugin name
- if (typeof plugin === 'object') {
- key = Object.keys(plugin)[0];
- } else {
- key = plugin;
- }
- // if there is such a plugin name
- if (key === name) {
- // don't replace plugin's params with true
- if (typeof plugin[key] !== 'object' || !state) {
- plugin[key] = state;
- }
- // mark it as matched
- matched = true;
- }
- });
- // if not matched and current config is not full
- if (!matched && !config.full) {
- var obj = {};
- obj[name] = state;
- // push new plugin Object
- config.plugins.push(obj);
- matched = true;
- }
- });
- // just push
- } else {
- config.plugins = [];
- names.forEach(function(name) {
- var obj = {};
- obj[name] = state;
- config.plugins.push(obj);
- });
- }
- return config;
- }
- function optimizeFolder(dir, config, output) {
- var svgo = new SVGO(config);
- if (!config.quiet) {
- console.log('Processing directory \'' + dir + '\':\n');
- }
- // absoluted folder path
- var path = PATH.resolve(dir);
- // list folder content
- FS.readdir(path, function(err, files) {
- if (err) {
- console.error(err);
- return;
- }
- if (!files.length) {
- console.log('Directory \'' + dir + '\' is empty.');
- return;
- }
- var i = 0,
- found = false;
- function optimizeFile(file) {
- // absoluted file path
- var filepath = PATH.resolve(path, file);
- var outfilepath = output ? PATH.resolve(output, file) : filepath;
- // check if file name matches *.svg
- if (regSVGFile.test(filepath)) {
- found = true;
- FS.readFile(filepath, 'utf8', function(err, data) {
- if (err) {
- console.error(err);
- return;
- }
- var startTime = Date.now(),
- time,
- inBytes = Buffer.byteLength(data, 'utf8'),
- outBytes;
- svgo.optimize(data, function(result) {
- if (result.error) {
- console.error(result.error);
- return;
- }
- outBytes = Buffer.byteLength(result.data, 'utf8');
- time = Date.now() - startTime;
- writeOutput();
- function writeOutput() {
- FS.writeFile(outfilepath, result.data, 'utf8', report);
- }
- function report(err) {
- if (err) {
- if (err.code === 'ENOENT') {
- mkdirp(output, writeOutput);
- return;
- } else if (err.code === 'ENOTDIR') {
- console.error('Error: output \'' + output + '\' is not a directory.');
- return;
- }
- console.error(err);
- return;
- }
- if (!config.quiet) {
- console.log(file + ':');
- // print time info
- printTimeInfo(time);
- // print optimization profit info
- printProfitInfo(inBytes, outBytes);
- }
- //move on to the next file
- if (++i < files.length) {
- optimizeFile(files[i]);
- }
- }
- });
- });
- }
- //move on to the next file
- else if (++i < files.length) {
- optimizeFile(files[i]);
- } else if (!found) {
- console.log('No SVG files have been found.');
- }
- }
- optimizeFile(files[i]);
- });
- }
- var showAvailablePlugins = function () {
- var svgo = new SVGO(),
- // Flatten an array of plugins grouped per type and sort alphabetically
- list = Array.prototype.concat.apply([], svgo.config.plugins).sort(function(a, b) {
- return a.name > b.name ? 1 : -1;
- });
- console.log('Currently available plugins:');
- list.forEach(function (plugin) {
- console.log(' [ ' + plugin.name.green + ' ] ' + plugin.description);
- });
- //console.log(JSON.stringify(svgo, null, 4));
- };
|