diff --git a/README.md b/README.md index cf98322..72ef363 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -rework-calc -=================== +# rework-calc [![Build Status](https://travis-ci.org/reworkcss/rework-calc.png)](https://travis-ci.org/reworkcss/rework-calc) -A `calc()` plugin for the CSS Preprocessor [rework](https://github.com/visionmedia/rework). +A [Rework](https://github.com/reworkcss/rework) plugin to support `calc()`. +Particularly useful with the [rework-vars](https://github.com/reworkcss/rework-vars) ## Installation @@ -9,9 +9,9 @@ A `calc()` plugin for the CSS Preprocessor [rework](https://github.com/visionmed npm install rework-calc ``` -## Usage +## Use -An example of how to use `rework-calc`: +As a Rework plugin: ```javascript var rework = require('rework'), @@ -20,17 +20,15 @@ var rework = require('rework'), var css = rework(cssString).use(calc).toString(); ``` -For available plugins see plugins section below. +## Supported feature -## calc() plugin +This simply add `calc()` support, a feature to do simple calculations. +This can be particularly useful with the [rework-vars](https://github.com/reworkcss/rework-vars) plugin. -Add calculations support. A feature to do simple calculations, and can be -particularly useful together with the [rework-vars](https://npmjs.org/package/rework-vars) plugin. +**Note:** When multiple units are mixed together in the same expression, the `calc()` statement +is left as is, to fallback to the CSS3 calc feature. -When multiple units are mixed together in the same expression, the calc() statement -is left as is, to fallback to the CSS3 Calc feature. - -**Example** (with rework-vars enabled as well): +**Example** (with [rework-vars](https://github.com/reworkcss/rework-vars) enabled as well): ```css :root { @@ -70,10 +68,6 @@ Make sure the dev-dependencies are installed, and then run: npm test ``` -## Contributing - -Feel free to contribute! - ## License MIT diff --git a/lib/calc.js b/lib/calc.js index 979495a..c3499ae 100644 --- a/lib/calc.js +++ b/lib/calc.js @@ -1,85 +1,44 @@ - /** - * Calculation Plugin - * - * Useful in combination with the [rework-vars](https://npmjs.org/package/rework-vars) plugin, e.g: - * - * :root { - * var-base-font-size: 16px; - * } - * body { - * font-size: var(base-font-size); - * } - * h1 { - * font-size: calc(var(base-font-size) * 2); - * } - * - * Yields: - * - * :root { - * var-base-font-size: 16px; - * } - * body { - * font-size: 16px; - * } - * h1 { - * font-size: 32px; - * } - * + * Module dependencies. */ -module.exports = function (style) { - rules(style.rules); -}; +var balanced = require('balanced-match'); +var visit = require('rework-visit'); /** - * Constants + * Constants. */ var DEFAULT_UNIT = 'px', - EXPRESSION_METHOD_NAME = 'calc', + CALC_FUNC_IDENTIFIER = 'calc', + EXPRESSION_OPT_VENDOR_PREFIX = '(\\-[a-z]+\\-)?', - EXPRESSION_METHOD_REGEXP = EXPRESSION_OPT_VENDOR_PREFIX + EXPRESSION_METHOD_NAME, + EXPRESSION_METHOD_REGEXP = EXPRESSION_OPT_VENDOR_PREFIX + CALC_FUNC_IDENTIFIER, EXPRESSION_REGEXP = '\\b' + EXPRESSION_METHOD_REGEXP + '\\('; /** - * Visit all rules - * - * @param {Array} arr Array with css rules - * @api private + * Module export. */ -function rules(arr) { - arr.forEach(function (rule) { - if (rule.rules) rules(rule.rules); - if (rule.declarations) visit(rule.declarations); - }); -} -/** - * Visit all declarations (in a rule) - * - * @param {Array} declarations - * @api private - */ -function visit(declarations) { - declarations.forEach(function (decl) { - if (!hasExpressions(decl.value)) return; +module.exports = function calc(style) { + // resolve calculations + visit(style, function (declarations, node) { + var decl; + var resolvedValue; + var value; - var expressions = getExpressionsFromValue(decl.value); + for (var i = 0; i < declarations.length; i++) { + decl = declarations[i]; + value = decl.value; - evaluateAndApplyExpressions(expressions, decl); - }); -} + // skip comments + if (decl.type !== 'declaration') continue; + // skip values that don't contain calc() functions + if (!value || value.indexOf(CALC_FUNC_IDENTIFIER + '(') === -1) continue; -/** - * Checks if a value contains an expression - * - * @param {String} value - * @returns {Boolean} - * @api private - */ -function hasExpressions(value) { - return (new RegExp(EXPRESSION_REGEXP)).exec(value); -} + decl.value = resolveValue(value); + } + }); +}; /** * Parses expressions in a value @@ -95,7 +54,7 @@ function getExpressionsFromValue(value) { // Parse value and extract expressions: for (var i = 0; i < value.length; i++) { - if (value[i] == '(' && value.slice(i - 4, i) == EXPRESSION_METHOD_NAME && !start) { + if (value[i] == '(' && value.slice(i - 4, i) == CALC_FUNC_IDENTIFIER && !start) { start = i; parentheses++; } else if (value[i] == '(' && start !== null) { @@ -118,19 +77,25 @@ function getExpressionsFromValue(value) { * @param {Object} declaration * @api private */ -function evaluateAndApplyExpressions(expressions, declaration) { - expressions.forEach(function (expression) { - var result = evaluateExpression(expression); - - if (!result) return; - - // Insert the evaluated value: - var expRegexp = new RegExp( - EXPRESSION_METHOD_REGEXP + '\\(' + - escapeExp(expression) + '\\)' - ); - declaration.value = declaration.value.replace(expRegexp, result); - }); +function resolveValue(value) { + var balancedParens = balanced('(', ')', value); + var calcStartIndex = value.indexOf(CALC_FUNC_IDENTIFIER + '('); + var calcRef = balanced('(', ')', value.substring(calcStartIndex)); + + if (!balancedParens) throw new Error('rework-calc: missing closing ")" in the value "' + value + '"'); + if (!calcRef || calcRef.body === '') throw new Error('rework-calc: calc() must contain a non-whitespace string'); + + getExpressionsFromValue(value).forEach(function (expression) { + var result = evaluateExpression(expression); + + if (!result) return; + + // Insert the evaluated value: + var expRegexp = new RegExp(EXPRESSION_METHOD_REGEXP + '\\(' + escapeExp(expression) + '\\)'); + value = value.replace(expRegexp, result); + }); + + return value } /** @@ -141,7 +106,7 @@ function evaluateAndApplyExpressions(expressions, declaration) { * @api private */ function evaluateExpression (expression) { - var originalExpression = EXPRESSION_METHOD_NAME + '(' + expression + ')'; + var originalExpression = CALC_FUNC_IDENTIFIER + '(' + expression + ')'; // Remove method names for possible nested expressions: expression = expression.replace(new RegExp(EXPRESSION_REGEXP, 'g'), '('); diff --git a/package.json b/package.json index 35bc87d..b4fe543 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,33 @@ { "name": "rework-calc", "version": "0.2.2", - "description": "Adding calc() support to rework", - "main": "index.js", + "description": "calc() support for Rework", + "dependencies": { + "balanced-match": "^0.1.0", + "rework-visit": "^1.0.0" + }, + "devDependencies": { + "mocha": "~1.15.1", + "rework": "^1.0.0", + "chai": "~1.8.1" + }, + "files": [ + "index.js" + ], "scripts": { - "test": "./node_modules/.bin/mocha -R spec" + "test": "mocha --no-colors", + "watch": "mocha --slow 30 --reporter spec --watch" }, - "repository": "git://github.com/rework/rework-calc.git", + "repository": { + "type": "git", + "url": "https://github.com/reworkcss/rework-calc.git" + }, + "license": "MIT", "keywords": [ + "css", "rework", "rework-plugins", - "css", + "calculation", "calc" - ], - "author": "Joakim Bengtson <joakim@klei.se>", - "license": "MIT", - "devDependencies": { - "mocha": "~1.15.1", - "rework": "~0.18.3", - "chai": "~1.8.1" - } + ] } diff --git a/test/calc-complex.in.css b/test/fixtures/calc-complex.in.css similarity index 100% rename from test/calc-complex.in.css rename to test/fixtures/calc-complex.in.css diff --git a/test/calc-complex.out.css b/test/fixtures/calc-complex.out.css similarity index 100% rename from test/calc-complex.out.css rename to test/fixtures/calc-complex.out.css diff --git a/test/calc-percent.in.css b/test/fixtures/calc-percent.in.css similarity index 100% rename from test/calc-percent.in.css rename to test/fixtures/calc-percent.in.css diff --git a/test/calc-percent.out.css b/test/fixtures/calc-percent.out.css similarity index 100% rename from test/calc-percent.out.css rename to test/fixtures/calc-percent.out.css diff --git a/test/calc-prefix.in.css b/test/fixtures/calc-prefix.in.css similarity index 100% rename from test/calc-prefix.in.css rename to test/fixtures/calc-prefix.in.css diff --git a/test/calc-prefix.out.css b/test/fixtures/calc-prefix.out.css similarity index 100% rename from test/calc-prefix.out.css rename to test/fixtures/calc-prefix.out.css diff --git a/test/calc.in.css b/test/fixtures/calc.in.css similarity index 100% rename from test/calc.in.css rename to test/fixtures/calc.in.css diff --git a/test/calc.out.css b/test/fixtures/calc.out.css similarity index 100% rename from test/calc.out.css rename to test/fixtures/calc.out.css diff --git a/test/fixtures/substitution-empty.css b/test/fixtures/substitution-empty.css new file mode 100644 index 0000000..1bb8b5b --- /dev/null +++ b/test/fixtures/substitution-empty.css @@ -0,0 +1,3 @@ +div { + width: calc(); +} diff --git a/test/fixtures/substitution-malformed.css b/test/fixtures/substitution-malformed.css new file mode 100644 index 0000000..00ba839 --- /dev/null +++ b/test/fixtures/substitution-malformed.css @@ -0,0 +1,4 @@ +div { + /* missing closing ')' */ + width: calc(10px - 5px; +} diff --git a/test/plugins.js b/test/plugins.js deleted file mode 100644 index a86700a..0000000 --- a/test/plugins.js +++ /dev/null @@ -1,47 +0,0 @@ -var calc = require('../index'), - rework = require('rework'), - should = require('chai').Should(), - read = require('fs').readFileSync; - -var css = { - in: function (name) { - return this._read(name, 'in'); - }, - out: function (name) { - return this._read(name, 'out'); - }, - _read: function (name, type) { - return read(__dirname + '/' + name + '.' + type + '.css', 'utf8'); - } -}; - -describe('rework-calc', function() { - it('should calculate expressions with only one unit involved', function() { - rework(css.in('calc')) - .use(calc) - .toString() - .should.equal(css.out('calc')); - }); - - it('should calculate expressions with percents correctly', function () { - rework(css.in('calc-percent')) - .use(calc) - .toString() - .should.equal(css.out('calc-percent')); - }); - - it('should use CSS3 Calc function as fallback for expressions with multiple units', function () { - rework(css.in('calc-complex')) - .use(calc) - .toString() - .should.equal(css.out('calc-complex')); - }); - - it('should handle vendor prefixed expressions', function () { - rework(css.in('calc-prefix')) - .use(calc) - .toString() - .should.equal(css.out('calc-prefix')); - }); - -}); diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..fc7ad88 --- /dev/null +++ b/test/test.js @@ -0,0 +1,49 @@ +var calc = require('../index'), + rework = require('rework'), + expect = require('chai').expect, + read = require('fs').readFileSync; + +function fixture(name){ + return read('test/fixtures/' + name + '.css', 'utf8').trim(); +} + +function compareFixtures(name){ + return expect( + rework(fixture(name + '.in')) + .use(calc) + .toString().trim() + ).to.equal(fixture(name + '.out')); +} + +describe('rework-calc', function() { + it('throws an error when a calc function is empty', function () { + var output = function () { + return rework(fixture('substitution-empty')).use(calc).toString(); + }; + expect(output).to.Throw(Error, 'rework-calc: calc() must contain a non-whitespace string'); + }); + + it('throws an error when a calc function is malformed', function () { + var output = function () { + return rework(fixture('substitution-malformed')).use(calc).toString(); + }; + expect(output).to.Throw(Error, 'rework-calc: missing closing ")" in the value "calc(10px - 5px"'); + }); + + + it('should calculate expressions with only one unit involved', function() { + compareFixtures('calc'); + }); + + it('should calculate expressions with percents correctly', function () { + compareFixtures('calc-percent'); + }); + + it('should use CSS3 Calc function as fallback for expressions with multiple units', function () { + compareFixtures('calc-complex'); + }); + + it('should handle vendor prefixed expressions', function () { + compareFixtures('calc-prefix'); + }); +});