diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8ab60dc --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + python -m SimpleHTTPServer 3000 diff --git a/bower.json b/bower.json index d4b43e8..ce7fb79 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "gradient-parser", - "version": "0.1.0", - "main": "index.js", + "version": "0.1.1", + "main": "build/web.js", "ignore": [ ".editorconfig", ".gitattributes", diff --git a/build/node.js b/build/node.js new file mode 100644 index 0000000..b54734a --- /dev/null +++ b/build/node.js @@ -0,0 +1,339 @@ +// Copyright (c) 2014 Rafael Caricio. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var GradientParser = {}; + +GradientParser.parse = (function() { + + var tokens = { + linearGradient: /^linear\-gradient/i, + repeatingLinearGradient: /^repeating\-linear\-gradient/i, + radialGradient: /^radial\-gradient/i, + repeatingRadialGradient: /^repeating\-radial\-gradient/i, + sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i, + extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/, + positionKeywords: /^(left|center|right|top|bottom)/i, + pixelValue: /^([0-9]+)px/, + percentageValue: /^([0-9]+)\%/, + emValue: /^([0-9]+)em/, + angleValue: /^([0-9]+)deg/, + startCall: /^\(/, + endCall: /^\)/, + comma: /^,/, + hexColor: /^\#([0-9a-fA-F]+)/, + literalColor: /^([a-zA-Z]+)/, + rgbColor: /^rgb/i, + rgbaColor: /^rgba/i, + number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/ + }; + + var input = ''; + + function error(msg) { + var err = new Error(input + ': ' + msg); + err.source = input; + throw err; + } + + function getAST() { + var ast = matchListDefinitions(); + + if (input.length > 0) { + error('Invalid input not EOF'); + } + + return ast; + } + + function matchListDefinitions() { + return matchListing(matchDefinition); + } + + function matchDefinition() { + return matchGradient( + 'linear-gradient', + tokens.linearGradient, + matchLinearOrientation) || + + matchGradient( + 'repeating-linear-gradient', + tokens.repeatingLinearGradient, + matchLinearOrientation) || + + matchGradient( + 'radial-gradient', + tokens.radialGradient, + matchListRadialOrientations) || + + matchGradient( + 'repeating-radial-gradient', + tokens.repeatingRadialGradient, + matchListRadialOrientations); + } + + function matchGradient(gradientType, pattern, orientationMatcher) { + return matchCall(pattern, function(captures) { + + var orientation = orientationMatcher(); + if (orientation) { + if (!scan(tokens.comma)) { + error('Missing comma before color stops'); + } + } + + return { + type: gradientType, + orientation: orientation, + colorStops: matchListing(matchColorStop) + }; + }); + } + + function matchCall(pattern, callback) { + var captures = scan(pattern); + + if (captures) { + if (!scan(tokens.startCall)) { + error('Missing ('); + } + + result = callback(captures); + + if (!scan(tokens.endCall)) { + error('Missing )'); + } + + return result; + } + } + + function matchLinearOrientation() { + return matchSideOrCorner() || + matchAngle(); + } + + function matchSideOrCorner() { + return match('directional', tokens.sideOrCorner, 1); + } + + function matchAngle() { + return match('angular', tokens.angleValue, 1); + } + + function matchListRadialOrientations() { + var radialOrientations, + radialOrientation = matchRadialOrientation(), + lookaheadCache; + + if (radialOrientation) { + radialOrientations = []; + radialOrientations.push(radialOrientation); + + lookaheadCache = input; + if (scan(tokens.comma)) { + radialOrientation = matchRadialOrientation(); + if (radialOrientation) { + radialOrientations.push(radialOrientation); + } else { + input = lookaheadCache; + } + } + } + + return radialOrientations; + } + + function matchRadialOrientation() { + var radialType = matchCircle() || + matchEllipse(); + + if (radialType) { + radialType.at = matchAtPosition(); + } else { + var defaultPosition = matchPositioning(); + if (defaultPosition) { + radialType = { + type: 'default-radial', + at: defaultPosition + }; + } + } + + return radialType; + } + + function matchCircle() { + var circle = match('shape', /^(circle)/i, 0); + + if (circle) { + circle.style = matchLength() || matchExtentKeyword(); + } + + return circle; + } + + function matchEllipse() { + var ellipse = match('shape', /^(ellipse)/i, 0); + + if (ellipse) { + ellipse.style = matchDistance() || matchExtentKeyword(); + } + + return ellipse; + } + + function matchExtentKeyword() { + return match('extent-keyword', tokens.extentKeywords, 1); + } + + function matchAtPosition() { + if (match('position', /^at/, 0)) { + var positioning = matchPositioning(); + + if (!positioning) { + error('Missing positioning value'); + } + + return positioning; + } + } + + function matchPositioning() { + var location = matchCoordinates(); + + if (location.x || location.y) { + return { + type: 'position', + value: location + }; + } + } + + function matchCoordinates() { + return { + x: matchDistance(), + y: matchDistance() + }; + } + + function matchListing(matcher) { + var captures = matcher(), + result = []; + + if (captures) { + result.push(captures); + while (scan(tokens.comma)) { + captures = matcher(); + if (captures) { + result.push(captures); + } else { + error('One extra comma'); + } + } + } + + return result; + } + + function matchColorStop() { + var color = matchColor(); + + if (!color) { + error('Expected color definition'); + } + + color.length = matchDistance(); + return color; + } + + function matchColor() { + return matchHexColor() || + matchRGBAColor() || + matchRGBColor() || + matchLiteralColor(); + } + + function matchLiteralColor() { + return match('literal', tokens.literalColor, 0); + } + + function matchHexColor() { + return match('hex', tokens.hexColor, 1); + } + + function matchRGBColor() { + return matchCall(tokens.rgbColor, function() { + return { + type: 'rgb', + value: matchListing(matchNumber) + }; + }); + } + + function matchRGBAColor() { + return matchCall(tokens.rgbaColor, function() { + return { + type: 'rgba', + value: matchListing(matchNumber) + }; + }); + } + + function matchNumber() { + return scan(tokens.number)[1]; + } + + function matchDistance() { + return match('%', tokens.percentageValue, 1) || + matchPositionKeyword() || + matchLength(); + } + + function matchPositionKeyword() { + return match('position-keyword', tokens.positionKeywords, 1); + } + + function matchLength() { + return match('px', tokens.pixelValue, 1) || + match('em', tokens.emValue, 1); + } + + function match(type, pattern, captureIndex) { + var captures = scan(pattern); + if (captures) { + return { + type: type, + value: captures[captureIndex] + }; + } + } + + function scan(regexp) { + var captures, + blankCaptures; + + blankCaptures = /^[\n\r\t\s]+/.exec(input); + if (blankCaptures) { + consume(blankCaptures[0].length); + } + + captures = regexp.exec(input); + if (captures) { + consume(captures[0].length); + } + + return captures; + } + + function consume(size) { + input = input.substr(size); + } + + return function(code) { + input = code.toString(); + return getAST(); + }; +})(); + +exports.parse = (GradientParser || {}).parse; diff --git a/build/web.js b/build/web.js new file mode 100644 index 0000000..64417f1 --- /dev/null +++ b/build/web.js @@ -0,0 +1,337 @@ +// Copyright (c) 2014 Rafael Caricio. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var GradientParser = {}; + +GradientParser.parse = (function() { + + var tokens = { + linearGradient: /^linear\-gradient/i, + repeatingLinearGradient: /^repeating\-linear\-gradient/i, + radialGradient: /^radial\-gradient/i, + repeatingRadialGradient: /^repeating\-radial\-gradient/i, + sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i, + extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/, + positionKeywords: /^(left|center|right|top|bottom)/i, + pixelValue: /^([0-9]+)px/, + percentageValue: /^([0-9]+)\%/, + emValue: /^([0-9]+)em/, + angleValue: /^([0-9]+)deg/, + startCall: /^\(/, + endCall: /^\)/, + comma: /^,/, + hexColor: /^\#([0-9a-fA-F]+)/, + literalColor: /^([a-zA-Z]+)/, + rgbColor: /^rgb/i, + rgbaColor: /^rgba/i, + number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/ + }; + + var input = ''; + + function error(msg) { + var err = new Error(input + ': ' + msg); + err.source = input; + throw err; + } + + function getAST() { + var ast = matchListDefinitions(); + + if (input.length > 0) { + error('Invalid input not EOF'); + } + + return ast; + } + + function matchListDefinitions() { + return matchListing(matchDefinition); + } + + function matchDefinition() { + return matchGradient( + 'linear-gradient', + tokens.linearGradient, + matchLinearOrientation) || + + matchGradient( + 'repeating-linear-gradient', + tokens.repeatingLinearGradient, + matchLinearOrientation) || + + matchGradient( + 'radial-gradient', + tokens.radialGradient, + matchListRadialOrientations) || + + matchGradient( + 'repeating-radial-gradient', + tokens.repeatingRadialGradient, + matchListRadialOrientations); + } + + function matchGradient(gradientType, pattern, orientationMatcher) { + return matchCall(pattern, function(captures) { + + var orientation = orientationMatcher(); + if (orientation) { + if (!scan(tokens.comma)) { + error('Missing comma before color stops'); + } + } + + return { + type: gradientType, + orientation: orientation, + colorStops: matchListing(matchColorStop) + }; + }); + } + + function matchCall(pattern, callback) { + var captures = scan(pattern); + + if (captures) { + if (!scan(tokens.startCall)) { + error('Missing ('); + } + + result = callback(captures); + + if (!scan(tokens.endCall)) { + error('Missing )'); + } + + return result; + } + } + + function matchLinearOrientation() { + return matchSideOrCorner() || + matchAngle(); + } + + function matchSideOrCorner() { + return match('directional', tokens.sideOrCorner, 1); + } + + function matchAngle() { + return match('angular', tokens.angleValue, 1); + } + + function matchListRadialOrientations() { + var radialOrientations, + radialOrientation = matchRadialOrientation(), + lookaheadCache; + + if (radialOrientation) { + radialOrientations = []; + radialOrientations.push(radialOrientation); + + lookaheadCache = input; + if (scan(tokens.comma)) { + radialOrientation = matchRadialOrientation(); + if (radialOrientation) { + radialOrientations.push(radialOrientation); + } else { + input = lookaheadCache; + } + } + } + + return radialOrientations; + } + + function matchRadialOrientation() { + var radialType = matchCircle() || + matchEllipse(); + + if (radialType) { + radialType.at = matchAtPosition(); + } else { + var defaultPosition = matchPositioning(); + if (defaultPosition) { + radialType = { + type: 'default-radial', + at: defaultPosition + }; + } + } + + return radialType; + } + + function matchCircle() { + var circle = match('shape', /^(circle)/i, 0); + + if (circle) { + circle.style = matchLength() || matchExtentKeyword(); + } + + return circle; + } + + function matchEllipse() { + var ellipse = match('shape', /^(ellipse)/i, 0); + + if (ellipse) { + ellipse.style = matchDistance() || matchExtentKeyword(); + } + + return ellipse; + } + + function matchExtentKeyword() { + return match('extent-keyword', tokens.extentKeywords, 1); + } + + function matchAtPosition() { + if (match('position', /^at/, 0)) { + var positioning = matchPositioning(); + + if (!positioning) { + error('Missing positioning value'); + } + + return positioning; + } + } + + function matchPositioning() { + var location = matchCoordinates(); + + if (location.x || location.y) { + return { + type: 'position', + value: location + }; + } + } + + function matchCoordinates() { + return { + x: matchDistance(), + y: matchDistance() + }; + } + + function matchListing(matcher) { + var captures = matcher(), + result = []; + + if (captures) { + result.push(captures); + while (scan(tokens.comma)) { + captures = matcher(); + if (captures) { + result.push(captures); + } else { + error('One extra comma'); + } + } + } + + return result; + } + + function matchColorStop() { + var color = matchColor(); + + if (!color) { + error('Expected color definition'); + } + + color.length = matchDistance(); + return color; + } + + function matchColor() { + return matchHexColor() || + matchRGBAColor() || + matchRGBColor() || + matchLiteralColor(); + } + + function matchLiteralColor() { + return match('literal', tokens.literalColor, 0); + } + + function matchHexColor() { + return match('hex', tokens.hexColor, 1); + } + + function matchRGBColor() { + return matchCall(tokens.rgbColor, function() { + return { + type: 'rgb', + value: matchListing(matchNumber) + }; + }); + } + + function matchRGBAColor() { + return matchCall(tokens.rgbaColor, function() { + return { + type: 'rgba', + value: matchListing(matchNumber) + }; + }); + } + + function matchNumber() { + return scan(tokens.number)[1]; + } + + function matchDistance() { + return match('%', tokens.percentageValue, 1) || + matchPositionKeyword() || + matchLength(); + } + + function matchPositionKeyword() { + return match('position-keyword', tokens.positionKeywords, 1); + } + + function matchLength() { + return match('px', tokens.pixelValue, 1) || + match('em', tokens.emValue, 1); + } + + function match(type, pattern, captureIndex) { + var captures = scan(pattern); + if (captures) { + return { + type: type, + value: captures[captureIndex] + }; + } + } + + function scan(regexp) { + var captures, + blankCaptures; + + blankCaptures = /^[\n\r\t\s]+/.exec(input); + if (blankCaptures) { + consume(blankCaptures[0].length); + } + + captures = regexp.exec(input); + if (captures) { + consume(captures[0].length); + } + + return captures; + } + + function consume(size) { + input = input.substr(size); + } + + return function(code) { + input = code.toString(); + return getAST(); + }; +})(); diff --git a/gruntfile.js b/gruntfile.js index b8eda72..c9e35cf 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -15,12 +15,22 @@ module.exports = function (grunt) { }, src: ['spec/**/*.js'] } + }, + concat: { + release: { + files: { + 'build/node.js': ['lib/parser.js', 'index.js'], + 'build/web.js': ['lib/parser.js'] + } + } } }); grunt.loadNpmTasks('grunt-mocha-test'); + grunt.loadNpmTasks('grunt-contrib-concat'); grunt.registerTask('default', [ + 'concat', 'mochaTest' ]); }; diff --git a/index.html b/index.html new file mode 100644 index 0000000..87b528c --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + +
+ + + + + + diff --git a/index.js b/index.js index 51cfecd..e007297 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -exports.parse = require('./lib/parser'); +exports.parse = (GradientParser || {}).parse; diff --git a/lib/parser.js b/lib/parser.js index 53c091b..64417f1 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -2,26 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -module.exports = (function() { +var GradientParser = {}; - var types = { - gradients: [ - 'linear-gradient', - 'repeating-linear-gradient', - 'radial-gradient', - 'repeating-radial-gradient' - ], - colors: [ - 'hex', - 'rgb', - 'rgba', - 'hsl', - 'literal' - ], - metrics: [ - 'px' - ] - }; +GradientParser.parse = (function() { var tokens = { linearGradient: /^linear\-gradient/i, diff --git a/package.json b/package.json index 60212b8..4ada9bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradient-parser", - "version": "0.1.0", + "version": "0.1.1", "description": "Parse CSS3 gradient definitions and return an AST.", "author": { "name": "Rafael Carcicio", @@ -23,7 +23,7 @@ "type": "git", "url": "git://github.com/rafaelcaricio/gradient-parser.git" }, - "main": "index.js", + "main": "build/node.js", "scripts": { "test": "grunt" }, @@ -35,7 +35,9 @@ "dependencies": {}, "devDependencies": { "grunt": "*", + "grunt-browserify": "^3.0.1", "grunt-complexity": "*", + "grunt-contrib-concat": "^0.5.0", "grunt-contrib-uglify": "^0.5.1", "grunt-mocha-test": "^0.11.0", "mocha": "*" diff --git a/spec/parser.spec.js b/spec/parser.spec.js index add899a..c785361 100644 --- a/spec/parser.spec.js +++ b/spec/parser.spec.js @@ -1,35 +1,8 @@ 'use strict'; var expect = require('expect.js'); -var gradients = require('index'); +var gradients = require('build/node'); -// [ -// { -// type: 'linear-gradient', -// orientation: { -// type: 'directional', -// value: 'right' -// }, -// colorStops: [ -// { -// type: 'literal', -// value: 'transparent', -// length: { -// value: '10', -// type: 'px' -// } -// }, -// { -// type: 'hex', -// value: 'c2c2c2', -// length: { -// value: '10', -// type: 'px' -// } -// } -// ] -// } -// ] describe('gradient-parser.js', function () { var ast,