diff options
Diffstat (limited to 'node_modules/csso/lib/cli.js')
| -rw-r--r-- | node_modules/csso/lib/cli.js | 338 |
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; + } +}; |
