aboutsummaryrefslogtreecommitdiff
path: root/node_modules/html-minifier
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2017-05-03 15:35:00 +0200
committerFlorian Dold <florian.dold@gmail.com>2017-05-03 15:35:00 +0200
commitde98e0b232509d5f40c135d540a70e415272ff85 (patch)
treea79222a5b58484ab3b80d18efcaaa7ccc4769b33 /node_modules/html-minifier
parente0c9d480a73fa629c1e4a47d3e721f1d2d345406 (diff)
downloadwallet-core-de98e0b232509d5f40c135d540a70e415272ff85.tar.xz
node_modules
Diffstat (limited to 'node_modules/html-minifier')
-rw-r--r--node_modules/html-minifier/LICENSE22
-rw-r--r--node_modules/html-minifier/README.md159
-rwxr-xr-xnode_modules/html-minifier/cli.js309
l---------node_modules/html-minifier/node_modules/.bin/he1
l---------node_modules/html-minifier/node_modules/.bin/uglifyjs1
-rw-r--r--node_modules/html-minifier/node_modules/commander/History.md261
-rw-r--r--node_modules/html-minifier/node_modules/commander/LICENSE22
-rw-r--r--node_modules/html-minifier/node_modules/commander/Readme.md351
-rw-r--r--node_modules/html-minifier/node_modules/commander/index.js1110
-rw-r--r--node_modules/html-minifier/node_modules/commander/package.json33
-rw-r--r--node_modules/html-minifier/package.json88
-rw-r--r--node_modules/html-minifier/sample-cli-config-file.conf39
-rw-r--r--node_modules/html-minifier/src/htmlminifier.js1308
-rw-r--r--node_modules/html-minifier/src/htmlparser.js528
-rw-r--r--node_modules/html-minifier/src/tokenchain.js71
-rw-r--r--node_modules/html-minifier/src/utils.js18
16 files changed, 4321 insertions, 0 deletions
diff --git a/node_modules/html-minifier/LICENSE b/node_modules/html-minifier/LICENSE
new file mode 100644
index 000000000..ed8f360a6
--- /dev/null
+++ b/node_modules/html-minifier/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2016 Juriy "kangax" Zaytsev
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/html-minifier/README.md b/node_modules/html-minifier/README.md
new file mode 100644
index 000000000..5338e9cc5
--- /dev/null
+++ b/node_modules/html-minifier/README.md
@@ -0,0 +1,159 @@
+# HTMLMinifier
+
+[![NPM version](https://img.shields.io/npm/v/html-minifier.svg)](https://www.npmjs.com/package/html-minifier)
+[![Build Status](https://img.shields.io/travis/kangax/html-minifier.svg)](https://travis-ci.org/kangax/html-minifier)
+[![Dependency Status](https://img.shields.io/david/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier)
+[![devDependency Status](https://img.shields.io/david/dev/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier?type=dev)
+[![Gitter](https://img.shields.io/gitter/room/kangax/html-minifier.svg)](https://gitter.im/kangax/html-minifier)
+
+[HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, JavaScript-based HTML minifier.
+
+See [corresponding blog post](http://perfectionkills.com/experimenting-with-html-minifier/) for all the gory details of [how it works](http://perfectionkills.com/experimenting-with-html-minifier/#how_it_works), [description of each option](http://perfectionkills.com/experimenting-with-html-minifier/#options), [testing results](http://perfectionkills.com/experimenting-with-html-minifier/#field_testing) and [conclusions](http://perfectionkills.com/experimenting-with-html-minifier/#cost_and_benefits).
+
+[Test suite is available online](http://kangax.github.io/html-minifier/tests/).
+
+Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_minifier), and for Node.js, [Grunt plugin](https://github.com/gruntjs/grunt-contrib-htmlmin), [Gulp module](https://github.com/jonschlinkert/gulp-htmlmin), [Koa middleware wrapper](https://github.com/koajs/html-minifier) and [Express middleware wrapper](https://github.com/melonmanchan/express-minify-html).
+
+For lint-like capabilities take a look at [HTMLLint](https://github.com/kangax/html-lint).
+
+## Minification comparison
+
+How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Peavy](http://www.willpeavy.com/minifier/) (1st result in [Google search for "html minifier"](https://www.google.com/#q=html+minifier)) as well as [htmlcompressor.com](http://htmlcompressor.com) and [minimize](https://github.com/Swaagie/minimize)?
+
+| Site | Original size *(KB)* | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
+| --------------------------------------------------------------------------- |:--------------------:| ------------:| --------:| ----------:| ------------------:|
+| [Google](https://www.google.com/) | 44 | **42** | 45 | 46 | 45 |
+| [HTMLMinifier](https://github.com/kangax/html-minifier) | 124 | **97** | 105 | 109 | 104 |
+| [CNN](http://www.cnn.com/) | 131 | **121** | 129 | 130 | 124 |
+| [Amazon](http://www.amazon.co.uk/) | 189 | **158** | 181 | 184 | n/a |
+| [New York Times](http://www.nytimes.com/) | 200 | **131** | 149 | 148 | 139 |
+| [Stack Overflow](http://stackoverflow.com/) | 223 | **172** | 181 | 189 | 178 |
+| [BBC](http://www.bbc.co.uk/) | 224 | **185** | 217 | 223 | 211 |
+| [Bootstrap CSS](http://getbootstrap.com/css/) | 272 | **260** | 269 | 229 | 269 |
+| [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 547 | **500** | 527 | 545 | 526 |
+| [NBC](http://www.nbc.com/) | 572 | **549** | 570 | 572 | 555 |
+| [Eloquent Javascript](http://eloquentjavascript.net/1st_edition/print.html) | 870 | **815** | 840 | 864 | n/a |
+| [ES6 table](http://kangax.github.io/compat-table/es6/) | 4453 | **3763** | 4205 | n/a | n/a |
+| [ES6 draft](https://tc39.github.io/ecma262/) | 5505 | **4913** | 5060 | n/a | n/a |
+
+## Options Quick Reference
+
+Most of the options are disabled by default.
+
+| Option | Description | Default |
+|--------------------------------|-----------------|---------|
+| `caseSensitive` | Treat attributes in case sensitive manner (useful for custom HTML tags) | `false` |
+| `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
+| `collapseInlineTagWhitespace` | Don't leave any spaces between `display:inline;` elements when collapsing. Must be used in conjunction with `collapseWhitespace=true` | `false` |
+| `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace) | `false` |
+| `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
+| `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `'<div flex?="{{mode != cover}}"></div>'`) | `[ ]` |
+| `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng-class/`) | |
+| `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. `<input {{#if value}}checked="checked"{{/if}}>`) | `[ ]` |
+| `customEventAttributes` | Arrays of regex'es that allow to support custom event attributes for `minifyJS` (e.g. `ng-click`) | `[ /^on[a-z]{3,}$/ ]` |
+| `decodeEntities` | Use direct Unicode characters whenever possible | `false` |
+| `html5` | Parse input according to HTML5 specifications | `true` |
+| `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ /^!/ ]` |
+| `ignoreCustomFragments` | Array of regex'es that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
+| `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
+| `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
+| `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points |
+| `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `Object`, `Function(text)`) |
+| `minifyJS` | Minify JavaScript in script elements and event attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
+| `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `String`, `Object`, `Function(text)`) |
+| `preserveLineBreaks` | Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break. Must be used in conjunction with `collapseWhitespace=true` | `false` |
+| `preventAttributesEscaping` | Prevents the escaping of the values of attributes | `false` |
+| `processConditionalComments` | Process contents of conditional comments through minifier | `false` |
+| `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. `text/ng-template`, `text/x-handlebars-template`, etc.) | `[ ]` |
+| `quoteCharacter` | Type of quote to use for attribute values (' or ") | |
+| `removeAttributeQuotes` | [Remove quotes around attributes when possible](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` |
+| `removeComments` | [Strip HTML comments](http://perfectionkills.com/experimenting-with-html-minifier/#remove_comments) | `false` |
+| `removeEmptyAttributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes) | `false` (could be `true`, `Function(attrName, tag)`) |
+| `removeEmptyElements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
+| `removeOptionalTags` | [Remove optional tags](http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
+| `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` |
+| `removeScriptTypeAttributes` | Remove `type="text/javascript"` from `script` tags. Other `type` attribute values are left intact | `false` |
+| `removeStyleLinkTypeAttributes`| Remove `type="text/css"` from `style` and `link` tags. Other `type` attribute values are left intact | `false` |
+| `removeTagWhitespace` | Remove space between attributes whenever possible. **Note that this will result in invalid HTML!** | `false` |
+| `sortAttributes` | [Sort attributes by frequency](#sorting-attributes--style-classes) | `false` |
+| `sortClassName` | [Sort style classes by frequency](#sorting-attributes--style-classes) | `false` |
+| `trimCustomFragments` | Trim white space around `ignoreCustomFragments`. | `false` |
+| `useShortDoctype` | [Replaces the `doctype` with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
+
+### Sorting attributes / style classes
+
+Minifier options like `sortAttributes` and `sortClassName` won't impact the plain-text size of the output. However, they form long repetitive chains of characters that should improve compression ratio of gzip used in HTTP compression.
+
+## Special cases
+
+### Ignoring chunks of markup
+
+If you have chunks of markup you would like preserved, you can wrap them `<!-- htmlmin:ignore -->`.
+
+### Preserving SVG tags
+
+SVG tags are automatically recognized, and when they are minified, both case-sensitivity and closing-slashes are preserved, regardless of the minification settings used for the rest of the file.
+
+### Working with invalid markup
+
+HTMLMinifier **can't work with invalid or partial chunks of markup**. This is because it parses markup into a tree structure, then modifies it (removing anything that was specified for removal, ignoring anything that was specified to be ignored, etc.), then it creates a markup out of that tree and returns it.
+
+Input markup (e.g. `<p id="">foo`)
+
+↓
+
+Internal representation of markup in a form of tree (e.g. `{ tag: "p", attr: "id", children: ["foo"] }`)
+
+↓
+
+Transformation of internal representation (e.g. removal of `id` attribute)
+
+↓
+
+Output of resulting markup (e.g. `<p>foo</p>`)
+
+HTMLMinifier can't know that original markup was only half of the tree; it does its best to try to parse it as a full tree and it loses information about tree being malformed or partial in the beginning. As a result, it can't create a partial/malformed tree at the time of the output.
+
+## Installation Instructions
+
+From NPM for use as a command line app:
+
+```shell
+npm install html-minifier -g
+```
+
+From NPM for programmatic use:
+
+```shell
+npm install html-minifier
+```
+
+From Git:
+
+```shell
+git clone git://github.com/kangax/html-minifier.git
+cd html-minifier
+npm link .
+```
+
+## Usage
+
+For command line usage please see `html-minifier --help`
+
+### Node.js
+
+```js
+var minify = require('html-minifier').minify;
+var result = minify('<p title="blah" id="moo">foo</p>', {
+ removeAttributeQuotes: true
+});
+result; // '<p title=blah id=moo>foo</p>'
+```
+
+## Running benchmarks
+
+Benchmarks for minified HTML:
+
+```shell
+node benchmark.js
+```
diff --git a/node_modules/html-minifier/cli.js b/node_modules/html-minifier/cli.js
new file mode 100755
index 000000000..aba7ec876
--- /dev/null
+++ b/node_modules/html-minifier/cli.js
@@ -0,0 +1,309 @@
+#!/usr/bin/env node
+/**
+ * html-minifier CLI tool
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2016 Zoltan Frombach
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+'use strict';
+
+var camelCase = require('camel-case');
+var fs = require('fs');
+var info = require('./package.json');
+var minify = require('./' + info.main).minify;
+var paramCase = require('param-case');
+var path = require('path');
+var program = require('commander');
+
+program._name = info.name;
+program.version(info.version);
+
+function fatal(message) {
+ console.error(message);
+ process.exit(1);
+}
+
+/**
+ * JSON does not support regexes, so, e.g., JSON.parse() will not create
+ * a RegExp from the JSON value `[ "/matchString/" ]`, which is
+ * technically just an array containing a string that begins and end with
+ * a forward slash. To get a RegExp from a JSON string, it must be
+ * constructed explicitly in JavaScript.
+ *
+ * The likelihood of actually wanting to match text that is enclosed in
+ * forward slashes is probably quite rare, so if forward slashes were
+ * included in an argument that requires a regex, the user most likely
+ * thought they were part of the syntax for specifying a regex.
+ *
+ * In the unlikely case that forward slashes are indeed desired in the
+ * search string, the user would need to enclose the expression in a
+ * second set of slashes:
+ *
+ * --customAttrSrround "[\"//matchString//\"]"
+ */
+function parseRegExp(value) {
+ if (value) {
+ return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
+ }
+}
+
+function parseJSON(value) {
+ if (value) {
+ try {
+ return JSON.parse(value);
+ }
+ catch (e) {
+ if (/^{/.test(value)) {
+ fatal('Could not parse JSON value \'' + value + '\'');
+ }
+ return value;
+ }
+ }
+}
+
+function parseJSONArray(value) {
+ if (value) {
+ value = parseJSON(value);
+ return Array.isArray(value) ? value : [value];
+ }
+}
+
+function parseJSONRegExpArray(value) {
+ value = parseJSONArray(value);
+ return value && value.map(parseRegExp);
+}
+
+function parseString(value) {
+ return value;
+}
+
+var mainOptions = {
+ caseSensitive: 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)',
+ collapseBooleanAttributes: 'Omit attribute values from boolean attributes',
+ collapseInlineTagWhitespace: 'Collapse white space around inline tag',
+ collapseWhitespace: 'Collapse white space that contributes to text nodes in a document tree.',
+ conservativeCollapse: 'Always collapse to 1 space (never remove it entirely)',
+ customAttrAssign: ['Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'<div flex?="{{mode != cover}}"></div>\')', parseJSONRegExpArray],
+ customAttrCollapse: ['Regex that specifies custom attribute to strip newlines from (e.g. /ng-class/)', parseRegExp],
+ customAttrSurround: ['Arrays of regex\'es that allow to support custom attribute surround expressions (e.g. <input {{#if value}}checked="checked"{{/if}}>)', parseJSONRegExpArray],
+ customEventAttributes: ['Arrays of regex\'es that allow to support custom event attributes for minifyJS (e.g. ng-click)', parseJSONRegExpArray],
+ decodeEntities: 'Use direct Unicode characters whenever possible',
+ html5: 'Parse input according to HTML5 specifications',
+ ignoreCustomComments: ['Array of regex\'es that allow to ignore certain comments, when matched', parseJSONRegExpArray],
+ ignoreCustomFragments: ['Array of regex\'es that allow to ignore certain fragments, when matched (e.g. <?php ... ?>, {{ ... }})', parseJSONRegExpArray],
+ includeAutoGeneratedTags: 'Insert tags generated by HTML parser',
+ keepClosingSlash: 'Keep the trailing slash on singleton elements',
+ maxLineLength: ['Max line length', parseInt],
+ minifyCSS: ['Minify CSS in style elements and style attributes (uses clean-css)', parseJSON],
+ minifyJS: ['Minify Javascript in script elements and on* attributes (uses uglify-js)', parseJSON],
+ minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
+ preserveLineBreaks: 'Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break.',
+ preventAttributesEscaping: 'Prevents the escaping of the values of attributes.',
+ processConditionalComments: 'Process contents of conditional comments through minifier',
+ processScripts: ['Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', parseJSONArray],
+ quoteCharacter: ['Type of quote to use for attribute values (\' or ")', parseString],
+ removeAttributeQuotes: 'Remove quotes around attributes when possible.',
+ removeComments: 'Strip HTML comments',
+ removeEmptyAttributes: 'Remove all attributes with whitespace-only values',
+ removeEmptyElements: 'Remove all elements with empty contents',
+ removeOptionalTags: 'Remove unrequired tags',
+ removeRedundantAttributes: 'Remove attributes when value matches default.',
+ removeScriptTypeAttributes: 'Remove type="text/javascript" from script tags. Other type attribute values are left intact.',
+ removeStyleLinkTypeAttributes: 'Remove type="text/css" from style and link tags. Other type attribute values are left intact.',
+ removeTagWhitespace: 'Remove space between attributes whenever possible',
+ sortAttributes: 'Sort attributes by frequency',
+ sortClassName: 'Sort style classes by frequency',
+ trimCustomFragments: 'Trim white space around ignoreCustomFragments.',
+ useShortDoctype: 'Replaces the doctype with the short (HTML5) doctype'
+};
+var mainOptionKeys = Object.keys(mainOptions);
+mainOptionKeys.forEach(function(key) {
+ var option = mainOptions[key];
+ key = '--' + paramCase(key);
+ if (Array.isArray(option)) {
+ var optional = option[1] === parseJSON;
+ program.option(key + (optional ? ' [value]' : ' <value>'), option[0], option[1]);
+ }
+ else {
+ program.option(key, option);
+ }
+});
+program.option('-o --output <file>', 'Specify output file (if not specified STDOUT will be used for output)', function(outputPath) {
+ return fs.createWriteStream(outputPath).on('error', function(e) {
+ fatal('Cannot write ' + outputPath + '\n' + e.message);
+ });
+}, process.stdout);
+
+function readFile(file) {
+ try {
+ return fs.readFileSync(file, { encoding: 'utf8' });
+ }
+ catch (e) {
+ fatal('Cannot read ' + file + '\n' + e.message);
+ }
+}
+
+var config = {};
+program.option('-c --config-file <file>', 'Use config file', function(configPath) {
+ var data = readFile(configPath);
+ try {
+ config = JSON.parse(data);
+ }
+ catch (je) {
+ try {
+ config = require(path.resolve(configPath));
+ }
+ catch (ne) {
+ fatal('Cannot read the specified config file.\nAs JSON: ' + je.message + '\nAs module: ' + ne.message);
+ }
+ }
+ mainOptionKeys.forEach(function(key) {
+ if (key in config) {
+ var option = mainOptions[key];
+ if (Array.isArray(option)) {
+ var value = config[key];
+ config[key] = option[1](typeof value === 'string' ? value : JSON.stringify(value));
+ }
+ }
+ });
+});
+program.option('--input-dir <dir>', 'Specify an input directory');
+program.option('--output-dir <dir>', 'Specify an output directory');
+program.option('--file-ext <text>', 'Specify an extension to be read, ex: html');
+var content;
+program.arguments('[files...]').action(function(files) {
+ content = files.map(readFile).join('');
+}).parse(process.argv);
+
+function createOptions() {
+ var options = {};
+ mainOptionKeys.forEach(function(key) {
+ var param = program[camelCase(key)];
+ if (typeof param !== 'undefined') {
+ options[key] = param;
+ }
+ else if (key in config) {
+ options[key] = config[key];
+ }
+ });
+ return options;
+}
+
+function mkdir(outputDir, callback) {
+ fs.mkdir(outputDir, function(err) {
+ if (err) {
+ switch (err.code) {
+ case 'ENOENT':
+ return mkdir(path.join(outputDir, '..'), function() {
+ mkdir(outputDir, callback);
+ });
+ case 'EEXIST':
+ break;
+ default:
+ fatal('Cannot create directory ' + outputDir + '\n' + err.message);
+ }
+ }
+ callback();
+ });
+}
+
+function processFile(inputFile, outputFile) {
+ fs.readFile(inputFile, { encoding: 'utf8' }, function(err, data) {
+ if (err) {
+ fatal('Cannot read ' + inputFile + '\n' + err.message);
+ }
+ var minified;
+ try {
+ minified = minify(data, createOptions());
+ }
+ catch (e) {
+ fatal('Minification error on ' + inputFile + '\n' + e.message);
+ }
+ fs.writeFile(outputFile, minified, { encoding: 'utf8' }, function(err) {
+ if (err) {
+ fatal('Cannot write ' + outputFile + '\n' + err.message);
+ }
+ });
+ });
+}
+
+function processDirectory(inputDir, outputDir, fileExt) {
+ fs.readdir(inputDir, function(err, files) {
+ if (err) {
+ fatal('Cannot read directory ' + inputDir + '\n' + err.message);
+ }
+ files.forEach(function(file) {
+ var inputFile = path.join(inputDir, file);
+ var outputFile = path.join(outputDir, file);
+ fs.stat(inputFile, function(err, stat) {
+ if (err) {
+ fatal('Cannot read ' + inputFile + '\n' + err.message);
+ }
+ else if (stat.isDirectory()) {
+ processDirectory(inputFile, outputFile, fileExt);
+ }
+ else if (!fileExt || path.extname(file) === '.' + fileExt) {
+ mkdir(outputDir, function() {
+ processFile(inputFile, outputFile);
+ });
+ }
+ });
+ });
+ });
+}
+
+function writeMinify() {
+ var minified;
+ try {
+ minified = minify(content, createOptions());
+ }
+ catch (e) {
+ fatal('Minification error:\n' + e.message);
+ }
+ program.output.write(minified);
+}
+
+var inputDir = program.inputDir;
+var outputDir = program.outputDir;
+var fileExt = program.fileExt;
+if (inputDir || outputDir) {
+ if (!inputDir) {
+ fatal('The option output-dir needs to be used with the option input-dir. If you are working with a single file, use -o.');
+ }
+ else if (!outputDir) {
+ fatal('You need to specify where to write the output files with the option --output-dir');
+ }
+ processDirectory(inputDir, outputDir, fileExt);
+}
+// Minifying one or more files specified on the CMD line
+else if (typeof content === 'string') {
+ writeMinify();
+}
+// Minifying input coming from STDIN
+else {
+ content = '';
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('data', function(data) {
+ content += data;
+ }).on('end', writeMinify);
+}
diff --git a/node_modules/html-minifier/node_modules/.bin/he b/node_modules/html-minifier/node_modules/.bin/he
new file mode 120000
index 000000000..a81124c26
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/.bin/he
@@ -0,0 +1 @@
+../../../he/bin/he \ No newline at end of file
diff --git a/node_modules/html-minifier/node_modules/.bin/uglifyjs b/node_modules/html-minifier/node_modules/.bin/uglifyjs
new file mode 120000
index 000000000..aaaeee8ac
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/.bin/uglifyjs
@@ -0,0 +1 @@
+../../../uglify-js/bin/uglifyjs \ No newline at end of file
diff --git a/node_modules/html-minifier/node_modules/commander/History.md b/node_modules/html-minifier/node_modules/commander/History.md
new file mode 100644
index 000000000..1b47439cb
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/commander/History.md
@@ -0,0 +1,261 @@
+
+2.9.0 / 2015-10-13
+==================
+
+ * Add option `isDefault` to set default subcommand #415 @Qix-
+ * Add callback to allow filtering or post-processing of help text #434 @djulien
+ * Fix `undefined` text in help information close #414 #416 @zhiyelee
+
+2.8.1 / 2015-04-22
+==================
+
+ * Back out `support multiline description` Close #396 #397
+
+2.8.0 / 2015-04-07
+==================
+
+ * Add `process.execArg` support, execution args like `--harmony` will be passed to sub-commands #387 @DigitalIO @zhiyelee
+ * Fix bug in Git-style sub-commands #372 @zhiyelee
+ * Allow commands to be hidden from help #383 @tonylukasavage
+ * When git-style sub-commands are in use, yet none are called, display help #382 @claylo
+ * Add ability to specify arguments syntax for top-level command #258 @rrthomas
+ * Support multiline descriptions #208 @zxqfox
+
+2.7.1 / 2015-03-11
+==================
+
+ * Revert #347 (fix collisions when option and first arg have same name) which causes a bug in #367.
+
+2.7.0 / 2015-03-09
+==================
+
+ * Fix git-style bug when installed globally. Close #335 #349 @zhiyelee
+ * Fix collisions when option and first arg have same name. Close #346 #347 @tonylukasavage
+ * Add support for camelCase on `opts()`. Close #353 @nkzawa
+ * Add node.js 0.12 and io.js to travis.yml
+ * Allow RegEx options. #337 @palanik
+ * Fixes exit code when sub-command failing. Close #260 #332 @pirelenito
+ * git-style `bin` files in $PATH make sense. Close #196 #327 @zhiyelee
+
+2.6.0 / 2014-12-30
+==================
+
+ * added `Command#allowUnknownOption` method. Close #138 #318 @doozr @zhiyelee
+ * Add application description to the help msg. Close #112 @dalssoft
+
+2.5.1 / 2014-12-15
+==================
+
+ * fixed two bugs incurred by variadic arguments. Close #291 @Quentin01 #302 @zhiyelee
+
+2.5.0 / 2014-10-24
+==================
+
+ * add support for variadic arguments. Closes #277 @whitlockjc
+
+2.4.0 / 2014-10-17
+==================
+
+ * fixed a bug on executing the coercion function of subcommands option. Closes #270
+ * added `Command.prototype.name` to retrieve command name. Closes #264 #266 @tonylukasavage
+ * added `Command.prototype.opts` to retrieve all the options as a simple object of key-value pairs. Closes #262 @tonylukasavage
+ * fixed a bug on subcommand name. Closes #248 @jonathandelgado
+ * fixed function normalize doesn’t honor option terminator. Closes #216 @abbr
+
+2.3.0 / 2014-07-16
+==================
+
+ * add command alias'. Closes PR #210
+ * fix: Typos. Closes #99
+ * fix: Unused fs module. Closes #217
+
+2.2.0 / 2014-03-29
+==================
+
+ * add passing of previous option value
+ * fix: support subcommands on windows. Closes #142
+ * Now the defaultValue passed as the second argument of the coercion function.
+
+2.1.0 / 2013-11-21
+==================
+
+ * add: allow cflag style option params, unit test, fixes #174
+
+2.0.0 / 2013-07-18
+==================
+
+ * remove input methods (.prompt, .confirm, etc)
+
+1.3.2 / 2013-07-18
+==================
+
+ * add support for sub-commands to co-exist with the original command
+
+1.3.1 / 2013-07-18
+==================
+
+ * add quick .runningCommand hack so you can opt-out of other logic when running a sub command
+
+1.3.0 / 2013-07-09
+==================
+
+ * add EACCES error handling
+ * fix sub-command --help
+
+1.2.0 / 2013-06-13
+==================
+
+ * allow "-" hyphen as an option argument
+ * support for RegExp coercion
+
+1.1.1 / 2012-11-20
+==================
+
+ * add more sub-command padding
+ * fix .usage() when args are present. Closes #106
+
+1.1.0 / 2012-11-16
+==================
+
+ * add git-style executable subcommand support. Closes #94
+
+1.0.5 / 2012-10-09
+==================
+
+ * fix `--name` clobbering. Closes #92
+ * fix examples/help. Closes #89
+
+1.0.4 / 2012-09-03
+==================
+
+ * add `outputHelp()` method.
+
+1.0.3 / 2012-08-30
+==================
+
+ * remove invalid .version() defaulting
+
+1.0.2 / 2012-08-24
+==================
+
+ * add `--foo=bar` support [arv]
+ * fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus]
+
+1.0.1 / 2012-08-03
+==================
+
+ * fix issue #56
+ * fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode())
+
+1.0.0 / 2012-07-05
+==================
+
+ * add support for optional option descriptions
+ * add defaulting of `.version()` to package.json's version
+
+0.6.1 / 2012-06-01
+==================
+
+ * Added: append (yes or no) on confirmation
+ * Added: allow node.js v0.7.x
+
+0.6.0 / 2012-04-10
+==================
+
+ * Added `.prompt(obj, callback)` support. Closes #49
+ * Added default support to .choose(). Closes #41
+ * Fixed the choice example
+
+0.5.1 / 2011-12-20
+==================
+
+ * Fixed `password()` for recent nodes. Closes #36
+
+0.5.0 / 2011-12-04
+==================
+
+ * Added sub-command option support [itay]
+
+0.4.3 / 2011-12-04
+==================
+
+ * Fixed custom help ordering. Closes #32
+
+0.4.2 / 2011-11-24
+==================
+
+ * Added travis support
+ * Fixed: line-buffered input automatically trimmed. Closes #31
+
+0.4.1 / 2011-11-18
+==================
+
+ * Removed listening for "close" on --help
+
+0.4.0 / 2011-11-15
+==================
+
+ * Added support for `--`. Closes #24
+
+0.3.3 / 2011-11-14
+==================
+
+ * Fixed: wait for close event when writing help info [Jerry Hamlet]
+
+0.3.2 / 2011-11-01
+==================
+
+ * Fixed long flag definitions with values [felixge]
+
+0.3.1 / 2011-10-31
+==================
+
+ * Changed `--version` short flag to `-V` from `-v`
+ * Changed `.version()` so it's configurable [felixge]
+
+0.3.0 / 2011-10-31
+==================
+
+ * Added support for long flags only. Closes #18
+
+0.2.1 / 2011-10-24
+==================
+
+ * "node": ">= 0.4.x < 0.7.0". Closes #20
+
+0.2.0 / 2011-09-26
+==================
+
+ * Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs]
+
+0.1.0 / 2011-08-24
+==================
+
+ * Added support for custom `--help` output
+
+0.0.5 / 2011-08-18
+==================
+
+ * Changed: when the user enters nothing prompt for password again
+ * Fixed issue with passwords beginning with numbers [NuckChorris]
+
+0.0.4 / 2011-08-15
+==================
+
+ * Fixed `Commander#args`
+
+0.0.3 / 2011-08-15
+==================
+
+ * Added default option value support
+
+0.0.2 / 2011-08-15
+==================
+
+ * Added mask support to `Command#password(str[, mask], fn)`
+ * Added `Command#password(str, fn)`
+
+0.0.1 / 2010-01-03
+==================
+
+ * Initial release
diff --git a/node_modules/html-minifier/node_modules/commander/LICENSE b/node_modules/html-minifier/node_modules/commander/LICENSE
new file mode 100644
index 000000000..10f997ab1
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/commander/LICENSE
@@ -0,0 +1,22 @@
+(The MIT License)
+
+Copyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/html-minifier/node_modules/commander/Readme.md b/node_modules/html-minifier/node_modules/commander/Readme.md
new file mode 100644
index 000000000..08b9e4cb9
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/commander/Readme.md
@@ -0,0 +1,351 @@
+# Commander.js
+
+
+[![Build Status](https://api.travis-ci.org/tj/commander.js.svg)](http://travis-ci.org/tj/commander.js)
+[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://www.npmjs.org/package/commander)
+[![Join the chat at https://gitter.im/tj/commander.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/tj/commander.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+ The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/tj/commander).
+ [API documentation](http://tj.github.com/commander.js/)
+
+
+## Installation
+
+ $ npm install commander
+
+## Option parsing
+
+ Options with commander are defined with the `.option()` method, also serving as documentation for the options. The example below parses args and options from `process.argv`, leaving remaining args as the `program.args` array which were not consumed by options.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+ .version('0.0.1')
+ .option('-p, --peppers', 'Add peppers')
+ .option('-P, --pineapple', 'Add pineapple')
+ .option('-b, --bbq-sauce', 'Add bbq sauce')
+ .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
+ .parse(process.argv);
+
+console.log('you ordered a pizza with:');
+if (program.peppers) console.log(' - peppers');
+if (program.pineapple) console.log(' - pineapple');
+if (program.bbqSauce) console.log(' - bbq');
+console.log(' - %s cheese', program.cheese);
+```
+
+ Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
+
+
+## Coercion
+
+```js
+function range(val) {
+ return val.split('..').map(Number);
+}
+
+function list(val) {
+ return val.split(',');
+}
+
+function collect(val, memo) {
+ memo.push(val);
+ return memo;
+}
+
+function increaseVerbosity(v, total) {
+ return total + 1;
+}
+
+program
+ .version('0.0.1')
+ .usage('[options] <file ...>')
+ .option('-i, --integer <n>', 'An integer argument', parseInt)
+ .option('-f, --float <n>', 'A float argument', parseFloat)
+ .option('-r, --range <a>..<b>', 'A range', range)
+ .option('-l, --list <items>', 'A list', list)
+ .option('-o, --optional [value]', 'An optional value')
+ .option('-c, --collect [value]', 'A repeatable value', collect, [])
+ .option('-v, --verbose', 'A value that can be increased', increaseVerbosity, 0)
+ .parse(process.argv);
+
+console.log(' int: %j', program.integer);
+console.log(' float: %j', program.float);
+console.log(' optional: %j', program.optional);
+program.range = program.range || [];
+console.log(' range: %j..%j', program.range[0], program.range[1]);
+console.log(' list: %j', program.list);
+console.log(' collect: %j', program.collect);
+console.log(' verbosity: %j', program.verbose);
+console.log(' args: %j', program.args);
+```
+
+## Regular Expression
+```js
+program
+ .version('0.0.1')
+ .option('-s --size <size>', 'Pizza size', /^(large|medium|small)$/i, 'medium')
+ .option('-d --drink [drink]', 'Drink', /^(coke|pepsi|izze)$/i)
+ .parse(process.argv);
+
+console.log(' size: %j', program.size);
+console.log(' drink: %j', program.drink);
+```
+
+## Variadic arguments
+
+ The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to
+ append `...` to the argument name. Here is an example:
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+ .version('0.0.1')
+ .command('rmdir <dir> [otherDirs...]')
+ .action(function (dir, otherDirs) {
+ console.log('rmdir %s', dir);
+ if (otherDirs) {
+ otherDirs.forEach(function (oDir) {
+ console.log('rmdir %s', oDir);
+ });
+ }
+ });
+
+program.parse(process.argv);
+```
+
+ An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed
+ to your action as demonstrated above.
+
+## Specify the argument syntax
+
+```js
+#!/usr/bin/env node
+
+var program = require('../');
+
+program
+ .version('0.0.1')
+ .arguments('<cmd> [env]')
+ .action(function (cmd, env) {
+ cmdValue = cmd;
+ envValue = env;
+ });
+
+program.parse(process.argv);
+
+if (typeof cmdValue === 'undefined') {
+ console.error('no command given!');
+ process.exit(1);
+}
+console.log('command:', cmdValue);
+console.log('environment:', envValue || "no environment given");
+```
+
+## Git-style sub-commands
+
+```js
+// file: ./examples/pm
+var program = require('..');
+
+program
+ .version('0.0.1')
+ .command('install [name]', 'install one or more packages')
+ .command('search [query]', 'search with optional query')
+ .command('list', 'list packages installed', {isDefault: true})
+ .parse(process.argv);
+```
+
+When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
+The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.
+
+Options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the option from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.
+
+If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.
+
+### `--harmony`
+
+You can enable `--harmony` option in two ways:
+* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. Note some os version don’t support this pattern.
+* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process.
+
+## Automated --help
+
+ The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
+
+```
+ $ ./examples/pizza --help
+
+ Usage: pizza [options]
+
+ An application for pizzas ordering
+
+ Options:
+
+ -h, --help output usage information
+ -V, --version output the version number
+ -p, --peppers Add peppers
+ -P, --pineapple Add pineapple
+ -b, --bbq Add bbq sauce
+ -c, --cheese <type> Add the specified type of cheese [marble]
+ -C, --no-cheese You do not want any cheese
+
+```
+
+## Custom help
+
+ You can display arbitrary `-h, --help` information
+ by listening for "--help". Commander will automatically
+ exit once you are done so that the remainder of your program
+ does not execute causing undesired behaviours, for example
+ in the following executable "stuff" will not output when
+ `--help` is used.
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+ .version('0.0.1')
+ .option('-f, --foo', 'enable some foo')
+ .option('-b, --bar', 'enable some bar')
+ .option('-B, --baz', 'enable some baz');
+
+// must be before .parse() since
+// node's emit() is immediate
+
+program.on('--help', function(){
+ console.log(' Examples:');
+ console.log('');
+ console.log(' $ custom-help --help');
+ console.log(' $ custom-help -h');
+ console.log('');
+});
+
+program.parse(process.argv);
+
+console.log('stuff');
+```
+
+Yields the following help output when `node script-name.js -h` or `node script-name.js --help` are run:
+
+```
+
+Usage: custom-help [options]
+
+Options:
+
+ -h, --help output usage information
+ -V, --version output the version number
+ -f, --foo enable some foo
+ -b, --bar enable some bar
+ -B, --baz enable some baz
+
+Examples:
+
+ $ custom-help --help
+ $ custom-help -h
+
+```
+
+## .outputHelp(cb)
+
+Output help information without exiting.
+Optional callback cb allows post-processing of help text before it is displayed.
+
+If you want to display help by default (e.g. if no command was provided), you can use something like:
+
+```js
+var program = require('commander');
+var colors = require('colors');
+
+program
+ .version('0.0.1')
+ .command('getstream [url]', 'get stream URL')
+ .parse(process.argv);
+
+ if (!process.argv.slice(2).length) {
+ program.outputHelp(make_red);
+ }
+
+function make_red(txt) {
+ return colors.red(txt); //display the help text in red on the console
+}
+```
+
+## .help(cb)
+
+ Output help information and exit immediately.
+ Optional callback cb allows post-processing of help text before it is displayed.
+
+## Examples
+
+```js
+var program = require('commander');
+
+program
+ .version('0.0.1')
+ .option('-C, --chdir <path>', 'change the working directory')
+ .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+ .option('-T, --no-tests', 'ignore test hook')
+
+program
+ .command('setup [env]')
+ .description('run setup commands for all envs')
+ .option("-s, --setup_mode [mode]", "Which setup mode to use")
+ .action(function(env, options){
+ var mode = options.setup_mode || "normal";
+ env = env || 'all';
+ console.log('setup for %s env(s) with %s mode', env, mode);
+ });
+
+program
+ .command('exec <cmd>')
+ .alias('ex')
+ .description('execute the given remote cmd')
+ .option("-e, --exec_mode <mode>", "Which exec mode to use")
+ .action(function(cmd, options){
+ console.log('exec "%s" using %s mode', cmd, options.exec_mode);
+ }).on('--help', function() {
+ console.log(' Examples:');
+ console.log();
+ console.log(' $ deploy exec sequential');
+ console.log(' $ deploy exec async');
+ console.log();
+ });
+
+program
+ .command('*')
+ .action(function(env){
+ console.log('deploying "%s"', env);
+ });
+
+program.parse(process.argv);
+```
+
+More Demos can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory.
+
+## License
+
+MIT
+
diff --git a/node_modules/html-minifier/node_modules/commander/index.js b/node_modules/html-minifier/node_modules/commander/index.js
new file mode 100644
index 000000000..a19c05d2e
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/commander/index.js
@@ -0,0 +1,1110 @@
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('events').EventEmitter;
+var spawn = require('child_process').spawn;
+var readlink = require('graceful-readlink').readlinkSync;
+var path = require('path');
+var dirname = path.dirname;
+var basename = path.basename;
+var fs = require('fs');
+
+/**
+ * Expose the root command.
+ */
+
+exports = module.exports = new Command();
+
+/**
+ * Expose `Command`.
+ */
+
+exports.Command = Command;
+
+/**
+ * Expose `Option`.
+ */
+
+exports.Option = Option;
+
+/**
+ * Initialize a new `Option` with the given `flags` and `description`.
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @api public
+ */
+
+function Option(flags, description) {
+ this.flags = flags;
+ this.required = ~flags.indexOf('<');
+ this.optional = ~flags.indexOf('[');
+ this.bool = !~flags.indexOf('-no-');
+ flags = flags.split(/[ ,|]+/);
+ if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
+ this.long = flags.shift();
+ this.description = description || '';
+}
+
+/**
+ * Return option name.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Option.prototype.name = function() {
+ return this.long
+ .replace('--', '')
+ .replace('no-', '');
+};
+
+/**
+ * Check if `arg` matches the short or long flag.
+ *
+ * @param {String} arg
+ * @return {Boolean}
+ * @api private
+ */
+
+Option.prototype.is = function(arg) {
+ return arg == this.short || arg == this.long;
+};
+
+/**
+ * Initialize a new `Command`.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+function Command(name) {
+ this.commands = [];
+ this.options = [];
+ this._execs = {};
+ this._allowUnknownOption = false;
+ this._args = [];
+ this._name = name || '';
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+Command.prototype.__proto__ = EventEmitter.prototype;
+
+/**
+ * Add command `name`.
+ *
+ * The `.action()` callback is invoked when the
+ * command `name` is specified via __ARGV__,
+ * and the remaining arguments are applied to the
+ * function for access.
+ *
+ * When the `name` is "*" an un-matched command
+ * will be passed as the first arg, followed by
+ * the rest of __ARGV__ remaining.
+ *
+ * Examples:
+ *
+ * program
+ * .version('0.0.1')
+ * .option('-C, --chdir <path>', 'change the working directory')
+ * .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
+ * .option('-T, --no-tests', 'ignore test hook')
+ *
+ * program
+ * .command('setup')
+ * .description('run remote setup commands')
+ * .action(function() {
+ * console.log('setup');
+ * });
+ *
+ * program
+ * .command('exec <cmd>')
+ * .description('run the given remote command')
+ * .action(function(cmd) {
+ * console.log('exec "%s"', cmd);
+ * });
+ *
+ * program
+ * .command('teardown <dir> [otherDirs...]')
+ * .description('run teardown commands')
+ * .action(function(dir, otherDirs) {
+ * console.log('dir "%s"', dir);
+ * if (otherDirs) {
+ * otherDirs.forEach(function (oDir) {
+ * console.log('dir "%s"', oDir);
+ * });
+ * }
+ * });
+ *
+ * program
+ * .command('*')
+ * .description('deploy the given env')
+ * .action(function(env) {
+ * console.log('deploying "%s"', env);
+ * });
+ *
+ * program.parse(process.argv);
+ *
+ * @param {String} name
+ * @param {String} [desc] for git-style sub-commands
+ * @return {Command} the new command
+ * @api public
+ */
+
+Command.prototype.command = function(name, desc, opts) {
+ opts = opts || {};
+ var args = name.split(/ +/);
+ var cmd = new Command(args.shift());
+
+ if (desc) {
+ cmd.description(desc);
+ this.executables = true;
+ this._execs[cmd._name] = true;
+ if (opts.isDefault) this.defaultExecutable = cmd._name;
+ }
+
+ cmd._noHelp = !!opts.noHelp;
+ this.commands.push(cmd);
+ cmd.parseExpectedArgs(args);
+ cmd.parent = this;
+
+ if (desc) return this;
+ return cmd;
+};
+
+/**
+ * Define argument syntax for the top-level command.
+ *
+ * @api public
+ */
+
+Command.prototype.arguments = function (desc) {
+ return this.parseExpectedArgs(desc.split(/ +/));
+};
+
+/**
+ * Add an implicit `help [cmd]` subcommand
+ * which invokes `--help` for the given command.
+ *
+ * @api private
+ */
+
+Command.prototype.addImplicitHelpCommand = function() {
+ this.command('help [cmd]', 'display help for [cmd]');
+};
+
+/**
+ * Parse expected `args`.
+ *
+ * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parseExpectedArgs = function(args) {
+ if (!args.length) return;
+ var self = this;
+ args.forEach(function(arg) {
+ var argDetails = {
+ required: false,
+ name: '',
+ variadic: false
+ };
+
+ switch (arg[0]) {
+ case '<':
+ argDetails.required = true;
+ argDetails.name = arg.slice(1, -1);
+ break;
+ case '[':
+ argDetails.name = arg.slice(1, -1);
+ break;
+ }
+
+ if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
+ argDetails.variadic = true;
+ argDetails.name = argDetails.name.slice(0, -3);
+ }
+ if (argDetails.name) {
+ self._args.push(argDetails);
+ }
+ });
+ return this;
+};
+
+/**
+ * Register callback `fn` for the command.
+ *
+ * Examples:
+ *
+ * program
+ * .command('help')
+ * .description('display verbose help')
+ * .action(function() {
+ * // output help here
+ * });
+ *
+ * @param {Function} fn
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.action = function(fn) {
+ var self = this;
+ var listener = function(args, unknown) {
+ // Parse any so-far unknown options
+ args = args || [];
+ unknown = unknown || [];
+
+ var parsed = self.parseOptions(unknown);
+
+ // Output help if necessary
+ outputHelpIfNecessary(self, parsed.unknown);
+
+ // If there are still any unknown options, then we simply
+ // die, unless someone asked for help, in which case we give it
+ // to them, and then we die.
+ if (parsed.unknown.length > 0) {
+ self.unknownOption(parsed.unknown[0]);
+ }
+
+ // Leftover arguments need to be pushed back. Fixes issue #56
+ if (parsed.args.length) args = parsed.args.concat(args);
+
+ self._args.forEach(function(arg, i) {
+ if (arg.required && null == args[i]) {
+ self.missingArgument(arg.name);
+ } else if (arg.variadic) {
+ if (i !== self._args.length - 1) {
+ self.variadicArgNotLast(arg.name);
+ }
+
+ args[i] = args.splice(i);
+ }
+ });
+
+ // Always append ourselves to the end of the arguments,
+ // to make sure we match the number of arguments the user
+ // expects
+ if (self._args.length) {
+ args[self._args.length] = self;
+ } else {
+ args.push(self);
+ }
+
+ fn.apply(self, args);
+ };
+ var parent = this.parent || this;
+ var name = parent === this ? '*' : this._name;
+ parent.on(name, listener);
+ if (this._alias) parent.on(this._alias, listener);
+ return this;
+};
+
+/**
+ * Define option with `flags`, `description` and optional
+ * coercion `fn`.
+ *
+ * The `flags` string should contain both the short and long flags,
+ * separated by comma, a pipe or space. The following are all valid
+ * all will output this way when `--help` is used.
+ *
+ * "-p, --pepper"
+ * "-p|--pepper"
+ * "-p --pepper"
+ *
+ * Examples:
+ *
+ * // simple boolean defaulting to false
+ * program.option('-p, --pepper', 'add pepper');
+ *
+ * --pepper
+ * program.pepper
+ * // => Boolean
+ *
+ * // simple boolean defaulting to true
+ * program.option('-C, --no-cheese', 'remove cheese');
+ *
+ * program.cheese
+ * // => true
+ *
+ * --no-cheese
+ * program.cheese
+ * // => false
+ *
+ * // required argument
+ * program.option('-C, --chdir <path>', 'change the working directory');
+ *
+ * --chdir /tmp
+ * program.chdir
+ * // => "/tmp"
+ *
+ * // optional argument
+ * program.option('-c, --cheese [type]', 'add cheese [marble]');
+ *
+ * @param {String} flags
+ * @param {String} description
+ * @param {Function|Mixed} fn or default
+ * @param {Mixed} defaultValue
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.option = function(flags, description, fn, defaultValue) {
+ var self = this
+ , option = new Option(flags, description)
+ , oname = option.name()
+ , name = camelcase(oname);
+
+ // default as 3rd arg
+ if (typeof fn != 'function') {
+ if (fn instanceof RegExp) {
+ var regex = fn;
+ fn = function(val, def) {
+ var m = regex.exec(val);
+ return m ? m[0] : def;
+ }
+ }
+ else {
+ defaultValue = fn;
+ fn = null;
+ }
+ }
+
+ // preassign default value only for --no-*, [optional], or <required>
+ if (false == option.bool || option.optional || option.required) {
+ // when --no-* we make sure default is true
+ if (false == option.bool) defaultValue = true;
+ // preassign only if we have a default
+ if (undefined !== defaultValue) self[name] = defaultValue;
+ }
+
+ // register the option
+ this.options.push(option);
+
+ // when it's passed assign the value
+ // and conditionally invoke the callback
+ this.on(oname, function(val) {
+ // coercion
+ if (null !== val && fn) val = fn(val, undefined === self[name]
+ ? defaultValue
+ : self[name]);
+
+ // unassigned or bool
+ if ('boolean' == typeof self[name] || 'undefined' == typeof self[name]) {
+ // if no value, bool true, and we have a default, then use it!
+ if (null == val) {
+ self[name] = option.bool
+ ? defaultValue || true
+ : false;
+ } else {
+ self[name] = val;
+ }
+ } else if (null !== val) {
+ // reassign
+ self[name] = val;
+ }
+ });
+
+ return this;
+};
+
+/**
+ * Allow unknown options on the command line.
+ *
+ * @param {Boolean} arg if `true` or omitted, no error will be thrown
+ * for unknown options.
+ * @api public
+ */
+Command.prototype.allowUnknownOption = function(arg) {
+ this._allowUnknownOption = arguments.length === 0 || arg;
+ return this;
+};
+
+/**
+ * Parse `argv`, settings options and invoking commands when defined.
+ *
+ * @param {Array} argv
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.parse = function(argv) {
+ // implicit help
+ if (this.executables) this.addImplicitHelpCommand();
+
+ // store raw args
+ this.rawArgs = argv;
+
+ // guess name
+ this._name = this._name || basename(argv[1], '.js');
+
+ // github-style sub-commands with no sub-command
+ if (this.executables && argv.length < 3 && !this.defaultExecutable) {
+ // this user needs help
+ argv.push('--help');
+ }
+
+ // process argv
+ var parsed = this.parseOptions(this.normalize(argv.slice(2)));
+ var args = this.args = parsed.args;
+
+ var result = this.parseArgs(this.args, parsed.unknown);
+
+ // executable sub-commands
+ var name = result.args[0];
+ if (this._execs[name] && typeof this._execs[name] != "function") {
+ return this.executeSubCommand(argv, args, parsed.unknown);
+ } else if (this.defaultExecutable) {
+ // use the default subcommand
+ args.unshift(name = this.defaultExecutable);
+ return this.executeSubCommand(argv, args, parsed.unknown);
+ }
+
+ return result;
+};
+
+/**
+ * Execute a sub-command executable.
+ *
+ * @param {Array} argv
+ * @param {Array} args
+ * @param {Array} unknown
+ * @api private
+ */
+
+Command.prototype.executeSubCommand = function(argv, args, unknown) {
+ args = args.concat(unknown);
+
+ if (!args.length) this.help();
+ if ('help' == args[0] && 1 == args.length) this.help();
+
+ // <cmd> --help
+ if ('help' == args[0]) {
+ args[0] = args[1];
+ args[1] = '--help';
+ }
+
+ // executable
+ var f = argv[1];
+ // name of the subcommand, link `pm-install`
+ var bin = basename(f, '.js') + '-' + args[0];
+
+
+ // In case of globally installed, get the base dir where executable
+ // subcommand file should be located at
+ var baseDir
+ , link = readlink(f);
+
+ // when symbolink is relative path
+ if (link !== f && link.charAt(0) !== '/') {
+ link = path.join(dirname(f), link)
+ }
+ baseDir = dirname(link);
+
+ // prefer local `./<bin>` to bin in the $PATH
+ var localBin = path.join(baseDir, bin);
+
+ // whether bin file is a js script with explicit `.js` extension
+ var isExplicitJS = false;
+ if (exists(localBin + '.js')) {
+ bin = localBin + '.js';
+ isExplicitJS = true;
+ } else if (exists(localBin)) {
+ bin = localBin;
+ }
+
+ args = args.slice(1);
+
+ var proc;
+ if (process.platform !== 'win32') {
+ if (isExplicitJS) {
+ args.unshift(localBin);
+ // add executable arguments to spawn
+ args = (process.execArgv || []).concat(args);
+
+ proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] });
+ } else {
+ proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
+ }
+ } else {
+ args.unshift(localBin);
+ proc = spawn(process.execPath, args, { stdio: 'inherit'});
+ }
+
+ proc.on('close', process.exit.bind(process));
+ proc.on('error', function(err) {
+ if (err.code == "ENOENT") {
+ console.error('\n %s(1) does not exist, try --help\n', bin);
+ } else if (err.code == "EACCES") {
+ console.error('\n %s(1) not executable. try chmod or run with root\n', bin);
+ }
+ process.exit(1);
+ });
+
+ // Store the reference to the child process
+ this.runningCommand = proc;
+};
+
+/**
+ * Normalize `args`, splitting joined short flags. For example
+ * the arg "-abc" is equivalent to "-a -b -c".
+ * This also normalizes equal sign and splits "--abc=def" into "--abc def".
+ *
+ * @param {Array} args
+ * @return {Array}
+ * @api private
+ */
+
+Command.prototype.normalize = function(args) {
+ var ret = []
+ , arg
+ , lastOpt
+ , index;
+
+ for (var i = 0, len = args.length; i < len; ++i) {
+ arg = args[i];
+ if (i > 0) {
+ lastOpt = this.optionFor(args[i-1]);
+ }
+
+ if (arg === '--') {
+ // Honor option terminator
+ ret = ret.concat(args.slice(i));
+ break;
+ } else if (lastOpt && lastOpt.required) {
+ ret.push(arg);
+ } else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
+ arg.slice(1).split('').forEach(function(c) {
+ ret.push('-' + c);
+ });
+ } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
+ ret.push(arg.slice(0, index), arg.slice(index + 1));
+ } else {
+ ret.push(arg);
+ }
+ }
+
+ return ret;
+};
+
+/**
+ * Parse command `args`.
+ *
+ * When listener(s) are available those
+ * callbacks are invoked, otherwise the "*"
+ * event is emitted and those actions are invoked.
+ *
+ * @param {Array} args
+ * @return {Command} for chaining
+ * @api private
+ */
+
+Command.prototype.parseArgs = function(args, unknown) {
+ var name;
+
+ if (args.length) {
+ name = args[0];
+ if (this.listeners(name).length) {
+ this.emit(args.shift(), args, unknown);
+ } else {
+ this.emit('*', args);
+ }
+ } else {
+ outputHelpIfNecessary(this, unknown);
+
+ // If there were no args and we have unknown options,
+ // then they are extraneous and we need to error.
+ if (unknown.length > 0) {
+ this.unknownOption(unknown[0]);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return an option matching `arg` if any.
+ *
+ * @param {String} arg
+ * @return {Option}
+ * @api private
+ */
+
+Command.prototype.optionFor = function(arg) {
+ for (var i = 0, len = this.options.length; i < len; ++i) {
+ if (this.options[i].is(arg)) {
+ return this.options[i];
+ }
+ }
+};
+
+/**
+ * Parse options from `argv` returning `argv`
+ * void of these options.
+ *
+ * @param {Array} argv
+ * @return {Array}
+ * @api public
+ */
+
+Command.prototype.parseOptions = function(argv) {
+ var args = []
+ , len = argv.length
+ , literal
+ , option
+ , arg;
+
+ var unknownOptions = [];
+
+ // parse options
+ for (var i = 0; i < len; ++i) {
+ arg = argv[i];
+
+ // literal args after --
+ if ('--' == arg) {
+ literal = true;
+ continue;
+ }
+
+ if (literal) {
+ args.push(arg);
+ continue;
+ }
+
+ // find matching Option
+ option = this.optionFor(arg);
+
+ // option is defined
+ if (option) {
+ // requires arg
+ if (option.required) {
+ arg = argv[++i];
+ if (null == arg) return this.optionMissingArgument(option);
+ this.emit(option.name(), arg);
+ // optional arg
+ } else if (option.optional) {
+ arg = argv[i+1];
+ if (null == arg || ('-' == arg[0] && '-' != arg)) {
+ arg = null;
+ } else {
+ ++i;
+ }
+ this.emit(option.name(), arg);
+ // bool
+ } else {
+ this.emit(option.name());
+ }
+ continue;
+ }
+
+ // looks like an option
+ if (arg.length > 1 && '-' == arg[0]) {
+ unknownOptions.push(arg);
+
+ // If the next argument looks like it might be
+ // an argument for this option, we pass it on.
+ // If it isn't, then it'll simply be ignored
+ if (argv[i+1] && '-' != argv[i+1][0]) {
+ unknownOptions.push(argv[++i]);
+ }
+ continue;
+ }
+
+ // arg
+ args.push(arg);
+ }
+
+ return { args: args, unknown: unknownOptions };
+};
+
+/**
+ * Return an object containing options as key-value pairs
+ *
+ * @return {Object}
+ * @api public
+ */
+Command.prototype.opts = function() {
+ var result = {}
+ , len = this.options.length;
+
+ for (var i = 0 ; i < len; i++) {
+ var key = camelcase(this.options[i].name());
+ result[key] = key === 'version' ? this._version : this[key];
+ }
+ return result;
+};
+
+/**
+ * Argument `name` is missing.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.missingArgument = function(name) {
+ console.error();
+ console.error(" error: missing required argument `%s'", name);
+ console.error();
+ process.exit(1);
+};
+
+/**
+ * `Option` is missing an argument, but received `flag` or nothing.
+ *
+ * @param {String} option
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.optionMissingArgument = function(option, flag) {
+ console.error();
+ if (flag) {
+ console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag);
+ } else {
+ console.error(" error: option `%s' argument missing", option.flags);
+ }
+ console.error();
+ process.exit(1);
+};
+
+/**
+ * Unknown option `flag`.
+ *
+ * @param {String} flag
+ * @api private
+ */
+
+Command.prototype.unknownOption = function(flag) {
+ if (this._allowUnknownOption) return;
+ console.error();
+ console.error(" error: unknown option `%s'", flag);
+ console.error();
+ process.exit(1);
+};
+
+/**
+ * Variadic argument with `name` is not the last argument as required.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.variadicArgNotLast = function(name) {
+ console.error();
+ console.error(" error: variadic arguments must be last `%s'", name);
+ console.error();
+ process.exit(1);
+};
+
+/**
+ * Set the program version to `str`.
+ *
+ * This method auto-registers the "-V, --version" flag
+ * which will print the version number when passed.
+ *
+ * @param {String} str
+ * @param {String} flags
+ * @return {Command} for chaining
+ * @api public
+ */
+
+Command.prototype.version = function(str, flags) {
+ if (0 == arguments.length) return this._version;
+ this._version = str;
+ flags = flags || '-V, --version';
+ this.option(flags, 'output the version number');
+ this.on('version', function() {
+ process.stdout.write(str + '\n');
+ process.exit(0);
+ });
+ return this;
+};
+
+/**
+ * Set the description to `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.description = function(str) {
+ if (0 === arguments.length) return this._description;
+ this._description = str;
+ return this;
+};
+
+/**
+ * Set an alias for the command
+ *
+ * @param {String} alias
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.alias = function(alias) {
+ if (0 == arguments.length) return this._alias;
+ this._alias = alias;
+ return this;
+};
+
+/**
+ * Set / get the command usage `str`.
+ *
+ * @param {String} str
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.usage = function(str) {
+ var args = this._args.map(function(arg) {
+ return humanReadableArgName(arg);
+ });
+
+ var usage = '[options]'
+ + (this.commands.length ? ' [command]' : '')
+ + (this._args.length ? ' ' + args.join(' ') : '');
+
+ if (0 == arguments.length) return this._usage || usage;
+ this._usage = str;
+
+ return this;
+};
+
+/**
+ * Get the name of the command
+ *
+ * @param {String} name
+ * @return {String|Command}
+ * @api public
+ */
+
+Command.prototype.name = function() {
+ return this._name;
+};
+
+/**
+ * Return the largest option length.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+Command.prototype.largestOptionLength = function() {
+ return this.options.reduce(function(max, option) {
+ return Math.max(max, option.flags.length);
+ }, 0);
+};
+
+/**
+ * Return help for options.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.optionHelp = function() {
+ var width = this.largestOptionLength();
+
+ // Prepend the help information
+ return [pad('-h, --help', width) + ' ' + 'output usage information']
+ .concat(this.options.map(function(option) {
+ return pad(option.flags, width) + ' ' + option.description;
+ }))
+ .join('\n');
+};
+
+/**
+ * Return command help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.commandHelp = function() {
+ if (!this.commands.length) return '';
+
+ var commands = this.commands.filter(function(cmd) {
+ return !cmd._noHelp;
+ }).map(function(cmd) {
+ var args = cmd._args.map(function(arg) {
+ return humanReadableArgName(arg);
+ }).join(' ');
+
+ return [
+ cmd._name
+ + (cmd._alias ? '|' + cmd._alias : '')
+ + (cmd.options.length ? ' [options]' : '')
+ + ' ' + args
+ , cmd.description()
+ ];
+ });
+
+ var width = commands.reduce(function(max, command) {
+ return Math.max(max, command[0].length);
+ }, 0);
+
+ return [
+ ''
+ , ' Commands:'
+ , ''
+ , commands.map(function(cmd) {
+ var desc = cmd[1] ? ' ' + cmd[1] : '';
+ return pad(cmd[0], width) + desc;
+ }).join('\n').replace(/^/gm, ' ')
+ , ''
+ ].join('\n');
+};
+
+/**
+ * Return program help documentation.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Command.prototype.helpInformation = function() {
+ var desc = [];
+ if (this._description) {
+ desc = [
+ ' ' + this._description
+ , ''
+ ];
+ }
+
+ var cmdName = this._name;
+ if (this._alias) {
+ cmdName = cmdName + '|' + this._alias;
+ }
+ var usage = [
+ ''
+ ,' Usage: ' + cmdName + ' ' + this.usage()
+ , ''
+ ];
+
+ var cmds = [];
+ var commandHelp = this.commandHelp();
+ if (commandHelp) cmds = [commandHelp];
+
+ var options = [
+ ' Options:'
+ , ''
+ , '' + this.optionHelp().replace(/^/gm, ' ')
+ , ''
+ , ''
+ ];
+
+ return usage
+ .concat(cmds)
+ .concat(desc)
+ .concat(options)
+ .join('\n');
+};
+
+/**
+ * Output help information for this command
+ *
+ * @api public
+ */
+
+Command.prototype.outputHelp = function(cb) {
+ if (!cb) {
+ cb = function(passthru) {
+ return passthru;
+ }
+ }
+ process.stdout.write(cb(this.helpInformation()));
+ this.emit('--help');
+};
+
+/**
+ * Output help information and exit.
+ *
+ * @api public
+ */
+
+Command.prototype.help = function(cb) {
+ this.outputHelp(cb);
+ process.exit();
+};
+
+/**
+ * Camel-case the given `flag`
+ *
+ * @param {String} flag
+ * @return {String}
+ * @api private
+ */
+
+function camelcase(flag) {
+ return flag.split('-').reduce(function(str, word) {
+ return str + word[0].toUpperCase() + word.slice(1);
+ });
+}
+
+/**
+ * Pad `str` to `width`.
+ *
+ * @param {String} str
+ * @param {Number} width
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, width) {
+ var len = Math.max(0, width - str.length);
+ return str + Array(len + 1).join(' ');
+}
+
+/**
+ * Output help information if necessary
+ *
+ * @param {Command} command to output help for
+ * @param {Array} array of options to search for -h or --help
+ * @api private
+ */
+
+function outputHelpIfNecessary(cmd, options) {
+ options = options || [];
+ for (var i = 0; i < options.length; i++) {
+ if (options[i] == '--help' || options[i] == '-h') {
+ cmd.outputHelp();
+ process.exit(0);
+ }
+ }
+}
+
+/**
+ * Takes an argument an returns its human readable equivalent for help usage.
+ *
+ * @param {Object} arg
+ * @return {String}
+ * @api private
+ */
+
+function humanReadableArgName(arg) {
+ var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
+
+ return arg.required
+ ? '<' + nameOutput + '>'
+ : '[' + nameOutput + ']'
+}
+
+// for versions before node v0.8 when there weren't `fs.existsSync`
+function exists(file) {
+ try {
+ if (fs.statSync(file).isFile()) {
+ return true;
+ }
+ } catch (e) {
+ return false;
+ }
+}
+
diff --git a/node_modules/html-minifier/node_modules/commander/package.json b/node_modules/html-minifier/node_modules/commander/package.json
new file mode 100644
index 000000000..77311492d
--- /dev/null
+++ b/node_modules/html-minifier/node_modules/commander/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "commander",
+ "version": "2.9.0",
+ "description": "the complete solution for node.js command-line programs",
+ "keywords": [
+ "command",
+ "option",
+ "parser"
+ ],
+ "author": "TJ Holowaychuk <tj@vision-media.ca>",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/tj/commander.js.git"
+ },
+ "devDependencies": {
+ "should": ">= 0.0.1",
+ "sinon": ">=1.17.1"
+ },
+ "scripts": {
+ "test": "make test"
+ },
+ "main": "index",
+ "engines": {
+ "node": ">= 0.6.x"
+ },
+ "files": [
+ "index.js"
+ ],
+ "dependencies": {
+ "graceful-readlink": ">= 1.0.0"
+ }
+}
diff --git a/node_modules/html-minifier/package.json b/node_modules/html-minifier/package.json
new file mode 100644
index 000000000..0bb9c717a
--- /dev/null
+++ b/node_modules/html-minifier/package.json
@@ -0,0 +1,88 @@
+{
+ "name": "html-minifier",
+ "description": "Highly configurable, well-tested, JavaScript-based HTML minifier.",
+ "version": "3.4.3",
+ "keywords": [
+ "cli",
+ "compress",
+ "compressor",
+ "css",
+ "html",
+ "htmlmin",
+ "javascript",
+ "min",
+ "minification",
+ "minifier",
+ "minify",
+ "optimize",
+ "optimizer",
+ "pack",
+ "packer",
+ "parse",
+ "parser",
+ "uglifier",
+ "uglify"
+ ],
+ "homepage": "http://kangax.github.io/html-minifier/",
+ "author": "Juriy \"kangax\" Zaytsev",
+ "maintainers": [
+ "Alex Lam <alexlamsl@gmail.com>",
+ "Juriy Zaytsev <kangax@gmail.com> (http://perfectionkills.com)"
+ ],
+ "contributors": [
+ "Gilmore Davidson (https://github.com/gilmoreorless)",
+ "Hugo Wetterberg <hugo@wetterberg.nu>",
+ "Zoltan Frombach <tssajo@gmail.com>"
+ ],
+ "license": "MIT",
+ "bin": {
+ "html-minifier": "./cli.js"
+ },
+ "main": "src/htmlminifier.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kangax/html-minifier.git"
+ },
+ "bugs": {
+ "url": "https://github.com/kangax/html-minifier/issues"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "scripts": {
+ "dist": "grunt dist",
+ "test": "grunt test"
+ },
+ "dependencies": {
+ "camel-case": "3.0.x",
+ "clean-css": "4.0.x",
+ "commander": "2.9.x",
+ "he": "1.1.x",
+ "ncname": "1.0.x",
+ "param-case": "2.1.x",
+ "relateurl": "0.2.x",
+ "uglify-js": "~2.8.22"
+ },
+ "devDependencies": {
+ "grunt": "1.0.x",
+ "grunt-browserify": "5.0.x",
+ "grunt-contrib-uglify": "2.3.x",
+ "gruntify-eslint": "3.1.x",
+ "phantomjs-prebuilt": "2.1.x",
+ "qunitjs": "2.x",
+ "uglify-to-browserify": "1.0.x"
+ },
+ "benchmarkDependencies": {
+ "brotli": "1.3.x",
+ "chalk": "1.1.x",
+ "cli-table": "0.3.x",
+ "lzma": "2.3.x",
+ "minimize": "2.1.x",
+ "progress": "2.0.x"
+ },
+ "files": [
+ "src/*.js",
+ "cli.js",
+ "sample-cli-config-file.conf"
+ ]
+}
diff --git a/node_modules/html-minifier/sample-cli-config-file.conf b/node_modules/html-minifier/sample-cli-config-file.conf
new file mode 100644
index 000000000..02ea6cd65
--- /dev/null
+++ b/node_modules/html-minifier/sample-cli-config-file.conf
@@ -0,0 +1,39 @@
+{
+ "caseSensitive": false,
+ "collapseBooleanAttributes": true,
+ "collapseInlineTagWhitespace": false,
+ "collapseWhitespace": true,
+ "conservativeCollapse": false,
+ "customAttrCollapse": ".*",
+ "decodeEntities": true,
+ "html5": true,
+ "ignoreCustomFragments": [
+ "<#[\\s\\S]*?#>",
+ "<%[\\s\\S]*?%>",
+ "<\\?[\\s\\S]*?\\?>"
+ ],
+ "includeAutoGeneratedTags": false,
+ "keepClosingSlash": false,
+ "maxLineLength": 0,
+ "minifyCSS": true,
+ "minifyJS": true,
+ "preserveLineBreaks": false,
+ "preventAttributesEscaping": false,
+ "processConditionalComments": true,
+ "processScripts": [
+ "text/html"
+ ],
+ "removeAttributeQuotes": true,
+ "removeComments": true,
+ "removeEmptyAttributes": true,
+ "removeEmptyElements": true,
+ "removeOptionalTags": true,
+ "removeRedundantAttributes": true,
+ "removeScriptTypeAttributes": true,
+ "removeStyleLinkTypeAttributes": true,
+ "removeTagWhitespace": true,
+ "sortAttributes": true,
+ "sortClassName": true,
+ "trimCustomFragments": true,
+ "useShortDoctype": true
+}
diff --git a/node_modules/html-minifier/src/htmlminifier.js b/node_modules/html-minifier/src/htmlminifier.js
new file mode 100644
index 000000000..f7917cd4a
--- /dev/null
+++ b/node_modules/html-minifier/src/htmlminifier.js
@@ -0,0 +1,1308 @@
+'use strict';
+
+var CleanCSS = require('clean-css');
+var decode = require('he').decode;
+var HTMLParser = require('./htmlparser').HTMLParser;
+var RelateUrl = require('relateurl');
+var TokenChain = require('./tokenchain');
+var UglifyJS = require('uglify-js');
+var utils = require('./utils');
+
+var trimWhitespace = String.prototype.trim ? function(str) {
+ if (typeof str !== 'string') {
+ return str;
+ }
+ return str.trim();
+} : function(str) {
+ if (typeof str !== 'string') {
+ return str;
+ }
+ return str.replace(/^\s+/, '').replace(/\s+$/, '');
+};
+
+function collapseWhitespaceAll(str) {
+ return str && str.replace(/\s+/g, function(spaces) {
+ return spaces === '\t' ? '\t' : spaces.replace(/(^|\xA0+)[^\xA0]+/g, '$1 ');
+ });
+}
+
+function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
+ var lineBreakBefore = '', lineBreakAfter = '';
+
+ if (options.preserveLineBreaks) {
+ str = str.replace(/^\s*?[\n\r]\s*/, function() {
+ lineBreakBefore = '\n';
+ return '';
+ }).replace(/\s*?[\n\r]\s*$/, function() {
+ lineBreakAfter = '\n';
+ return '';
+ });
+ }
+
+ if (trimLeft) {
+ str = str.replace(/^\s+/, function(spaces) {
+ var conservative = !lineBreakBefore && options.conservativeCollapse;
+ if (conservative && spaces === '\t') {
+ return '\t';
+ }
+ return spaces.replace(/^[^\xA0]+/, '').replace(/(\xA0+)[^\xA0]+/g, '$1 ') || (conservative ? ' ' : '');
+ });
+ }
+
+ if (trimRight) {
+ str = str.replace(/\s+$/, function(spaces) {
+ var conservative = !lineBreakAfter && options.conservativeCollapse;
+ if (conservative && spaces === '\t') {
+ return '\t';
+ }
+ return spaces.replace(/[^\xA0]+(\xA0+)/g, ' $1').replace(/[^\xA0]+$/, '') || (conservative ? ' ' : '');
+ });
+ }
+
+ if (collapseAll) {
+ // strip non space whitespace then compress spaces to one
+ str = collapseWhitespaceAll(str);
+ }
+
+ return lineBreakBefore + str + lineBreakAfter;
+}
+
+var createMapFromString = utils.createMapFromString;
+// non-empty tags that will maintain whitespace around them
+var inlineTags = createMapFromString('a,abbr,acronym,b,bdi,bdo,big,button,cite,code,del,dfn,em,font,i,ins,kbd,mark,math,nobr,q,rt,rp,s,samp,small,span,strike,strong,sub,sup,svg,time,tt,u,var');
+// non-empty tags that will maintain whitespace within them
+var inlineTextTags = createMapFromString('a,abbr,acronym,b,big,del,em,font,i,ins,kbd,mark,nobr,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var');
+// self-closing tags that will maintain whitespace around them
+var selfClosingInlineTags = createMapFromString('comment,img,input,wbr');
+
+function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
+ var trimLeft = prevTag && !selfClosingInlineTags(prevTag);
+ if (trimLeft && !options.collapseInlineTagWhitespace) {
+ trimLeft = prevTag.charAt(0) === '/' ? !inlineTags(prevTag.slice(1)) : !inlineTextTags(prevTag);
+ }
+ var trimRight = nextTag && !selfClosingInlineTags(nextTag);
+ if (trimRight && !options.collapseInlineTagWhitespace) {
+ trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags(nextTag.slice(1)) : !inlineTags(nextTag);
+ }
+ return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
+}
+
+function isConditionalComment(text) {
+ return /^\[if\s[^\]]+]|\[endif]$/.test(text);
+}
+
+function isIgnoredComment(text, options) {
+ for (var i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
+ if (options.ignoreCustomComments[i].test(text)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function isEventAttribute(attrName, options) {
+ var patterns = options.customEventAttributes;
+ if (patterns) {
+ for (var i = patterns.length; i--;) {
+ if (patterns[i].test(attrName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return /^on[a-z]{3,}$/.test(attrName);
+}
+
+function canRemoveAttributeQuotes(value) {
+ // http://mathiasbynens.be/notes/unquoted-attribute-values
+ return /^[^ \t\n\f\r"'`=<>]+$/.test(value);
+}
+
+function attributesInclude(attributes, attribute) {
+ for (var i = attributes.length; i--;) {
+ if (attributes[i].name.toLowerCase() === attribute) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function isAttributeRedundant(tag, attrName, attrValue, attrs) {
+ attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
+
+ return (
+ tag === 'script' &&
+ attrName === 'language' &&
+ attrValue === 'javascript' ||
+
+ tag === 'form' &&
+ attrName === 'method' &&
+ attrValue === 'get' ||
+
+ tag === 'input' &&
+ attrName === 'type' &&
+ attrValue === 'text' ||
+
+ tag === 'script' &&
+ attrName === 'charset' &&
+ !attributesInclude(attrs, 'src') ||
+
+ tag === 'a' &&
+ attrName === 'name' &&
+ attributesInclude(attrs, 'id') ||
+
+ tag === 'area' &&
+ attrName === 'shape' &&
+ attrValue === 'rect'
+ );
+}
+
+// https://mathiasbynens.be/demo/javascript-mime-type
+// https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
+var executableScriptsMimetypes = utils.createMap([
+ 'text/javascript',
+ 'text/ecmascript',
+ 'text/jscript',
+ 'application/javascript',
+ 'application/x-javascript',
+ 'application/ecmascript'
+]);
+
+function isScriptTypeAttribute(attrValue) {
+ attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
+ return attrValue === '' || executableScriptsMimetypes(attrValue);
+}
+
+function isExecutableScript(tag, attrs) {
+ if (tag !== 'script') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ var attrName = attrs[i].name.toLowerCase();
+ if (attrName === 'type') {
+ return isScriptTypeAttribute(attrs[i].value);
+ }
+ }
+ return true;
+}
+
+function isStyleLinkTypeAttribute(attrValue) {
+ attrValue = trimWhitespace(attrValue).toLowerCase();
+ return attrValue === '' || attrValue === 'text/css';
+}
+
+function isStyleSheet(tag, attrs) {
+ if (tag !== 'style') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ var attrName = attrs[i].name.toLowerCase();
+ if (attrName === 'type') {
+ return isStyleLinkTypeAttribute(attrs[i].value);
+ }
+ }
+ return true;
+}
+
+var isSimpleBoolean = createMapFromString('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible');
+var isBooleanValue = createMapFromString('true,false');
+
+function isBooleanAttribute(attrName, attrValue) {
+ return isSimpleBoolean(attrName) || attrName === 'draggable' && !isBooleanValue(attrValue);
+}
+
+function isUriTypeAttribute(attrName, tag) {
+ return (
+ /^(?:a|area|link|base)$/.test(tag) && attrName === 'href' ||
+ tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName) ||
+ tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName) ||
+ tag === 'q' && attrName === 'cite' ||
+ tag === 'blockquote' && attrName === 'cite' ||
+ (tag === 'ins' || tag === 'del') && attrName === 'cite' ||
+ tag === 'form' && attrName === 'action' ||
+ tag === 'input' && (attrName === 'src' || attrName === 'usemap') ||
+ tag === 'head' && attrName === 'profile' ||
+ tag === 'script' && (attrName === 'src' || attrName === 'for')
+ );
+}
+
+function isNumberTypeAttribute(attrName, tag) {
+ return (
+ /^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex' ||
+ tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex') ||
+ tag === 'select' && (attrName === 'size' || attrName === 'tabindex') ||
+ tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName) ||
+ tag === 'colgroup' && attrName === 'span' ||
+ tag === 'col' && attrName === 'span' ||
+ (tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan')
+ );
+}
+
+function isLinkType(tag, attrs, value) {
+ if (tag !== 'link') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ if (attrs[i].name === 'rel' && attrs[i].value === value) {
+ return true;
+ }
+ }
+}
+
+function isMediaQuery(tag, attrs, attrName) {
+ return attrName === 'media' && (isLinkType(tag, attrs, 'stylesheet') || isStyleSheet(tag, attrs));
+}
+
+var srcsetTags = createMapFromString('img,source');
+
+function isSrcset(attrName, tag) {
+ return attrName === 'srcset' && srcsetTags(tag);
+}
+
+function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
+ if (attrValue && isEventAttribute(attrName, options)) {
+ attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
+ return options.minifyJS(attrValue, true);
+ }
+ else if (attrName === 'class') {
+ attrValue = trimWhitespace(attrValue);
+ if (options.sortClassName) {
+ attrValue = options.sortClassName(attrValue);
+ }
+ else {
+ attrValue = collapseWhitespaceAll(attrValue);
+ }
+ return attrValue;
+ }
+ else if (isUriTypeAttribute(attrName, tag)) {
+ attrValue = trimWhitespace(attrValue);
+ return isLinkType(tag, attrs, 'canonical') ? attrValue : options.minifyURLs(attrValue);
+ }
+ else if (isNumberTypeAttribute(attrName, tag)) {
+ return trimWhitespace(attrValue);
+ }
+ else if (attrName === 'style') {
+ attrValue = trimWhitespace(attrValue);
+ if (attrValue) {
+ if (/;$/.test(attrValue) && !/&#?[0-9a-zA-Z]+;$/.test(attrValue)) {
+ attrValue = attrValue.replace(/\s*;$/, '');
+ }
+ attrValue = unwrapInlineCSS(options.minifyCSS(wrapInlineCSS(attrValue)));
+ }
+ return attrValue;
+ }
+ else if (isSrcset(attrName, tag)) {
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
+ attrValue = trimWhitespace(attrValue).split(/\s+,\s*|\s*,\s+/).map(function(candidate) {
+ var url = candidate;
+ var descriptor = '';
+ var match = candidate.match(/\s+([1-9][0-9]*w|[0-9]+(?:\.[0-9]+)?x)$/);
+ if (match) {
+ url = url.slice(0, -match[0].length);
+ var num = +match[1].slice(0, -1);
+ var suffix = match[1].slice(-1);
+ if (num !== 1 || suffix !== 'x') {
+ descriptor = ' ' + num + suffix;
+ }
+ }
+ return options.minifyURLs(url) + descriptor;
+ }).join(', ');
+ }
+ else if (isMetaViewport(tag, attrs) && attrName === 'content') {
+ attrValue = attrValue.replace(/\s+/g, '').replace(/[0-9]+\.[0-9]+/g, function(numString) {
+ // "0.90000" -> "0.9"
+ // "1.0" -> "1"
+ // "1.0001" -> "1.0001" (unchanged)
+ return (+numString).toString();
+ });
+ }
+ else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
+ attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
+ }
+ else if (tag === 'script' && attrName === 'type') {
+ attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
+ }
+ else if (isMediaQuery(tag, attrs, attrName)) {
+ attrValue = trimWhitespace(attrValue);
+ return unwrapMediaQuery(options.minifyCSS(wrapMediaQuery(attrValue)));
+ }
+ return attrValue;
+}
+
+function isMetaViewport(tag, attrs) {
+ if (tag !== 'meta') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
+ return true;
+ }
+ }
+}
+
+// Wrap CSS declarations for CleanCSS > 3.x
+// See https://github.com/jakubpawlowicz/clean-css/issues/418
+function wrapInlineCSS(text) {
+ return '*{' + text + '}';
+}
+
+function unwrapInlineCSS(text) {
+ var matches = text.match(/^\*\{([\s\S]*)\}$/);
+ return matches ? matches[1] : text;
+}
+
+function wrapMediaQuery(text) {
+ return '@media ' + text + '{a{top:0}}';
+}
+
+function unwrapMediaQuery(text) {
+ var matches = text.match(/^@media ([\s\S]*?)\s*{[\s\S]*}$/);
+ return matches ? matches[1] : text;
+}
+
+function cleanConditionalComment(comment, options) {
+ return options.processConditionalComments ? comment.replace(/^(\[if\s[^\]]+]>)([\s\S]*?)(<!\[endif])$/, function(match, prefix, text, suffix) {
+ return prefix + minify(text, options, true) + suffix;
+ }) : comment;
+}
+
+function processScript(text, options, currentAttrs) {
+ for (var i = 0, len = currentAttrs.length; i < len; i++) {
+ if (currentAttrs[i].name.toLowerCase() === 'type' &&
+ options.processScripts.indexOf(currentAttrs[i].value) > -1) {
+ return minify(text, options);
+ }
+ }
+ return text;
+}
+
+// Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
+// with the following deviations:
+// - retain <body> if followed by <noscript>
+// - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow http://www.w3.org/TR/html5/syntax.html#optional-tags
+// - retain all tags which are adjacent to non-standard HTML tags
+var optionalStartTags = createMapFromString('html,head,body,colgroup,tbody');
+var optionalEndTags = createMapFromString('html,head,body,li,dt,dd,p,rb,rt,rtc,rp,optgroup,option,colgroup,caption,thead,tbody,tfoot,tr,td,th');
+var headerTags = createMapFromString('meta,link,script,style,template,noscript');
+var descriptionTags = createMapFromString('dt,dd');
+var pBlockTags = createMapFromString('address,article,aside,blockquote,details,div,dl,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,main,menu,nav,ol,p,pre,section,table,ul');
+var pInlineTags = createMapFromString('a,audio,del,ins,map,noscript,video');
+var rubyTags = createMapFromString('rb,rt,rtc,rp');
+var rtcTag = createMapFromString('rb,rtc,rp');
+var optionTag = createMapFromString('option,optgroup');
+var tableContentTags = createMapFromString('tbody,tfoot');
+var tableSectionTags = createMapFromString('thead,tbody,tfoot');
+var cellTags = createMapFromString('td,th');
+var topLevelTags = createMapFromString('html,head,body');
+var compactTags = createMapFromString('html,body');
+var looseTags = createMapFromString('head,colgroup,caption');
+var trailingTags = createMapFromString('dt,thead');
+var htmlTags = createMapFromString('a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,element,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,isindex,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,multicol,nav,nobr,noembed,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp');
+
+function canRemoveParentTag(optionalStartTag, tag) {
+ switch (optionalStartTag) {
+ case 'html':
+ case 'head':
+ return true;
+ case 'body':
+ return !headerTags(tag);
+ case 'colgroup':
+ return tag === 'col';
+ case 'tbody':
+ return tag === 'tr';
+ }
+ return false;
+}
+
+function isStartTagMandatory(optionalEndTag, tag) {
+ switch (tag) {
+ case 'colgroup':
+ return optionalEndTag === 'colgroup';
+ case 'tbody':
+ return tableSectionTags(optionalEndTag);
+ }
+ return false;
+}
+
+function canRemovePrecedingTag(optionalEndTag, tag) {
+ switch (optionalEndTag) {
+ case 'html':
+ case 'head':
+ case 'body':
+ case 'colgroup':
+ case 'caption':
+ return true;
+ case 'li':
+ case 'optgroup':
+ case 'tr':
+ return tag === optionalEndTag;
+ case 'dt':
+ case 'dd':
+ return descriptionTags(tag);
+ case 'p':
+ return pBlockTags(tag);
+ case 'rb':
+ case 'rt':
+ case 'rp':
+ return rubyTags(tag);
+ case 'rtc':
+ return rtcTag(tag);
+ case 'option':
+ return optionTag(tag);
+ case 'thead':
+ case 'tbody':
+ return tableContentTags(tag);
+ case 'tfoot':
+ return tag === 'tbody';
+ case 'td':
+ case 'th':
+ return cellTags(tag);
+ }
+ return false;
+}
+
+var reEmptyAttribute = new RegExp(
+ '^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
+ '?:down|up|over|move|out)|key(?:press|down|up)))$');
+
+function canDeleteEmptyAttribute(tag, attrName, attrValue, options) {
+ var isValueEmpty = !attrValue || /^\s*$/.test(attrValue);
+ if (!isValueEmpty) {
+ return false;
+ }
+ if (typeof options.removeEmptyAttributes === 'function') {
+ return options.removeEmptyAttributes(attrName, tag);
+ }
+ return tag === 'input' && attrName === 'value' || reEmptyAttribute.test(attrName);
+}
+
+function hasAttrName(name, attrs) {
+ for (var i = attrs.length - 1; i >= 0; i--) {
+ if (attrs[i].name === name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function canRemoveElement(tag, attrs) {
+ switch (tag) {
+ case 'textarea':
+ return false;
+ case 'audio':
+ case 'script':
+ case 'video':
+ if (hasAttrName('src', attrs)) {
+ return false;
+ }
+ break;
+ case 'iframe':
+ if (hasAttrName('src', attrs) || hasAttrName('srcdoc', attrs)) {
+ return false;
+ }
+ break;
+ case 'object':
+ if (hasAttrName('data', attrs)) {
+ return false;
+ }
+ break;
+ case 'applet':
+ if (hasAttrName('code', attrs)) {
+ return false;
+ }
+ break;
+ }
+ return true;
+}
+
+function canCollapseWhitespace(tag) {
+ return !/^(?:script|style|pre|textarea)$/.test(tag);
+}
+
+function canTrimWhitespace(tag) {
+ return !/^(?:pre|textarea)$/.test(tag);
+}
+
+function normalizeAttr(attr, attrs, tag, options) {
+ var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
+ attrValue = attr.value;
+
+ if (options.decodeEntities && attrValue) {
+ attrValue = decode(attrValue, { isAttributeValue: true });
+ }
+
+ if (options.removeRedundantAttributes &&
+ isAttributeRedundant(tag, attrName, attrValue, attrs) ||
+ options.removeScriptTypeAttributes && tag === 'script' &&
+ attrName === 'type' && isScriptTypeAttribute(attrValue) ||
+ options.removeStyleLinkTypeAttributes && (tag === 'style' || tag === 'link') &&
+ attrName === 'type' && isStyleLinkTypeAttribute(attrValue)) {
+ return;
+ }
+
+ attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
+
+ if (options.removeEmptyAttributes &&
+ canDeleteEmptyAttribute(tag, attrName, attrValue, options)) {
+ return;
+ }
+
+ if (options.decodeEntities && attrValue) {
+ attrValue = attrValue.replace(/&(#?[0-9a-zA-Z]+;)/g, '&amp;$1');
+ }
+
+ return {
+ attr: attr,
+ name: attrName,
+ value: attrValue
+ };
+}
+
+function buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr) {
+ var attrName = normalized.name,
+ attrValue = normalized.value,
+ attr = normalized.attr,
+ attrQuote = attr.quote,
+ attrFragment,
+ emittedAttrValue;
+
+ if (typeof attrValue !== 'undefined' && (!options.removeAttributeQuotes ||
+ ~attrValue.indexOf(uidAttr) || !canRemoveAttributeQuotes(attrValue))) {
+ if (!options.preventAttributesEscaping) {
+ if (typeof options.quoteCharacter === 'undefined') {
+ var apos = (attrValue.match(/'/g) || []).length;
+ var quot = (attrValue.match(/"/g) || []).length;
+ attrQuote = apos < quot ? '\'' : '"';
+ }
+ else {
+ attrQuote = options.quoteCharacter === '\'' ? '\'' : '"';
+ }
+ if (attrQuote === '"') {
+ attrValue = attrValue.replace(/"/g, '&#34;');
+ }
+ else {
+ attrValue = attrValue.replace(/'/g, '&#39;');
+ }
+ }
+ emittedAttrValue = attrQuote + attrValue + attrQuote;
+ if (!isLast && !options.removeTagWhitespace) {
+ emittedAttrValue += ' ';
+ }
+ }
+ // make sure trailing slash is not interpreted as HTML self-closing tag
+ else if (isLast && !hasUnarySlash && !/\/$/.test(attrValue)) {
+ emittedAttrValue = attrValue;
+ }
+ else {
+ emittedAttrValue = attrValue + ' ';
+ }
+
+ if (typeof attrValue === 'undefined' || options.collapseBooleanAttributes &&
+ isBooleanAttribute(attrName.toLowerCase(), attrValue.toLowerCase())) {
+ attrFragment = attrName;
+ if (!isLast) {
+ attrFragment += ' ';
+ }
+ }
+ else {
+ attrFragment = attrName + attr.customAssign + emittedAttrValue;
+ }
+
+ return attr.customOpen + attrFragment + attr.customClose;
+}
+
+function identity(value) {
+ return value;
+}
+
+function processOptions(options) {
+ ['html5', 'includeAutoGeneratedTags'].forEach(function(key) {
+ if (!(key in options)) {
+ options[key] = true;
+ }
+ });
+
+ if (typeof options.log !== 'function') {
+ options.log = identity;
+ }
+
+ var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];
+ for (var i = 0, len = defaultTesters.length; i < len; i++) {
+ if (!options[defaultTesters[i]]) {
+ options[defaultTesters[i]] = function() {
+ return false;
+ };
+ }
+ }
+
+ if (!('ignoreCustomComments' in options)) {
+ options.ignoreCustomComments = [/^!/];
+ }
+
+ if (!('ignoreCustomFragments' in options)) {
+ options.ignoreCustomFragments = [
+ /<%[\s\S]*?%>/,
+ /<\?[\s\S]*?\?>/
+ ];
+ }
+
+ if (!options.minifyURLs) {
+ options.minifyURLs = identity;
+ }
+ if (typeof options.minifyURLs !== 'function') {
+ var minifyURLs = options.minifyURLs;
+ if (typeof minifyURLs === 'string') {
+ minifyURLs = { site: minifyURLs };
+ }
+ else if (typeof minifyURLs !== 'object') {
+ minifyURLs = {};
+ }
+ options.minifyURLs = function(text) {
+ try {
+ return RelateUrl.relate(text, minifyURLs);
+ }
+ catch (err) {
+ options.log(err);
+ return text;
+ }
+ };
+ }
+
+ if (!options.minifyJS) {
+ options.minifyJS = identity;
+ }
+ if (typeof options.minifyJS !== 'function') {
+ var minifyJS = options.minifyJS;
+ if (typeof minifyJS !== 'object') {
+ minifyJS = {};
+ }
+ minifyJS.fromString = true;
+ (minifyJS.parse || (minifyJS.parse = {})).bare_returns = false;
+ options.minifyJS = function(text, inline) {
+ var start = text.match(/^\s*<!--.*/);
+ var code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
+ try {
+ minifyJS.parse.bare_returns = inline;
+ code = UglifyJS.minify(code, minifyJS).code;
+ if (/;$/.test(code)) {
+ code = code.slice(0, -1);
+ }
+ return code;
+ }
+ catch (err) {
+ options.log(err);
+ return text;
+ }
+ };
+ }
+
+ if (!options.minifyCSS) {
+ options.minifyCSS = identity;
+ }
+ if (typeof options.minifyCSS !== 'function') {
+ var minifyCSS = options.minifyCSS;
+ if (typeof minifyCSS !== 'object') {
+ minifyCSS = {};
+ }
+ options.minifyCSS = function(text) {
+ text = text.replace(/(url\s*\(\s*)("|'|)(.*?)\2(\s*\))/ig, function(match, prefix, quote, url, suffix) {
+ return prefix + quote + options.minifyURLs(url) + quote + suffix;
+ });
+ try {
+ return new CleanCSS(minifyCSS).minify(text).styles;
+ }
+ catch (err) {
+ options.log(err);
+ return text;
+ }
+ };
+ }
+}
+
+function uniqueId(value) {
+ var id;
+ do {
+ id = Math.random().toString(36).replace(/^0\.[0-9]*/, '');
+ } while (~value.indexOf(id));
+ return id;
+}
+
+var specialContentTags = createMapFromString('script,style');
+
+function createSortFns(value, options, uidIgnore, uidAttr) {
+ var attrChains = options.sortAttributes && Object.create(null);
+ var classChain = options.sortClassName && new TokenChain();
+
+ function attrNames(attrs) {
+ return attrs.map(function(attr) {
+ return options.caseSensitive ? attr.name : attr.name.toLowerCase();
+ });
+ }
+
+ function shouldSkipUID(token, uid) {
+ return !uid || token.indexOf(uid) === -1;
+ }
+
+ function shouldSkipUIDs(token) {
+ return shouldSkipUID(token, uidIgnore) && shouldSkipUID(token, uidAttr);
+ }
+
+ function scan(input) {
+ var currentTag, currentType;
+ new HTMLParser(input, {
+ start: function(tag, attrs) {
+ if (attrChains) {
+ if (!attrChains[tag]) {
+ attrChains[tag] = new TokenChain();
+ }
+ attrChains[tag].add(attrNames(attrs).filter(shouldSkipUIDs));
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ var attr = attrs[i];
+ if (classChain && (options.caseSensitive ? attr.name : attr.name.toLowerCase()) === 'class') {
+ classChain.add(trimWhitespace(attr.value).split(/\s+/).filter(shouldSkipUIDs));
+ }
+ else if (options.processScripts && attr.name.toLowerCase() === 'type') {
+ currentTag = tag;
+ currentType = attr.value;
+ }
+ }
+ },
+ end: function() {
+ currentTag = '';
+ },
+ chars: function(text) {
+ if (options.processScripts && specialContentTags(currentTag) &&
+ options.processScripts.indexOf(currentType) > -1) {
+ scan(text);
+ }
+ }
+ });
+ }
+
+ var log = options.log;
+ options.log = null;
+ options.sortAttributes = false;
+ options.sortClassName = false;
+ scan(minify(value, options));
+ options.log = log;
+ if (attrChains) {
+ var attrSorters = Object.create(null);
+ for (var tag in attrChains) {
+ attrSorters[tag] = attrChains[tag].createSorter();
+ }
+ options.sortAttributes = function(tag, attrs) {
+ var sorter = attrSorters[tag];
+ if (sorter) {
+ var attrMap = Object.create(null);
+ var names = attrNames(attrs);
+ names.forEach(function(name, index) {
+ (attrMap[name] || (attrMap[name] = [])).push(attrs[index]);
+ });
+ sorter.sort(names).forEach(function(name, index) {
+ attrs[index] = attrMap[name].shift();
+ });
+ }
+ };
+ }
+ if (classChain) {
+ var sorter = classChain.createSorter();
+ options.sortClassName = function(value) {
+ return sorter.sort(value.split(/\s+/)).join(' ');
+ };
+ }
+}
+
+function minify(value, options, partialMarkup) {
+ options = options || {};
+ var optionsStack = [];
+ processOptions(options);
+ if (options.collapseWhitespace) {
+ value = collapseWhitespace(value, options, true, true);
+ }
+
+ var buffer = [],
+ charsPrevTag,
+ currentChars = '',
+ hasChars,
+ currentTag = '',
+ currentAttrs = [],
+ stackNoTrimWhitespace = [],
+ stackNoCollapseWhitespace = [],
+ optionalStartTag = '',
+ optionalEndTag = '',
+ t = Date.now(),
+ ignoredMarkupChunks = [],
+ ignoredCustomMarkupChunks = [],
+ uidIgnore,
+ uidAttr,
+ uidPattern;
+
+ // temporarily replace ignored chunks with comments,
+ // so that we don't have to worry what's there.
+ // for all we care there might be
+ // completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
+ value = value.replace(/<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g, function(match, group1) {
+ if (!uidIgnore) {
+ uidIgnore = uniqueId(value);
+ var pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
+ if (options.ignoreCustomComments) {
+ options.ignoreCustomComments.push(pattern);
+ }
+ else {
+ options.ignoreCustomComments = [pattern];
+ }
+ }
+ var token = '<!--' + uidIgnore + ignoredMarkupChunks.length + '-->';
+ ignoredMarkupChunks.push(group1);
+ return token;
+ });
+
+ function escapeFragments(text) {
+ return text.replace(uidPattern, function(match, prefix, index) {
+ var chunks = ignoredCustomMarkupChunks[+index];
+ return chunks[1] + uidAttr + index + chunks[2];
+ });
+ }
+
+ var customFragments = options.ignoreCustomFragments.map(function(re) {
+ return re.source;
+ });
+ if (customFragments.length) {
+ var reCustomIgnore = new RegExp('\\s*(?:' + customFragments.join('|') + ')+\\s*', 'g');
+ // temporarily replace custom ignored fragments with unique attributes
+ value = value.replace(reCustomIgnore, function(match) {
+ if (!uidAttr) {
+ uidAttr = uniqueId(value);
+ uidPattern = new RegExp('(\\s*)' + uidAttr + '([0-9]+)(\\s*)', 'g');
+ var minifyCSS = options.minifyCSS;
+ if (minifyCSS) {
+ options.minifyCSS = function(text) {
+ return minifyCSS(escapeFragments(text));
+ };
+ }
+ var minifyJS = options.minifyJS;
+ if (minifyJS) {
+ options.minifyJS = function(text, inline) {
+ return minifyJS(escapeFragments(text), inline);
+ };
+ }
+ }
+ var token = uidAttr + ignoredCustomMarkupChunks.length;
+ ignoredCustomMarkupChunks.push(/^(\s*)[\s\S]*?(\s*)$/.exec(match));
+ return '\t' + token + '\t';
+ });
+ }
+
+ if (options.sortAttributes && typeof options.sortAttributes !== 'function' ||
+ options.sortClassName && typeof options.sortClassName !== 'function') {
+ createSortFns(value, options, uidIgnore, uidAttr);
+ }
+
+ function _canCollapseWhitespace(tag, attrs) {
+ return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
+ }
+
+ function _canTrimWhitespace(tag, attrs) {
+ return canTrimWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
+ }
+
+ function removeStartTag() {
+ var index = buffer.length - 1;
+ while (index > 0 && !/^<[^/!]/.test(buffer[index])) {
+ index--;
+ }
+ buffer.length = Math.max(0, index);
+ }
+
+ function removeEndTag() {
+ var index = buffer.length - 1;
+ while (index > 0 && !/^<\//.test(buffer[index])) {
+ index--;
+ }
+ buffer.length = Math.max(0, index);
+ }
+
+ // look for trailing whitespaces, bypass any inline tags
+ function trimTrailingWhitespace(index, nextTag) {
+ for (var endTag = null; index >= 0 && _canTrimWhitespace(endTag); index--) {
+ var str = buffer[index];
+ var match = str.match(/^<\/([\w:-]+)>$/);
+ if (match) {
+ endTag = match[1];
+ }
+ else if (/>$/.test(str) || (buffer[index] = collapseWhitespaceSmart(str, null, nextTag, options))) {
+ break;
+ }
+ }
+ }
+
+ // look for trailing whitespaces from previously processed text
+ // which may not be trimmed due to a following comment or an empty
+ // element which has now been removed
+ function squashTrailingWhitespace(nextTag) {
+ var charsIndex = buffer.length - 1;
+ if (buffer.length > 1) {
+ var item = buffer[buffer.length - 1];
+ if (/^(?:<!|$)/.test(item) && item.indexOf(uidIgnore) === -1) {
+ charsIndex--;
+ }
+ }
+ trimTrailingWhitespace(charsIndex, nextTag);
+ }
+
+ new HTMLParser(value, {
+ partialMarkup: partialMarkup,
+ html5: options.html5,
+
+ start: function(tag, attrs, unary, unarySlash, autoGenerated) {
+ var lowerTag = tag.toLowerCase();
+
+ if (lowerTag === 'svg') {
+ optionsStack.push(options);
+ var nextOptions = {};
+ for (var key in options) {
+ nextOptions[key] = options[key];
+ }
+ nextOptions.keepClosingSlash = true;
+ nextOptions.caseSensitive = true;
+ options = nextOptions;
+ }
+
+ tag = options.caseSensitive ? tag : lowerTag;
+
+ currentTag = tag;
+ charsPrevTag = tag;
+ if (!inlineTextTags(tag)) {
+ currentChars = '';
+ }
+ hasChars = false;
+ currentAttrs = attrs;
+
+ var optional = options.removeOptionalTags;
+ if (optional) {
+ var htmlTag = htmlTags(tag);
+ // <html> may be omitted if first thing inside is not comment
+ // <head> may be omitted if first thing inside is an element
+ // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
+ // <colgroup> may be omitted if first thing inside is <col>
+ // <tbody> may be omitted if first thing inside is <tr>
+ if (htmlTag && canRemoveParentTag(optionalStartTag, tag)) {
+ removeStartTag();
+ }
+ optionalStartTag = '';
+ // end-tag-followed-by-start-tag omission rules
+ if (htmlTag && canRemovePrecedingTag(optionalEndTag, tag)) {
+ removeEndTag();
+ // <colgroup> cannot be omitted if preceding </colgroup> is omitted
+ // <tbody> cannot be omitted if preceding </tbody>, </thead> or </tfoot> is omitted
+ optional = !isStartTagMandatory(optionalEndTag, tag);
+ }
+ optionalEndTag = '';
+ }
+
+ // set whitespace flags for nested tags (eg. <code> within a <pre>)
+ if (options.collapseWhitespace) {
+ if (!stackNoTrimWhitespace.length) {
+ squashTrailingWhitespace(tag);
+ }
+ if (!_canTrimWhitespace(tag, attrs)) {
+ stackNoTrimWhitespace.push(tag);
+ }
+ if (!_canCollapseWhitespace(tag, attrs)) {
+ stackNoCollapseWhitespace.push(tag);
+ }
+ }
+
+ var openTag = '<' + tag;
+ var hasUnarySlash = unarySlash && options.keepClosingSlash;
+
+ buffer.push(openTag);
+
+ if (options.sortAttributes) {
+ options.sortAttributes(tag, attrs);
+ }
+
+ var parts = [];
+ for (var i = attrs.length, isLast = true; --i >= 0;) {
+ var normalized = normalizeAttr(attrs[i], attrs, tag, options);
+ if (normalized) {
+ parts.unshift(buildAttr(normalized, hasUnarySlash, options, isLast, uidAttr));
+ isLast = false;
+ }
+ }
+ if (parts.length > 0) {
+ buffer.push(' ');
+ buffer.push.apply(buffer, parts);
+ }
+ // start tag must never be omitted if it has any attributes
+ else if (optional && optionalStartTags(tag)) {
+ optionalStartTag = tag;
+ }
+
+ buffer.push(buffer.pop() + (hasUnarySlash ? '/' : '') + '>');
+
+ if (autoGenerated && !options.includeAutoGeneratedTags) {
+ removeStartTag();
+ optionalStartTag = '';
+ }
+ },
+ end: function(tag, attrs, autoGenerated) {
+ var lowerTag = tag.toLowerCase();
+ if (lowerTag === 'svg') {
+ options = optionsStack.pop();
+ }
+ tag = options.caseSensitive ? tag : lowerTag;
+
+ // check if current tag is in a whitespace stack
+ if (options.collapseWhitespace) {
+ if (stackNoTrimWhitespace.length) {
+ if (tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
+ stackNoTrimWhitespace.pop();
+ }
+ }
+ else {
+ squashTrailingWhitespace('/' + tag);
+ }
+ if (stackNoCollapseWhitespace.length &&
+ tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
+ stackNoCollapseWhitespace.pop();
+ }
+ }
+
+ var isElementEmpty = false;
+ if (tag === currentTag) {
+ currentTag = '';
+ isElementEmpty = !hasChars;
+ }
+
+ if (options.removeOptionalTags) {
+ // <html>, <head> or <body> may be omitted if the element is empty
+ if (isElementEmpty && topLevelTags(optionalStartTag)) {
+ removeStartTag();
+ }
+ optionalStartTag = '';
+ // </html> or </body> may be omitted if not followed by comment
+ // </head> may be omitted if not followed by space or comment
+ // </p> may be omitted if no more content in non-</a> parent
+ // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
+ if (htmlTags(tag) && optionalEndTag && !trailingTags(optionalEndTag) && (optionalEndTag !== 'p' || !pInlineTags(tag))) {
+ removeEndTag();
+ }
+ optionalEndTag = optionalEndTags(tag) ? tag : '';
+ }
+
+ if (options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs)) {
+ // remove last "element" from buffer
+ removeStartTag();
+ optionalStartTag = '';
+ optionalEndTag = '';
+ }
+ else {
+ if (autoGenerated && !options.includeAutoGeneratedTags) {
+ optionalEndTag = '';
+ }
+ else {
+ buffer.push('</' + tag + '>');
+ }
+ charsPrevTag = '/' + tag;
+ if (!inlineTags(tag)) {
+ currentChars = '';
+ }
+ else if (isElementEmpty) {
+ currentChars += '|';
+ }
+ }
+ },
+ chars: function(text, prevTag, nextTag) {
+ prevTag = prevTag === '' ? 'comment' : prevTag;
+ nextTag = nextTag === '' ? 'comment' : nextTag;
+ if (options.decodeEntities && text && !specialContentTags(currentTag)) {
+ text = decode(text);
+ }
+ if (options.collapseWhitespace) {
+ if (!stackNoTrimWhitespace.length) {
+ if (prevTag === 'comment') {
+ var prevComment = buffer[buffer.length - 1];
+ if (prevComment.indexOf(uidIgnore) === -1) {
+ if (!prevComment) {
+ prevTag = charsPrevTag;
+ }
+ if (buffer.length > 1 && (!prevComment || !options.conservativeCollapse && / $/.test(currentChars))) {
+ var charsIndex = buffer.length - 2;
+ buffer[charsIndex] = buffer[charsIndex].replace(/\s+$/, function(trailingSpaces) {
+ text = trailingSpaces + text;
+ return '';
+ });
+ }
+ }
+ }
+ if (prevTag) {
+ if (prevTag === '/nobr' || prevTag === 'wbr') {
+ if (/^\s/.test(text)) {
+ var tagIndex = buffer.length - 1;
+ while (tagIndex > 0 && buffer[tagIndex].lastIndexOf('<' + prevTag) !== 0) {
+ tagIndex--;
+ }
+ trimTrailingWhitespace(tagIndex - 1, 'br');
+ }
+ }
+ else if (inlineTextTags(prevTag.charAt(0) === '/' ? prevTag.slice(1) : prevTag)) {
+ text = collapseWhitespace(text, options, /(?:^|\s)$/.test(currentChars));
+ }
+ }
+ if (prevTag || nextTag) {
+ text = collapseWhitespaceSmart(text, prevTag, nextTag, options);
+ }
+ else {
+ text = collapseWhitespace(text, options, true, true);
+ }
+ if (!text && /\s$/.test(currentChars) && prevTag && prevTag.charAt(0) === '/') {
+ trimTrailingWhitespace(buffer.length - 1, nextTag);
+ }
+ }
+ if (!stackNoCollapseWhitespace.length && nextTag !== 'html' && !(prevTag && nextTag)) {
+ text = collapseWhitespace(text, options, false, false, true);
+ }
+ }
+ if (options.processScripts && specialContentTags(currentTag)) {
+ text = processScript(text, options, currentAttrs);
+ }
+ if (isExecutableScript(currentTag, currentAttrs)) {
+ text = options.minifyJS(text);
+ }
+ if (isStyleSheet(currentTag, currentAttrs)) {
+ text = options.minifyCSS(text);
+ }
+ if (options.removeOptionalTags && text) {
+ // <html> may be omitted if first thing inside is not comment
+ // <body> may be omitted if first thing inside is not space, comment, <meta>, <link>, <script>, <style> or <template>
+ if (optionalStartTag === 'html' || optionalStartTag === 'body' && !/^\s/.test(text)) {
+ removeStartTag();
+ }
+ optionalStartTag = '';
+ // </html> or </body> may be omitted if not followed by comment
+ // </head>, </colgroup> or </caption> may be omitted if not followed by space or comment
+ if (compactTags(optionalEndTag) || looseTags(optionalEndTag) && !/^\s/.test(text)) {
+ removeEndTag();
+ }
+ optionalEndTag = '';
+ }
+ charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
+ if (options.decodeEntities && text && !specialContentTags(currentTag)) {
+ // semi-colon can be omitted
+ // https://mathiasbynens.be/notes/ambiguous-ampersands
+ text = text.replace(/&(#?[0-9a-zA-Z]+;)/g, '&amp$1').replace(/</g, '&lt;');
+ }
+ currentChars += text;
+ if (text) {
+ hasChars = true;
+ }
+ buffer.push(text);
+ },
+ comment: function(text, nonStandard) {
+ var prefix = nonStandard ? '<!' : '<!--';
+ var suffix = nonStandard ? '>' : '-->';
+ if (isConditionalComment(text)) {
+ text = prefix + cleanConditionalComment(text, options) + suffix;
+ }
+ else if (options.removeComments) {
+ if (isIgnoredComment(text, options)) {
+ text = '<!--' + text + '-->';
+ }
+ else {
+ text = '';
+ }
+ }
+ else {
+ text = prefix + text + suffix;
+ }
+ if (options.removeOptionalTags && text) {
+ // preceding comments suppress tag omissions
+ optionalStartTag = '';
+ optionalEndTag = '';
+ }
+ buffer.push(text);
+ },
+ doctype: function(doctype) {
+ buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespaceAll(doctype));
+ },
+ customAttrAssign: options.customAttrAssign,
+ customAttrSurround: options.customAttrSurround
+ });
+
+ if (options.removeOptionalTags) {
+ // <html> may be omitted if first thing inside is not comment
+ // <head> or <body> may be omitted if empty
+ if (topLevelTags(optionalStartTag)) {
+ removeStartTag();
+ }
+ // except for </dt> or </thead>, end tags may be omitted if no more content in parent element
+ if (optionalEndTag && !trailingTags(optionalEndTag)) {
+ removeEndTag();
+ }
+ }
+ if (options.collapseWhitespace) {
+ squashTrailingWhitespace('br');
+ }
+
+ var str = joinResultSegments(buffer, options);
+
+ if (uidPattern) {
+ str = str.replace(uidPattern, function(match, prefix, index, suffix) {
+ var chunk = ignoredCustomMarkupChunks[+index][0];
+ if (options.collapseWhitespace) {
+ if (prefix !== '\t') {
+ chunk = prefix + chunk;
+ }
+ if (suffix !== '\t') {
+ chunk += suffix;
+ }
+ return collapseWhitespace(chunk, {
+ preserveLineBreaks: options.preserveLineBreaks,
+ conservativeCollapse: !options.trimCustomFragments
+ }, /^\s/.test(chunk), /\s$/.test(chunk));
+ }
+ return chunk;
+ });
+ }
+ if (uidIgnore) {
+ str = str.replace(new RegExp('<!--' + uidIgnore + '([0-9]+)-->', 'g'), function(match, index) {
+ return ignoredMarkupChunks[+index];
+ });
+ }
+
+ options.log('minified in: ' + (Date.now() - t) + 'ms');
+ return str;
+}
+
+function joinResultSegments(results, options) {
+ var str;
+ var maxLineLength = options.maxLineLength;
+ if (maxLineLength) {
+ var token;
+ var lines = [];
+ var line = '';
+ for (var i = 0, len = results.length; i < len; i++) {
+ token = results[i];
+ if (line.length + token.length < maxLineLength) {
+ line += token;
+ }
+ else {
+ lines.push(line.replace(/^\n/, ''));
+ line = token;
+ }
+ }
+ lines.push(line);
+
+ str = lines.join('\n');
+ }
+ else {
+ str = results.join('');
+ }
+ return options.collapseWhitespace ? collapseWhitespace(str, options, true, true) : str;
+}
+
+exports.minify = function(value, options) {
+ return minify(value, options);
+};
diff --git a/node_modules/html-minifier/src/htmlparser.js b/node_modules/html-minifier/src/htmlparser.js
new file mode 100644
index 000000000..2195347dc
--- /dev/null
+++ b/node_modules/html-minifier/src/htmlparser.js
@@ -0,0 +1,528 @@
+/*!
+ * HTML Parser By John Resig (ejohn.org)
+ * Modified by Juriy "kangax" Zaytsev
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ */
+
+/*
+ * // Use like so:
+ * HTMLParser(htmlString, {
+ * start: function(tag, attrs, unary) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * });
+ *
+ * // or to get an XML string:
+ * HTMLtoXML(htmlString);
+ *
+ * // or to get an XML DOM Document
+ * HTMLtoDOM(htmlString);
+ *
+ * // or to inject into an existing document/DOM node
+ * HTMLtoDOM(htmlString, document);
+ * HTMLtoDOM(htmlString, document.body);
+ *
+ */
+
+ /* global ActiveXObject, DOMDocument */
+
+'use strict';
+
+var createMapFromString = require('./utils').createMapFromString;
+
+function makeMap(values) {
+ return createMapFromString(values, true);
+}
+
+// Regular Expressions for parsing tags and attributes
+var singleAttrIdentifier = /([^\s"'<>/=]+)/,
+ singleAttrAssigns = [/=/],
+ singleAttrValues = [
+ // attr value double quotes
+ /"([^"]*)"+/.source,
+ // attr value, single quotes
+ /'([^']*)'+/.source,
+ // attr value, no quotes
+ /([^ \t\n\f\r"'`=<>]+)/.source
+ ],
+ // https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
+ qnameCapture = (function() {
+ var ncname = require('ncname').source.slice(1, -1);
+ return '((?:' + ncname + '\\:)?' + ncname + ')';
+ })(),
+ startTagOpen = new RegExp('^<' + qnameCapture),
+ startTagClose = /^\s*(\/?)>/,
+ endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>'),
+ doctype = /^<!DOCTYPE [^>]+>/i;
+
+var IS_REGEX_CAPTURING_BROKEN = false;
+'x'.replace(/x(.)?/g, function(m, g) {
+ IS_REGEX_CAPTURING_BROKEN = g === '';
+});
+
+// Empty Elements
+var empty = makeMap('area,base,basefont,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr');
+
+// Inline Elements
+var inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,noscript,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,svg,textarea,tt,u,var');
+
+// Elements that you can, intentionally, leave open
+// (and which close themselves)
+var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source');
+
+// Attributes that have their values filled in disabled='disabled'
+var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+// Special Elements (can contain anything)
+var special = makeMap('script,style');
+
+// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
+// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
+var nonPhrasing = makeMap('address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track');
+
+var reCache = {};
+
+function attrForHandler(handler) {
+ var pattern = singleAttrIdentifier.source +
+ '(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' +
+ '[ \\t\\n\\f\\r]*(?:' + singleAttrValues.join('|') + '))?';
+ if (handler.customAttrSurround) {
+ var attrClauses = [];
+ for (var i = handler.customAttrSurround.length - 1; i >= 0; i--) {
+ attrClauses[i] = '(?:' +
+ '(' + handler.customAttrSurround[i][0].source + ')\\s*' +
+ pattern +
+ '\\s*(' + handler.customAttrSurround[i][1].source + ')' +
+ ')';
+ }
+ attrClauses.push('(?:' + pattern + ')');
+ pattern = '(?:' + attrClauses.join('|') + ')';
+ }
+ return new RegExp('^\\s*' + pattern);
+}
+
+function joinSingleAttrAssigns(handler) {
+ return singleAttrAssigns.concat(
+ handler.customAttrAssign || []
+ ).map(function(assign) {
+ return '(?:' + assign.source + ')';
+ }).join('|');
+}
+
+function HTMLParser(html, handler) {
+ var stack = [], lastTag;
+ var attribute = attrForHandler(handler);
+ var last, prevTag, nextTag;
+ while (html) {
+ last = html;
+ // Make sure we're not in a script or style element
+ if (!lastTag || !special(lastTag)) {
+ var textEnd = html.indexOf('<');
+ if (textEnd === 0) {
+ // Comment:
+ if (/^<!--/.test(html)) {
+ var commentEnd = html.indexOf('-->');
+
+ if (commentEnd >= 0) {
+ if (handler.comment) {
+ handler.comment(html.substring(4, commentEnd));
+ }
+ html = html.substring(commentEnd + 3);
+ prevTag = '';
+ continue;
+ }
+ }
+
+ // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
+ if (/^<!\[/.test(html)) {
+ var conditionalEnd = html.indexOf(']>');
+
+ if (conditionalEnd >= 0) {
+ if (handler.comment) {
+ handler.comment(html.substring(2, conditionalEnd + 1), true /* non-standard */);
+ }
+ html = html.substring(conditionalEnd + 2);
+ prevTag = '';
+ continue;
+ }
+ }
+
+ // Doctype:
+ var doctypeMatch = html.match(doctype);
+ if (doctypeMatch) {
+ if (handler.doctype) {
+ handler.doctype(doctypeMatch[0]);
+ }
+ html = html.substring(doctypeMatch[0].length);
+ prevTag = '';
+ continue;
+ }
+
+ // End tag:
+ var endTagMatch = html.match(endTag);
+ if (endTagMatch) {
+ html = html.substring(endTagMatch[0].length);
+ endTagMatch[0].replace(endTag, parseEndTag);
+ prevTag = '/' + endTagMatch[1].toLowerCase();
+ continue;
+ }
+
+ // Start tag:
+ var startTagMatch = parseStartTag(html);
+ if (startTagMatch) {
+ html = startTagMatch.rest;
+ handleStartTag(startTagMatch);
+ prevTag = startTagMatch.tagName.toLowerCase();
+ continue;
+ }
+ }
+
+ var text;
+ if (textEnd >= 0) {
+ text = html.substring(0, textEnd);
+ html = html.substring(textEnd);
+ }
+ else {
+ text = html;
+ html = '';
+ }
+
+ // next tag
+ var nextTagMatch = parseStartTag(html);
+ if (nextTagMatch) {
+ nextTag = nextTagMatch.tagName;
+ }
+ else {
+ nextTagMatch = html.match(endTag);
+ if (nextTagMatch) {
+ nextTag = '/' + nextTagMatch[1];
+ }
+ else {
+ nextTag = '';
+ }
+ }
+
+ if (handler.chars) {
+ handler.chars(text, prevTag, nextTag);
+ }
+ prevTag = '';
+
+ }
+ else {
+ var stackedTag = lastTag.toLowerCase();
+ var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)</' + stackedTag + '[^>]*>', 'i'));
+
+ html = html.replace(reStackedTag, function(all, text) {
+ if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
+ text = text
+ .replace(/<!--([\s\S]*?)-->/g, '$1')
+ .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
+ }
+
+ if (handler.chars) {
+ handler.chars(text);
+ }
+
+ return '';
+ });
+
+ parseEndTag('</' + stackedTag + '>', stackedTag);
+ }
+
+ if (html === last) {
+ throw new Error('Parse Error: ' + html);
+ }
+ }
+
+ if (!handler.partialMarkup) {
+ // Clean up any remaining tags
+ parseEndTag();
+ }
+
+ function parseStartTag(input) {
+ var start = input.match(startTagOpen);
+ if (start) {
+ var match = {
+ tagName: start[1],
+ attrs: []
+ };
+ input = input.slice(start[0].length);
+ var end, attr;
+ while (!(end = input.match(startTagClose)) && (attr = input.match(attribute))) {
+ input = input.slice(attr[0].length);
+ match.attrs.push(attr);
+ }
+ if (end) {
+ match.unarySlash = end[1];
+ match.rest = input.slice(end[0].length);
+ return match;
+ }
+ }
+ }
+
+ function handleStartTag(match) {
+ var tagName = match.tagName;
+ var unarySlash = match.unarySlash;
+
+ if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
+ parseEndTag('', lastTag);
+ }
+
+ if (!handler.html5) {
+ while (lastTag && inline(lastTag)) {
+ parseEndTag('', lastTag);
+ }
+ }
+
+ if (closeSelf(tagName) && lastTag === tagName) {
+ parseEndTag('', tagName);
+ }
+
+ var unary = empty(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
+
+ var attrs = match.attrs.map(function(args) {
+ var name, value, customOpen, customClose, customAssign, quote;
+ var ncp = 7; // number of captured parts, scalar
+
+ // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
+ if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
+ if (args[3] === '') { delete args[3]; }
+ if (args[4] === '') { delete args[4]; }
+ if (args[5] === '') { delete args[5]; }
+ }
+
+ function populate(index) {
+ customAssign = args[index];
+ value = args[index + 1];
+ if (typeof value !== 'undefined') {
+ return '"';
+ }
+ value = args[index + 2];
+ if (typeof value !== 'undefined') {
+ return '\'';
+ }
+ value = args[index + 3];
+ if (typeof value === 'undefined' && fillAttrs(name)) {
+ value = name;
+ }
+ return '';
+ }
+
+ var j = 1;
+ if (handler.customAttrSurround) {
+ for (var i = 0, l = handler.customAttrSurround.length; i < l; i++, j += ncp) {
+ name = args[j + 1];
+ if (name) {
+ quote = populate(j + 2);
+ customOpen = args[j];
+ customClose = args[j + 6];
+ break;
+ }
+ }
+ }
+
+ if (!name && (name = args[j])) {
+ quote = populate(j + 1);
+ }
+
+ return {
+ name: name,
+ value: value,
+ customAssign: customAssign || '=',
+ customOpen: customOpen || '',
+ customClose: customClose || '',
+ quote: quote || ''
+ };
+ });
+
+ if (!unary) {
+ stack.push({ tag: tagName, attrs: attrs });
+ lastTag = tagName;
+ unarySlash = '';
+ }
+
+ if (handler.start) {
+ handler.start(tagName, attrs, unary, unarySlash);
+ }
+ }
+
+ function parseEndTag(tag, tagName) {
+ var pos;
+
+ // Find the closest opened tag of the same type
+ if (tagName) {
+ var needle = tagName.toLowerCase();
+ for (pos = stack.length - 1; pos >= 0; pos--) {
+ if (stack[pos].tag.toLowerCase() === needle) {
+ break;
+ }
+ }
+ }
+ // If no tag name is provided, clean shop
+ else {
+ pos = 0;
+ }
+
+ if (pos >= 0) {
+ // Close all the open elements, up the stack
+ for (var i = stack.length - 1; i >= pos; i--) {
+ if (handler.end) {
+ handler.end(stack[i].tag, stack[i].attrs, i > pos || !tag);
+ }
+ }
+
+ // Remove the open elements from the stack
+ stack.length = pos;
+ lastTag = pos && stack[pos - 1].tag;
+ }
+ else if (tagName.toLowerCase() === 'br') {
+ if (handler.start) {
+ handler.start(tagName, [], true, '');
+ }
+ }
+ else if (tagName.toLowerCase() === 'p') {
+ if (handler.start) {
+ handler.start(tagName, [], false, '', true);
+ }
+ if (handler.end) {
+ handler.end(tagName, []);
+ }
+ }
+ }
+}
+
+exports.HTMLParser = HTMLParser;
+exports.HTMLtoXML = function(html) {
+ var results = '';
+
+ new HTMLParser(html, {
+ start: function(tag, attrs, unary) {
+ results += '<' + tag;
+
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ results += ' ' + attrs[i].name + '="' + (attrs[i].value || '').replace(/"/g, '&#34;') + '"';
+ }
+
+ results += (unary ? '/' : '') + '>';
+ },
+ end: function(tag) {
+ results += '</' + tag + '>';
+ },
+ chars: function(text) {
+ results += text;
+ },
+ comment: function(text) {
+ results += '<!--' + text + '-->';
+ },
+ ignore: function(text) {
+ results += text;
+ }
+ });
+
+ return results;
+};
+
+exports.HTMLtoDOM = function(html, doc) {
+ // There can be only one of these elements
+ var one = {
+ html: true,
+ head: true,
+ body: true,
+ title: true
+ };
+
+ // Enforce a structure for the document
+ var structure = {
+ link: 'head',
+ base: 'head'
+ };
+
+ if (doc) {
+ doc = doc.ownerDocument || doc.getOwnerDocument && doc.getOwnerDocument() || doc;
+ }
+ else if (typeof DOMDocument !== 'undefined') {
+ doc = new DOMDocument();
+ }
+ else if (typeof document !== 'undefined' && document.implementation && document.implementation.createDocument) {
+ doc = document.implementation.createDocument('', '', null);
+ }
+ else if (typeof ActiveX !== 'undefined') {
+ doc = new ActiveXObject('Msxml.DOMDocument');
+ }
+
+ var elems = [],
+ documentElement = doc.documentElement ||
+ doc.getDocumentElement && doc.getDocumentElement();
+
+ // If we're dealing with an empty document then we
+ // need to pre-populate it with the HTML document structure
+ if (!documentElement && doc.createElement) {
+ (function() {
+ var html = doc.createElement('html');
+ var head = doc.createElement('head');
+ head.appendChild(doc.createElement('title'));
+ html.appendChild(head);
+ html.appendChild(doc.createElement('body'));
+ doc.appendChild(html);
+ })();
+ }
+
+ // Find all the unique elements
+ if (doc.getElementsByTagName) {
+ for (var i in one) {
+ one[i] = doc.getElementsByTagName(i)[0];
+ }
+ }
+
+ // If we're working with a document, inject contents into
+ // the body element
+ var curParentNode = one.body;
+
+ new HTMLParser(html, {
+ start: function(tagName, attrs, unary) {
+ // If it's a pre-built element, then we can ignore
+ // its construction
+ if (one[tagName]) {
+ curParentNode = one[tagName];
+ return;
+ }
+
+ var elem = doc.createElement(tagName);
+
+ for (var attr in attrs) {
+ elem.setAttribute(attrs[attr].name, attrs[attr].value);
+ }
+
+ if (structure[tagName] && typeof one[structure[tagName]] !== 'boolean') {
+ one[structure[tagName]].appendChild(elem);
+ }
+ else if (curParentNode && curParentNode.appendChild) {
+ curParentNode.appendChild(elem);
+ }
+
+ if (!unary) {
+ elems.push(elem);
+ curParentNode = elem;
+ }
+ },
+ end: function(/* tag */) {
+ elems.length -= 1;
+
+ // Init the new parentNode
+ curParentNode = elems[elems.length - 1];
+ },
+ chars: function(text) {
+ curParentNode.appendChild(doc.createTextNode(text));
+ },
+ comment: function(/* text */) {
+ // create comment node
+ },
+ ignore: function(/* text */) {
+ // What to do here?
+ }
+ });
+
+ return doc;
+};
diff --git a/node_modules/html-minifier/src/tokenchain.js b/node_modules/html-minifier/src/tokenchain.js
new file mode 100644
index 000000000..f8892d017
--- /dev/null
+++ b/node_modules/html-minifier/src/tokenchain.js
@@ -0,0 +1,71 @@
+'use strict';
+
+function Sorter() {
+}
+
+Sorter.prototype.sort = function(tokens, fromIndex) {
+ fromIndex = fromIndex || 0;
+ for (var i = 0, len = this.keys.length; i < len; i++) {
+ var key = this.keys[i];
+ var token = key.slice(1);
+ var index = tokens.indexOf(token, fromIndex);
+ if (index !== -1) {
+ do {
+ if (index !== fromIndex) {
+ tokens.splice(index, 1);
+ tokens.splice(fromIndex, 0, token);
+ }
+ fromIndex++;
+ } while ((index = tokens.indexOf(token, fromIndex)) !== -1);
+ return this[key].sort(tokens, fromIndex);
+ }
+ }
+ return tokens;
+};
+
+function TokenChain() {
+}
+
+TokenChain.prototype = {
+ add: function(tokens) {
+ var self = this;
+ tokens.forEach(function(token) {
+ var key = '$' + token;
+ if (!self[key]) {
+ self[key] = [];
+ self[key].processed = 0;
+ }
+ self[key].push(tokens);
+ });
+ },
+ createSorter: function() {
+ var self = this;
+ var sorter = new Sorter();
+ sorter.keys = Object.keys(self).sort(function(j, k) {
+ var m = self[j].length;
+ var n = self[k].length;
+ return m < n ? 1 : m > n ? -1 : j < k ? -1 : j > k ? 1 : 0;
+ }).filter(function(key) {
+ if (self[key].processed < self[key].length) {
+ var token = key.slice(1);
+ var chain = new TokenChain();
+ self[key].forEach(function(tokens) {
+ var index;
+ while ((index = tokens.indexOf(token)) !== -1) {
+ tokens.splice(index, 1);
+ }
+ tokens.forEach(function(token) {
+ self['$' + token].processed++;
+ });
+ chain.add(tokens.slice(0));
+ });
+ sorter[key] = chain.createSorter();
+ return true;
+ }
+ return false;
+ });
+ return sorter;
+ }
+};
+
+module.exports = TokenChain;
diff --git a/node_modules/html-minifier/src/utils.js b/node_modules/html-minifier/src/utils.js
new file mode 100644
index 000000000..62b43636d
--- /dev/null
+++ b/node_modules/html-minifier/src/utils.js
@@ -0,0 +1,18 @@
+'use strict';
+
+function createMap(values, ignoreCase) {
+ var map = {};
+ values.forEach(function(value) {
+ map[value] = 1;
+ });
+ return ignoreCase ? function(value) {
+ return map[value.toLowerCase()] === 1;
+ } : function(value) {
+ return map[value] === 1;
+ };
+}
+
+exports.createMap = createMap;
+exports.createMapFromString = function(values, ignoreCase) {
+ return createMap(values.split(/,/), ignoreCase);
+};