gradient-parser
Advanced tools
| name: Node.js CI | ||
| on: | ||
| push: | ||
| branches: | ||
| - master # Trigger workflow on pushes to the master branch | ||
| pull_request: # Trigger workflow on any pull request | ||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| matrix: | ||
| node-version: [16.x, 18.x, 20.x] # Test on multiple Node.js versions | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Use Node.js ${{ matrix.node-version }} | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: ${{ matrix.node-version }} | ||
| cache: 'npm' # Enable caching for npm dependencies | ||
| - name: Install dependencies | ||
| run: npm install | ||
| - name: Run tests | ||
| run: npm test |
+93
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const esbuild = require('esbuild'); | ||
| // Determine which build to run based on command-line args | ||
| const args = process.argv.slice(2); | ||
| const buildNode = args.includes('--node') || !args.length; | ||
| const buildWeb = args.includes('--web') || !args.length; | ||
| const minify = args.includes('--minify'); | ||
| // Helper to ensure directories exist | ||
| const ensureDir = (dirPath) => { | ||
| if (!fs.existsSync(dirPath)) { | ||
| fs.mkdirSync(dirPath, { recursive: true }); | ||
| } | ||
| }; | ||
| // Helper function for minification | ||
| const minifyCode = async (code, outputFile) => { | ||
| const result = await esbuild.transform(code, { | ||
| minify: true, | ||
| target: 'es6', | ||
| }); | ||
| fs.writeFileSync(outputFile, result.code); | ||
| return result.code; | ||
| }; | ||
| // Make sure the build directory exists | ||
| ensureDir(path.join(__dirname, 'build')); | ||
| // Async function to handle potential async operations like minification | ||
| async function build() { | ||
| // Build for Node.js | ||
| if (buildNode) { | ||
| console.log('Building node.js bundle...'); | ||
| // Read the source files | ||
| const stringifyContent = fs.readFileSync(path.join(__dirname, 'lib', 'stringify.js'), 'utf8'); | ||
| const parserContent = fs.readFileSync(path.join(__dirname, 'lib', 'parser.js'), 'utf8'); | ||
| const indexContent = fs.readFileSync(path.join(__dirname, 'index.js'), 'utf8'); | ||
| // Concatenate the files | ||
| const nodeBundle = `${stringifyContent}\n${parserContent}\n${indexContent}`; | ||
| // Write the bundle | ||
| const outputPath = path.join(__dirname, 'build', 'node.js'); | ||
| if (minify) { | ||
| await minifyCode(nodeBundle, outputPath); | ||
| console.log('✓ Node.js bundle created and minified successfully'); | ||
| } else { | ||
| fs.writeFileSync(outputPath, nodeBundle); | ||
| console.log('✓ Node.js bundle created successfully'); | ||
| } | ||
| } | ||
| // Build for Web | ||
| if (buildWeb) { | ||
| console.log('Building web.js bundle...'); | ||
| // Read the source files | ||
| const webifyContent = fs.readFileSync(path.join(__dirname, 'webify.js'), 'utf8'); | ||
| const parserContent = fs.readFileSync(path.join(__dirname, 'lib', 'parser.js'), 'utf8'); | ||
| const stringifyContent = fs.readFileSync(path.join(__dirname, 'lib', 'stringify.js'), 'utf8'); | ||
| // Concatenate the files | ||
| const webBundle = `${webifyContent}\n${parserContent}\n${stringifyContent}`; | ||
| // Write the bundle | ||
| const outputPath = path.join(__dirname, 'build', 'web.js'); | ||
| if (minify) { | ||
| await minifyCode(webBundle, outputPath); | ||
| console.log('✓ Web bundle created and minified successfully'); | ||
| } else { | ||
| fs.writeFileSync(outputPath, webBundle); | ||
| console.log('✓ Web bundle created successfully'); | ||
| } | ||
| } | ||
| // If no arguments were provided, we built both bundles | ||
| if (!args.length) { | ||
| console.log('✓ All bundles built successfully'); | ||
| } | ||
| } | ||
| // Execute the build | ||
| console.log(`Building with options: ${minify ? 'minify' : 'no minify'} ${buildNode ? 'node' : ''} ${buildWeb ? 'web' : ''}`) | ||
| build().catch(err => { | ||
| console.error('Build failed:', err); | ||
| process.exit(1); | ||
| }); |
+103
-5
@@ -109,2 +109,14 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved. | ||
| 'visit_hsl': function(node) { | ||
| return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node); | ||
| }, | ||
| 'visit_hsla': function(node) { | ||
| return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node); | ||
| }, | ||
| 'visit_var': function(node) { | ||
| return visitor.visit_color('var(' + node.value + ')', node); | ||
| }, | ||
| 'visit_color': function(resultColor, node) { | ||
@@ -142,2 +154,9 @@ var result = resultColor, | ||
| 'visit_object': function(obj) { | ||
| if (obj.width && obj.height) { | ||
| return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height); | ||
| } | ||
| return ''; | ||
| }, | ||
| 'visit': function(element) { | ||
@@ -151,2 +170,4 @@ if (!element) { | ||
| return visitor.visit_array(element, result); | ||
| } else if (typeof element === 'object' && !element.type) { | ||
| return visitor.visit_object(element); | ||
| } else if (element.type) { | ||
@@ -184,3 +205,3 @@ var nodeVisitor = visitor['visit_' + element.type]; | ||
| repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i, | ||
| extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/, | ||
@@ -192,2 +213,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i, | ||
| angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/, | ||
| radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/, | ||
| startCall: /^\(/, | ||
@@ -200,3 +222,7 @@ endCall: /^\)/, | ||
| rgbaColor: /^rgba/i, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/ | ||
| varColor: /^var/i, | ||
| variableName: /^(--[a-zA-Z0-9-,\s\#]+)/, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/, | ||
| hslColor: /^hsl/i, | ||
| hslaColor: /^hsla/i, | ||
| }; | ||
@@ -294,3 +320,4 @@ | ||
| function matchAngle() { | ||
| return match('angular', tokens.angleValue, 1); | ||
| return match('angular', tokens.angleValue, 1) || | ||
| match('angular', tokens.radianValue, 1); | ||
| } | ||
@@ -363,3 +390,3 @@ | ||
| if (ellipse) { | ||
| ellipse.style = matchDistance() || matchExtentKeyword(); | ||
| ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword(); | ||
| } | ||
@@ -436,4 +463,7 @@ | ||
| return matchHexColor() || | ||
| matchHSLAColor() || | ||
| matchHSLColor() || | ||
| matchRGBAColor() || | ||
| matchRGBColor() || | ||
| matchVarColor() || | ||
| matchLiteralColor(); | ||
@@ -468,2 +498,66 @@ } | ||
| function matchVarColor() { | ||
| return matchCall(tokens.varColor, function () { | ||
| return { | ||
| type: 'var', | ||
| value: matchVariableName() | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLColor() { | ||
| return matchCall(tokens.hslColor, function() { | ||
| // Check for percentage before trying to parse the hue | ||
| var lookahead = scan(tokens.percentageValue); | ||
| if (lookahead) { | ||
| error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage'); | ||
| } | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSL'); | ||
| } | ||
| return { | ||
| type: 'hsl', | ||
| value: [hue, sat, light] | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLAColor() { | ||
| return matchCall(tokens.hslaColor, function() { | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| var alpha = matchNumber(); | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSLA'); | ||
| } | ||
| return { | ||
| type: 'hsla', | ||
| value: [hue, sat, light, alpha] | ||
| }; | ||
| }); | ||
| } | ||
| function matchPercentage() { | ||
| var captures = scan(tokens.percentageValue); | ||
| return captures ? captures[1] : null; | ||
| } | ||
| function matchVariableName() { | ||
| return scan(tokens.variableName)[1]; | ||
| } | ||
| function matchNumber() { | ||
@@ -520,3 +614,7 @@ return scan(tokens.number)[1]; | ||
| return function(code) { | ||
| input = code.toString(); | ||
| input = code.toString().trim(); | ||
| // Remove trailing semicolon if present | ||
| if (input.endsWith(';')) { | ||
| input = input.slice(0, -1); | ||
| } | ||
| return getAST(); | ||
@@ -523,0 +621,0 @@ }; |
+103
-5
@@ -16,3 +16,3 @@ var GradientParser = (window.GradientParser || {}); | ||
| repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i, | ||
| extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/, | ||
@@ -24,2 +24,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i, | ||
| angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/, | ||
| radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/, | ||
| startCall: /^\(/, | ||
@@ -32,3 +33,7 @@ endCall: /^\)/, | ||
| rgbaColor: /^rgba/i, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/ | ||
| varColor: /^var/i, | ||
| variableName: /^(--[a-zA-Z0-9-,\s\#]+)/, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/, | ||
| hslColor: /^hsl/i, | ||
| hslaColor: /^hsla/i, | ||
| }; | ||
@@ -126,3 +131,4 @@ | ||
| function matchAngle() { | ||
| return match('angular', tokens.angleValue, 1); | ||
| return match('angular', tokens.angleValue, 1) || | ||
| match('angular', tokens.radianValue, 1); | ||
| } | ||
@@ -195,3 +201,3 @@ | ||
| if (ellipse) { | ||
| ellipse.style = matchDistance() || matchExtentKeyword(); | ||
| ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword(); | ||
| } | ||
@@ -268,4 +274,7 @@ | ||
| return matchHexColor() || | ||
| matchHSLAColor() || | ||
| matchHSLColor() || | ||
| matchRGBAColor() || | ||
| matchRGBColor() || | ||
| matchVarColor() || | ||
| matchLiteralColor(); | ||
@@ -300,2 +309,66 @@ } | ||
| function matchVarColor() { | ||
| return matchCall(tokens.varColor, function () { | ||
| return { | ||
| type: 'var', | ||
| value: matchVariableName() | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLColor() { | ||
| return matchCall(tokens.hslColor, function() { | ||
| // Check for percentage before trying to parse the hue | ||
| var lookahead = scan(tokens.percentageValue); | ||
| if (lookahead) { | ||
| error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage'); | ||
| } | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSL'); | ||
| } | ||
| return { | ||
| type: 'hsl', | ||
| value: [hue, sat, light] | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLAColor() { | ||
| return matchCall(tokens.hslaColor, function() { | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| var alpha = matchNumber(); | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSLA'); | ||
| } | ||
| return { | ||
| type: 'hsla', | ||
| value: [hue, sat, light, alpha] | ||
| }; | ||
| }); | ||
| } | ||
| function matchPercentage() { | ||
| var captures = scan(tokens.percentageValue); | ||
| return captures ? captures[1] : null; | ||
| } | ||
| function matchVariableName() { | ||
| return scan(tokens.variableName)[1]; | ||
| } | ||
| function matchNumber() { | ||
@@ -352,3 +425,7 @@ return scan(tokens.number)[1]; | ||
| return function(code) { | ||
| input = code.toString(); | ||
| input = code.toString().trim(); | ||
| // Remove trailing semicolon if present | ||
| if (input.endsWith(';')) { | ||
| input = input.slice(0, -1); | ||
| } | ||
| return getAST(); | ||
@@ -466,2 +543,14 @@ }; | ||
| 'visit_hsl': function(node) { | ||
| return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node); | ||
| }, | ||
| 'visit_hsla': function(node) { | ||
| return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node); | ||
| }, | ||
| 'visit_var': function(node) { | ||
| return visitor.visit_color('var(' + node.value + ')', node); | ||
| }, | ||
| 'visit_color': function(resultColor, node) { | ||
@@ -499,2 +588,9 @@ var result = resultColor, | ||
| 'visit_object': function(obj) { | ||
| if (obj.width && obj.height) { | ||
| return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height); | ||
| } | ||
| return ''; | ||
| }, | ||
| 'visit': function(element) { | ||
@@ -508,2 +604,4 @@ if (!element) { | ||
| return visitor.visit_array(element, result); | ||
| } else if (typeof element === 'object' && !element.type) { | ||
| return visitor.visit_object(element); | ||
| } else if (element.type) { | ||
@@ -510,0 +608,0 @@ var nodeVisitor = visitor['visit_' + element.type]; |
+82
-5
@@ -14,3 +14,3 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved. | ||
| repeatingRadialGradient: /^(\-(webkit|o|ms|moz)\-)?(repeating\-radial\-gradient)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|left|right|top|bottom)/i, | ||
| sideOrCorner: /^to (left (top|bottom)|right (top|bottom)|top (left|right)|bottom (left|right)|left|right|top|bottom)/i, | ||
| extentKeywords: /^(closest\-side|closest\-corner|farthest\-side|farthest\-corner|contain|cover)/, | ||
@@ -22,2 +22,3 @@ positionKeywords: /^(left|center|right|top|bottom)/i, | ||
| angleValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))deg/, | ||
| radianValue: /^(-?(([0-9]*\.[0-9]+)|([0-9]+\.?)))rad/, | ||
| startCall: /^\(/, | ||
@@ -30,3 +31,7 @@ endCall: /^\)/, | ||
| rgbaColor: /^rgba/i, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/ | ||
| varColor: /^var/i, | ||
| variableName: /^(--[a-zA-Z0-9-,\s\#]+)/, | ||
| number: /^(([0-9]*\.[0-9]+)|([0-9]+\.?))/, | ||
| hslColor: /^hsl/i, | ||
| hslaColor: /^hsla/i, | ||
| }; | ||
@@ -124,3 +129,4 @@ | ||
| function matchAngle() { | ||
| return match('angular', tokens.angleValue, 1); | ||
| return match('angular', tokens.angleValue, 1) || | ||
| match('angular', tokens.radianValue, 1); | ||
| } | ||
@@ -193,3 +199,3 @@ | ||
| if (ellipse) { | ||
| ellipse.style = matchDistance() || matchExtentKeyword(); | ||
| ellipse.style = matchPositioning() || matchDistance() || matchExtentKeyword(); | ||
| } | ||
@@ -266,4 +272,7 @@ | ||
| return matchHexColor() || | ||
| matchHSLAColor() || | ||
| matchHSLColor() || | ||
| matchRGBAColor() || | ||
| matchRGBColor() || | ||
| matchVarColor() || | ||
| matchLiteralColor(); | ||
@@ -298,2 +307,66 @@ } | ||
| function matchVarColor() { | ||
| return matchCall(tokens.varColor, function () { | ||
| return { | ||
| type: 'var', | ||
| value: matchVariableName() | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLColor() { | ||
| return matchCall(tokens.hslColor, function() { | ||
| // Check for percentage before trying to parse the hue | ||
| var lookahead = scan(tokens.percentageValue); | ||
| if (lookahead) { | ||
| error('HSL hue value must be a number in degrees (0-360) or normalized (-360 to 360), not a percentage'); | ||
| } | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSL'); | ||
| } | ||
| return { | ||
| type: 'hsl', | ||
| value: [hue, sat, light] | ||
| }; | ||
| }); | ||
| } | ||
| function matchHSLAColor() { | ||
| return matchCall(tokens.hslaColor, function() { | ||
| var hue = matchNumber(); | ||
| scan(tokens.comma); | ||
| var captures = scan(tokens.percentageValue); | ||
| var sat = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| captures = scan(tokens.percentageValue); | ||
| var light = captures ? captures[1] : null; | ||
| scan(tokens.comma); | ||
| var alpha = matchNumber(); | ||
| if (!sat || !light) { | ||
| error('Expected percentage value for saturation and lightness in HSLA'); | ||
| } | ||
| return { | ||
| type: 'hsla', | ||
| value: [hue, sat, light, alpha] | ||
| }; | ||
| }); | ||
| } | ||
| function matchPercentage() { | ||
| var captures = scan(tokens.percentageValue); | ||
| return captures ? captures[1] : null; | ||
| } | ||
| function matchVariableName() { | ||
| return scan(tokens.variableName)[1]; | ||
| } | ||
| function matchNumber() { | ||
@@ -350,5 +423,9 @@ return scan(tokens.number)[1]; | ||
| return function(code) { | ||
| input = code.toString(); | ||
| input = code.toString().trim(); | ||
| // Remove trailing semicolon if present | ||
| if (input.endsWith(';')) { | ||
| input = input.slice(0, -1); | ||
| } | ||
| return getAST(); | ||
| }; | ||
| })(); |
+21
-0
@@ -109,2 +109,14 @@ // Copyright (c) 2014 Rafael Caricio. All rights reserved. | ||
| 'visit_hsl': function(node) { | ||
| return visitor.visit_color('hsl(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%)', node); | ||
| }, | ||
| 'visit_hsla': function(node) { | ||
| return visitor.visit_color('hsla(' + node.value[0] + ', ' + node.value[1] + '%, ' + node.value[2] + '%, ' + node.value[3] + ')', node); | ||
| }, | ||
| 'visit_var': function(node) { | ||
| return visitor.visit_color('var(' + node.value + ')', node); | ||
| }, | ||
| 'visit_color': function(resultColor, node) { | ||
@@ -142,2 +154,9 @@ var result = resultColor, | ||
| 'visit_object': function(obj) { | ||
| if (obj.width && obj.height) { | ||
| return visitor.visit(obj.width) + ' ' + visitor.visit(obj.height); | ||
| } | ||
| return ''; | ||
| }, | ||
| 'visit': function(element) { | ||
@@ -151,2 +170,4 @@ if (!element) { | ||
| return visitor.visit_array(element, result); | ||
| } else if (typeof element === 'object' && !element.type) { | ||
| return visitor.visit_object(element); | ||
| } else if (element.type) { | ||
@@ -153,0 +174,0 @@ var nodeVisitor = visitor['visit_' + element.type]; |
+11
-10
| { | ||
| "name": "gradient-parser", | ||
| "version": "1.0.2", | ||
| "version": "1.1.0", | ||
| "description": "Parse CSS3 gradient definitions and return an AST.", | ||
@@ -28,3 +28,9 @@ "author": { | ||
| "scripts": { | ||
| "test": "grunt" | ||
| "test": "mocha spec/**/*.js", | ||
| "build": "node build.js", | ||
| "build:node": "node build.js --node", | ||
| "build:web": "node build.js --web", | ||
| "build:minify": "node build.js --minify", | ||
| "start": "python -m SimpleHTTPServer 3000", | ||
| "prepublish": "npm run build" | ||
| }, | ||
@@ -37,10 +43,5 @@ "keywords": [ | ||
| "devDependencies": { | ||
| "expect.js": "*", | ||
| "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": "*" | ||
| "expect.js": "^0.3.1", | ||
| "esbuild": "^0.20.2", | ||
| "mocha": "^10.3.0" | ||
| }, | ||
@@ -47,0 +48,0 @@ "engines": { |
+66
-4
| # Gradient Parser | ||
| [](https://badge.fury.io/js/gradient-parser) | ||
| ## About | ||
@@ -39,6 +41,66 @@ | ||
| ## Install Choices | ||
| - `bower install gradient-parser` | ||
| - [download the zip](https://github.com/rafaelcaricio/gradient-parser/archive/master.zip) | ||
| ## Installation | ||
| Install via npm: | ||
| ```bash | ||
| npm install gradient-parser | ||
| ``` | ||
| Import in Node.js: | ||
| ```javascript | ||
| const gradient = require('gradient-parser'); | ||
| ``` | ||
| For browser usage: | ||
| ```html | ||
| <script src="node_modules/gradient-parser/build/web.js"></script> | ||
| ``` | ||
| Or [download the zip](https://github.com/rafaelcaricio/gradient-parser/archive/master.zip) | ||
| ## Development | ||
| ### Project Status | ||
| Gradient-parser has been modernized (as of v1.1.0): | ||
| - Removed Bower support in favor of npm exclusively | ||
| - Replaced Grunt with a modern build system using esbuild | ||
| - Added minification support | ||
| - Updated dependencies to specific versions | ||
| - Improved npm scripts for better developer experience | ||
| ### Build | ||
| Gradient-parser uses a modern build system with esbuild for building and minification. | ||
| ```bash | ||
| # Build both Node.js and web bundles | ||
| npm run build | ||
| # Build only Node.js bundle | ||
| npm run build:node | ||
| # Build only web bundle | ||
| npm run build:web | ||
| # Build minified bundles | ||
| npm run build:minify | ||
| ``` | ||
| ### Testing | ||
| Run the test suite with: | ||
| ```bash | ||
| npm test | ||
| ``` | ||
| ### Starting a local server | ||
| You can run a simple HTTP server for development: | ||
| ```bash | ||
| npm start | ||
| ``` | ||
| ## API | ||
@@ -134,3 +196,3 @@ | ||
| Copyright (c) 2014 Rafael Caricio rafael@caricio.com | ||
| Copyright (c) 2014-2025 Rafael Caricio rafael@caricio.com | ||
@@ -137,0 +199,0 @@ Permission is hereby granted, free of charge, to any person obtaining |
+170
-2
@@ -137,3 +137,8 @@ 'use strict'; | ||
| {type: 'angular', unparsedValue: '-145deg', value: '-145'}, | ||
| {type: 'directional', unparsedValue: 'to left top', value: 'left top'} | ||
| {type: 'angular', unparsedValue: '1rad', value: '1'}, | ||
| {type: 'directional', unparsedValue: 'to left top', value: 'left top'}, | ||
| {type: 'directional', unparsedValue: 'to top left', value: 'top left'}, | ||
| {type: 'directional', unparsedValue: 'to top right', value: 'top right'}, | ||
| {type: 'directional', unparsedValue: 'to bottom left', value: 'bottom left'}, | ||
| {type: 'directional', unparsedValue: 'to bottom right', value: 'bottom right'} | ||
| ].forEach(function(orientation) { | ||
@@ -159,3 +164,7 @@ describe('parse orientation ' + orientation.type, function() { | ||
| {type: 'rgb', unparsedValue: 'rgb(243, 226, 195)', value: ['243', '226', '195']}, | ||
| {type: 'rgba', unparsedValue: 'rgba(243, 226, 195)', value: ['243', '226', '195']} | ||
| {type: 'rgba', unparsedValue: 'rgba(243, 226, 195)', value: ['243', '226', '195']}, | ||
| {type: 'hsl', unparsedValue: 'hsl(120, 60%, 70%)', value: ['120', '60', '70']}, | ||
| {type: 'hsla', unparsedValue: 'hsla(120, 60%, 70%, 0.3)', value: ['120', '60', '70', '0.3']}, | ||
| {type: 'hsla', unparsedValue: 'hsla(240, 100%, 50%, 0.5)', value: ['240', '100', '50', '0.5']}, | ||
| {type: 'var', unparsedValue: 'var(--color-red)', value: '--color-red'}, | ||
| ].forEach(function(color) { | ||
@@ -174,2 +183,22 @@ describe('parse color type '+ color.type, function() { | ||
| }); | ||
| describe('error cases for HSL/HSLA', function() { | ||
| it('should error on missing percentage for saturation', function() { | ||
| expect(function() { | ||
| gradients.parse('linear-gradient(hsl(120, 60, 70%))'); | ||
| }).to.throwException(/Expected percentage value/); | ||
| }); | ||
| it('should error on missing percentage for lightness', function() { | ||
| expect(function() { | ||
| gradients.parse('linear-gradient(hsl(120, 60%, 70))'); | ||
| }).to.throwException(/Expected percentage value/); | ||
| }); | ||
| it('should error on percentage for hue', function() { | ||
| expect(function() { | ||
| gradients.parse('linear-gradient(hsl(120%, 60%, 70%))'); | ||
| }).to.throwException(/HSL hue value must be a number in degrees \(0-360\) or normalized \(-360 to 360\), not a percentage/); | ||
| }); | ||
| }); | ||
| }); | ||
@@ -214,4 +243,143 @@ | ||
| }); | ||
| it('should parse ellipse with dimensions and position', function() { | ||
| const gradient = 'repeating-radial-gradient(ellipse 40px 134px at 50% 96%, rgb(0, 165, 223) 0%, rgb(62, 20, 123) 6.6%)'; | ||
| const ast = gradients.parse(gradient); | ||
| expect(ast[0].type).to.equal('repeating-radial-gradient'); | ||
| expect(ast[0].orientation[0].type).to.equal('shape'); | ||
| expect(ast[0].orientation[0].value).to.equal('ellipse'); | ||
| // Check the style (size dimensions) | ||
| expect(ast[0].orientation[0].style.type).to.equal('position'); | ||
| expect(ast[0].orientation[0].style.value.x.type).to.equal('px'); | ||
| expect(ast[0].orientation[0].style.value.x.value).to.equal('40'); | ||
| expect(ast[0].orientation[0].style.value.y.type).to.equal('px'); | ||
| expect(ast[0].orientation[0].style.value.y.value).to.equal('134'); | ||
| // Check the position | ||
| expect(ast[0].orientation[0].at.type).to.equal('position'); | ||
| expect(ast[0].orientation[0].at.value.x.type).to.equal('%'); | ||
| expect(ast[0].orientation[0].at.value.x.value).to.equal('50'); | ||
| expect(ast[0].orientation[0].at.value.y.type).to.equal('%'); | ||
| expect(ast[0].orientation[0].at.value.y.value).to.equal('96'); | ||
| // Check the color stops | ||
| expect(ast[0].colorStops).to.have.length(2); | ||
| expect(ast[0].colorStops[0].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[0].value).to.eql(['0', '165', '223']); | ||
| expect(ast[0].colorStops[0].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[0].length.value).to.equal('0'); | ||
| expect(ast[0].colorStops[1].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[1].value).to.eql(['62', '20', '123']); | ||
| expect(ast[0].colorStops[1].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[1].length.value).to.equal('6.6'); | ||
| }); | ||
| it('should parse full Pride flag gradient', function() { | ||
| const gradient = 'repeating-radial-gradient(ellipse 40px 134px at 50% 96%,rgb(0, 165, 223) 0%,rgb(62, 20, 123) 6.6%,rgb(226, 0, 121) 13.2%,rgb(223, 19, 44) 18.8%,rgb(243, 239, 21) 24.1%,rgb(0, 152, 71) 33.3%)'; | ||
| const ast = gradients.parse(gradient); | ||
| expect(ast[0].type).to.equal('repeating-radial-gradient'); | ||
| expect(ast[0].orientation[0].type).to.equal('shape'); | ||
| expect(ast[0].orientation[0].value).to.equal('ellipse'); | ||
| // Check dimensions and position | ||
| expect(ast[0].orientation[0].style.type).to.equal('position'); | ||
| expect(ast[0].orientation[0].at.type).to.equal('position'); | ||
| // Verify all color stops are present (Pride flag colors) | ||
| expect(ast[0].colorStops).to.have.length(6); | ||
| // Check the first and last color stops | ||
| expect(ast[0].colorStops[0].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[0].value).to.eql(['0', '165', '223']); | ||
| expect(ast[0].colorStops[5].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[5].value).to.eql(['0', '152', '71']); | ||
| expect(ast[0].colorStops[5].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[5].length.value).to.equal('33.3'); | ||
| }); | ||
| }); | ||
| describe('parse gradient strings with trailing semicolons', function() { | ||
| it('should parse linear-gradient with trailing semicolon', function() { | ||
| const inputWithSemicolon = 'linear-gradient(red, blue);'; | ||
| const ast = gradients.parse(inputWithSemicolon); | ||
| expect(ast[0].type).to.equal('linear-gradient'); | ||
| expect(ast[0].colorStops).to.have.length(2); | ||
| expect(ast[0].colorStops[0].value).to.equal('red'); | ||
| expect(ast[0].colorStops[1].value).to.equal('blue'); | ||
| }); | ||
| it('should parse radial-gradient with trailing semicolon', function() { | ||
| const inputWithSemicolon = 'radial-gradient(circle, red, blue);'; | ||
| const ast = gradients.parse(inputWithSemicolon); | ||
| expect(ast[0].type).to.equal('radial-gradient'); | ||
| expect(ast[0].colorStops).to.have.length(2); | ||
| expect(ast[0].colorStops[0].value).to.equal('red'); | ||
| expect(ast[0].colorStops[1].value).to.equal('blue'); | ||
| }); | ||
| it('should parse complex gradient with trailing semicolon', function() { | ||
| const inputWithSemicolon = 'linear-gradient(to right, rgb(22, 234, 174) 0%, rgb(126, 32, 207) 100%);'; | ||
| const ast = gradients.parse(inputWithSemicolon); | ||
| expect(ast[0].type).to.equal('linear-gradient'); | ||
| expect(ast[0].orientation.type).to.equal('directional'); | ||
| expect(ast[0].orientation.value).to.equal('right'); | ||
| expect(ast[0].colorStops).to.have.length(2); | ||
| expect(ast[0].colorStops[0].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[0].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[0].length.value).to.equal('0'); | ||
| expect(ast[0].colorStops[1].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[1].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[1].length.value).to.equal('100'); | ||
| }); | ||
| }); | ||
| describe('parse gradient strings', function() { | ||
| it('should parse repeating linear gradient with bottom right direction', function() { | ||
| const gradient = 'repeating-linear-gradient(to bottom right,rgb(254, 158, 150) 0%,rgb(172, 79, 115) 100%)'; | ||
| const ast = gradients.parse(gradient); | ||
| expect(ast[0].type).to.equal('repeating-linear-gradient'); | ||
| expect(ast[0].orientation.type).to.equal('directional'); | ||
| expect(ast[0].orientation.value).to.equal('bottom right'); | ||
| expect(ast[0].colorStops).to.have.length(2); | ||
| expect(ast[0].colorStops[0].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[0].value).to.eql(['254', '158', '150']); | ||
| expect(ast[0].colorStops[0].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[0].length.value).to.equal('0'); | ||
| expect(ast[0].colorStops[1].type).to.equal('rgb'); | ||
| expect(ast[0].colorStops[1].value).to.eql(['172', '79', '115']); | ||
| expect(ast[0].colorStops[1].length.type).to.equal('%'); | ||
| expect(ast[0].colorStops[1].length.value).to.equal('100'); | ||
| }); | ||
| describe('parse different color formats', function() { | ||
| const testGradients = [ | ||
| 'linear-gradient(red, blue)', | ||
| 'linear-gradient(red, #00f)', | ||
| 'linear-gradient(red, #0000ff)', | ||
| 'linear-gradient(red, rgb(0, 0, 255))', | ||
| 'linear-gradient(red, rgba(0, 0, 255, 1))', | ||
| 'linear-gradient(red, hsl(240, 50%, 100%))', | ||
| 'linear-gradient(red, hsla(240, 50%, 100%, 1))' | ||
| ]; | ||
| testGradients.forEach(function(gradient) { | ||
| it('should parse ' + gradient, function() { | ||
| const result = gradients.parse(gradient); | ||
| expect(result[0].type).to.equal('linear-gradient'); | ||
| expect(result[0].colorStops).to.have.length(2); | ||
| expect(result[0].colorStops[0].type).to.equal('literal'); | ||
| expect(result[0].colorStops[0].value).to.equal('red'); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
@@ -33,2 +33,7 @@ 'use strict'; | ||
| it('should serialize gradient with var', function() { | ||
| var gradientDef = 'linear-gradient(var(--color-black), white)'; | ||
| expect(gradients.stringify(gradients.parse(gradientDef))).to.equal(gradientDef); | ||
| }); | ||
| it('should serialize gradient with rgb', function() { | ||
@@ -35,0 +40,0 @@ var gradientDef = 'linear-gradient(rgb(1, 2, 3), white)'; |
-29
| { | ||
| "name": "gradient-parser", | ||
| "version": "1.0.0", | ||
| "main": "build/web.js", | ||
| "ignore": [ | ||
| ".editorconfig", | ||
| ".gitattributes", | ||
| ".gitignore", | ||
| ".jshintrc", | ||
| ".npmignore", | ||
| ".travis.yml", | ||
| ".umd", | ||
| "package.json", | ||
| "index.html", | ||
| "Makefile", | ||
| "build/node.js", | ||
| "README.md", | ||
| "*.js", | ||
| "lib/*", | ||
| "spec/*", | ||
| "LICENSE" | ||
| ], | ||
| "dependencies": { | ||
| }, | ||
| "devDependencies": { | ||
| } | ||
| } |
-36
| 'use strict'; | ||
| module.exports = function (grunt) { | ||
| var config = { | ||
| app: '.', | ||
| dist: '.' | ||
| }; | ||
| grunt.initConfig({ | ||
| config: config, | ||
| mochaTest: { | ||
| test: { | ||
| options: { | ||
| reporter: 'spec' | ||
| }, | ||
| src: ['spec/**/*.js'] | ||
| } | ||
| }, | ||
| concat: { | ||
| release: { | ||
| files: { | ||
| 'build/node.js': ['lib/stringify.js', 'lib/parser.js', 'index.js'], | ||
| 'build/web.js': ['webify.js', 'lib/parser.js', 'lib/stringify.js'] | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| grunt.loadNpmTasks('grunt-mocha-test'); | ||
| grunt.loadNpmTasks('grunt-contrib-concat'); | ||
| grunt.registerTask('default', [ | ||
| 'concat', | ||
| 'mochaTest' | ||
| ]); | ||
| }; |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
75641
40.26%3
-62.5%1975
27.75%215
40.52%1
Infinity%