diff --git a/.gitignore b/.gitignore index 12eb472..611cbac 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ npm-debug.log # directory creating by the Sass conversion which is done when testing grunt task # against Compass stylesheets. .sass-cache/ + +# convenient directory for storing files that should be ignored by Git. +ignore/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a5a7b55 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +### 0.6.2 (28th April 2014) + +* Updated grunt dependency to 0.4.2. +* Fixed example in README of outputting analysis to a file. +* Fixed issue causing task to crash when processing files with large amounts of duplication. + +### 0.6.1 (September 13th 2013) + +* Fixed typo in documentation example courtesy of [joshmatz](https://github.com/joshmatz). ([#9](https://github.com/peterkeating/grunt-csscss/pull/9)) + +### 0.6.0 (August 17th 2013) + +* Removed `compassConfig` option. +* Refactored task to use [`this.files` instead of `this.data`](http://dontkry.com/posts/code/2013-04-24-use-this-files.html). +* Added ability to write output from CSSCSS to a file. ([#8](https://github.com/peterkeating/grunt-csscss/issues/8)) + +### 0.5.0 (June 4th 2013) + +* Specifying which files CSSCSS should analyse now supports glob patterns. ([#7](https://github.com/peterkeating/grunt-csscss/issues/7)) +* Fixed typos in the documentation. ([#5](https://github.com/peterkeating/grunt-csscss/issues/5)) +* Added `bundleExec` option to run CSSCSS in the context of a bundle. ([#6](https://github.com/peterkeating/grunt-csscss/issues/6)) + +### 0.4.0 (April 21st 2013) + +* Added `ignoreSassMixins` option to ignore matches in Sass mixins. +* Added `require` option for loading Ruby file before running CSSCSS. +* Updated the `compassConfig` option to use the `--require` argument instead of the now deprecated `--compass-with-config` argument. +* Deprecated `compassConfig` option. + +### 0.3.1 (April 17th 2013) + +* Fixed bug with grunt task failing when CSSCSS outputs JSON even though duplicates weren't found. ([#3](https://github.com/peterkeating/grunt-csscss/issues/3)) +* Performance improvements by moving re-used argument construction outside the file loop. + +### 0.3.0 (April 16th 2013) + +* Added `failWhenDuplicates` option to fail Grunt task when CSSCSS finds rule sets with duplicate declarations. ([#2](https://github.com/peterkeating/grunt-csscss/issues/2)) + +### 0.2.0 (April 13th 2013) + +* Added `showParserErrors` option to output parser errors. +* Added `shorthand` option to determined whether shorthand should be parsed. +* Added `compassConfig` option to specify path to Compass config file. + +### 0.1.0 (April 12th 2013) + +* Initial release supporting all options for CSSCSS v1.0.0. diff --git a/README.md b/README.md index 5c44c42..587109a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Grunt task that runs [CSSCSS](http://zmoazeni.github.io/csscss/), a CSS redundan CSSCSS runs on Ruby (`v1.9.x` and up), to check Ruby is installed on your machine use `ruby -v`. To install the CSSCSS gem run `gem install csscss` command, this will grab the latest version. -Currently grunt-csscss handles all the features for CSSCSS that are available with version **1.2.0**. +Currently grunt-csscss handles all the features for CSSCSS that are available with version **1.3.1**. ## Getting Started @@ -27,6 +27,13 @@ grunt.loadNpmTasks('grunt-csscss'); ``` ## Options +### bundleExec + +Type: `Boolean` +Default: `false` + +Run CSSCSS with [bundle exec](http://gembundler.com/). + ### colorize Type: `Boolean` @@ -43,14 +50,6 @@ Enables Compass extensions when parsing Sass. *[Compass](http://compass-style.org/) must be installed in order to use this option.* -### compassConfig - -Type: `String` - -Enables Compass extension and specifies path to a config file. - -*[Compass](http://compass-style.org/) must be installed in order to use this option.* - ### failWhenDuplicates Type: `Boolean` @@ -64,6 +63,15 @@ Type: `String` Comma seperated list of CSS properties that should be ignored when finding matches. +### ignoreSassMixins + +Type: `Boolean` +Default: `false` + +Flag indicating whether matches in Sass mixins should be ignored. + +*This is an experimental feature.* + ### ignoreSelectors Type: `String` @@ -98,6 +106,14 @@ Default: `false` Outputs parser errors. +### require + +Type: `string` + +Path to a ruby file that is loaded before running CSSCSS. + +*[Compass](http://compass-style.org/) must be installed in order to use this option.* + ### verbose Type: `Boolean` @@ -137,7 +153,7 @@ csscss: { ### Specifying Options -Example of using the [options](https://github.com/peterkeating/grunt-csscss/edit/master/README.md#options). +Example of using the [options](https://github.com/peterkeating/grunt-csscss#options). ```js csscss: { @@ -163,7 +179,7 @@ Example of using CSSCSS to analyze Sass files that are converted using Compass. ```js csscss: { options: { - compassConfig: 'my/config/file/config.rb' + require: 'my/config/file.rb' }, dist: { src: ['sass/style.scss'] @@ -171,21 +187,38 @@ csscss: { } ``` -## Release History +### Specifying Files with Glob Pattern -### 0.3.0 (April 16th 2013) +Example of using a glob pattern to target many files that should be analysed by CSSCSS. The example below will analyse all the files in the `css` directory that have an extension of `.css`. -* Added `failWhenDuplicates` option to fail Grunt task when CSSCSS finds rule sets with duplicate declarations. ([#2](https://github.com/peterkeating/grunt-csscss/issues/2)) +```js +csscss: { + dist: { + src: ['css/*.css'] + } +} +``` -### 0.2.0 (April 13th 2013) +### Outputting analysis to a file. -* Added `showParserErrors` option to output parser errors. -* Added `shorthand` option to determined whether shorthand should be parsed. -* Added `compassConfig` option to specify path to Compass config file. +Example of using the Grunt task to output the analysis from CSSCSS to a local file. The example below will create (if necessary) `output.json` file, where it will write the output from CSSCSS. If the `outputJson` property is set to true (like in the example below) then valid JSON will be written to the file. -### 0.1.0 (April 12th 2013) +```js +csscss: { + dist: { + options: { + outputJson: true + }, + files: { + 'output.json': ['css/style.css'] + } + } +} +``` + +## Release History -* Initial release supporting all options for CSSCSS v1.0.0. +See [CHANGELOG](https://github.com/peterkeating/grunt-csscss/blob/master/CHANGELOG.md). ## Credits diff --git a/gruntfile.js b/gruntfile.js index 01e2bc4..69499a1 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -26,19 +26,58 @@ module.exports = function(grunt) { verbose: true, outputJson: false, minMatch: 2, - compass: true, ignoreProperties: 'padding', ignoreSelectors: '.rule-a', showParserErrors: true, shorthand: false, - compassConfig: 'test/example/compass/config.rb', failWhenDuplicates: false }, dist: { - src: ['test/example/style.css', 'test/example/shorthand.css', 'test/example/compass/sass/screen.scss'] + src: ['test/example/style.css', 'test/example/shorthand.css'] + }, + + /** + * Tests running the grunt task in a Compass setup. + */ + compass: { + options: { + require: 'test/example/compass/config.rb', + ignoreSassMixins: true + }, + src: ['test/example/compass/sass/screen.scss'] + }, + + /** + * Tests that the grunt task doesn't fail when outputting JSON and the failWhenDuplicates + * option is set to true. + */ + outputJsonTest: { + options: { + failWhenDuplicates: true, + outputJson: true + }, + src: ['test/example/no-duplicates.css'] + }, + + /** + * Tests that CSSCSS handles a glob pattern. + */ + globbing: { + src: ['test/example/*.css'] + }, + + /** + * Tests outputting CSSCSS findings to a file. + */ + outputToFile: { + options: { + outputJson: true + }, + files: { + 'ignore/output.json': ['test/example/style.css', 'test/example/shorthand.css'] + } } } - }); /** diff --git a/package.json b/package.json index 4595981..cba9f46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grunt-csscss", - "version": "0.3.0", + "version": "0.6.2", "description": "Grunt task that executes CSSCSS.", "homepage": "https://github.com/peterkeating/grunt-csscss", "author": { @@ -29,7 +29,7 @@ "test": "grunt" }, "devDependencies": { - "grunt": "~0.4.1", + "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.4.3" }, "keywords": [ diff --git a/tasks/csscss.js b/tasks/csscss.js index 30800b1..2126400 100644 --- a/tasks/csscss.js +++ b/tasks/csscss.js @@ -7,6 +7,123 @@ module.exports = function(grunt) { grunt.registerMultiTask('csscss', 'CSSCSS redundancy analyzer.', function() { + + /** + * Asynchronously loops through the provided files and puts them through the + * CSSCSS tool. + */ + function analyze(files) { + + /** + * Loops over all the files specified in the src array. + */ + grunt.util.async.forEachSeries(files, function(file, next) { + + /** + * Stores the output that is to be written to the file.dest, if one is + * specified. + */ + var output = options.outputJson ? '{' : ''; + + /** + * Loops over all the matches files in the src in case there are multiple. + */ + grunt.util.async.forEachSeries(file.src, function (fileToBeAnalyzed, innerNext) { + + /** + * adds the file path as the final argument, this goes into a new array so + * the file doesn't get used in the next iteration. + */ + var cmdArgs = args.concat([fileToBeAnalyzed]), + + /** + * Stores the output from CSSCSS. + */ + childOutput = ''; + + /** + * Outputs the file that is being analysed. + */ + grunt.log.writeln(fileToBeAnalyzed); + + output += (options.outputJson) ? '\n\t"' + fileToBeAnalyzed + '": ' : fileToBeAnalyzed + '\n'; + + grunt.verbose.writeln('csscss ' + cmdArgs.join(' ')); + + /** + * Executes the csscss command. + */ + var child = grunt.util.spawn({ + cmd: cmdArgs.shift(), + args: cmdArgs + }, function(error, result, code) { + if (code === 127) { + return grunt.warn('Ruby and csscss have to be installed and in your PATH for this task to run.'); + } + + grunt.log.writeln(childOutput); + + /** + * Substring removes the carriage return from CSSCSS. + */ + output += (options.outputJson) ? childOutput.substring(0, childOutput.length - 2) + ',' : childOutput; + + /** + * When outputting JSON from CSSCSS an empty array will be outputted, this + * should be ignored and shouldn't cause the grunt task to fail if no other + * duplicates are found. + */ + if (!(options.outputJson && JSON.parse(childOutput).length === 0)) { + hasDuplicates = true; + } + + innerNext(error); + }); + + /** + * Displays the output and error streams via the parent process. + */ + child.stdout.on('data', function(buf) { + childOutput += String(buf); + }); + + child.stderr.on('data', function(buf) { + grunt.log.writeln(String(buf)); + }); + + }, function () { + + /** + * Removes the final comma so valid JSON is outputted. Also the end brace + * for the JSON object is included. + */ + if (options.outputJson) { + output = output.substring(0, output.length - 1); + output += '\n}'; + } + + /** + * Write the output to the destination file if one was specified. + */ + if (file.dest) { + grunt.file.write(file.dest, output); + } + next(); + }); + }, function () { + + /** + * If instructed to fail when a match happens and matches found lets fail + * the grunt build. + */ + if (options.failWhenDuplicates && hasDuplicates) { + grunt.fail.warn("Failed due to matches found by CSSCSS."); + } + + done(); + }); + } + /** * Retrieves defined options. */ @@ -17,139 +134,107 @@ module.exports = function(grunt) { * Flag for whether any duplicates were found by CSSCSS. */ var hasDuplicates = false; - var done = this.async(); - grunt.util.async.forEachSeries(this.data.src, function(f, next) { - var args = []; - - /** - * Outputs the rules that have been matched. - */ - if (options.verbose) { - args.push('-v'); - } - - /** - * Flag indicating whether the output should be colorized. This is true by - * default. - */ - if (options.colorize === false) { - args.push('--no-color'); - } - - /** - * Enables Compass extensions when parsing Sass files. This argument is not - * set if the compassConfig property has been defined. - */ - if (options.compass && !options.compassConfig) { - args.push('--compass'); - } - - /** - * Enables Compass extensions when parsing Sass files and specifies the path - * to the config file. - */ - if (options.compassConfig) { - args.push('--compass-with-config'); - args.push(options.compassConfig); - } - - /** - * Returns analysis in JSON format. - */ - if (options.outputJson) { - args.push('-j'); - } - - /** - * Sets the minimum number of rules before a match is found. - */ - if (options.minMatch) { - args.push('-n'); - args.push(options.minMatch); - } + /** + * Contains the arguments that are to be passed to CSSCSS. + */ + var args = []; - /** - * Sets which properties should be ignored. - */ - if (options.ignoreProperties) { - args.push('--ignore-properties'); - args.push(options.ignoreProperties); - } + args.push('csscss'); - /** - * Sets which selectors should be ignored. - */ - if (options.ignoreSelectors) { - args.push('--ignore-selectors'); - args.push(options.ignoreSelectors); - } + /** + * Flag indicating whether CSSCSS should be run in the context of a bundle. + */ + if (options.bundleExec) { + args.unshift('bundle', 'exec'); + } - /** - * Sets whether parser errors should be outputted. - */ - if (options.showParserErrors) { - args.push('--show-parser-errors'); - } + /** + * Outputs the rules that have been matched. + */ + if (options.verbose) { + args.push('-v'); + } - /** - * Sets whether shorthand should be matched. - */ - if (options.shorthand === false) { - args.push('--no-match-shorthand'); - } + /** + * Flag indicating whether the output should be colorized. This is true by + * default. + */ + if (options.colorize === false) { + args.push('--no-color'); + } - /** - * adds path to file, to be analysed, as an argument. - */ - args.push(f); + /** + * Enables Compass extensions when parsing Sass files. This argument is not + * set if the compassConfig property has been defined. + */ + if (options.compass && !options.compassConfig) { + args.push('--compass'); + } - /** - * Outputs the file that is being analysed. - */ - grunt.log.writeln(f); - grunt.verbose.writeln('csscss ' + args.join(' ')); + /** + * Enables Compass extensions when parsing Sass files and specifies the path + * to the config file. + */ + if (options.require) { + args.push('--compass'); + args.push('--require'); + args.push(options.require); + } - /** - * Executes the csscss command. - */ - var child = grunt.util.spawn({ - cmd: 'csscss', - args: args - }, function(error, result, code) { - if (code === 127) { - return grunt.warn('Ruby and csscss have to be installed and in your PATH for this task to run.'); - } + /** + * Returns analysis in JSON format. + */ + if (options.outputJson) { + args.push('-j'); + } - next(error); - }); + /** + * Sets the minimum number of rules before a match is found. + */ + if (options.minMatch) { + args.push('-n'); + args.push(options.minMatch); + } - /** - * displays the output and error streams via the parent process. - */ - child.stdout.on('data', function(buf) { - grunt.log.writeln(String(buf)); - hasDuplicates = true; - }); + /** + * Sets which properties should be ignored. + */ + if (options.ignoreProperties) { + args.push('--ignore-properties'); + args.push(options.ignoreProperties); + } - child.stderr.on('data', function(buf) { - grunt.log.writeln(String(buf)); - }); + /** + * Sets whether Sass mixins should be ignored. + */ + if (options.ignoreSassMixins) { + args.push('--ignore-sass-mixins'); + } - }, function () { + /** + * Sets which selectors should be ignored. + */ + if (options.ignoreSelectors) { + args.push('--ignore-selectors'); + args.push(options.ignoreSelectors); + } - /** - * If instructed to fail when a match happens and matches found lets fail - * the grunt build. - */ - if (options.failWhenDuplicates && hasDuplicates) { - grunt.fail.warn("Failed due to matches found by CSSCSS."); - } + /** + * Sets whether parser errors should be outputted. + */ + if (options.showParserErrors) { + args.push('--show-parser-errors'); + } - done(); - }); + /** + * Sets whether shorthand should be matched. + */ + if (options.shorthand === false) { + args.push('--no-match-shorthand'); + } + analyze(this.files); }); - }; diff --git a/test/example/no-duplicates.css b/test/example/no-duplicates.css new file mode 100644 index 0000000..4762979 --- /dev/null +++ b/test/example/no-duplicates.css @@ -0,0 +1,16 @@ +.rule-a { + font-size: 1em; + margin: 1em; + padding: 0; +} + +.rule-b { + background-color: #ff0; + margin: 0; + padding: 0; +} + +.rule-c { + border: 1px solid #f0f; + margin: 14px; +}