aboutsummaryrefslogtreecommitdiff
path: root/node_modules/csso/lib/cli.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/csso/lib/cli.js')
-rw-r--r--node_modules/csso/lib/cli.js338
1 files changed, 338 insertions, 0 deletions
diff --git a/node_modules/csso/lib/cli.js b/node_modules/csso/lib/cli.js
new file mode 100644
index 00000000..83846875
--- /dev/null
+++ b/node_modules/csso/lib/cli.js
@@ -0,0 +1,338 @@
+var fs = require('fs');
+var path = require('path');
+var cli = require('clap');
+var SourceMapConsumer = require('source-map').SourceMapConsumer;
+var csso = require('./index.js');
+
+function readFromStream(stream, minify) {
+ var buffer = [];
+
+ // FIXME: don't chain until node.js 0.10 drop, since setEncoding isn't chainable in 0.10
+ stream.setEncoding('utf8');
+ stream
+ .on('data', function(chunk) {
+ buffer.push(chunk);
+ })
+ .on('end', function() {
+ minify(buffer.join(''));
+ });
+}
+
+function showStat(filename, source, result, inputMap, map, time, mem) {
+ function fmt(size) {
+ return String(size).split('').reverse().reduce(function(size, digit, idx) {
+ if (idx && idx % 3 === 0) {
+ size = ' ' + size;
+ }
+ return digit + size;
+ }, '');
+ }
+
+ map = map || 0;
+ result -= map;
+
+ console.error('Source: ', filename === '<stdin>' ? filename : path.relative(process.cwd(), filename));
+ if (inputMap) {
+ console.error('Map source:', inputMap);
+ }
+ console.error('Original: ', fmt(source), 'bytes');
+ console.error('Compressed:', fmt(result), 'bytes', '(' + (100 * result / source).toFixed(2) + '%)');
+ console.error('Saving: ', fmt(source - result), 'bytes', '(' + (100 * (source - result) / source).toFixed(2) + '%)');
+ if (map) {
+ console.error('Source map:', fmt(map), 'bytes', '(' + (100 * map / (result + map)).toFixed(2) + '% of total)');
+ console.error('Total: ', fmt(map + result), 'bytes');
+ }
+ console.error('Time: ', time, 'ms');
+ console.error('Memory: ', (mem / (1024 * 1024)).toFixed(3), 'MB');
+}
+
+function showParseError(source, filename, details, message) {
+ function processLines(start, end) {
+ return lines.slice(start, end).map(function(line, idx) {
+ var num = String(start + idx + 1);
+
+ while (num.length < maxNumLength) {
+ num = ' ' + num;
+ }
+
+ return num + ' |' + line;
+ }).join('\n');
+ }
+
+ var lines = source.split(/\n|\r\n?|\f/);
+ var column = details.column;
+ var line = details.line;
+ var startLine = Math.max(1, line - 2);
+ var endLine = Math.min(line + 2, lines.length + 1);
+ var maxNumLength = Math.max(4, String(endLine).length) + 1;
+
+ console.error('\nParse error ' + filename + ': ' + message);
+ console.error(processLines(startLine - 1, line));
+ console.error(new Array(column + maxNumLength + 2).join('-') + '^');
+ console.error(processLines(line, endLine));
+ console.error();
+}
+
+function debugLevel(level) {
+ // level is undefined when no param -> 1
+ return isNaN(level) ? 1 : Math.max(Number(level), 0);
+}
+
+function resolveSourceMap(source, inputMap, map, inputFile, outputFile) {
+ var inputMapContent = null;
+ var inputMapFile = null;
+ var outputMapFile = null;
+
+ switch (map) {
+ case 'none':
+ // don't generate source map
+ map = false;
+ inputMap = 'none';
+ break;
+
+ case 'inline':
+ // nothing to do
+ break;
+
+ case 'file':
+ if (!outputFile) {
+ console.error('Output filename should be specified when `--map file` is used');
+ process.exit(2);
+ }
+
+ outputMapFile = outputFile + '.map';
+ break;
+
+ default:
+ // process filename
+ if (map) {
+ // check path is reachable
+ if (!fs.existsSync(path.dirname(map))) {
+ console.error('Directory for map file should exists:', path.dirname(path.resolve(map)));
+ process.exit(2);
+ }
+
+ // resolve to absolute path
+ outputMapFile = path.resolve(process.cwd(), map);
+ }
+ }
+
+ switch (inputMap) {
+ case 'none':
+ // nothing to do
+ break;
+
+ case 'auto':
+ if (map) {
+ // try fetch source map from source
+ var inputMapComment = source.match(/\/\*# sourceMappingURL=(\S+)\s*\*\/\s*$/);
+
+ if (inputFile === '<stdin>') {
+ inputFile = false;
+ }
+
+ if (inputMapComment) {
+ // if comment found – value is filename or base64-encoded source map
+ inputMapComment = inputMapComment[1];
+
+ if (inputMapComment.substr(0, 5) === 'data:') {
+ // decode source map content from comment
+ inputMapContent = new Buffer(inputMapComment.substr(inputMapComment.indexOf('base64,') + 7), 'base64').toString();
+ } else {
+ // value is filename – resolve it as absolute path
+ if (inputFile) {
+ inputMapFile = path.resolve(path.dirname(inputFile), inputMapComment);
+ }
+ }
+ } else {
+ // comment doesn't found - look up file with `.map` extension nearby input file
+ if (inputFile && fs.existsSync(inputFile + '.map')) {
+ inputMapFile = inputFile + '.map';
+ }
+ }
+
+ }
+ break;
+
+ default:
+ if (inputMap) {
+ inputMapFile = inputMap;
+ }
+ }
+
+ // source map placed in external file
+ if (inputMapFile) {
+ inputMapContent = fs.readFileSync(inputMapFile, 'utf8');
+ }
+
+ return {
+ input: inputMapContent,
+ inputFile: inputMapFile || (inputMapContent ? '<inline>' : false),
+ output: map,
+ outputFile: outputMapFile
+ };
+}
+
+function processCommentsOption(value) {
+ switch (value) {
+ case 'exclamation':
+ case 'first-exclamation':
+ case 'none':
+ return value;
+ }
+
+ console.error('Wrong value for `comments` option: %s', value);
+ process.exit(2);
+}
+
+var command = cli.create('csso', '[input] [output]')
+ .version(require('../package.json').version)
+ .option('-i, --input <filename>', 'Input file')
+ .option('-o, --output <filename>', 'Output file (result outputs to stdout if not set)')
+ .option('-m, --map <destination>', 'Generate source map: none (default), inline, file or <filename>', 'none')
+ .option('-u, --usage <filenane>', 'Usage data file')
+ .option('--input-map <source>', 'Input source map: none, auto (default) or <filename>', 'auto')
+ .option('--restructure-off', 'Turns structure minimization off')
+ .option('--comments <value>', 'Comments to keep: exclamation (default), first-exclamation or none', 'exclamation')
+ .option('--stat', 'Output statistics in stderr')
+ .option('--debug [level]', 'Output intermediate state of CSS during compression', debugLevel, 0)
+ .action(function(args) {
+ var options = this.values;
+ var inputFile = options.input || args[0];
+ var outputFile = options.output || args[1];
+ var usageFile = options.usage;
+ var usageData = false;
+ var map = options.map;
+ var inputMap = options.inputMap;
+ var structureOptimisationOff = options.restructureOff;
+ var comments = processCommentsOption(options.comments);
+ var debug = options.debug;
+ var statistics = options.stat;
+ var inputStream;
+
+ if (process.stdin.isTTY && !inputFile && !outputFile) {
+ this.showHelp();
+ return;
+ }
+
+ if (!inputFile) {
+ inputFile = '<stdin>';
+ inputStream = process.stdin;
+ } else {
+ inputFile = path.resolve(process.cwd(), inputFile);
+ inputStream = fs.createReadStream(inputFile);
+ }
+
+ if (outputFile) {
+ outputFile = path.resolve(process.cwd(), outputFile);
+ }
+
+ if (usageFile) {
+ if (!fs.existsSync(usageFile)) {
+ console.error('Usage data file doesn\'t found (%s)', usageFile);
+ process.exit(2);
+ }
+
+ usageData = fs.readFileSync(usageFile, 'utf-8');
+
+ try {
+ usageData = JSON.parse(usageData);
+ } catch (e) {
+ console.error('Usage data parse error (%s)', usageFile);
+ process.exit(2);
+ }
+ }
+
+ readFromStream(inputStream, function(source) {
+ var time = process.hrtime();
+ var mem = process.memoryUsage().heapUsed;
+ var sourceMap = resolveSourceMap(source, inputMap, map, inputFile, outputFile);
+ var sourceMapAnnotation = '';
+ var result;
+
+ // main action
+ try {
+ result = csso.minify(source, {
+ filename: inputFile,
+ sourceMap: sourceMap.output,
+ usage: usageData,
+ restructure: !structureOptimisationOff,
+ comments: comments,
+ debug: debug
+ });
+
+ // for backward capability minify returns a string
+ if (typeof result === 'string') {
+ result = {
+ css: result,
+ map: null
+ };
+ }
+ } catch (e) {
+ if (e.parseError) {
+ showParseError(source, inputFile, e.parseError, e.message);
+ if (!debug) {
+ process.exit(2);
+ }
+ }
+
+ throw e;
+ }
+
+ if (sourceMap.output && result.map) {
+ // apply input map
+ if (sourceMap.input) {
+ result.map.applySourceMap(
+ new SourceMapConsumer(sourceMap.input),
+ inputFile
+ );
+ }
+
+ // add source map to result
+ if (sourceMap.outputFile) {
+ // write source map to file
+ fs.writeFileSync(sourceMap.outputFile, result.map.toString(), 'utf-8');
+ sourceMapAnnotation = '\n' +
+ '/*# sourceMappingURL=' +
+ path.relative(outputFile ? path.dirname(outputFile) : process.cwd(), sourceMap.outputFile) +
+ ' */';
+ } else {
+ // inline source map
+ sourceMapAnnotation = '\n' +
+ '/*# sourceMappingURL=data:application/json;base64,' +
+ new Buffer(result.map.toString()).toString('base64') +
+ ' */';
+ }
+
+ result.css += sourceMapAnnotation;
+ }
+
+ // output result
+ if (outputFile) {
+ fs.writeFileSync(outputFile, result.css, 'utf-8');
+ } else {
+ console.log(result.css);
+ }
+
+ // output statistics
+ if (statistics) {
+ var timeDiff = process.hrtime(time);
+ showStat(
+ path.relative(process.cwd(), inputFile),
+ source.length,
+ result.css.length,
+ sourceMap.inputFile,
+ sourceMapAnnotation.length,
+ parseInt(timeDiff[0] * 1e3 + timeDiff[1] / 1e6),
+ process.memoryUsage().heapUsed - mem
+ );
+ }
+ });
+ });
+
+module.exports = {
+ run: command.run.bind(command),
+ isCliError: function(err) {
+ return err instanceof cli.Error;
+ }
+};