initial commit
This commit is contained in:
commit
d3fcd7f10c
3159 files changed
+349702
No files matched your search
+379
@@ -0,0 +1,379 @@
|
||||
[2.0.8 / 2014-02-07](https://github.com/GoalSmashers/clean-css/compare/v2.0.7...v2.0.8)
|
||||
==================
|
||||
|
||||
* Fixed issue [#232](https://github.com/GoalSmashers/clean-css/issues/232) - edge case in non-adjacent selectors merging.
|
||||
|
||||
[2.0.7 / 2014-01-16](https://github.com/GoalSmashers/clean-css/compare/v2.0.6...v2.0.7)
|
||||
==================
|
||||
|
||||
* Fixed issue [#208](https://github.com/GoalSmashers/clean-css/issues/208) - don't swallow @page and @viewport.
|
||||
|
||||
[2.0.6 / 2014-01-04](https://github.com/GoalSmashers/clean-css/compare/v2.0.5...v2.0.6)
|
||||
==================
|
||||
|
||||
* Fixed issue [#198](https://github.com/GoalSmashers/clean-css/issues/198) - process comments and `@import`s correctly.
|
||||
* Fixed issue [#205](https://github.com/GoalSmashers/clean-css/issues/205) - freeze on broken @import declaration.
|
||||
|
||||
[2.0.5 / 2014-01-03](https://github.com/GoalSmashers/clean-css/compare/v2.0.4...v2.0.5)
|
||||
==================
|
||||
|
||||
* Fixed issue [#199](https://github.com/GoalSmashers/clean-css/issues/199) - keep line breaks with no advanced optimizations.
|
||||
* Fixed issue [#203](https://github.com/GoalSmashers/clean-css/issues/203) - Buffer as a first argument to minify method.
|
||||
|
||||
[2.0.4 / 2013-12-19](https://github.com/GoalSmashers/clean-css/compare/v2.0.3...v2.0.4)
|
||||
==================
|
||||
|
||||
* Fixed issue [#193](https://github.com/GoalSmashers/clean-css/issues/193) - HSL color space normalization.
|
||||
|
||||
[2.0.3 / 2013-12-18](https://github.com/GoalSmashers/clean-css/compare/v2.0.2...v2.0.3)
|
||||
==================
|
||||
|
||||
* Fixed issue [#191](https://github.com/GoalSmashers/clean-css/issues/191) - leading numbers in font/animation names.
|
||||
* Fixed issue [#192](https://github.com/GoalSmashers/clean-css/issues/192) - many `@import`s inside a comment.
|
||||
|
||||
[2.0.2 / 2013-11-18](https://github.com/GoalSmashers/clean-css/compare/v2.0.1...v2.0.2)
|
||||
==================
|
||||
|
||||
* Fixed issue [#177](https://github.com/GoalSmashers/clean-css/issues/177) - process broken content correctly.
|
||||
|
||||
[2.0.1 / 2013-11-14](https://github.com/GoalSmashers/clean-css/compare/v2.0.0...v2.0.1)
|
||||
==================
|
||||
|
||||
* Fixed issue [#176](https://github.com/GoalSmashers/clean-css/issues/176) - hangs on `undefined` keyword.
|
||||
|
||||
[2.0.0 / 2013-11-04](https://github.com/GoalSmashers/clean-css/compare/v1.1.7...v2.0.0)
|
||||
==================
|
||||
|
||||
* Adds simplified and more advanced text escaping / restoring via `EscapeStore` class.
|
||||
* Adds simplified and much faster empty elements removal.
|
||||
* Adds missing `@import` processing to our benchmark (run via `npm run bench`).
|
||||
* Adds CSS tokenizer which will make it possible to optimize content by reordering and/or merging selectors.
|
||||
* Adds basic optimizer removing duplicate selectors from a list.
|
||||
* Adds merging duplicate properties within a single selector's body.
|
||||
* Adds merging adjacent selectors within a scope (single and multiple ones).
|
||||
* Changes behavior of `--keep-line-breaks`/`keepBreaks` option to keep breaks after trailing braces only.
|
||||
* Makes all multiple selectors ordered alphabetically (aids merging).
|
||||
* Adds property overriding so more coarse properties override more granular ones.
|
||||
* Adds reducing non-adjacent selectors.
|
||||
* Adds `--skip-advanced`/`noAdvanced` switch to disable advanced optimizations.
|
||||
* Adds reducing non-adjacent selectors when overridden by more complex selectors.
|
||||
* Fixed issue [#138](https://github.com/GoalSmashers/clean-css/issues/138) - makes CleanCSS interface OO.
|
||||
* Fixed issue [#139](https://github.com/GoalSmashers/clean-css/issues/138) - consistent error & warning handling.
|
||||
* Fixed issue [#145](https://github.com/GoalSmashers/clean-css/issues/145) - debug mode in library too.
|
||||
* Fixed issue [#157](https://github.com/GoalSmashers/clean-css/issues/157) - gets rid of `removeEmpty` option.
|
||||
* Fixed issue [#159](https://github.com/GoalSmashers/clean-css/issues/159) - escaped quotes inside content.
|
||||
* Fixed issue [#162](https://github.com/GoalSmashers/clean-css/issues/162) - strip quotes from Base64 encoded URLs.
|
||||
* Fixed issue [#166](https://github.com/GoalSmashers/clean-css/issues/166) - `debug` formatting in CLI
|
||||
* Fixed issue [#167](https://github.com/GoalSmashers/clean-css/issues/167) - `background:transparent` minification.
|
||||
|
||||
[1.1.7 / 2013-10-28](https://github.com/GoalSmashers/clean-css/compare/v1.1.6...v1.1.7)
|
||||
==================
|
||||
|
||||
* Fixed issue [#156](https://github.com/GoalSmashers/clean-css/issues/156) - `@import`s inside comments.
|
||||
|
||||
[1.1.6 / 2013-10-26](https://github.com/GoalSmashers/clean-css/compare/v1.1.5...v1.1.6)
|
||||
==================
|
||||
|
||||
* Fixed issue [#155](https://github.com/GoalSmashers/clean-css/issues/155) - broken irregular CSS content.
|
||||
|
||||
[1.1.5 / 2013-10-24](https://github.com/GoalSmashers/clean-css/compare/v1.1.4...v1.1.5)
|
||||
==================
|
||||
|
||||
* Fixed issue [#153](https://github.com/GoalSmashers/clean-css/issues/153) - keepSpecialComments 0/1 as a string.
|
||||
|
||||
[1.1.4 / 2013-10-23](https://github.com/GoalSmashers/clean-css/compare/v1.1.3...v1.1.4)
|
||||
==================
|
||||
|
||||
* Fixed issue [#152](https://github.com/GoalSmashers/clean-css/issues/152) - adds an option to disable rebasing.
|
||||
|
||||
[1.1.3 / 2013-10-04](https://github.com/GoalSmashers/clean-css/compare/v1.1.2...v1.1.3)
|
||||
==================
|
||||
|
||||
* Fixed issue [#150](https://github.com/GoalSmashers/clean-css/issues/150) - minifying `background:none`.
|
||||
|
||||
[1.1.2 / 2013-09-29](https://github.com/GoalSmashers/clean-css/compare/v1.1.1...v1.1.2)
|
||||
==================
|
||||
|
||||
* Fixed issue [#149](https://github.com/GoalSmashers/clean-css/issues/149) - shorthand font property.
|
||||
|
||||
[1.1.1 / 2013-09-07](https://github.com/GoalSmashers/clean-css/compare/v1.1.0...v1.1.1)
|
||||
==================
|
||||
|
||||
* Fixed issue [#144](https://github.com/GoalSmashers/clean-css/issues/144) - skip URLs rebasing by default.
|
||||
|
||||
[1.1.0 / 2013-09-06](https://github.com/GoalSmashers/clean-css/compare/v1.0.12...v1.1.0)
|
||||
==================
|
||||
|
||||
* Renamed lib's `debug` option to `benchmark` when doing per-minification benchmarking.
|
||||
* Added simplified comments processing & imports.
|
||||
* Fixed issue [#43](https://github.com/GoalSmashers/clean-css/issues/43) - `--debug` switch for minification stats.
|
||||
* Fixed issue [#65](https://github.com/GoalSmashers/clean-css/issues/65) - full color name / hex shortening.
|
||||
* Fixed issue [#84](https://github.com/GoalSmashers/clean-css/issues/84) - support for `@import` with media queries.
|
||||
* Fixed issue [#124](https://github.com/GoalSmashers/clean-css/issues/124) - raise error on broken imports.
|
||||
* Fixed issue [#126](https://github.com/GoalSmashers/clean-css/issues/126) - proper CSS expressions handling.
|
||||
* Fixed issue [#129](https://github.com/GoalSmashers/clean-css/issues/129) - rebasing imported URLs.
|
||||
* Fixed issue [#130](https://github.com/GoalSmashers/clean-css/issues/130) - better code modularity.
|
||||
* Fixed issue [#135](https://github.com/GoalSmashers/clean-css/issues/135) - require node.js 0.8+.
|
||||
|
||||
[1.0.12 / 2013-07-19](https://github.com/GoalSmashers/clean-css/compare/v1.0.11...v1.0.12)
|
||||
===================
|
||||
|
||||
* Fixed issue [#121](https://github.com/GoalSmashers/clean-css/issues/121) - ability to skip `@import` processing.
|
||||
|
||||
[1.0.11 / 2013-07-08](https://github.com/GoalSmashers/clean-css/compare/v1.0.10...v1.0.11)
|
||||
===================
|
||||
|
||||
* Fixed issue [#117](https://github.com/GoalSmashers/clean-css/issues/117) - line break escaping in comments.
|
||||
|
||||
[1.0.10 / 2013-06-13](https://github.com/GoalSmashers/clean-css/compare/v1.0.9...v1.0.10)
|
||||
===================
|
||||
|
||||
* Fixed issue [#114](https://github.com/GoalSmashers/clean-css/issues/114) - comments in imported stylesheets.
|
||||
|
||||
[1.0.9 / 2013-06-11](https://github.com/GoalSmashers/clean-css/compare/v1.0.8...v1.0.9)
|
||||
==================
|
||||
|
||||
* Fixed issue [#113](https://github.com/GoalSmashers/clean-css/issues/113) - `@import` in comments.
|
||||
|
||||
[1.0.8 / 2013-06-10](https://github.com/GoalSmashers/clean-css/compare/v1.0.7...v1.0.8)
|
||||
==================
|
||||
|
||||
* Fixed issue [#112](https://github.com/GoalSmashers/clean-css/issues/112) - reducing `box-shadow` zeros.
|
||||
|
||||
[1.0.7 / 2013-06-05](https://github.com/GoalSmashers/clean-css/compare/v1.0.6...v1.0.7)
|
||||
==================
|
||||
|
||||
* Support for `@import` URLs starting with `//`.
|
||||
By [@petetak](https://github.com/petetak).
|
||||
|
||||
[1.0.6 / 2013-06-04](https://github.com/GoalSmashers/clean-css/compare/v1.0.5...v1.0.6)
|
||||
==================
|
||||
|
||||
* Fixed issue [#110](https://github.com/GoalSmashers/clean-css/issues/110) - data URIs in URLs.
|
||||
|
||||
[1.0.5 / 2013-05-26](https://github.com/GoalSmashers/clean-css/compare/v1.0.4...v1.0.5)
|
||||
==================
|
||||
|
||||
* Fixed issue [#107](https://github.com/GoalSmashers/clean-css/issues/107) - data URIs in imported stylesheets.
|
||||
|
||||
1.0.4 / 2013-05-23
|
||||
==================
|
||||
|
||||
* Rewrite relative URLs in imported stylesheets.
|
||||
By [@bluej100](https://github.com/bluej100).
|
||||
|
||||
1.0.3 / 2013-05-20
|
||||
==================
|
||||
|
||||
* Support alternative `@import` syntax with file name not wrapped inside `url()` statement.
|
||||
By [@bluej100](https://github.com/bluej100).
|
||||
|
||||
1.0.2 / 2013-04-29
|
||||
==================
|
||||
|
||||
* Fixed issue [#97](https://github.com/GoalSmashers/clean-css/issues/97) - `--remove-empty` & FontAwesome.
|
||||
|
||||
1.0.1 / 2013-04-08
|
||||
==================
|
||||
|
||||
* Do not pick up `bench` and `test` while building `npm` package.
|
||||
By [@sindresorhus](https://https://github.com/sindresorhus).
|
||||
|
||||
1.0.0 / 2013-03-30
|
||||
==================
|
||||
|
||||
* Fixed issue [#2](https://github.com/GoalSmashers/clean-css/issues/2) - resolving `@import` rules.
|
||||
* Fixed issue [#44](https://github.com/GoalSmashers/clean-css/issues/44) - examples in `--help`.
|
||||
* Fixed issue [#46](https://github.com/GoalSmashers/clean-css/issues/46) - preserving special characters in URLs and attributes.
|
||||
* Fixed issue [#80](https://github.com/GoalSmashers/clean-css/issues/80) - quotation in multi line strings.
|
||||
* Fixed issue [#83](https://github.com/GoalSmashers/clean-css/issues/83) - HSL to hex color conversions.
|
||||
* Fixed issue [#86](https://github.com/GoalSmashers/clean-css/issues/86) - broken `@charset` replacing.
|
||||
* Fixed issue [#88](https://github.com/GoalSmashers/clean-css/issues/88) - removes space in `! important`.
|
||||
* Fixed issue [#92](https://github.com/GoalSmashers/clean-css/issues/92) - uppercase hex to short versions.
|
||||
|
||||
0.10.2 / 2013-03-19
|
||||
===================
|
||||
|
||||
* Fixed issue [#79](https://github.com/GoalSmashers/clean-css/issues/79) - node.js 0.10.x compatibility.
|
||||
|
||||
0.10.1 / 2013-02-14
|
||||
===================
|
||||
|
||||
* Fixed issue [#66](https://github.com/GoalSmashers/clean-css/issues/66) - line breaks without extra spaces should
|
||||
be handled correctly.
|
||||
|
||||
0.10.0 / 2013-02-09
|
||||
===================
|
||||
|
||||
* Switched from [optimist](https://github.com/substack/node-optimist) to
|
||||
[commander](https://github.com/visionmedia/commander.js) for CLI processing.
|
||||
* Changed long options from `--removeempty` to `--remove-empty` and from `--keeplinebreaks` to `--keep-line-breaks`.
|
||||
* Fixed performance issue with replacing multiple `@charset` declarations and issue
|
||||
with line break after `@charset` when using `keepLineBreaks` option. By [@rrjaime](https://github.com/rrjamie).
|
||||
* Removed Makefile in favor to `npm run` commands (e.g. `make check` -> `npm run check`).
|
||||
* Fixed issue [#47](https://github.com/GoalSmashers/clean-css/issues/47) - commandline issues on Windows.
|
||||
* Fixed issue [#49](https://github.com/GoalSmashers/clean-css/issues/49) - remove empty selectors from media query.
|
||||
* Fixed issue [#52](https://github.com/GoalSmashers/clean-css/issues/52) - strip fraction zeros if not needed.
|
||||
* Fixed issue [#58](https://github.com/GoalSmashers/clean-css/issues/58) - remove colon where possible.
|
||||
* Fixed issue [#59](https://github.com/GoalSmashers/clean-css/issues/59) - content property handling.
|
||||
|
||||
0.9.1 / 2012-12-19
|
||||
==================
|
||||
|
||||
* Fixed issue [#37](https://github.com/GoalSmashers/clean-css/issues/37) - converting
|
||||
`white` and other colors in class names (reported by [@malgorithms](https://github.com/malgorithms)).
|
||||
|
||||
0.9.0 / 2012-12-15
|
||||
==================
|
||||
|
||||
* Added stripping quotation from font names (if possible).
|
||||
* Added stripping quotation from `@keyframes` declaration, `animation` and
|
||||
`animation-name` property.
|
||||
* Added stripping quotations from attributes' value (e.g. `[data-target='x']`).
|
||||
* Added better hex->name and name->hex color shortening.
|
||||
* Added `font: normal` and `font: bold` shortening the same way as `font-weight` is.
|
||||
* Refactored shorthand selectors and added `border-radius`, `border-style`
|
||||
and `border-color` shortening.
|
||||
* Added `margin`, `padding` and `border-width` shortening.
|
||||
* Added removing line break after commas.
|
||||
* Fixed removing whitespace inside media query definition.
|
||||
* Added removing line breaks after a comma, so all declarations are one-liners now.
|
||||
* Speed optimizations (~10% despite many new features).
|
||||
* Added [JSHint](https://github.com/jshint/jshint/) validation rules via `make check`.
|
||||
|
||||
0.8.3 / 2012-11-29
|
||||
==================
|
||||
|
||||
* Fixed HSL/HSLA colors processing.
|
||||
|
||||
0.8.2 / 2012-10-31
|
||||
==================
|
||||
|
||||
* Fixed shortening hex colors and their relation to hashes in URLs.
|
||||
* Cleanup by [@XhmikosR](https://github.com/XhmikosR).
|
||||
|
||||
0.8.1 / 2012-10-28
|
||||
==================
|
||||
|
||||
* Added better zeros processing for `rect(...)` syntax (clip property).
|
||||
|
||||
0.8.0 / 2012-10-21
|
||||
==================
|
||||
|
||||
* Added removing URLs quotation if possible.
|
||||
* Rewrote breaks processing.
|
||||
* Added `keepBreaks`/`-b` option to keep line breaks in the minimized file.
|
||||
* Reformatted [lib/clean.js](/lib/clean.js) so it's easier to follow the rules.
|
||||
* Minimized test data is now minimized with line breaks so it's easier to
|
||||
compare the changes line by line.
|
||||
|
||||
0.7.0 / 2012-10-14
|
||||
==================
|
||||
|
||||
* Added stripping special comments to CLI (`--s0` and `--s1` options).
|
||||
* Added stripping special comments to programmatic interface
|
||||
(`keepSpecialComments` option).
|
||||
|
||||
0.6.0 / 2012-08-05
|
||||
==================
|
||||
|
||||
* Full Windows support with tests (./test.bat).
|
||||
|
||||
0.5.0 / 2012-08-02
|
||||
==================
|
||||
|
||||
* Made path to vows local.
|
||||
* Explicit node.js 0.6 requirement.
|
||||
|
||||
0.4.2 / 2012-06-28
|
||||
==================
|
||||
|
||||
* Updated binary `-v` option (version).
|
||||
* Updated binary to output help when no options given (but not in piped mode).
|
||||
* Added binary tests.
|
||||
|
||||
0.4.1 / 2012-06-10
|
||||
==================
|
||||
|
||||
* Fixed stateless mode where calling `CleanCSS#process` directly was giving
|
||||
errors (reported by [@facelessuser](https://github.com/facelessuser)).
|
||||
|
||||
0.4.0 / 2012-06-04
|
||||
==================
|
||||
|
||||
* Speed improvements up to 4x thanks to the rewrite of comments and CSS' content
|
||||
processing.
|
||||
* Stripping empty CSS tags is now optional (see [bin/cleancss](/bin/cleancss) for details).
|
||||
* Improved debugging mode (see [test/bench.js](/test/bench.js))
|
||||
* Added `make bench` for a one-pass benchmark.
|
||||
|
||||
0.3.3 / 2012-05-27
|
||||
==================
|
||||
|
||||
* Fixed tests, [package.json](/package.json) for development, and regex
|
||||
for removing empty declarations (thanks to [@vvo](https://github.com/vvo)).
|
||||
|
||||
0.3.2 / 2012-01-17
|
||||
==================
|
||||
|
||||
* Fixed output method under node.js 0.6 which incorrectly tried to close
|
||||
`process.stdout`.
|
||||
|
||||
0.3.1 / 2011-12-16
|
||||
==================
|
||||
|
||||
* Fixed cleaning up `0 0 0 0` expressions.
|
||||
|
||||
0.3.0 / 2011-11-29
|
||||
==================
|
||||
|
||||
* Clean-css requires node.js 0.4.0+ to run.
|
||||
* Removed node.js's 0.2.x 'sys' package dependency
|
||||
(thanks to [@jmalonzo](https://github.com/jmalonzo) for a patch).
|
||||
|
||||
0.2.6 / 2011-11-27
|
||||
==================
|
||||
|
||||
* Fixed expanding `+` signs in `calc()` when mixed up with adjacent `+` selector.
|
||||
|
||||
0.2.5 / 2011-11-27
|
||||
==================
|
||||
|
||||
* Fixed issue with cleaning up spaces inside `calc`/`-moz-calc` declarations
|
||||
(thanks to [@cvan](https://github.com/cvan) for reporting it).
|
||||
* Fixed converting `#f00` to `red` in borders and gradients.
|
||||
|
||||
0.2.4 / 2011-05-25
|
||||
==================
|
||||
|
||||
* Fixed problem with expanding `none` to `0` in partial/full background
|
||||
declarations.
|
||||
* Fixed including clean-css library from binary (global to local).
|
||||
|
||||
0.2.3 / 2011-04-18
|
||||
==================
|
||||
|
||||
* Fixed problem with optimizing IE filters.
|
||||
|
||||
0.2.2 / 2011-04-17
|
||||
==================
|
||||
|
||||
* Fixed problem with space before color in `border` property.
|
||||
|
||||
0.2.1 / 2011-03-19
|
||||
==================
|
||||
|
||||
* Added stripping space before `!important` keyword.
|
||||
* Updated repository location and author information in [package.json](/package.json).
|
||||
|
||||
0.2.0 / 2011-03-02
|
||||
==================
|
||||
|
||||
* Added options parsing via optimist.
|
||||
* Changed code inclusion (thus the version bump).
|
||||
|
||||
0.1.0 / 2011-02-27
|
||||
==================
|
||||
|
||||
* First version of clean-css library.
|
||||
* Implemented all basic CSS transformations.
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011-2013 GoalSmashers.com
|
||||
|
||||
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.
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
[](https://badge.fury.io/js/clean-css)
|
||||
[](https://travis-ci.org/GoalSmashers/clean-css)
|
||||
[](https://david-dm.org/GoalSmashers/clean-css)
|
||||
[](https://david-dm.org/GoalSmashers/clean-css#info=devDependencies)
|
||||
|
||||
## What is clean-css?
|
||||
|
||||
Clean-css is a fast and efficient [node.js](http://nodejs.org/) library for minifying CSS files.
|
||||
|
||||
According to [tests](https://github.com/GoalSmashers/css-minification-benchmark) it is one of the best available.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### What are the requirements?
|
||||
|
||||
```
|
||||
node.js 0.8.0+ (tested on CentOS, Ubuntu, OS X 10.6+, and Windows 7+)
|
||||
```
|
||||
|
||||
### How to install clean-css?
|
||||
|
||||
```
|
||||
npm install clean-css
|
||||
```
|
||||
|
||||
### How to upgrade clean-css from 1.x to 2.x?
|
||||
|
||||
#### Command-line interface (CLI)
|
||||
|
||||
```
|
||||
npm update clean-css
|
||||
```
|
||||
|
||||
or point `package.json` to version 2.x. That's it!
|
||||
|
||||
#### Node.js module
|
||||
|
||||
Update `clean-css` as for CLI above.
|
||||
Then change your JavaScript code from:
|
||||
|
||||
```js
|
||||
var minimized = CleanCSS.process(source, options);
|
||||
```
|
||||
|
||||
into
|
||||
|
||||
```js
|
||||
var minimized = new CleanCSS(options).minify(source);
|
||||
```
|
||||
|
||||
And you are done.
|
||||
|
||||
### How to use clean-css CLI?
|
||||
|
||||
Clean-css accepts the following command line arguments (please make sure
|
||||
you use `<source-file>` as the very last argument to avoid potential issues):
|
||||
|
||||
```
|
||||
cleancss [options] <source-file>
|
||||
|
||||
-h, --help Output usage information
|
||||
-v, --version Output the version number
|
||||
-b, --keep-line-breaks Keep line breaks
|
||||
--s0 Remove all special comments, i.e. /*! comment */
|
||||
--s1 Remove all special comments but the first one
|
||||
-r, --root [root-path] A root path to which resolve absolute @import rules
|
||||
and rebase relative URLs
|
||||
-o, --output [output-file] Use [output-file] as output instead of STDOUT
|
||||
-s, --skip-import Disable @import processing
|
||||
--skip-rebase Disable URLs rebasing
|
||||
--skip-advanced Disable advanced optimizations - selector & property merging,
|
||||
reduction, etc.
|
||||
--selectors-merge-mode [ie8|*] Use `ie8` for compatibility mode, `*` for merge all (default)
|
||||
-d, --debug Shows debug information (minification time & compression efficiency)
|
||||
```
|
||||
|
||||
#### Examples:
|
||||
|
||||
To minify a **public.css** file into **public-min.css** do:
|
||||
|
||||
```
|
||||
cleancss -o public-min.css public.css
|
||||
```
|
||||
|
||||
To minify the same **public.css** into the standard output skip the `-o` parameter:
|
||||
|
||||
```
|
||||
cleancss public.css
|
||||
```
|
||||
|
||||
More likely you would like to concatenate a couple of files.
|
||||
If you are on a Unix-like system:
|
||||
|
||||
```bash
|
||||
cat one.css two.css three.css | cleancss -o merged-and-minified.css
|
||||
```
|
||||
|
||||
On Windows:
|
||||
|
||||
```bat
|
||||
type one.css two.css three.css | cleancss -o merged-and-minified.css
|
||||
```
|
||||
|
||||
Or even gzip the result at once:
|
||||
|
||||
```bash
|
||||
cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz
|
||||
```
|
||||
|
||||
### How to use clean-css programmatically?
|
||||
|
||||
```js
|
||||
var CleanCSS = require('clean-css');
|
||||
var source = 'a{font-weight:bold;}';
|
||||
var minimized = new CleanCSS().minify(source);
|
||||
```
|
||||
|
||||
CleanCSS constructor accepts a hash as a parameter, i.e.,
|
||||
`new CleanCSS(options).minify(source)` with the following options available:
|
||||
|
||||
* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one only, `0` for removing all
|
||||
* `keepBreaks` - whether to keep line breaks (default is false)
|
||||
* `benchmark` - turns on benchmarking mode measuring time spent on cleaning up
|
||||
(run `npm run bench` to see example)
|
||||
* `root` - path to resolve absolute `@import` rules and rebase relative URLs
|
||||
* `relativeTo` - path with which to resolve relative `@import` rules and URLs
|
||||
* `processImport` - whether to process `@import` rules
|
||||
* `noRebase` - whether to skip URLs rebasing
|
||||
* `noAdvanced` - set to true to disable advanced optimizations - selector & property merging, reduction, etc.
|
||||
* `selectorsMergeMode` - `ie8` for IE8 compatibility mode, `*` for merging all (default)
|
||||
* `debug` - set to true to get minification statistics under `stats` property (see `test/custom-test.js` for examples)
|
||||
|
||||
### What are the clean-css' dev commands?
|
||||
|
||||
First clone the source, then run:
|
||||
|
||||
* `npm run bench` for clean-css benchmarks (see [test/bench.js](https://github.com/GoalSmashers/clean-css/blob/master/test/bench.js) for details)
|
||||
* `npm run check` to check JS sources with [JSHint](https://github.com/jshint/jshint/)
|
||||
* `npm test` for the test suite
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
### How to preserve a comment block?
|
||||
|
||||
Use the `/*!` notation instead of the standard one `/*`:
|
||||
|
||||
```css
|
||||
/*!
|
||||
Important comments included in minified output.
|
||||
*/
|
||||
```
|
||||
|
||||
### How to rebase relative image URLs
|
||||
|
||||
Clean-css will handle it automatically for you (since version 1.1) in the following cases:
|
||||
|
||||
* When using the CLI:
|
||||
1. Use an output path via `-o`/`--output` to rebase URLs as relative to the output file.
|
||||
2. Use a root path via `-r`/`--root` to rebase URLs as absolute from the given root path.
|
||||
3. If you specify both then `-r`/`--root` takes precendence.
|
||||
* When using clean-css as a library:
|
||||
1. Use a combination of `relativeTo` and `target` options for relative rebase (same as 1 in CLI).
|
||||
2. Use a combination of `relativeTo` and `root` options for absolute rebase (same as 2 in CLI).
|
||||
3. `root` takes precendence over `target` as in CLI.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
* Vincent Voyer ([@vvo](https://github.com/vvo)) for a patch with better
|
||||
empty element regex and for inspiring us to do many performance improvements
|
||||
in 0.4 release.
|
||||
* Isaac ([@facelessuser](https://github.com/facelessuser)) for pointing out
|
||||
a flaw in clean-css' stateless mode.
|
||||
* Jan Michael Alonzo ([@jmalonzo](https://github.com/jmalonzo)) for a patch
|
||||
removing node.js' old `sys` package.
|
||||
* [@XhmikosR](https://github.com/XhmikosR) for suggesting new features
|
||||
(option to remove special comments and strip out URLs quotation) and
|
||||
pointing out numerous improvements (JSHint, media queries).
|
||||
* Anthony Barre ([@abarre](https://github.com/abarre)) for improvements to
|
||||
`@import` processing, namely introducing the `--skip-import` /
|
||||
`processImport` options.
|
||||
* Simon Altschuler ([@altschuler](https://github.com/altschuler)) for fixing
|
||||
`@import` processing inside comments.
|
||||
|
||||
## License
|
||||
|
||||
Clean-css is released under the [MIT License](https://github.com/GoalSmashers/clean-css/blob/master/LICENSE).
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var util = require('util');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var CleanCSS = require('../index');
|
||||
|
||||
var commands = require('commander');
|
||||
|
||||
var packageConfig = fs.readFileSync(path.join(path.dirname(fs.realpathSync(process.argv[1])), '../package.json'));
|
||||
var buildVersion = JSON.parse(packageConfig).version;
|
||||
|
||||
var isWindows = process.platform == 'win32';
|
||||
|
||||
// Specify commander options to parse command line params correctly
|
||||
commands
|
||||
.version(buildVersion, '-v, --version')
|
||||
.usage('[options] <source-file>')
|
||||
.option('-b, --keep-line-breaks', 'Keep line breaks')
|
||||
.option('--s0', 'Remove all special comments, i.e. /*! comment */')
|
||||
.option('--s1', 'Remove all special comments but the first one')
|
||||
.option('-r, --root [root-path]', 'Set a root path to which resolve absolute @import rules')
|
||||
.option('-o, --output [output-file]', 'Use [output-file] as output instead of STDOUT')
|
||||
.option('-s, --skip-import', 'Disable @import processing')
|
||||
.option('--skip-rebase', 'Disable URLs rebasing')
|
||||
.option('--skip-advanced', 'Disable advanced optimizations - selector & property merging, reduction, etc.')
|
||||
.option('--selectors-merge-mode [ie8|*]', 'Use `ie8` for compatibility mode, `*` for merge all (default).')
|
||||
.option('-d, --debug', 'Shows debug information (minification time & compression efficiency)');
|
||||
|
||||
commands.on('--help', function() {
|
||||
util.puts(' Examples:\n');
|
||||
util.puts(' %> cleancss one.css');
|
||||
util.puts(' %> cleancss -o one-min.css one.css');
|
||||
if (isWindows) {
|
||||
util.puts(' %> type one.css two.css three.css | cleancss -o merged-and-minified.css');
|
||||
} else {
|
||||
util.puts(' %> cat one.css two.css three.css | cleancss -o merged-and-minified.css');
|
||||
util.puts(' %> cat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz');
|
||||
}
|
||||
util.puts('');
|
||||
process.exit();
|
||||
});
|
||||
|
||||
commands.parse(process.argv);
|
||||
|
||||
var options = {
|
||||
source: null,
|
||||
target: null
|
||||
};
|
||||
var cleanOptions = {};
|
||||
var fromStdin = !process.env['__DIRECT__'] && !process.stdin.isTTY;
|
||||
|
||||
// If no sensible data passed in just print help and exit
|
||||
if (!fromStdin && commands.args.length == 0) {
|
||||
commands.outputHelp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Now coerce commands into CleanCSS configuration...
|
||||
if (commands.output)
|
||||
cleanOptions.target = options.target = commands.output;
|
||||
if (commands.keepLineBreaks)
|
||||
cleanOptions.keepBreaks = true;
|
||||
if (commands.s1)
|
||||
cleanOptions.keepSpecialComments = 1;
|
||||
if (commands.s0)
|
||||
cleanOptions.keepSpecialComments = 0;
|
||||
if (commands.root)
|
||||
cleanOptions.root = commands.root;
|
||||
if (commands.skipImport)
|
||||
cleanOptions.processImport = false;
|
||||
if (commands.skipRebase)
|
||||
cleanOptions.noRebase = true;
|
||||
if (commands.skipAdvanced)
|
||||
cleanOptions.noAdvanced = true;
|
||||
if (commands.selectorsMergeMode)
|
||||
cleanOptions.selectorsMergeMode = commands.selectorsMergeMode;
|
||||
if (commands.debug)
|
||||
cleanOptions.debug = true;
|
||||
if (commands.args.length > 0) {
|
||||
var source = commands.args[0];
|
||||
options.source = source;
|
||||
cleanOptions.relativeTo = path.dirname(path.resolve(source));
|
||||
}
|
||||
|
||||
// ... and do the magic!
|
||||
if (options.source) {
|
||||
fs.readFile(options.source, 'utf8', function(error, data) {
|
||||
if (error)
|
||||
throw error;
|
||||
output(minify(data));
|
||||
});
|
||||
} else {
|
||||
var stdin = process.openStdin();
|
||||
stdin.setEncoding('utf-8');
|
||||
var data = '';
|
||||
stdin.on('data', function(chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
stdin.on('end', function() {
|
||||
output(minify(data));
|
||||
});
|
||||
}
|
||||
|
||||
function minify(data) {
|
||||
var minifier = new CleanCSS(cleanOptions);
|
||||
var minified = minifier.minify(data);
|
||||
|
||||
if (cleanOptions.debug) {
|
||||
console.error('Original: %d bytes', minifier.stats.originalSize);
|
||||
console.error('Minified: %d bytes', minifier.stats.minifiedSize);
|
||||
console.error('Efficiency: %d%', ~~(minifier.stats.efficiency * 10000) / 100.0);
|
||||
console.error('Time spent: %dms', minifier.stats.timeSpent);
|
||||
}
|
||||
|
||||
outputFeedback(minifier.errors, true);
|
||||
outputFeedback(minifier.warnings);
|
||||
|
||||
if (minifier.errors.length > 0)
|
||||
process.exit(1);
|
||||
|
||||
return minified;
|
||||
}
|
||||
|
||||
function output(minified) {
|
||||
if (options.target)
|
||||
fs.writeFileSync(options.target, minified, 'utf8');
|
||||
else
|
||||
process.stdout.write(minified);
|
||||
};
|
||||
|
||||
function outputFeedback(messages, isError) {
|
||||
var prefix = isError ? '\x1B[31mERROR\x1B[39m:' : 'WARNING:';
|
||||
|
||||
messages.forEach(function(message) {
|
||||
console.error('%s %s', prefix, message);
|
||||
});
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
module.exports = require('./lib/clean');
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* Clean-css - https://github.com/GoalSmashers/clean-css
|
||||
* Released under the terms of MIT license
|
||||
*
|
||||
* Copyright (C) 2011-2013 GoalSmashers.com
|
||||
*/
|
||||
|
||||
var ColorShortener = require('./colors/shortener');
|
||||
var ColorHSLToHex = require('./colors/hsl-to-hex');
|
||||
var ColorRGBToHex = require('./colors/rgb-to-hex');
|
||||
var ColorLongToShortHex = require('./colors/long-to-short-hex');
|
||||
|
||||
var ShorthandNotations = require('./properties/shorthand-notations');
|
||||
var ImportInliner = require('./imports/inliner');
|
||||
var UrlRebase = require('./images/url-rebase');
|
||||
var EmptyRemoval = require('./selectors/empty-removal');
|
||||
|
||||
var CommentsProcessor = require('./text/comments');
|
||||
var ExpressionsProcessor = require('./text/expressions');
|
||||
var FreeTextProcessor = require('./text/free');
|
||||
var UrlsProcessor = require('./text/urls');
|
||||
|
||||
var SelectorsOptimizer = require('./selectors/optimizer');
|
||||
|
||||
module.exports = function(options) {
|
||||
var lineBreak = process.platform == 'win32' ? '\r\n' : '\n';
|
||||
var stats = {};
|
||||
var context = {
|
||||
errors: [],
|
||||
warnings: []
|
||||
};
|
||||
|
||||
options = options || {};
|
||||
options.keepBreaks = options.keepBreaks || false;
|
||||
|
||||
//active by default
|
||||
if (options.processImport === undefined)
|
||||
options.processImport = true;
|
||||
|
||||
var minify = function(data) {
|
||||
if (Buffer.isBuffer(data))
|
||||
data = data.toString();
|
||||
|
||||
var startedAt;
|
||||
if (options.debug) {
|
||||
startedAt = process.hrtime();
|
||||
stats.originalSize = data.length;
|
||||
}
|
||||
|
||||
var replace = function() {
|
||||
if (typeof arguments[0] == 'function')
|
||||
arguments[0]();
|
||||
else
|
||||
data = data.replace.apply(data, arguments);
|
||||
};
|
||||
|
||||
// replace function
|
||||
if (options.benchmark) {
|
||||
var originalReplace = replace;
|
||||
replace = function(pattern, replacement) {
|
||||
var name = typeof pattern == 'function' ?
|
||||
/function (\w+)\(/.exec(pattern.toString())[1] :
|
||||
pattern;
|
||||
|
||||
var start = process.hrtime();
|
||||
originalReplace(pattern, replacement);
|
||||
|
||||
var itTook = process.hrtime(start);
|
||||
console.log('%d ms: ' + name, 1000 * itTook[0] + itTook[1] / 1000000.0);
|
||||
};
|
||||
}
|
||||
|
||||
var commentsProcessor = new CommentsProcessor(
|
||||
'keepSpecialComments' in options ? options.keepSpecialComments : '*',
|
||||
options.keepBreaks,
|
||||
lineBreak
|
||||
);
|
||||
var expressionsProcessor = new ExpressionsProcessor();
|
||||
var freeTextProcessor = new FreeTextProcessor();
|
||||
var urlsProcessor = new UrlsProcessor();
|
||||
var importInliner = new ImportInliner(context);
|
||||
|
||||
if (options.processImport) {
|
||||
// inline all imports
|
||||
replace(function inlineImports() {
|
||||
data = importInliner.process(data, {
|
||||
root: options.root || process.cwd(),
|
||||
relativeTo: options.relativeTo
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
replace(function escapeComments() {
|
||||
data = commentsProcessor.escape(data);
|
||||
});
|
||||
|
||||
// replace all escaped line breaks
|
||||
replace(/\\(\r\n|\n)/mg, '');
|
||||
|
||||
// strip parentheses in urls if possible (no spaces inside)
|
||||
replace(/url\((['"])([^\)]+)['"]\)/g, function(match, quote, url) {
|
||||
var unsafeDataURI = url.indexOf('data:') === 0 && url.match(/data:\w+\/[^;]+;base64,/) === null;
|
||||
if (url.match(/[ \t]/g) !== null || unsafeDataURI)
|
||||
return 'url(' + quote + url + quote + ')';
|
||||
else
|
||||
return 'url(' + url + ')';
|
||||
});
|
||||
|
||||
// strip parentheses in animation & font names
|
||||
replace(/(animation|animation\-name|font|font\-family):([^;}]+)/g, function(match, propertyName, def) {
|
||||
return propertyName + ':' + def.replace(/['"]([a-zA-Z][a-zA-Z\d\-_]+)['"]/g, '$1');
|
||||
});
|
||||
|
||||
// strip parentheses in @keyframes
|
||||
replace(/@(\-moz\-|\-o\-|\-webkit\-)?keyframes ([^{]+)/g, function(match, prefix, name) {
|
||||
prefix = prefix || '';
|
||||
return '@' + prefix + 'keyframes ' + (name.indexOf(' ') > -1 ? name : name.replace(/['"]/g, ''));
|
||||
});
|
||||
|
||||
// IE shorter filters, but only if single (IE 7 issue)
|
||||
replace(/progid:DXImageTransform\.Microsoft\.(Alpha|Chroma)(\([^\)]+\))([;}'"])/g, function(match, filter, args, suffix) {
|
||||
return filter.toLowerCase() + args + suffix;
|
||||
});
|
||||
|
||||
replace(function escapeExpressions() {
|
||||
data = expressionsProcessor.escape(data);
|
||||
});
|
||||
|
||||
// strip parentheses in attribute values
|
||||
replace(/\[([^\]]+)\]/g, function(match, content) {
|
||||
var eqIndex = content.indexOf('=');
|
||||
var singleQuoteIndex = content.indexOf('\'');
|
||||
var doubleQuoteIndex = content.indexOf('"');
|
||||
if (eqIndex < 0 && singleQuoteIndex < 0 && doubleQuoteIndex < 0)
|
||||
return match;
|
||||
if (singleQuoteIndex === 0 || doubleQuoteIndex === 0)
|
||||
return match;
|
||||
|
||||
var key = content.substring(0, eqIndex);
|
||||
var value = content.substring(eqIndex + 1, content.length);
|
||||
|
||||
if (/^['"](?:[a-zA-Z][a-zA-Z\d\-_]+)['"]$/.test(value))
|
||||
return '[' + key + '=' + value.substring(1, value.length - 1) + ']';
|
||||
else
|
||||
return match;
|
||||
});
|
||||
|
||||
replace(function escapeFreeText() {
|
||||
data = freeTextProcessor.escape(data);
|
||||
});
|
||||
|
||||
replace(function escapeUrls() {
|
||||
data = urlsProcessor.escape(data);
|
||||
});
|
||||
|
||||
// line breaks
|
||||
replace(/[\r]?\n/g, ' ');
|
||||
|
||||
// multiple whitespace
|
||||
replace(/[\t ]+/g, ' ');
|
||||
|
||||
// multiple semicolons (with optional whitespace)
|
||||
replace(/;[ ]?;+/g, ';');
|
||||
|
||||
// multiple line breaks to one
|
||||
replace(/ (?:\r\n|\n)/g, lineBreak);
|
||||
replace(/(?:\r\n|\n)+/g, lineBreak);
|
||||
|
||||
// remove spaces around selectors
|
||||
replace(/ ([+~>]) /g, '$1');
|
||||
|
||||
// remove extra spaces inside content
|
||||
replace(/([!\(\{\}:;=,\n]) /g, '$1');
|
||||
replace(/ ([!\)\{\};=,\n])/g, '$1');
|
||||
replace(/(?:\r\n|\n)\}/g, '}');
|
||||
replace(/([\{;,])(?:\r\n|\n)/g, '$1');
|
||||
replace(/ :([^\{\};]+)([;}])/g, ':$1$2');
|
||||
|
||||
// restore spaces inside IE filters (IE 7 issue)
|
||||
replace(/progid:[^(]+\(([^\)]+)/g, function(match) {
|
||||
return match.replace(/,/g, ', ');
|
||||
});
|
||||
|
||||
// trailing semicolons
|
||||
replace(/;\}/g, '}');
|
||||
|
||||
replace(function hsl2Hex() {
|
||||
data = new ColorHSLToHex(data).process();
|
||||
});
|
||||
|
||||
replace(function rgb2Hex() {
|
||||
data = new ColorRGBToHex(data).process();
|
||||
});
|
||||
|
||||
replace(function longToShortHex() {
|
||||
data = new ColorLongToShortHex(data).process();
|
||||
});
|
||||
|
||||
replace(function shortenColors() {
|
||||
data = new ColorShortener(data).process();
|
||||
});
|
||||
|
||||
// replace font weight with numerical value
|
||||
replace(/(font\-weight|font):(normal|bold)([ ;\}!])(\w*)/g, function(match, property, weight, suffix, next) {
|
||||
if (suffix == ' ' && next.length > 0 && !/[.\d]/.test(next))
|
||||
return match;
|
||||
|
||||
if (weight == 'normal')
|
||||
return property + ':400' + suffix + next;
|
||||
else if (weight == 'bold')
|
||||
return property + ':700' + suffix + next;
|
||||
else
|
||||
return match;
|
||||
});
|
||||
|
||||
// zero + unit to zero
|
||||
replace(/(\s|:|,)0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, '$1' + '0');
|
||||
replace(/rect\(0(?:px|em|ex|cm|mm|in|pt|pc|%)/g, 'rect(0');
|
||||
|
||||
// fraction zeros removal
|
||||
replace(/\.([1-9]*)0+(\D)/g, function(match, nonZeroPart, suffix) {
|
||||
return (nonZeroPart ? '.' : '') + nonZeroPart + suffix;
|
||||
});
|
||||
|
||||
// restore 0% in hsl/hsla
|
||||
replace(/(hsl|hsla)\(([^\)]+)\)/g, function(match, colorFunction, colorDef) {
|
||||
var tokens = colorDef.split(',');
|
||||
if (tokens[1] == '0')
|
||||
tokens[1] = '0%';
|
||||
if (tokens[2] == '0')
|
||||
tokens[2] = '0%';
|
||||
return colorFunction + '(' + tokens.join(',') + ')';
|
||||
});
|
||||
|
||||
// none to 0
|
||||
replace(/(border|border-top|border-right|border-bottom|border-left|outline):none/g, '$1:0');
|
||||
|
||||
// background:none to background:0 0
|
||||
replace(/background:(?:none|transparent)([;}])/g, 'background:0 0$1');
|
||||
|
||||
// multiple zeros into one
|
||||
replace(/box-shadow:0 0 0 0([^\.])/g, 'box-shadow:0 0$1');
|
||||
replace(/:0 0 0 0([^\.])/g, ':0$1');
|
||||
replace(/([: ,=\-])0\.(\d)/g, '$1.$2');
|
||||
|
||||
replace(function shorthandNotations() {
|
||||
data = new ShorthandNotations(data).process();
|
||||
});
|
||||
|
||||
// restore rect(...) zeros syntax for 4 zeros
|
||||
replace(/rect\(\s?0(\s|,)0[ ,]0[ ,]0\s?\)/g, 'rect(0$10$10$10)');
|
||||
|
||||
// remove universal selector when not needed (*#id, *.class etc)
|
||||
replace(/\*([\.#:\[])/g, '$1');
|
||||
|
||||
// Restore spaces inside calc back
|
||||
replace(/calc\([^\}]+\}/g, function(match) {
|
||||
return match.replace(/\+/g, ' + ');
|
||||
});
|
||||
|
||||
if (options.noAdvanced) {
|
||||
if (options.keepBreaks)
|
||||
replace(/\}/g, '}' + lineBreak);
|
||||
} else {
|
||||
replace(function optimizeSelectors() {
|
||||
data = new SelectorsOptimizer(data, context, {
|
||||
keepBreaks: options.keepBreaks,
|
||||
lineBreak: lineBreak,
|
||||
selectorsMergeMode: options.selectorsMergeMode
|
||||
}).process();
|
||||
});
|
||||
}
|
||||
|
||||
replace(function restoreUrls() {
|
||||
data = urlsProcessor.restore(data);
|
||||
});
|
||||
replace(function rebaseUrls() {
|
||||
data = options.noRebase ? data : new UrlRebase(options, context).process(data);
|
||||
});
|
||||
replace(function restoreFreeText() {
|
||||
data = freeTextProcessor.restore(data);
|
||||
});
|
||||
replace(function restoreComments() {
|
||||
data = commentsProcessor.restore(data);
|
||||
});
|
||||
replace(function restoreExpressions() {
|
||||
data = expressionsProcessor.restore(data);
|
||||
});
|
||||
|
||||
// move first charset to the beginning
|
||||
replace(function moveCharset() {
|
||||
// get first charset in stylesheet
|
||||
var match = data.match(/@charset [^;]+;/);
|
||||
var firstCharset = match ? match[0] : null;
|
||||
if (!firstCharset)
|
||||
return;
|
||||
|
||||
// reattach first charset and remove all subsequent
|
||||
data = firstCharset +
|
||||
(options.keepBreaks ? lineBreak : '') +
|
||||
data.replace(new RegExp('@charset [^;]+;(' + lineBreak + ')?', 'g'), '').trim();
|
||||
});
|
||||
|
||||
replace(function removeEmptySelectors() {
|
||||
data = new EmptyRemoval(data).process();
|
||||
});
|
||||
|
||||
// trim spaces at beginning and end
|
||||
data = data.trim();
|
||||
|
||||
if (options.debug) {
|
||||
var elapsed = process.hrtime(startedAt);
|
||||
stats.timeSpent = ~~(elapsed[0] * 1e3 + elapsed[1] / 1e6);
|
||||
stats.efficiency = 1 - data.length / stats.originalSize;
|
||||
stats.minifiedSize = data.length;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
errors: context.errors,
|
||||
lineBreak: lineBreak,
|
||||
options: options,
|
||||
minify: minify,
|
||||
stats: stats,
|
||||
warnings: context.warnings
|
||||
};
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
module.exports = function HSLToHex(data) {
|
||||
// HSL to RGB converter. Both methods adapted from:
|
||||
// http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
||||
var hslToRgb = function(h, s, l) {
|
||||
var r, g, b;
|
||||
|
||||
// normalize hue orientation b/w 0 and 360 degrees
|
||||
h = h % 360;
|
||||
if (h < 0)
|
||||
h += 360;
|
||||
h = ~~h / 360;
|
||||
|
||||
if (s < 0)
|
||||
s = 0;
|
||||
else if (s > 100)
|
||||
s = 100;
|
||||
s = ~~s / 100;
|
||||
|
||||
if (l < 0)
|
||||
l = 0;
|
||||
else if (l > 100)
|
||||
l = 100;
|
||||
l = ~~l / 100;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l; // achromatic
|
||||
} else {
|
||||
var q = l < 0.5 ?
|
||||
l * (1 + s) :
|
||||
l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
r = hueToRgb(p, q, h + 1/3);
|
||||
g = hueToRgb(p, q, h);
|
||||
b = hueToRgb(p, q, h - 1/3);
|
||||
}
|
||||
|
||||
return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
|
||||
};
|
||||
|
||||
var hueToRgb = function(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||
if (t < 1/2) return q;
|
||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
return {
|
||||
process: function() {
|
||||
return data.replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function(match, hue, saturation, lightness) {
|
||||
var asRgb = hslToRgb(hue, saturation, lightness);
|
||||
var redAsHex = asRgb[0].toString(16);
|
||||
var greenAsHex = asRgb[1].toString(16);
|
||||
var blueAsHex = asRgb[2].toString(16);
|
||||
|
||||
return '#' +
|
||||
((redAsHex.length == 1 ? '0' : '') + redAsHex) +
|
||||
((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
|
||||
((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
module.exports = function LongToShortHex(data) {
|
||||
return {
|
||||
process: function() {
|
||||
return data.replace(/([,: \(])#([0-9a-f]{6})/gi, function(match, prefix, color) {
|
||||
if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
|
||||
return prefix + '#' + color[0] + color[2] + color[4];
|
||||
else
|
||||
return prefix + '#' + color;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
module.exports = function RGBToHex(data) {
|
||||
return {
|
||||
process: function() {
|
||||
return data.replace(/rgb\((\d+),(\d+),(\d+)\)/g, function(match, red, green, blue) {
|
||||
var redAsHex = parseInt(red, 10).toString(16);
|
||||
var greenAsHex = parseInt(green, 10).toString(16);
|
||||
var blueAsHex = parseInt(blue, 10).toString(16);
|
||||
|
||||
return '#' +
|
||||
((redAsHex.length == 1 ? '0' : '') + redAsHex) +
|
||||
((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
|
||||
((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
module.exports = function Shortener(data) {
|
||||
var COLORS = {
|
||||
aliceblue: '#f0f8ff',
|
||||
antiquewhite: '#faebd7',
|
||||
aqua: '#0ff',
|
||||
aquamarine: '#7fffd4',
|
||||
azure: '#f0ffff',
|
||||
beige: '#f5f5dc',
|
||||
bisque: '#ffe4c4',
|
||||
black: '#000',
|
||||
blanchedalmond: '#ffebcd',
|
||||
blue: '#00f',
|
||||
blueviolet: '#8a2be2',
|
||||
brown: '#a52a2a',
|
||||
burlywood: '#deb887',
|
||||
cadetblue: '#5f9ea0',
|
||||
chartreuse: '#7fff00',
|
||||
chocolate: '#d2691e',
|
||||
coral: '#ff7f50',
|
||||
cornflowerblue: '#6495ed',
|
||||
cornsilk: '#fff8dc',
|
||||
crimson: '#dc143c',
|
||||
cyan: '#0ff',
|
||||
darkblue: '#00008b',
|
||||
darkcyan: '#008b8b',
|
||||
darkgoldenrod: '#b8860b',
|
||||
darkgray: '#a9a9a9',
|
||||
darkgreen: '#006400',
|
||||
darkkhaki: '#bdb76b',
|
||||
darkmagenta: '#8b008b',
|
||||
darkolivegreen: '#556b2f',
|
||||
darkorange: '#ff8c00',
|
||||
darkorchid: '#9932cc',
|
||||
darkred: '#8b0000',
|
||||
darksalmon: '#e9967a',
|
||||
darkseagreen: '#8fbc8f',
|
||||
darkslateblue: '#483d8b',
|
||||
darkslategray: '#2f4f4f',
|
||||
darkturquoise: '#00ced1',
|
||||
darkviolet: '#9400d3',
|
||||
deeppink: '#ff1493',
|
||||
deepskyblue: '#00bfff',
|
||||
dimgray: '#696969',
|
||||
dodgerblue: '#1e90ff',
|
||||
firebrick: '#b22222',
|
||||
floralwhite: '#fffaf0',
|
||||
forestgreen: '#228b22',
|
||||
fuchsia: '#f0f',
|
||||
gainsboro: '#dcdcdc',
|
||||
ghostwhite: '#f8f8ff',
|
||||
gold: '#ffd700',
|
||||
goldenrod: '#daa520',
|
||||
gray: '#808080',
|
||||
green: '#008000',
|
||||
greenyellow: '#adff2f',
|
||||
honeydew: '#f0fff0',
|
||||
hotpink: '#ff69b4',
|
||||
indianred: '#cd5c5c',
|
||||
indigo: '#4b0082',
|
||||
ivory: '#fffff0',
|
||||
khaki: '#f0e68c',
|
||||
lavender: '#e6e6fa',
|
||||
lavenderblush: '#fff0f5',
|
||||
lawngreen: '#7cfc00',
|
||||
lemonchiffon: '#fffacd',
|
||||
lightblue: '#add8e6',
|
||||
lightcoral: '#f08080',
|
||||
lightcyan: '#e0ffff',
|
||||
lightgoldenrodyellow: '#fafad2',
|
||||
lightgray: '#d3d3d3',
|
||||
lightgreen: '#90ee90',
|
||||
lightpink: '#ffb6c1',
|
||||
lightsalmon: '#ffa07a',
|
||||
lightseagreen: '#20b2aa',
|
||||
lightskyblue: '#87cefa',
|
||||
lightslategray: '#778899',
|
||||
lightsteelblue: '#b0c4de',
|
||||
lightyellow: '#ffffe0',
|
||||
lime: '#0f0',
|
||||
limegreen: '#32cd32',
|
||||
linen: '#faf0e6',
|
||||
magenta: '#ff00ff',
|
||||
maroon: '#800000',
|
||||
mediumaquamarine: '#66cdaa',
|
||||
mediumblue: '#0000cd',
|
||||
mediumorchid: '#ba55d3',
|
||||
mediumpurple: '#9370db',
|
||||
mediumseagreen: '#3cb371',
|
||||
mediumslateblue: '#7b68ee',
|
||||
mediumspringgreen: '#00fa9a',
|
||||
mediumturquoise: '#48d1cc',
|
||||
mediumvioletred: '#c71585',
|
||||
midnightblue: '#191970',
|
||||
mintcream: '#f5fffa',
|
||||
mistyrose: '#ffe4e1',
|
||||
moccasin: '#ffe4b5',
|
||||
navajowhite: '#ffdead',
|
||||
navy: '#000080',
|
||||
oldlace: '#fdf5e6',
|
||||
olive: '#808000',
|
||||
olivedrab: '#6b8e23',
|
||||
orange: '#ffa500',
|
||||
orangered: '#ff4500',
|
||||
orchid: '#da70d6',
|
||||
palegoldenrod: '#eee8aa',
|
||||
palegreen: '#98fb98',
|
||||
paleturquoise: '#afeeee',
|
||||
palevioletred: '#db7093',
|
||||
papayawhip: '#ffefd5',
|
||||
peachpuff: '#ffdab9',
|
||||
peru: '#cd853f',
|
||||
pink: '#ffc0cb',
|
||||
plum: '#dda0dd',
|
||||
powderblue: '#b0e0e6',
|
||||
purple: '#800080',
|
||||
red: '#f00',
|
||||
rosybrown: '#bc8f8f',
|
||||
royalblue: '#4169e1',
|
||||
saddlebrown: '#8b4513',
|
||||
salmon: '#fa8072',
|
||||
sandybrown: '#f4a460',
|
||||
seagreen: '#2e8b57',
|
||||
seashell: '#fff5ee',
|
||||
sienna: '#a0522d',
|
||||
silver: '#c0c0c0',
|
||||
skyblue: '#87ceeb',
|
||||
slateblue: '#6a5acd',
|
||||
slategray: '#708090',
|
||||
snow: '#fffafa',
|
||||
springgreen: '#00ff7f',
|
||||
steelblue: '#4682b4',
|
||||
tan: '#d2b48c',
|
||||
teal: '#008080',
|
||||
thistle: '#d8bfd8',
|
||||
tomato: '#ff6347',
|
||||
turquoise: '#40e0d0',
|
||||
violet: '#ee82ee',
|
||||
wheat: '#f5deb3',
|
||||
white: '#fff',
|
||||
whitesmoke: '#f5f5f5',
|
||||
yellow: '#ff0',
|
||||
yellowgreen: '#9acd32'
|
||||
};
|
||||
|
||||
var toHex = {};
|
||||
var toName = {};
|
||||
|
||||
for (var name in COLORS) {
|
||||
var color = COLORS[name];
|
||||
if (name.length < color.length)
|
||||
toName[color] = name;
|
||||
else
|
||||
toHex[name] = color;
|
||||
}
|
||||
|
||||
return {
|
||||
toHex: toHex,
|
||||
toName: toName,
|
||||
|
||||
// replace color name with hex values if shorter (or the other way around)
|
||||
process: function() {
|
||||
[toHex, toName].forEach(function(conversion) {
|
||||
var pattern = '(' + Object.keys(conversion).join('|') + ')';
|
||||
var colorSwitcher = function(match, prefix, colorValue, suffix) {
|
||||
return prefix + conversion[colorValue.toLowerCase()] + suffix;
|
||||
};
|
||||
data = data.replace(new RegExp('([ :,\\(])' + pattern + '([;\\}!\\) ])', 'ig'), colorSwitcher);
|
||||
data = data.replace(new RegExp('(,)' + pattern + '(,)', 'ig'), colorSwitcher);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
var path = require('path');
|
||||
|
||||
var UrlRewriter = require('./url-rewriter');
|
||||
|
||||
module.exports = function UrlRebase(options, context) {
|
||||
var process = function(data) {
|
||||
var rebaseOpts = {
|
||||
absolute: !!options.root,
|
||||
relative: !options.root && !!options.target,
|
||||
fromBase: options.relativeTo
|
||||
};
|
||||
|
||||
if (!rebaseOpts.absolute && !rebaseOpts.relative)
|
||||
return data;
|
||||
|
||||
if (rebaseOpts.absolute && !!options.target)
|
||||
context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
|
||||
|
||||
if (rebaseOpts.absolute)
|
||||
rebaseOpts.toBase = path.resolve(options.root);
|
||||
|
||||
if (rebaseOpts.relative)
|
||||
rebaseOpts.toBase = path.resolve(path.dirname(options.target));
|
||||
|
||||
if (!rebaseOpts.fromBase || !rebaseOpts.toBase)
|
||||
return data;
|
||||
|
||||
return UrlRewriter.process(data, rebaseOpts);
|
||||
};
|
||||
|
||||
return {
|
||||
process: process
|
||||
};
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
process: function(data, options) {
|
||||
var tempData = [];
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
nextStart = data.indexOf('url(', nextEnd);
|
||||
if (nextStart == -1)
|
||||
break;
|
||||
|
||||
nextEnd = data.indexOf(')', nextStart + 4);
|
||||
if (nextEnd == -1)
|
||||
break;
|
||||
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
var url = data.substring(nextStart + 4, nextEnd).replace(/['"]/g, '');
|
||||
tempData.push('url(' + this._rebased(url, options) + ')');
|
||||
cursor = nextEnd + 1;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
},
|
||||
|
||||
_rebased: function(url, options) {
|
||||
var specialUrl = url[0] == '/' ||
|
||||
url.substring(url.length - 4) == '.css' ||
|
||||
url.indexOf('data:') === 0 ||
|
||||
/^https?:\/\//.exec(url) !== null ||
|
||||
/__\w+__/.exec(url) !== null;
|
||||
var rebased;
|
||||
|
||||
if (specialUrl)
|
||||
return url;
|
||||
|
||||
if (options.absolute) {
|
||||
rebased = path
|
||||
.resolve(path.join(options.fromBase, url))
|
||||
.replace(options.toBase, '');
|
||||
} else {
|
||||
rebased = path.relative(options.toBase, path.join(options.fromBase, url));
|
||||
}
|
||||
|
||||
return process.platform == 'win32' ?
|
||||
rebased.replace(/\\/g, '/') :
|
||||
rebased;
|
||||
}
|
||||
};
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var UrlRewriter = require('../images/url-rewriter');
|
||||
|
||||
module.exports = function Inliner(context) {
|
||||
var process = function(data, options) {
|
||||
var tempData = [];
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
var isComment = commentScanner(data);
|
||||
|
||||
options.relativeTo = options.relativeTo || options.root;
|
||||
options._baseRelativeTo = options._baseRelativeTo || options.relativeTo;
|
||||
options.visited = options.visited || [];
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
nextStart = data.indexOf('@import', cursor);
|
||||
if (nextStart == -1)
|
||||
break;
|
||||
|
||||
if (isComment(nextStart)) {
|
||||
cursor = nextStart + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
nextEnd = data.indexOf(';', nextStart);
|
||||
if (nextEnd == -1) {
|
||||
tempData.push('');
|
||||
cursor = data.length;
|
||||
break;
|
||||
}
|
||||
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
tempData.push(inlinedFile(data, nextStart, nextEnd, options));
|
||||
cursor = nextEnd + 1;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
};
|
||||
|
||||
var commentScanner = function(data) {
|
||||
var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
|
||||
var lastStartIndex = 0;
|
||||
var lastEndIndex = 0;
|
||||
var noComments = false;
|
||||
|
||||
// test whether an index is located within a comment
|
||||
var scanner = function(idx) {
|
||||
var comment;
|
||||
var localStartIndex = 0;
|
||||
var localEndIndex = 0;
|
||||
var globalStartIndex = 0;
|
||||
var globalEndIndex = 0;
|
||||
|
||||
// return if we know there are no more comments
|
||||
if (noComments)
|
||||
return false;
|
||||
|
||||
// idx can be still within last matched comment (many @import statements inside one comment)
|
||||
if (idx > lastStartIndex && idx < lastEndIndex)
|
||||
return true;
|
||||
|
||||
comment = data.match(commentRegex);
|
||||
|
||||
if (!comment) {
|
||||
noComments = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// get the indexes relative to the current data chunk
|
||||
lastStartIndex = localStartIndex = comment.index;
|
||||
localEndIndex = localStartIndex + comment[0].length;
|
||||
|
||||
// calculate the indexes relative to the full original data
|
||||
globalEndIndex = localEndIndex + lastEndIndex;
|
||||
globalStartIndex = globalEndIndex - comment[0].length;
|
||||
|
||||
// chop off data up to and including current comment block
|
||||
data = data.substring(localEndIndex);
|
||||
lastEndIndex = globalEndIndex;
|
||||
|
||||
// re-run scan if comment ended before the idx
|
||||
if (globalEndIndex < idx)
|
||||
return scanner(idx);
|
||||
|
||||
return globalEndIndex > idx && idx > globalStartIndex;
|
||||
};
|
||||
|
||||
return scanner;
|
||||
};
|
||||
|
||||
var inlinedFile = function(data, nextStart, nextEnd, options) {
|
||||
var strippedImport = data
|
||||
.substring(data.indexOf(' ', nextStart) + 1, nextEnd)
|
||||
.replace(/^url\(/, '')
|
||||
.replace(/['"]/g, '');
|
||||
|
||||
var separatorIndex = strippedImport.indexOf(' ');
|
||||
var importedFile = strippedImport
|
||||
.substring(0, separatorIndex > 0 ? separatorIndex : strippedImport.length)
|
||||
.replace(')', '');
|
||||
var mediaQuery = strippedImport
|
||||
.substring(importedFile.length + 1)
|
||||
.trim();
|
||||
|
||||
if (/^(http|https):\/\//.test(importedFile) || /^\/\//.test(importedFile))
|
||||
return '@import url(' + importedFile + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
|
||||
|
||||
var relativeTo = importedFile[0] == '/' ?
|
||||
options.root :
|
||||
options.relativeTo;
|
||||
|
||||
var fullPath = path.resolve(path.join(relativeTo, importedFile));
|
||||
|
||||
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
|
||||
context.errors.push('Broken @import declaration of "' + importedFile + '"');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (options.visited.indexOf(fullPath) != -1)
|
||||
return '';
|
||||
|
||||
options.visited.push(fullPath);
|
||||
|
||||
var importedData = fs.readFileSync(fullPath, 'utf8');
|
||||
var importRelativeTo = path.dirname(fullPath);
|
||||
importedData = UrlRewriter.process(importedData, {
|
||||
relative: true,
|
||||
fromBase: importRelativeTo,
|
||||
toBase: options._baseRelativeTo
|
||||
});
|
||||
|
||||
var inlinedData = process(importedData, {
|
||||
root: options.root,
|
||||
relativeTo: importRelativeTo,
|
||||
_baseRelativeTo: options.baseRelativeTo,
|
||||
visited: options.visited
|
||||
});
|
||||
return mediaQuery.length > 0 ?
|
||||
'@media ' + mediaQuery + '{' + inlinedData + '}' :
|
||||
inlinedData;
|
||||
};
|
||||
|
||||
return {
|
||||
// Inlines all imports taking care of repetitions, unknown files, and circular dependencies
|
||||
process: process
|
||||
};
|
||||
};
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
module.exports = function Optimizer() {
|
||||
var overridable = {
|
||||
'animation-delay': ['animation'],
|
||||
'animation-direction': ['animation'],
|
||||
'animation-duration': ['animation'],
|
||||
'animation-fill-mode': ['animation'],
|
||||
'animation-iteration-count': ['animation'],
|
||||
'animation-name': ['animation'],
|
||||
'animation-play-state': ['animation'],
|
||||
'animation-timing-function': ['animation'],
|
||||
'-moz-animation-delay': ['-moz-animation'],
|
||||
'-moz-animation-direction': ['-moz-animation'],
|
||||
'-moz-animation-duration': ['-moz-animation'],
|
||||
'-moz-animation-fill-mode': ['-moz-animation'],
|
||||
'-moz-animation-iteration-count': ['-moz-animation'],
|
||||
'-moz-animation-name': ['-moz-animation'],
|
||||
'-moz-animation-play-state': ['-moz-animation'],
|
||||
'-moz-animation-timing-function': ['-moz-animation'],
|
||||
'-o-animation-delay': ['-o-animation'],
|
||||
'-o-animation-direction': ['-o-animation'],
|
||||
'-o-animation-duration': ['-o-animation'],
|
||||
'-o-animation-fill-mode': ['-o-animation'],
|
||||
'-o-animation-iteration-count': ['-o-animation'],
|
||||
'-o-animation-name': ['-o-animation'],
|
||||
'-o-animation-play-state': ['-o-animation'],
|
||||
'-o-animation-timing-function': ['-o-animation'],
|
||||
'-webkit-animation-delay': ['-webkit-animation'],
|
||||
'-webkit-animation-direction': ['-webkit-animation'],
|
||||
'-webkit-animation-duration': ['-webkit-animation'],
|
||||
'-webkit-animation-fill-mode': ['-webkit-animation'],
|
||||
'-webkit-animation-iteration-count': ['-webkit-animation'],
|
||||
'-webkit-animation-name': ['-webkit-animation'],
|
||||
'-webkit-animation-play-state': ['-webkit-animation'],
|
||||
'-webkit-animation-timing-function': ['-webkit-animation'],
|
||||
'background-attachment': ['background'],
|
||||
'background-clip': ['background'],
|
||||
'background-color': ['background'],
|
||||
'background-image': ['background'],
|
||||
'background-origin': ['background'],
|
||||
'background-position': ['background'],
|
||||
'background-repeat': ['background'],
|
||||
'background-size': ['background'],
|
||||
'border-color': ['border'],
|
||||
'border-style': ['border'],
|
||||
'border-width': ['border'],
|
||||
'border-bottom': ['border'],
|
||||
'border-bottom-color': ['border-bottom', 'border-color', 'border'],
|
||||
'border-bottom-style': ['border-bottom', 'border-style', 'border'],
|
||||
'border-bottom-width': ['border-bottom', 'border-width', 'border'],
|
||||
'border-left': ['border'],
|
||||
'border-left-color': ['border-left', 'border-color', 'border'],
|
||||
'border-left-style': ['border-left', 'border-style', 'border'],
|
||||
'border-left-width': ['border-left', 'border-width', 'border'],
|
||||
'border-right': ['border'],
|
||||
'border-right-color': ['border-right', 'border-color', 'border'],
|
||||
'border-right-style': ['border-right', 'border-style', 'border'],
|
||||
'border-right-width': ['border-right', 'border-width', 'border'],
|
||||
'border-top': ['border'],
|
||||
'border-top-color': ['border-top', 'border-color', 'border'],
|
||||
'border-top-style': ['border-top', 'border-style', 'border'],
|
||||
'border-top-width': ['border-top', 'border-width', 'border'],
|
||||
'font-family': ['font'],
|
||||
'font-size': ['font'],
|
||||
'font-style': ['font'],
|
||||
'font-variant': ['font'],
|
||||
'font-weight': ['font'],
|
||||
'list-style-image': ['list'],
|
||||
'list-style-position': ['list'],
|
||||
'list-style-type': ['list'],
|
||||
'margin-bottom': ['margin'],
|
||||
'margin-left': ['margin'],
|
||||
'margin-right': ['margin'],
|
||||
'margin-top': ['margin'],
|
||||
'outline-color': ['outline'],
|
||||
'outline-style': ['outline'],
|
||||
'outline-width': ['outline'],
|
||||
'padding-bottom': ['padding'],
|
||||
'padding-left': ['padding'],
|
||||
'padding-right': ['padding'],
|
||||
'padding-top': ['padding'],
|
||||
'transition-delay': ['transition'],
|
||||
'transition-duration': ['transition'],
|
||||
'transition-property': ['transition'],
|
||||
'transition-timing-function': ['transition'],
|
||||
'-moz-transition-delay': ['-moz-transition'],
|
||||
'-moz-transition-duration': ['-moz-transition'],
|
||||
'-moz-transition-property': ['-moz-transition'],
|
||||
'-moz-transition-timing-function': ['-moz-transition'],
|
||||
'-o-transition-delay': ['-o-transition'],
|
||||
'-o-transition-duration': ['-o-transition'],
|
||||
'-o-transition-property': ['-o-transition'],
|
||||
'-o-transition-timing-function': ['-o-transition'],
|
||||
'-webkit-transition-delay': ['-webkit-transition'],
|
||||
'-webkit-transition-duration': ['-webkit-transition'],
|
||||
'-webkit-transition-property': ['-webkit-transition'],
|
||||
'-webkit-transition-timing-function': ['-webkit-transition']
|
||||
};
|
||||
|
||||
var overrides = {};
|
||||
for (var granular in overridable) {
|
||||
for (var i = 0; i < overridable[granular].length; i++) {
|
||||
var coarse = overridable[granular][i];
|
||||
var list = overrides[coarse];
|
||||
|
||||
if (list)
|
||||
list.push(granular);
|
||||
else
|
||||
overrides[coarse] = [granular];
|
||||
}
|
||||
}
|
||||
|
||||
var tokenize = function(body) {
|
||||
var tokens = body.split(';');
|
||||
var keyValues = [];
|
||||
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
var token = tokens[i];
|
||||
if (token === '')
|
||||
continue;
|
||||
|
||||
var firstColon = token.indexOf(':');
|
||||
keyValues.push([
|
||||
token.substring(0, firstColon),
|
||||
token.substring(firstColon + 1),
|
||||
token.indexOf('!important') > -1
|
||||
]);
|
||||
}
|
||||
|
||||
return keyValues;
|
||||
};
|
||||
|
||||
var optimize = function(tokens, allowAdjacent) {
|
||||
var merged = [];
|
||||
var properties = [];
|
||||
var lastProperty = null;
|
||||
var rescanTrigger = {};
|
||||
|
||||
var removeOverridenBy = function(property, isImportant) {
|
||||
var overrided = overrides[property];
|
||||
for (var i = 0, l = overrided.length; i < l; i++) {
|
||||
for (var j = 0; j < properties.length; j++) {
|
||||
if (properties[j] != overrided[i] || (merged[j][2] && !isImportant))
|
||||
continue;
|
||||
|
||||
merged.splice(j, 1);
|
||||
properties.splice(j, 1);
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var mergeablePosition = function(position) {
|
||||
if (allowAdjacent === false || allowAdjacent === true)
|
||||
return allowAdjacent;
|
||||
|
||||
return allowAdjacent.indexOf(position) > -1;
|
||||
};
|
||||
|
||||
tokensLoop:
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
var token = tokens[i];
|
||||
var property = token[0];
|
||||
var isImportant = token[2];
|
||||
var _property = (property == '-ms-filter' || property == 'filter') ?
|
||||
(lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) :
|
||||
property;
|
||||
var toOverridePosition = 0;
|
||||
|
||||
// comment is necessary - we assume that if two properties are one after another
|
||||
// then it is intentional way of redefining property which may not be widely supported
|
||||
// e.g. a{display:inline-block;display:-moz-inline-box}
|
||||
// however if `mergeablePosition` yields true then the rule does not apply
|
||||
// (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
|
||||
if (_property != lastProperty || mergeablePosition(i)) {
|
||||
while (true) {
|
||||
toOverridePosition = properties.indexOf(_property, toOverridePosition);
|
||||
if (toOverridePosition == -1)
|
||||
break;
|
||||
|
||||
if (merged[toOverridePosition][2] && !isImportant)
|
||||
continue tokensLoop;
|
||||
|
||||
merged.splice(toOverridePosition, 1);
|
||||
properties.splice(toOverridePosition, 1);
|
||||
}
|
||||
}
|
||||
|
||||
merged.push(token);
|
||||
properties.push(_property);
|
||||
|
||||
// certain properties (see values of `overridable`) should trigger removal of
|
||||
// more granular properties (see keys of `overridable`)
|
||||
if (rescanTrigger[_property])
|
||||
removeOverridenBy(_property, isImportant);
|
||||
|
||||
// add rescan triggers - if certain property appears later in the list a rescan needs
|
||||
// to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
|
||||
// 'border-top-color' as they can be removed
|
||||
for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++)
|
||||
rescanTrigger[list[j]] = true;
|
||||
|
||||
lastProperty = _property;
|
||||
}
|
||||
|
||||
return merged;
|
||||
};
|
||||
|
||||
var rebuild = function(tokens) {
|
||||
var flat = [];
|
||||
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
flat.push(tokens[i][0] + ':' + tokens[i][1]);
|
||||
}
|
||||
|
||||
return flat.join(';');
|
||||
};
|
||||
|
||||
return {
|
||||
process: function(body, allowAdjacent) {
|
||||
var tokens = tokenize(body);
|
||||
if (tokens.length < 2)
|
||||
return body;
|
||||
|
||||
var optimized = optimize(tokens, allowAdjacent);
|
||||
return rebuild(optimized);
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
module.exports = function ShorthandNotations(data) {
|
||||
// shorthand notations
|
||||
var shorthandRegex = function(repeats, hasSuffix) {
|
||||
var pattern = '(padding|margin|border\\-width|border\\-color|border\\-style|border\\-radius):';
|
||||
for (var i = 0; i < repeats; i++)
|
||||
pattern += '([\\d\\w\\.%#\\(\\),]+)' + (i < repeats - 1 ? ' ' : '');
|
||||
return new RegExp(pattern + (hasSuffix ? '([;}])' : ''), 'g');
|
||||
};
|
||||
|
||||
var from4Values = function() {
|
||||
return data.replace(shorthandRegex(4), function(match, property, size1, size2, size3, size4) {
|
||||
if (size1 === size2 && size1 === size3 && size1 === size4)
|
||||
return property + ':' + size1;
|
||||
else if (size1 === size3 && size2 === size4)
|
||||
return property + ':' + size1 + ' ' + size2;
|
||||
else if (size2 === size4)
|
||||
return property + ':' + size1 + ' ' + size2 + ' ' + size3;
|
||||
else
|
||||
return match;
|
||||
});
|
||||
};
|
||||
|
||||
var from3Values = function() {
|
||||
return data.replace(shorthandRegex(3, true), function(match, property, size1, size2, size3, suffix) {
|
||||
if (size1 === size2 && size1 === size3)
|
||||
return property + ':' + size1 + suffix;
|
||||
else if (size1 === size3)
|
||||
return property + ':' + size1 + ' ' + size2 + suffix;
|
||||
else
|
||||
return match;
|
||||
});
|
||||
};
|
||||
|
||||
var from2Values = function() {
|
||||
return data.replace(shorthandRegex(2, true), function(match, property, size1, size2, suffix) {
|
||||
if (size1 === size2)
|
||||
return property + ':' + size1 + suffix;
|
||||
else
|
||||
return match;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
process: function() {
|
||||
data = from4Values();
|
||||
data = from3Values();
|
||||
return from2Values();
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
module.exports = function EmptyRemoval(data) {
|
||||
var stripEmpty = function(cssData) {
|
||||
var tempData = [];
|
||||
var nextEmpty = 0;
|
||||
var cursor = 0;
|
||||
|
||||
for (; nextEmpty < cssData.length; ) {
|
||||
nextEmpty = cssData.indexOf('{}', cursor);
|
||||
if (nextEmpty == -1)
|
||||
break;
|
||||
|
||||
var startsAt = nextEmpty - 1;
|
||||
while (cssData[startsAt] && cssData[startsAt] != '}' && cssData[startsAt] != '{')
|
||||
startsAt--;
|
||||
|
||||
tempData.push(cssData.substring(cursor, startsAt + 1));
|
||||
cursor = nextEmpty + 2;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
stripEmpty(tempData.join('') + cssData.substring(cursor, cssData.length)) :
|
||||
cssData;
|
||||
};
|
||||
|
||||
return {
|
||||
process: function() {
|
||||
return stripEmpty(data);
|
||||
}
|
||||
};
|
||||
};
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
var Tokenizer = require('./tokenizer');
|
||||
var PropertyOptimizer = require('../properties/optimizer');
|
||||
|
||||
module.exports = function Optimizer(data, context, options) {
|
||||
var specialSelectors = {
|
||||
'*': /\-(moz|ms|o|webkit)\-/,
|
||||
'ie8': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:not|:target|:visited|:empty|:first\-of|:last|:nth|:only|:root)/
|
||||
};
|
||||
|
||||
var propertyOptimizer = new PropertyOptimizer();
|
||||
|
||||
var cleanUpSelector = function(selectors) {
|
||||
var plain = [];
|
||||
selectors = selectors.split(',');
|
||||
|
||||
for (var i = 0, l = selectors.length; i < l; i++) {
|
||||
var sel = selectors[i];
|
||||
|
||||
if (plain.indexOf(sel) == -1)
|
||||
plain.push(sel);
|
||||
}
|
||||
|
||||
return plain.sort().join(',');
|
||||
};
|
||||
|
||||
var isSpecial = function(selector) {
|
||||
return specialSelectors[options.selectorsMergeMode || '*'].test(selector);
|
||||
};
|
||||
|
||||
var removeDuplicates = function(tokens) {
|
||||
var matched = {};
|
||||
var forRemoval = [];
|
||||
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
if (typeof(tokens[i]) == 'string' || tokens[i].block)
|
||||
continue;
|
||||
|
||||
var selector = tokens[i].selector;
|
||||
var body = tokens[i].body;
|
||||
var id = body + '@' + selector;
|
||||
var alreadyMatched = matched[id];
|
||||
|
||||
if (alreadyMatched) {
|
||||
forRemoval.push(alreadyMatched[alreadyMatched.length - 1]);
|
||||
alreadyMatched.push(i);
|
||||
} else {
|
||||
matched[id] = [i];
|
||||
}
|
||||
}
|
||||
|
||||
forRemoval = forRemoval.sort(function(a, b) { return a > b ? 1 : -1; });
|
||||
for (var j = 0, n = forRemoval.length; j < n; j++) {
|
||||
tokens.splice(forRemoval[j] - j, 1);
|
||||
}
|
||||
};
|
||||
|
||||
var mergeAdjacent = function(tokens) {
|
||||
var forRemoval = [];
|
||||
var lastToken = { selector: null, body: null };
|
||||
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
var token = tokens[i];
|
||||
|
||||
if (typeof(token) == 'string' || token.block)
|
||||
continue;
|
||||
|
||||
if (token.selector == lastToken.selector) {
|
||||
var joinAt = [lastToken.body.split(';').length];
|
||||
lastToken.body = propertyOptimizer.process(lastToken.body + ';' + token.body, joinAt);
|
||||
forRemoval.push(i);
|
||||
} else if (token.body == lastToken.body && !isSpecial(token.selector) && !isSpecial(lastToken.selector)) {
|
||||
lastToken.selector = cleanUpSelector(lastToken.selector + ',' + token.selector);
|
||||
forRemoval.push(i);
|
||||
} else {
|
||||
lastToken = token;
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0, m = forRemoval.length; j < m; j++) {
|
||||
tokens.splice(forRemoval[j] - j, 1);
|
||||
}
|
||||
};
|
||||
|
||||
var reduceNonAdjacent = function(tokens) {
|
||||
var matched = {};
|
||||
var matchedMoreThanOnce = [];
|
||||
var partiallyReduced = [];
|
||||
var token, selector, selectors;
|
||||
var removeEmpty = function(value) {
|
||||
return value.length > 0 ? value : '';
|
||||
};
|
||||
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
token = tokens[i];
|
||||
selector = token.selector;
|
||||
|
||||
if (typeof(token) == 'string' || token.block)
|
||||
continue;
|
||||
|
||||
selectors = selector.split(',');
|
||||
if (selectors.length > 1)
|
||||
selectors.push(selector);
|
||||
|
||||
for (var j = 0, m = selectors.length; j < m; j++) {
|
||||
var sel = selectors[j];
|
||||
var alreadyMatched = matched[sel];
|
||||
if (alreadyMatched) {
|
||||
if (alreadyMatched.length == 1)
|
||||
matchedMoreThanOnce.push(sel);
|
||||
alreadyMatched.push(i);
|
||||
} else {
|
||||
matched[sel] = [i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matchedMoreThanOnce.forEach(function(selector) {
|
||||
var matchPositions = matched[selector];
|
||||
var bodies = [];
|
||||
var joinsAt = [];
|
||||
for (var j = 0, m = matchPositions.length; j < m; j++) {
|
||||
var body = tokens[matchPositions[j]].body;
|
||||
bodies.push(body);
|
||||
joinsAt.push((joinsAt[j - 1] || 0) + body.split(';').length);
|
||||
}
|
||||
|
||||
var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt);
|
||||
var optimizedTokens = optimizedBody.split(';');
|
||||
|
||||
var k = optimizedTokens.length - 1;
|
||||
var currentMatch = matchPositions.length - 1;
|
||||
|
||||
while (currentMatch >= 0) {
|
||||
if (bodies[currentMatch].indexOf(optimizedTokens[k]) > -1 && k > -1) {
|
||||
k -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var tokenIndex = matchPositions[currentMatch];
|
||||
var token = tokens[tokenIndex];
|
||||
var reducedBody = optimizedTokens
|
||||
.splice(k + 1)
|
||||
.filter(removeEmpty)
|
||||
.join(';');
|
||||
|
||||
if (token.selector == selector) {
|
||||
token.body = reducedBody;
|
||||
} else {
|
||||
token._partials = token._partials || [];
|
||||
token._partials.push(reducedBody);
|
||||
|
||||
if (partiallyReduced.indexOf(tokenIndex) == -1)
|
||||
partiallyReduced.push(tokenIndex);
|
||||
}
|
||||
|
||||
currentMatch -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
// process those tokens which were partially reduced
|
||||
// i.e. at least one of token's selectors saw reduction
|
||||
// if all selectors were reduced to same value we can override it
|
||||
for (i = 0, l = partiallyReduced.length; i < l; i++) {
|
||||
token = tokens[partiallyReduced[i]];
|
||||
selectors = token.selector.split(',');
|
||||
|
||||
if (token._partials.length == selectors.length && token.body != token._partials[0]) {
|
||||
var newBody = token._partials[0];
|
||||
for (var k = 1, n = token._partials.length; k < n; k++) {
|
||||
if (token._partials[k] != newBody)
|
||||
break;
|
||||
}
|
||||
|
||||
if (k == n)
|
||||
token.body = newBody;
|
||||
}
|
||||
|
||||
delete token._partials;
|
||||
}
|
||||
};
|
||||
|
||||
var optimize = function(tokens) {
|
||||
tokens = (Array.isArray(tokens) ? tokens : [tokens]);
|
||||
for (var i = 0, l = tokens.length; i < l; i++) {
|
||||
var token = tokens[i];
|
||||
|
||||
if (token.selector) {
|
||||
token.selector = cleanUpSelector(token.selector);
|
||||
token.body = propertyOptimizer.process(token.body, false);
|
||||
} else if (token.block) {
|
||||
optimize(token.body);
|
||||
}
|
||||
}
|
||||
|
||||
removeDuplicates(tokens);
|
||||
mergeAdjacent(tokens);
|
||||
reduceNonAdjacent(tokens);
|
||||
};
|
||||
|
||||
var rebuild = function(tokens) {
|
||||
return (Array.isArray(tokens) ? tokens : [tokens])
|
||||
.map(function(token) {
|
||||
if (typeof token == 'string')
|
||||
return token;
|
||||
|
||||
if (token.block)
|
||||
return token.block + '{' + rebuild(token.body) + '}';
|
||||
else
|
||||
return token.selector + '{' + token.body + '}';
|
||||
})
|
||||
.join(options.keepBreaks ? options.lineBreak : '');
|
||||
};
|
||||
|
||||
return {
|
||||
process: function() {
|
||||
var tokenized = new Tokenizer(data, context).process();
|
||||
optimize(tokenized);
|
||||
return rebuild(tokenized);
|
||||
}
|
||||
};
|
||||
};
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
module.exports = function Tokenizer(data, minifyContext) {
|
||||
var flatBlock = /^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)/;
|
||||
|
||||
var whatsNext = function(context) {
|
||||
var cursor = context.cursor;
|
||||
var mode = context.mode;
|
||||
var closest;
|
||||
|
||||
if (mode == 'body') {
|
||||
closest = data.indexOf('}', cursor);
|
||||
return closest > -1 ?
|
||||
[closest, 'bodyEnd'] :
|
||||
null;
|
||||
}
|
||||
|
||||
var nextSpecial = data.indexOf('@', cursor);
|
||||
var nextEscape = mode == 'top' ? data.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', cursor) : -1;
|
||||
var nextBodyStart = data.indexOf('{', cursor);
|
||||
var nextBodyEnd = data.indexOf('}', cursor);
|
||||
|
||||
closest = nextSpecial;
|
||||
if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
|
||||
closest = nextEscape;
|
||||
if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
|
||||
closest = nextBodyStart;
|
||||
if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
|
||||
closest = nextBodyEnd;
|
||||
|
||||
if (closest == -1)
|
||||
return;
|
||||
if (nextEscape === closest)
|
||||
return [closest, 'escape'];
|
||||
if (nextBodyStart === closest)
|
||||
return [closest, 'bodyStart'];
|
||||
if (nextBodyEnd === closest)
|
||||
return [closest, 'bodyEnd'];
|
||||
if (nextSpecial === closest)
|
||||
return [closest, 'special'];
|
||||
};
|
||||
|
||||
var tokenize = function(context) {
|
||||
var tokenized = [];
|
||||
|
||||
context = context || { cursor: 0, mode: 'top' };
|
||||
|
||||
while (true) {
|
||||
var next = whatsNext(context);
|
||||
if (!next) {
|
||||
var whatsLeft = data.substring(context.cursor);
|
||||
if (whatsLeft.length > 0) {
|
||||
tokenized.push(whatsLeft);
|
||||
context.cursor += whatsLeft.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var nextSpecial = next[0];
|
||||
var what = next[1];
|
||||
var nextEnd;
|
||||
var oldMode;
|
||||
|
||||
if (what == 'special') {
|
||||
var fragment = data.substring(nextSpecial, context.cursor + '@font-face'.length + 1);
|
||||
var isSingle = fragment.indexOf('@import') === 0 || fragment.indexOf('@charset') === 0;
|
||||
if (isSingle) {
|
||||
nextEnd = data.indexOf(';', nextSpecial + 1);
|
||||
tokenized.push(data.substring(context.cursor, nextEnd + 1));
|
||||
|
||||
context.cursor = nextEnd + 1;
|
||||
} else {
|
||||
nextEnd = data.indexOf('{', nextSpecial + 1);
|
||||
var block = data.substring(context.cursor, nextEnd).trim();
|
||||
|
||||
var isFlat = flatBlock.test(block);
|
||||
oldMode = context.mode;
|
||||
context.cursor = nextEnd + 1;
|
||||
context.mode = isFlat ? 'body' : 'block';
|
||||
var specialBody = tokenize(context);
|
||||
context.mode = oldMode;
|
||||
|
||||
tokenized.push({ block: block, body: specialBody });
|
||||
}
|
||||
} else if (what == 'escape') {
|
||||
nextEnd = data.indexOf('__', nextSpecial + 1);
|
||||
var escaped = data.substring(context.cursor, nextEnd + 2);
|
||||
tokenized.push(escaped);
|
||||
|
||||
context.cursor = nextEnd + 2;
|
||||
} else if (what == 'bodyStart') {
|
||||
var selector = data.substring(context.cursor, nextSpecial).trim();
|
||||
|
||||
oldMode = context.mode;
|
||||
context.cursor = nextSpecial + 1;
|
||||
context.mode = 'body';
|
||||
var body = tokenize(context);
|
||||
context.mode = oldMode;
|
||||
|
||||
tokenized.push({ selector: selector, body: body });
|
||||
} else if (what == 'bodyEnd') {
|
||||
// extra closing brace at the top level can be safely ignored
|
||||
if (context.mode == 'top') {
|
||||
var at = context.cursor;
|
||||
var warning = data[context.cursor] == '}' ?
|
||||
'Unexpected \'}\' in \'' + data.substring(at - 20, at + 20) + '\'. Ignoring.' :
|
||||
'Unexpected content: \'' + data.substring(at, nextSpecial + 1) + '\'. Ignoring.';
|
||||
|
||||
minifyContext.warnings.push(warning);
|
||||
context.cursor = nextSpecial + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.mode != 'block')
|
||||
tokenized = data.substring(context.cursor, nextSpecial);
|
||||
|
||||
context.cursor = nextSpecial + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return tokenized;
|
||||
};
|
||||
|
||||
return {
|
||||
process: function() {
|
||||
return tokenize();
|
||||
}
|
||||
};
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
var EscapeStore = require('./escape-store');
|
||||
|
||||
module.exports = function Comments(keepSpecialComments, keepBreaks, lineBreak) {
|
||||
var comments = new EscapeStore('COMMENT');
|
||||
|
||||
return {
|
||||
// Strip special comments (/*! ... */) by replacing them by a special marker
|
||||
// for further restoring. Plain comments are removed. It's done by scanning data using
|
||||
// String#indexOf scanning instead of regexps to speed up the process.
|
||||
escape: function(data) {
|
||||
var tempData = [];
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
nextStart = data.indexOf('/*', nextEnd);
|
||||
nextEnd = data.indexOf('*/', nextStart + 2);
|
||||
if (nextStart == -1 || nextEnd == -1)
|
||||
break;
|
||||
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
if (data[nextStart + 2] == '!') {
|
||||
// in case of special comments, replace them with a placeholder
|
||||
var comment = data.substring(nextStart, nextEnd + 2);
|
||||
var placeholder = comments.store(comment);
|
||||
tempData.push(placeholder);
|
||||
}
|
||||
cursor = nextEnd + 2;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
},
|
||||
|
||||
restore: function(data) {
|
||||
var restored = 0;
|
||||
var breakSuffix = keepBreaks ? lineBreak : '';
|
||||
|
||||
return data.replace(new RegExp(comments.placeholderPattern + '(' + lineBreak + '| )?', 'g'), function(match, placeholder) {
|
||||
restored++;
|
||||
|
||||
switch (keepSpecialComments) {
|
||||
case '*':
|
||||
return comments.restore(placeholder) + breakSuffix;
|
||||
case 1:
|
||||
case '1':
|
||||
return restored == 1 ?
|
||||
comments.restore(placeholder) + breakSuffix :
|
||||
'';
|
||||
case 0:
|
||||
case '0':
|
||||
return '';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
module.exports = function EscapeStore(placeholderRoot) {
|
||||
placeholderRoot = 'ESCAPED_' + placeholderRoot + '_CLEAN_CSS';
|
||||
|
||||
var placeholderToData = {};
|
||||
var dataToPlaceholder = {};
|
||||
var count = 0;
|
||||
var nextPlaceholder = function() {
|
||||
return '__' + placeholderRoot + (count++) + '__';
|
||||
};
|
||||
var pattern = '(__' + placeholderRoot + '\\d{1,}__)';
|
||||
|
||||
return {
|
||||
placeholderPattern: pattern,
|
||||
|
||||
placeholderRegExp: new RegExp(pattern, 'g'),
|
||||
|
||||
store: function(data) {
|
||||
var placeholder = dataToPlaceholder[data];
|
||||
if (!placeholder) {
|
||||
placeholder = nextPlaceholder();
|
||||
placeholderToData[placeholder] = data;
|
||||
dataToPlaceholder[data] = placeholder;
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
},
|
||||
|
||||
restore: function(placeholder) {
|
||||
return placeholderToData[placeholder];
|
||||
}
|
||||
};
|
||||
};
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
var EscapeStore = require('./escape-store');
|
||||
|
||||
module.exports = function Expressions() {
|
||||
var expressions = new EscapeStore('EXPRESSION');
|
||||
|
||||
var findEnd = function(data, start) {
|
||||
var end = start + 'expression'.length;
|
||||
var level = 0;
|
||||
var quoted = false;
|
||||
|
||||
while(true) {
|
||||
var next = data[end++];
|
||||
|
||||
if (quoted) {
|
||||
quoted = next != '\'' && next != '"';
|
||||
} else {
|
||||
quoted = next == '\'' || next == '"';
|
||||
|
||||
if (next == '(')
|
||||
level++;
|
||||
if (next == ')')
|
||||
level--;
|
||||
if (next == '}' && level == 1) {
|
||||
end--;
|
||||
level--;
|
||||
}
|
||||
}
|
||||
|
||||
if (level === 0 || !next)
|
||||
break;
|
||||
}
|
||||
|
||||
return end;
|
||||
};
|
||||
|
||||
return {
|
||||
// Escapes expressions by replacing them by a special
|
||||
// marker for further restoring. It's done via string scanning
|
||||
// instead of regexps to speed up the process.
|
||||
escape: function(data) {
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
var tempData = [];
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
nextStart = data.indexOf('expression(', nextEnd);
|
||||
if (nextStart == -1)
|
||||
break;
|
||||
|
||||
nextEnd = findEnd(data, nextStart);
|
||||
|
||||
var expression = data.substring(nextStart, nextEnd);
|
||||
var placeholder = expressions.store(expression);
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
tempData.push(placeholder);
|
||||
cursor = nextEnd;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
},
|
||||
|
||||
restore: function(data) {
|
||||
return data.replace(expressions.placeholderRegExp, expressions.restore);
|
||||
}
|
||||
};
|
||||
};
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
var EscapeStore = require('./escape-store');
|
||||
|
||||
module.exports = function Free() {
|
||||
var texts = new EscapeStore('FREE_TEXT');
|
||||
|
||||
var findNonEscapedEnd = function(data, matched, start) {
|
||||
var end = start;
|
||||
while (true) {
|
||||
end = data.indexOf(matched, end);
|
||||
|
||||
if (end > -1 && data[end - 1] == '\\') {
|
||||
end += 1;
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return end;
|
||||
};
|
||||
|
||||
return {
|
||||
// Strip content tags by replacing them by the a special
|
||||
// marker for further restoring. It's done via string scanning
|
||||
// instead of regexps to speed up the process.
|
||||
escape: function(data) {
|
||||
var tempData = [];
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
var matchedParenthesis = null;
|
||||
var singleParenthesis = '\'';
|
||||
var doubleParenthesis = '"';
|
||||
var dataLength = data.length;
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
var nextStartSingle = data.indexOf(singleParenthesis, nextEnd + 1);
|
||||
var nextStartDouble = data.indexOf(doubleParenthesis, nextEnd + 1);
|
||||
|
||||
if (nextStartSingle == -1)
|
||||
nextStartSingle = dataLength;
|
||||
if (nextStartDouble == -1)
|
||||
nextStartDouble = dataLength;
|
||||
|
||||
if (nextStartSingle < nextStartDouble) {
|
||||
nextStart = nextStartSingle;
|
||||
matchedParenthesis = singleParenthesis;
|
||||
} else {
|
||||
nextStart = nextStartDouble;
|
||||
matchedParenthesis = doubleParenthesis;
|
||||
}
|
||||
|
||||
if (nextStart == -1)
|
||||
break;
|
||||
|
||||
nextEnd = findNonEscapedEnd(data, matchedParenthesis, nextStart + 1);
|
||||
if (nextEnd == -1)
|
||||
break;
|
||||
|
||||
var text = data.substring(nextStart, nextEnd + 1);
|
||||
var placeholder = texts.store(text);
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
tempData.push(placeholder);
|
||||
cursor = nextEnd + 1;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
},
|
||||
|
||||
restore: function(data) {
|
||||
return data.replace(texts.placeholderRegExp, texts.restore);
|
||||
}
|
||||
};
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
var EscapeStore = require('./escape-store');
|
||||
|
||||
module.exports = function Urls() {
|
||||
var urls = new EscapeStore('URL');
|
||||
|
||||
return {
|
||||
// Strip urls by replacing them by a special
|
||||
// marker for further restoring. It's done via string scanning
|
||||
// instead of regexps to speed up the process.
|
||||
escape: function(data) {
|
||||
var nextStart = 0;
|
||||
var nextEnd = 0;
|
||||
var cursor = 0;
|
||||
var tempData = [];
|
||||
|
||||
for (; nextEnd < data.length; ) {
|
||||
nextStart = data.indexOf('url(', nextEnd);
|
||||
if (nextStart == -1)
|
||||
break;
|
||||
|
||||
nextEnd = data.indexOf(')', nextStart);
|
||||
|
||||
var url = data.substring(nextStart, nextEnd + 1);
|
||||
var placeholder = urls.store(url);
|
||||
tempData.push(data.substring(cursor, nextStart));
|
||||
tempData.push(placeholder);
|
||||
cursor = nextEnd + 1;
|
||||
}
|
||||
|
||||
return tempData.length > 0 ?
|
||||
tempData.join('') + data.substring(cursor, data.length) :
|
||||
data;
|
||||
},
|
||||
|
||||
restore: function(data) {
|
||||
return data.replace(urls.placeholderRegExp, urls.restore);
|
||||
}
|
||||
};
|
||||
};
|
||||
Generated
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
|
||||
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
|
||||
Generated
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
# Commander.js
|
||||
|
||||
The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander).
|
||||
|
||||
[](http://travis-ci.org/visionmedia/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', '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.bbq) 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.
|
||||
|
||||
## 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]
|
||||
|
||||
Options:
|
||||
|
||||
-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]
|
||||
-h, --help output usage information
|
||||
|
||||
```
|
||||
|
||||
## Coercion
|
||||
|
||||
```js
|
||||
function range(val) {
|
||||
return val.split('..').map(Number);
|
||||
}
|
||||
|
||||
function list(val) {
|
||||
return val.split(',');
|
||||
}
|
||||
|
||||
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')
|
||||
.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(' args: %j', program.args);
|
||||
```
|
||||
|
||||
## 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('../');
|
||||
|
||||
function list(val) {
|
||||
return val.split(',').map(Number);
|
||||
}
|
||||
|
||||
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');
|
||||
```
|
||||
|
||||
yielding the following help output:
|
||||
|
||||
```
|
||||
|
||||
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()
|
||||
|
||||
Output help information without exiting.
|
||||
|
||||
## .help()
|
||||
|
||||
Output help information and exit immediately.
|
||||
|
||||
## Links
|
||||
|
||||
- [API documentation](http://visionmedia.github.com/commander.js/)
|
||||
- [ascii tables](https://github.com/LearnBoost/cli-table)
|
||||
- [progress bars](https://github.com/visionmedia/node-progress)
|
||||
- [more progress bars](https://github.com/substack/node-multimeter)
|
||||
- [examples](https://github.com/visionmedia/commander.js/tree/master/examples)
|
||||
|
||||
## License
|
||||
|
||||
(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.
|
||||
Generated
Vendored
+847
@@ -0,0 +1,847 @@
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
var exists = fs.existsSync;
|
||||
var path = require('path');
|
||||
var dirname = path.dirname;
|
||||
var basename = path.basename;
|
||||
|
||||
/**
|
||||
* 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._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('*')
|
||||
* .description('deploy the given env')
|
||||
* .action(function(env){
|
||||
* console.log('deploying "%s"', env);
|
||||
* });
|
||||
*
|
||||
* program.parse(process.argv);
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} [desc]
|
||||
* @return {Command} the new command
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Command.prototype.command = function(name, desc){
|
||||
var args = name.split(/ +/);
|
||||
var cmd = new Command(args.shift());
|
||||
if (desc) cmd.description(desc);
|
||||
if (desc) this.executables = true;
|
||||
if (desc) this._execs[cmd._name] = true;
|
||||
this.commands.push(cmd);
|
||||
cmd.parseExpectedArgs(args);
|
||||
cmd.parent = this;
|
||||
if (desc) return this;
|
||||
return cmd;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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){
|
||||
switch (arg[0]) {
|
||||
case '<':
|
||||
self._args.push({ required: true, name: arg.slice(1, -1) });
|
||||
break;
|
||||
case '[':
|
||||
self._args.push({ required: false, name: arg.slice(1, -1) });
|
||||
break;
|
||||
}
|
||||
});
|
||||
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;
|
||||
this.parent.on(this._name, function(args, unknown){
|
||||
// Parse any so-far unknown options
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
// 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(this, args);
|
||||
});
|
||||
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 false
|
||||
* program.option('-C, --no-cheese', 'remove cheese');
|
||||
*
|
||||
* program.cheese
|
||||
* // => true
|
||||
*
|
||||
* --no-cheese
|
||||
* program.cheese
|
||||
* // => true
|
||||
*
|
||||
* // 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 ('function' != typeof fn) 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);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
|
||||
// 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]) 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 dir = dirname(argv[1]);
|
||||
var bin = basename(argv[1]) + '-' + args[0];
|
||||
|
||||
// check for ./<bin> first
|
||||
var local = path.join(dir, bin);
|
||||
|
||||
// run it
|
||||
args = args.slice(1);
|
||||
var proc = spawn(local, args, { stdio: 'inherit', customFds: [0, 1, 2] });
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
, index;
|
||||
|
||||
for (var i = 0, len = args.length; i < len; ++i) {
|
||||
arg = args[i];
|
||||
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 cmds = this.commands
|
||||
, len = cmds.length
|
||||
, 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);
|
||||
if ('-' == arg[0] && '-' != arg) return this.optionMissingArgument(option, arg);
|
||||
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 };
|
||||
};
|
||||
|
||||
/**
|
||||
* 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){
|
||||
console.error();
|
||||
console.error(" error: unknown option `%s'", flag);
|
||||
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(){
|
||||
console.log(str);
|
||||
process.exit(0);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the description `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 / 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 arg.required
|
||||
? '<' + arg.name + '>'
|
||||
: '[' + arg.name + ']';
|
||||
});
|
||||
|
||||
var usage = '[options'
|
||||
+ (this.commands.length ? '] [command' : '')
|
||||
+ ']'
|
||||
+ (this._args.length ? ' ' + args : '');
|
||||
|
||||
if (0 == arguments.length) return this._usage || usage;
|
||||
this._usage = str;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 '';
|
||||
return [
|
||||
''
|
||||
, ' Commands:'
|
||||
, ''
|
||||
, this.commands.map(function(cmd){
|
||||
var args = cmd._args.map(function(arg){
|
||||
return arg.required
|
||||
? '<' + arg.name + '>'
|
||||
: '[' + arg.name + ']';
|
||||
}).join(' ');
|
||||
|
||||
return pad(cmd._name
|
||||
+ (cmd.options.length
|
||||
? ' [options]'
|
||||
: '') + ' ' + args, 22)
|
||||
+ (cmd.description()
|
||||
? ' ' + cmd.description()
|
||||
: '');
|
||||
}).join('\n').replace(/^/gm, ' ')
|
||||
, ''
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return program help documentation.
|
||||
*
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Command.prototype.helpInformation = function(){
|
||||
return [
|
||||
''
|
||||
, ' Usage: ' + this._name + ' ' + this.usage()
|
||||
, '' + this.commandHelp()
|
||||
, ' Options:'
|
||||
, ''
|
||||
, '' + this.optionHelp().replace(/^/gm, ' ')
|
||||
, ''
|
||||
, ''
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Output help information for this command
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Command.prototype.outputHelp = function(){
|
||||
process.stdout.write(this.helpInformation());
|
||||
this.emit('--help');
|
||||
};
|
||||
|
||||
/**
|
||||
* Output help information and exit.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Command.prototype.help = function(){
|
||||
this.outputHelp();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "commander",
|
||||
"version": "2.0.0",
|
||||
"description": "the complete solution for node.js command-line programs",
|
||||
"keywords": [
|
||||
"command",
|
||||
"option",
|
||||
"parser",
|
||||
"prompt",
|
||||
"stdin"
|
||||
],
|
||||
"author": {
|
||||
"name": "TJ Holowaychuk",
|
||||
"email": "tj@vision-media.ca"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/visionmedia/commander.js.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"should": ">= 0.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "make test"
|
||||
},
|
||||
"main": "index",
|
||||
"engines": {
|
||||
"node": ">= 0.6.x"
|
||||
},
|
||||
"readme": "# Commander.js\n\n The complete solution for [node.js](http://nodejs.org) command-line interfaces, inspired by Ruby's [commander](https://github.com/visionmedia/commander).\n\n [](http://travis-ci.org/visionmedia/commander.js)\n\n## Installation\n\n $ npm install commander\n\n## Option parsing\n\n 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.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('commander');\n\nprogram\n .version('0.0.1')\n .option('-p, --peppers', 'Add peppers')\n .option('-P, --pineapple', 'Add pineapple')\n .option('-b, --bbq', 'Add bbq sauce')\n .option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')\n .parse(process.argv);\n\nconsole.log('you ordered a pizza with:');\nif (program.peppers) console.log(' - peppers');\nif (program.pineapple) console.log(' - pineapple');\nif (program.bbq) console.log(' - bbq');\nconsole.log(' - %s cheese', program.cheese);\n```\n\n 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.\n\n## Automated --help\n\n The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:\n\n``` \n $ ./examples/pizza --help\n\n Usage: pizza [options]\n\n Options:\n\n -V, --version output the version number\n -p, --peppers Add peppers\n -P, --pineapple Add pineapple\n -b, --bbq Add bbq sauce\n -c, --cheese <type> Add the specified type of cheese [marble]\n -h, --help output usage information\n\n```\n\n## Coercion\n\n```js\nfunction range(val) {\n return val.split('..').map(Number);\n}\n\nfunction list(val) {\n return val.split(',');\n}\n\nprogram\n .version('0.0.1')\n .usage('[options] <file ...>')\n .option('-i, --integer <n>', 'An integer argument', parseInt)\n .option('-f, --float <n>', 'A float argument', parseFloat)\n .option('-r, --range <a>..<b>', 'A range', range)\n .option('-l, --list <items>', 'A list', list)\n .option('-o, --optional [value]', 'An optional value')\n .parse(process.argv);\n\nconsole.log(' int: %j', program.integer);\nconsole.log(' float: %j', program.float);\nconsole.log(' optional: %j', program.optional);\nprogram.range = program.range || [];\nconsole.log(' range: %j..%j', program.range[0], program.range[1]);\nconsole.log(' list: %j', program.list);\nconsole.log(' args: %j', program.args);\n```\n\n## Custom help\n\n You can display arbitrary `-h, --help` information\n by listening for \"--help\". Commander will automatically\n exit once you are done so that the remainder of your program\n does not execute causing undesired behaviours, for example\n in the following executable \"stuff\" will not output when\n `--help` is used.\n\n```js\n#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar program = require('../');\n\nfunction list(val) {\n return val.split(',').map(Number);\n}\n\nprogram\n .version('0.0.1')\n .option('-f, --foo', 'enable some foo')\n .option('-b, --bar', 'enable some bar')\n .option('-B, --baz', 'enable some baz');\n\n// must be before .parse() since\n// node's emit() is immediate\n\nprogram.on('--help', function(){\n console.log(' Examples:');\n console.log('');\n console.log(' $ custom-help --help');\n console.log(' $ custom-help -h');\n console.log('');\n});\n\nprogram.parse(process.argv);\n\nconsole.log('stuff');\n```\n\nyielding the following help output:\n\n```\n\nUsage: custom-help [options]\n\nOptions:\n\n -h, --help output usage information\n -V, --version output the version number\n -f, --foo enable some foo\n -b, --bar enable some bar\n -B, --baz enable some baz\n\nExamples:\n\n $ custom-help --help\n $ custom-help -h\n\n```\n\n## .outputHelp()\n\n Output help information without exiting.\n\n## .help()\n\n Output help information and exit immediately.\n\n## Links\n\n - [API documentation](http://visionmedia.github.com/commander.js/)\n - [ascii tables](https://github.com/LearnBoost/cli-table)\n - [progress bars](https://github.com/visionmedia/node-progress)\n - [more progress bars](https://github.com/substack/node-multimeter)\n - [examples](https://github.com/visionmedia/commander.js/tree/master/examples)\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk <tj@vision-media.ca>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software withLine truncated
|
||||
"readmeFilename": "Readme.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/visionmedia/commander.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/visionmedia/commander.js",
|
||||
"_id": "commander@2.0.0",
|
||||
"_from": "commander@2.0.x"
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "clean-css",
|
||||
"version": "2.0.8",
|
||||
"author": {
|
||||
"name": "Jakub Pawlowicz",
|
||||
"email": "jakub@goalsmashers.com",
|
||||
"url": "http://twitter.com/GoalSmashers"
|
||||
},
|
||||
"description": "A well-tested CSS minifier",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"css",
|
||||
"minifier"
|
||||
],
|
||||
"homepage": "https://github.com/GoalSmashers/clean-css",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoalSmashers/clean-css.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/GoalSmashers/clean-css/issues"
|
||||
},
|
||||
"bin": {
|
||||
"cleancss": "./bin/cleancss"
|
||||
},
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"History.md",
|
||||
"index.js",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"bench": "node ./test/bench.js",
|
||||
"check": "jshint .",
|
||||
"prepublish": "jshint .",
|
||||
"test": "vows"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "2.0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jshint": "2.3.x",
|
||||
"vows": "0.7.x"
|
||||
},
|
||||
"jshintConfig": {
|
||||
"browser": false,
|
||||
"camelcase": true,
|
||||
"curly": false,
|
||||
"eqeqeq": false,
|
||||
"eqnull": true,
|
||||
"immed": true,
|
||||
"latedef": true,
|
||||
"multistr": true,
|
||||
"noarg": true,
|
||||
"node": true,
|
||||
"plusplus": false,
|
||||
"quotmark": "single",
|
||||
"strict": false,
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"unused": true
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
"readme": "[](https://badge.fury.io/js/clean-css)\n[](https://travis-ci.org/GoalSmashers/clean-css)\n[](https://david-dm.org/GoalSmashers/clean-css)\n[](https://david-dm.org/GoalSmashers/clean-css#info=devDependencies)\n\n## What is clean-css?\n\nClean-css is a fast and efficient [node.js](http://nodejs.org/) library for minifying CSS files.\n\nAccording to [tests](https://github.com/GoalSmashers/css-minification-benchmark) it is one of the best available.\n\n\n## Usage\n\n### What are the requirements?\n\n```\nnode.js 0.8.0+ (tested on CentOS, Ubuntu, OS X 10.6+, and Windows 7+)\n```\n\n### How to install clean-css?\n\n```\nnpm install clean-css\n```\n\n### How to upgrade clean-css from 1.x to 2.x?\n\n#### Command-line interface (CLI)\n\n```\nnpm update clean-css\n```\n\nor point `package.json` to version 2.x. That's it!\n\n#### Node.js module\n\nUpdate `clean-css` as for CLI above.\nThen change your JavaScript code from:\n\n```js\nvar minimized = CleanCSS.process(source, options);\n```\n\ninto\n\n```js\nvar minimized = new CleanCSS(options).minify(source);\n```\n\nAnd you are done.\n\n### How to use clean-css CLI?\n\nClean-css accepts the following command line arguments (please make sure\nyou use `<source-file>` as the very last argument to avoid potential issues):\n\n```\ncleancss [options] <source-file>\n\n-h, --help Output usage information\n-v, --version Output the version number\n-b, --keep-line-breaks Keep line breaks\n--s0 Remove all special comments, i.e. /*! comment */\n--s1 Remove all special comments but the first one\n-r, --root [root-path] A root path to which resolve absolute @import rules\n and rebase relative URLs\n-o, --output [output-file] Use [output-file] as output instead of STDOUT\n-s, --skip-import Disable @import processing\n--skip-rebase Disable URLs rebasing\n--skip-advanced Disable advanced optimizations - selector & property merging,\n reduction, etc.\n--selectors-merge-mode [ie8|*] Use `ie8` for compatibility mode, `*` for merge all (default)\n-d, --debug Shows debug information (minification time & compression efficiency)\n```\n\n#### Examples:\n\nTo minify a **public.css** file into **public-min.css** do:\n\n```\ncleancss -o public-min.css public.css\n```\n\nTo minify the same **public.css** into the standard output skip the `-o` parameter:\n\n```\ncleancss public.css\n```\n\nMore likely you would like to concatenate a couple of files.\nIf you are on a Unix-like system:\n\n```bash\ncat one.css two.css three.css | cleancss -o merged-and-minified.css\n```\n\nOn Windows:\n\n```bat\ntype one.css two.css three.css | cleancss -o merged-and-minified.css\n```\n\nOr even gzip the result at once:\n\n```bash\ncat one.css two.css three.css | cleancss | gzip -9 -c > merged-minified-and-gzipped.css.gz\n```\n\n### How to use clean-css programmatically?\n\n```js\nvar CleanCSS = require('clean-css');\nvar source = 'a{font-weight:bold;}';\nvar minimized = new CleanCSS().minify(source);\n```\n\nCleanCSS constructor accepts a hash as a parameter, i.e.,\n`new CleanCSS(options).minify(source)` with the following options available:\n\n* `keepSpecialComments` - `*` for keeping all (default), `1` for keeping first one only, `0` for removing all\n* `keepBreaks` - whether to keep line breaks (default is false)\n* `benchmark` - turns on benchmarking mode measuring time spent on cleaning up\n (run `npm run bench` to see example)\n* `root` - path to resolve absolute `@import` rules and rebase relative URLs\n* `relativeTo` - path with which to resolve relative `@import` rules and URLs\n* `processImport` - whether to process `@import` rules\n* `noRebase` - whether to skip URLs rebasing\n* `noAdvanced` - set to true to disable advanced optimizations - selector & property merging, reduction, etc.\n* `selectorsMergeMode` - `ie8` for IE8 compatibility mode, `*` for merging all (default)\n* `debug` - set to true to get minification statistics under `stats` property (see `test/custom-test.js` for examples)\n\n### What are the clean-css' dev commands?\n\nFirst clone the source, then run:\n\n* `npm run bench` for clean-css benchmarks (see [test/bench.js](https://github.com/GoalSmashers/clean-css/blob/master/test/bench.js) for details)\n* `npm run check` to check JS sources with [JSHint](https://github.com/jshint/jshint/)\n* `npm test` for the test suite\n\n## Tips & Tricks\n\n### How to preserve a comment block?\n\nUse the `/*!` notation instead of the standard one `/*`:\n\n```css\n/*!\n ImporLine truncated
|
||||
"readmeFilename": "README.md",
|
||||
"_id": "clean-css@2.0.8",
|
||||
"_from": "clean-css@~2.0.7"
|
||||
}
|
||||
Reference in new issue
Block a user