abachrome 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/CLA.md +45 -0
- data/CODE-OF-CONDUCT.md +9 -0
- data/LICENSE +19 -0
- data/README.md +212 -6
- data/SECURITY.md +94 -0
- data/abachrome.gemspec +36 -0
- data/devenv.lock +8 -8
- data/lib/abachrome/color.rb +9 -1
- data/lib/abachrome/parsers/css.rb +437 -0
- data/lib/abachrome/version.rb +1 -1
- data/lib/abachrome.rb +10 -1
- data/security/assesments/2025-10-12-SECURITY_ASSESSMENT.md +53 -0
- data/security/vex.json +21 -0
- metadata +9 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51acc020e50052f3aacc9d0e847809f62d84111fad86feb8889e3dc324d17385
|
4
|
+
data.tar.gz: 8649cab609da6a1c70dd94f9e77adf01b3fee441b8586d73dbcb2822c15eb7f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23b5d9b1aa20997c618adf7a337d7f85d533cb937d6d5005f4030dcb96a0b4f88a106ad9c311ecf5a7139a3b09a0ee54502133e6586a78e2983056b41cad2648
|
7
|
+
data.tar.gz: 5a9850d26a1e83b6b803de4862d5267499dfe92c752deb54abfef992c80239f83bf8af9f3095c445dcc61bc7a2bd40daa6afaaf443fd997d521789db467aca50
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.4] - 2025-10-12
|
4
|
+
|
5
|
+
- Added `Color#to_hex` method for convenient hex string output
|
6
|
+
- Added `Abachrome.parse` method for parsing CSS color strings
|
7
|
+
|
8
|
+
## [0.1.3] - 2025-10-12
|
9
|
+
|
10
|
+
- Enhanced color parsing with support for additional CSS formats
|
11
|
+
- Improved documentation with comprehensive user guides
|
12
|
+
- Added LICENSE file for compliance
|
13
|
+
- Security contact information added to README
|
14
|
+
|
3
15
|
## [0.1.0] - 2025-02-09
|
4
16
|
|
5
|
-
- Initial release
|
17
|
+
- Initial release with core color manipulation functionality
|
18
|
+
- Support for RGB, OKLAB, OKLCH color spaces
|
19
|
+
- Basic color conversion and gamut mapping
|
20
|
+
- CSS color output formatting
|
21
|
+
- High-precision calculations using BigDecimal
|
data/CLA.md
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Contributor License Agreement
|
2
|
+
|
3
|
+
This Contributor License Agreement ("Agreement") is entered into between the undersigned contributor ("Contributor") and Durable Programming, LLC ("Company").
|
4
|
+
|
5
|
+
## Recitals
|
6
|
+
|
7
|
+
WHEREAS, Contributor desires to contribute to the abachrome project ("Project");
|
8
|
+
|
9
|
+
WHEREAS, Company desires to receive contributions to the Project;
|
10
|
+
|
11
|
+
NOW, THEREFORE, in consideration of the mutual promises contained herein, the parties agree as follows:
|
12
|
+
|
13
|
+
## Grant of Copyright
|
14
|
+
|
15
|
+
Contributor hereby assigns to Company all right, title, and interest in and to the copyright in the contributions made by Contributor to the Project, including all modifications, enhancements, and derivative works thereof.
|
16
|
+
|
17
|
+
## Scope
|
18
|
+
|
19
|
+
This Agreement applies to all contributions made by Contributor to the Project, whether made before or after the date of this Agreement.
|
20
|
+
|
21
|
+
## Representations and Warranties
|
22
|
+
|
23
|
+
Contributor represents and warrants that:
|
24
|
+
|
25
|
+
1. Contributor has the right to enter into this Agreement and to grant the rights granted herein.
|
26
|
+
|
27
|
+
2. The contributions are original works of Contributor and do not infringe any third-party rights.
|
28
|
+
|
29
|
+
3. Contributor has not assigned or licensed the contributions to any other party.
|
30
|
+
|
31
|
+
## Governing Law
|
32
|
+
|
33
|
+
This Agreement shall be governed by the laws of the State of Delaware, without regard to conflict of laws principles.
|
34
|
+
|
35
|
+
## Entire Agreement
|
36
|
+
|
37
|
+
This Agreement constitutes the entire agreement between the parties and supersedes all prior agreements.
|
38
|
+
|
39
|
+
IN WITNESS WHEREOF, the parties have executed this Agreement as of the date first above written.
|
40
|
+
|
41
|
+
Contributor: ___________________________ Date: __________
|
42
|
+
|
43
|
+
Company: Durable Programming, LLC
|
44
|
+
|
45
|
+
By: ___________________________ Date: __________
|
data/CODE-OF-CONDUCT.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# Code of Conduct
|
2
|
+
|
3
|
+
This document provides community guidelines for a safe, respectful, productive, and collaborative place for any person who is willing to contribute to the Abachrome community. It applies to all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.).
|
4
|
+
|
5
|
+
- Participants will be tolerant of opposing views.
|
6
|
+
- Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
7
|
+
- When interpreting the words and actions of others, participants should always assume good intentions.
|
8
|
+
- Behaviour which can be reasonably considered harassment will not be tolerated.
|
9
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2023 Durable Programming
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Abachrome
|
2
2
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
4
|
+
[](https://www.ruby-lang.org/en/)
|
5
|
+
[](https://badge.fury.io/rb/abachrome)
|
6
|
+
[](https://rubygems.org/gems/abachrome)
|
7
|
+
[](https://github.com/durableprogramming/abachrome)
|
8
|
+
[](http://makeapullrequest.com)
|
9
|
+
[](https://github.com/rubocop/rubocop)
|
10
|
+
[](https://github.com/tophat/getting-started/blob/master/scorecard.md)
|
11
|
+
[](https://durableprogramming.com)
|
12
|
+
|
3
13
|
Abachrome is a Ruby gem for parsing, manipulating, and managing colors. It provides a robust set of tools for working with various color formats including hex, RGB, HSL, and named colors.
|
4
14
|
|
5
15
|

|
@@ -67,20 +77,112 @@ color = Abachrome.from_rgb(1.2, -0.1, 0.5)
|
|
67
77
|
mapped_color = srgb_gamut.map(color.coordinates) # Map color to gamut
|
68
78
|
```
|
69
79
|
|
80
|
+
### Parsing Colors
|
81
|
+
|
82
|
+
Abachrome supports parsing colors from various string formats using the CSS parser:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# Hex colors
|
86
|
+
color = Abachrome::Parsers::CSS.parse('#ff0000')
|
87
|
+
color = Abachrome::Parsers::CSS.parse('#f00') # Short hex
|
88
|
+
|
89
|
+
# RGB and HSL strings
|
90
|
+
color = Abachrome::Parsers::CSS.parse('rgb(255, 0, 0)')
|
91
|
+
color = Abachrome::Parsers::CSS.parse('rgba(255, 0, 0, 0.5)')
|
92
|
+
color = Abachrome::Parsers::CSS.parse('hsl(0, 100%, 50%)')
|
93
|
+
|
94
|
+
# Named colors
|
95
|
+
color = Abachrome::Parsers::CSS.parse('red')
|
96
|
+
|
97
|
+
# Advanced formats
|
98
|
+
color = Abachrome::Parsers::CSS.parse('oklab(0.5 0.2 -0.1)')
|
99
|
+
color = Abachrome::Parsers::CSS.parse('oklch(0.5 0.2 120)')
|
100
|
+
```
|
101
|
+
|
102
|
+
### Color Manipulation
|
103
|
+
|
104
|
+
Abachrome provides methods for blending and adjusting colors:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
color1 = Abachrome.from_rgb(1.0, 0.0, 0.0)
|
108
|
+
color2 = Abachrome.from_rgb(0.0, 1.0, 0.0)
|
109
|
+
|
110
|
+
# Blend two colors
|
111
|
+
blended = color1.blend(color2, 0.5) # 50% blend
|
112
|
+
|
113
|
+
# Adjust lightness
|
114
|
+
lighter = color1.lighten(0.2) # Lighten by 20%
|
115
|
+
```
|
116
|
+
|
117
|
+
### Working with Palettes
|
118
|
+
|
119
|
+
Create and manipulate color palettes:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
palette = Abachrome::Palette.new([color1, color2])
|
123
|
+
|
124
|
+
# Interpolate between colors
|
125
|
+
interpolated = palette.interpolate(steps: 5) # Add 5 interpolated colors between each pair
|
126
|
+
```
|
127
|
+
|
128
|
+
### Advanced Gamut Mapping
|
129
|
+
|
130
|
+
Map colors to different color gamuts for accurate display:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
srgb_gamut = Abachrome::Gamut::SRGB.new
|
134
|
+
p3_gamut = Abachrome::Gamut::P3.new
|
135
|
+
|
136
|
+
out_of_gamut = Abachrome.from_rgb(1.2, -0.1, 0.5)
|
137
|
+
mapped_srgb = srgb_gamut.map(out_of_gamut.coordinates)
|
138
|
+
mapped_p3 = p3_gamut.map(out_of_gamut.coordinates)
|
139
|
+
```
|
140
|
+
|
70
141
|
## Features
|
71
142
|
|
72
|
-
- Support for multiple color spaces (RGB, HSL, Lab, Oklab)
|
73
|
-
- Color space conversion
|
74
|
-
-
|
75
|
-
- CSS color parsing and formatting
|
143
|
+
- Support for multiple color spaces (RGB, HSL, Lab, Oklab, OKLCH, XYZ, LMS)
|
144
|
+
- Color space conversion between all supported spaces
|
145
|
+
- Advanced gamut mapping for sRGB, P3, and Rec.2020
|
146
|
+
- Comprehensive CSS color parsing and formatting
|
76
147
|
- Support for CSS named colors
|
148
|
+
- Color blending and lightness adjustments
|
149
|
+
- Palette creation and interpolation
|
77
150
|
- High-precision color calculations using BigDecimal
|
78
|
-
- Alpha channel support
|
151
|
+
- Alpha channel support throughout all operations
|
79
152
|
|
80
153
|
## Requirements
|
81
154
|
|
82
155
|
- Ruby >= 3.0.0
|
83
156
|
|
157
|
+
## Dependencies
|
158
|
+
|
159
|
+
Abachrome has minimal dependencies to maintain performance and compatibility:
|
160
|
+
|
161
|
+
### Runtime Dependencies
|
162
|
+
- **dry-inflector** (~> 1.0): Provides utilities for dynamic class loading and color space management
|
163
|
+
|
164
|
+
### Development Dependencies
|
165
|
+
- Testing framework (minitest)
|
166
|
+
- Code quality tools (rubocop)
|
167
|
+
|
168
|
+
### Dependency Management
|
169
|
+
Dependencies are specified in `abachrome.gemspec` and managed via Bundler.
|
170
|
+
|
171
|
+
To check for dependency updates:
|
172
|
+
```bash
|
173
|
+
bundle outdated
|
174
|
+
```
|
175
|
+
|
176
|
+
To update dependencies:
|
177
|
+
```bash
|
178
|
+
bundle update
|
179
|
+
```
|
180
|
+
|
181
|
+
All dependencies are selected based on:
|
182
|
+
- Active maintenance and community support
|
183
|
+
- Minimal footprint and performance impact
|
184
|
+
- Compatibility with Ruby 3.0+ requirements
|
185
|
+
|
84
186
|
## License
|
85
187
|
|
86
188
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -91,9 +193,113 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
91
193
|
|
92
194
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
93
195
|
|
196
|
+
### Testing
|
197
|
+
|
198
|
+
Tests are run automatically in CI/CD pipelines for all pull requests and pushes to the main branch. The test suite includes:
|
199
|
+
|
200
|
+
- Unit tests for all color models and converters
|
201
|
+
- Integration tests for parsing and output functionality
|
202
|
+
- Performance benchmarks for critical operations
|
203
|
+
|
204
|
+
To run tests locally:
|
205
|
+
```bash
|
206
|
+
rake test
|
207
|
+
```
|
208
|
+
|
209
|
+
Tests must pass before any changes can be merged to the main branch.
|
210
|
+
|
211
|
+
### Testing Policy
|
212
|
+
|
213
|
+
- **All changes**: Must not break existing tests
|
214
|
+
- **New features**: Must include comprehensive unit tests
|
215
|
+
- **Bug fixes**: Must include regression tests
|
216
|
+
- **Major changes**: Require code review and may need additional integration tests
|
217
|
+
- **Performance changes**: Must include benchmark comparisons
|
218
|
+
|
219
|
+
All pull requests require tests to pass CI checks before merging.
|
220
|
+
|
94
221
|
## Contributing
|
95
222
|
|
96
|
-
|
223
|
+
We welcome contributions from the community! Here's how you can help:
|
224
|
+
|
225
|
+
### Reporting Issues
|
226
|
+
|
227
|
+
Please use GitHub issues to report bugs or request features.
|
228
|
+
|
229
|
+
### Contributing Code
|
230
|
+
|
231
|
+
1. Fork the repository
|
232
|
+
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
233
|
+
3. Make your changes
|
234
|
+
4. Ensure tests pass (`rake test`)
|
235
|
+
5. Follow the code style (run `rubocop` if available)
|
236
|
+
6. Commit your changes
|
237
|
+
7. Push to your fork
|
238
|
+
8. Open a pull request
|
239
|
+
|
240
|
+
### Code Style
|
241
|
+
|
242
|
+
- Follow Ruby style guidelines
|
243
|
+
- Use meaningful variable and method names
|
244
|
+
- Add tests for new functionality
|
245
|
+
- Update documentation as needed
|
246
|
+
|
247
|
+
### Testing
|
248
|
+
|
249
|
+
All contributions must include appropriate tests. Run the test suite with `rake test`.
|
250
|
+
|
251
|
+
### Developer Certificate of Origin
|
252
|
+
|
253
|
+
By contributing to this project, you agree to the Developer Certificate of Origin (DCO). To certify your contribution, add a sign-off to your commits:
|
254
|
+
|
255
|
+
```
|
256
|
+
git commit -s -m "Your commit message"
|
257
|
+
```
|
258
|
+
|
259
|
+
This adds `Signed-off-by: Your Name <your.email@example.com>` to certify that you have the right to submit the work.
|
260
|
+
|
261
|
+
## Reporting Defects
|
262
|
+
|
263
|
+
To report bugs or defects, please use GitHub issues at https://github.com/durableprogramming/abachrome/issues. Provide as much detail as possible, including:
|
264
|
+
|
265
|
+
- Steps to reproduce the issue
|
266
|
+
- Expected behavior
|
267
|
+
- Actual behavior
|
268
|
+
- Ruby version and gem version
|
269
|
+
- Any relevant code snippets
|
270
|
+
|
271
|
+
## Security
|
272
|
+
|
273
|
+
For security-related issues, please contact us directly at commercial@durableprogramming.com instead of using public issues.
|
274
|
+
|
275
|
+
We take security seriously and will respond to vulnerability reports within 48 hours.
|
276
|
+
|
277
|
+
## Governance
|
278
|
+
|
279
|
+
### Project Members
|
280
|
+
- **Durable Programming**: Lead developer and maintainer (commercial@durableprogramming.com)
|
281
|
+
|
282
|
+
### Roles and Responsibilities
|
283
|
+
- **Maintainer**: Responsible for code reviews, releases, and overall project direction
|
284
|
+
- **Security Contact**: Handles security vulnerability reports and coordinates responses
|
285
|
+
|
286
|
+
### Sensitive Resources
|
287
|
+
Access to the following resources is limited to project maintainers:
|
288
|
+
- GitHub repository (push access)
|
289
|
+
- RubyGems publishing credentials
|
290
|
+
- CI/CD pipeline configurations
|
291
|
+
|
292
|
+
### Contributor Permissions Policy
|
293
|
+
|
294
|
+
Before granting elevated permissions (write access, maintainer status):
|
295
|
+
|
296
|
+
1. **Review Process**: Contributors must demonstrate consistent quality contributions over at least 3 months
|
297
|
+
2. **Code Quality**: All contributions must follow project standards and pass CI checks
|
298
|
+
3. **Security Review**: Security-related contributions receive additional scrutiny
|
299
|
+
4. **Community Engagement**: Active participation in issue discussions and reviews
|
300
|
+
5. **Maintainer Approval**: Final decision by existing maintainers via consensus
|
301
|
+
|
302
|
+
New contributors start with read-only access and issue reporting. Permissions are escalated gradually based on demonstrated responsibility.
|
97
303
|
|
98
304
|
# Acknowledgement
|
99
305
|
|
data/SECURITY.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Reporting Vulnerabilities
|
4
|
+
|
5
|
+
If you discover a security vulnerability in Abachrome, please report it to us as follows:
|
6
|
+
|
7
|
+
### Contact
|
8
|
+
Email: security@durableprogramming.com
|
9
|
+
|
10
|
+
### Response Time
|
11
|
+
We will acknowledge receipt of your report within 72 hours and provide a more detailed response within 7 days indicating our next steps.
|
12
|
+
|
13
|
+
### Disclosure
|
14
|
+
We ask that you do not publicly disclose the vulnerability until we have had a chance to address it. We will work with you to determine an appropriate disclosure timeline.
|
15
|
+
|
16
|
+
## Vulnerability Management
|
17
|
+
|
18
|
+
### Classification
|
19
|
+
Vulnerabilities are classified as:
|
20
|
+
- **Critical**: Immediate threat to user data or system security
|
21
|
+
- **High**: Significant security risk
|
22
|
+
- **Medium**: Moderate security concern
|
23
|
+
- **Low**: Minor security issue
|
24
|
+
|
25
|
+
### Remediation Timeline
|
26
|
+
- **Critical**: Within 48 hours
|
27
|
+
- **High**: Within 7 days
|
28
|
+
- **Medium**: Within 30 days
|
29
|
+
- **Low**: Within 90 days
|
30
|
+
|
31
|
+
### Process
|
32
|
+
1. Report received and acknowledged
|
33
|
+
2. Assessment and classification
|
34
|
+
3. Development of fix
|
35
|
+
4. Testing and validation
|
36
|
+
5. Release of patched version
|
37
|
+
6. Public disclosure (if applicable)
|
38
|
+
|
39
|
+
## Automated Security Scanning
|
40
|
+
|
41
|
+
### SCA (Software Composition Analysis)
|
42
|
+
- **Tool**: bundle-audit
|
43
|
+
- **Frequency**: Run on all CI builds and weekly scheduled scans
|
44
|
+
- **Threshold**: All vulnerabilities must be addressed before release
|
45
|
+
- **Policy**: Critical and High severity vulnerabilities must be fixed immediately. Medium severity within 30 days. Low severity tracked but may be accepted with risk assessment.
|
46
|
+
|
47
|
+
### SAST (Static Application Security Testing)
|
48
|
+
- **Tool**: Brakeman
|
49
|
+
- **Frequency**: Run on all CI builds
|
50
|
+
- **Threshold**: All warnings must be reviewed. High-confidence findings must be fixed. False positives documented.
|
51
|
+
- **Policy**: Security findings block releases. Code review required for waivers.
|
52
|
+
|
53
|
+
### Dependency Updates
|
54
|
+
- **Process**: Automated PRs for patch updates. Manual review for minor/major updates.
|
55
|
+
- **Testing**: Full test suite run on dependency updates.
|
56
|
+
- **Rollback**: Ability to rollback if issues discovered post-update.
|
57
|
+
|
58
|
+
## Security Updates
|
59
|
+
Security updates will be released as patch versions with descriptive changelogs. Subscribe to releases on GitHub to stay informed.
|
60
|
+
|
61
|
+
## Verifying Releases
|
62
|
+
|
63
|
+
To ensure the integrity and authenticity of Abachrome releases:
|
64
|
+
|
65
|
+
1. **Checksum Verification**: Each release includes SHA256 checksums for all assets. Download the checksum file and verify using:
|
66
|
+
```
|
67
|
+
sha256sum -c abachrome-<version>.gem.sha256
|
68
|
+
```
|
69
|
+
|
70
|
+
2. **Signature Verification** (future): Releases will be signed with GPG. Verify using:
|
71
|
+
```
|
72
|
+
gpg --verify abachrome-<version>.gem.asc abachrome-<version>.gem
|
73
|
+
```
|
74
|
+
|
75
|
+
3. **Source Verification**: Always build from the official GitHub repository source.
|
76
|
+
|
77
|
+
## Support and Maintenance
|
78
|
+
|
79
|
+
### Support Scope
|
80
|
+
Abachrome provides support for:
|
81
|
+
- Current stable Ruby versions (3.0+)
|
82
|
+
- Reported security vulnerabilities
|
83
|
+
- Critical bugs affecting core functionality
|
84
|
+
|
85
|
+
### Support Duration
|
86
|
+
- **Active Support**: Latest major version receives full support
|
87
|
+
- **Security Support**: Versions receive security updates for 1 year after release
|
88
|
+
- **End of Life**: Versions without security support are documented in release notes
|
89
|
+
|
90
|
+
### Security Update Policy
|
91
|
+
Security updates are provided for the current major version and the previous major version for up to 1 year after the new major version's release. Older versions may receive critical security fixes at our discretion.
|
92
|
+
|
93
|
+
## Current Known Vulnerabilities
|
94
|
+
None at this time.
|
data/abachrome.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/abachrome/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "abachrome"
|
7
|
+
spec.version = Abachrome::VERSION
|
8
|
+
spec.authors = ["Durable Programming"]
|
9
|
+
spec.email = ["commercial@durableprogramming.com"]
|
10
|
+
|
11
|
+
spec.summary = "A Ruby gem for parsing, manipulating, and managing colors"
|
12
|
+
spec.description = "Abachrome provides a robust set of tools for working with various color formats including hex, RGB, HSL, and named colors. Features support for multiple color spaces (RGB, HSL, Lab, Oklab), color space conversion, gamut mapping, CSS color parsing and formatting, and high-precision color calculations using BigDecimal."
|
13
|
+
spec.homepage = "https://github.com/durableprogramming/abachrome"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/durableprogramming/abachrome"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/durableprogramming/abachrome/blob/main/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) ||
|
26
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
+
|
32
|
+
# Runtime dependencies
|
33
|
+
spec.add_dependency "dry-inflector", "~> 1.0"
|
34
|
+
|
35
|
+
end
|
36
|
+
|
data/devenv.lock
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
"devenv": {
|
4
4
|
"locked": {
|
5
5
|
"dir": "src/modules",
|
6
|
-
"lastModified":
|
6
|
+
"lastModified": 1760162706,
|
7
7
|
"owner": "cachix",
|
8
8
|
"repo": "devenv",
|
9
|
-
"rev": "
|
9
|
+
"rev": "0d5ad578728fe4bce66eb4398b8b1e66deceb4e4",
|
10
10
|
"type": "github"
|
11
11
|
},
|
12
12
|
"original": {
|
@@ -72,10 +72,10 @@
|
|
72
72
|
]
|
73
73
|
},
|
74
74
|
"locked": {
|
75
|
-
"lastModified":
|
75
|
+
"lastModified": 1759523803,
|
76
76
|
"owner": "cachix",
|
77
77
|
"repo": "git-hooks.nix",
|
78
|
-
"rev": "
|
78
|
+
"rev": "cfc9f7bb163ad8542029d303e599c0f7eee09835",
|
79
79
|
"type": "github"
|
80
80
|
},
|
81
81
|
"original": {
|
@@ -106,10 +106,10 @@
|
|
106
106
|
},
|
107
107
|
"nixpkgs": {
|
108
108
|
"locked": {
|
109
|
-
"lastModified":
|
109
|
+
"lastModified": 1758532697,
|
110
110
|
"owner": "cachix",
|
111
111
|
"repo": "devenv-nixpkgs",
|
112
|
-
"rev": "
|
112
|
+
"rev": "207a4cb0e1253c7658c6736becc6eb9cace1f25f",
|
113
113
|
"type": "github"
|
114
114
|
},
|
115
115
|
"original": {
|
@@ -128,10 +128,10 @@
|
|
128
128
|
]
|
129
129
|
},
|
130
130
|
"locked": {
|
131
|
-
"lastModified":
|
131
|
+
"lastModified": 1759902829,
|
132
132
|
"owner": "bobvanderlinden",
|
133
133
|
"repo": "nixpkgs-ruby",
|
134
|
-
"rev": "
|
134
|
+
"rev": "5fba6c022a63f1e76dee4da71edddad8959f088a",
|
135
135
|
"type": "github"
|
136
136
|
},
|
137
137
|
"original": {
|
data/lib/abachrome/color.rb
CHANGED
@@ -134,7 +134,7 @@ module Abachrome
|
|
134
134
|
end
|
135
135
|
|
136
136
|
# Returns a string representation of the color in the format "ColorSpaceName(coord1, coord2, coord3, alpha)"
|
137
|
-
#
|
137
|
+
#
|
138
138
|
# @return [String] A human-readable string representation of the color showing its
|
139
139
|
# color space name, coordinate values rounded to 3 decimal places, and alpha value
|
140
140
|
# (if not 1.0)
|
@@ -144,6 +144,14 @@ module Abachrome
|
|
144
144
|
"#{color_space.name}(#{coord_str}#{alpha_str})"
|
145
145
|
end
|
146
146
|
|
147
|
+
# Returns the color as a hexadecimal string representation.
|
148
|
+
#
|
149
|
+
# @return [String] A hex color string in the format "#RRGGBB" or "#RRGGBBAA" if alpha < 1.0
|
150
|
+
def to_hex
|
151
|
+
require_relative "outputs/css"
|
152
|
+
Outputs::CSS.format_hex(self)
|
153
|
+
end
|
154
|
+
|
147
155
|
private
|
148
156
|
|
149
157
|
# Validates that the number of coordinates matches the expected number for the color space.
|
@@ -0,0 +1,437 @@
|
|
1
|
+
#
|
2
|
+
# Abachrome::Parsers::CSS - CSS color format parser
|
3
|
+
#
|
4
|
+
# This parser handles various CSS color formats including:
|
5
|
+
# - Named colors (red, blue, etc.)
|
6
|
+
# - Hex colors (#rgb, #rrggbb, #rgba, #rrggbbaa)
|
7
|
+
# - rgb() and rgba() functions
|
8
|
+
# - hsl() and hsla() functions
|
9
|
+
# - hwb() function
|
10
|
+
# - lab() and lch() functions
|
11
|
+
# - oklab() and oklch() functions
|
12
|
+
# - color() function
|
13
|
+
#
|
14
|
+
|
15
|
+
require_relative "hex"
|
16
|
+
require_relative "../named/css"
|
17
|
+
require_relative "../color"
|
18
|
+
|
19
|
+
module Abachrome
|
20
|
+
module Parsers
|
21
|
+
class CSS
|
22
|
+
def self.parse(input)
|
23
|
+
return nil unless input.is_a?(String)
|
24
|
+
|
25
|
+
input = input.strip.downcase
|
26
|
+
|
27
|
+
# Try named colors first
|
28
|
+
named_color = parse_named_color(input)
|
29
|
+
return named_color if named_color
|
30
|
+
|
31
|
+
# Try hex colors
|
32
|
+
hex_color = Hex.parse(input)
|
33
|
+
return hex_color if hex_color
|
34
|
+
|
35
|
+
# Try functional notation
|
36
|
+
parse_functional_color(input)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.parse_named_color(input)
|
42
|
+
# Check if input matches a named color
|
43
|
+
rgb_values = Named::CSS.method(input.to_sym)&.call
|
44
|
+
return nil unless rgb_values
|
45
|
+
|
46
|
+
# Convert 0-255 RGB values to 0-1 range
|
47
|
+
r, g, b = rgb_values.map { |v| v / 255.0 }
|
48
|
+
Color.from_rgb(r, g, b)
|
49
|
+
rescue NameError
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.parse_functional_color(input)
|
54
|
+
case input
|
55
|
+
when /^rgb\((.+)\)$/
|
56
|
+
parse_rgb($1)
|
57
|
+
when /^rgba\((.+)\)$/
|
58
|
+
parse_rgba($1)
|
59
|
+
when /^hsl\((.+)\)$/
|
60
|
+
parse_hsl($1)
|
61
|
+
when /^hsla\((.+)\)$/
|
62
|
+
parse_hsla($1)
|
63
|
+
when /^hwb\((.+)\)$/
|
64
|
+
parse_hwb($1)
|
65
|
+
when /^lab\((.+)\)$/
|
66
|
+
parse_lab($1)
|
67
|
+
when /^lch\((.+)\)$/
|
68
|
+
parse_lch($1)
|
69
|
+
when /^oklab\((.+)\)$/
|
70
|
+
parse_oklab($1)
|
71
|
+
when /^oklch\((.+)\)$/
|
72
|
+
parse_oklch($1)
|
73
|
+
when /^color\((.+)\)$/
|
74
|
+
parse_color_function($1)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.parse_rgb(params)
|
81
|
+
values = parse_color_values(params, 3)
|
82
|
+
return nil unless values
|
83
|
+
|
84
|
+
r, g, b = values
|
85
|
+
Color.from_rgb(r, g, b)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.parse_rgba(params)
|
89
|
+
values = parse_color_values(params, 4)
|
90
|
+
return nil unless values
|
91
|
+
|
92
|
+
r, g, b, a = values
|
93
|
+
Color.from_rgb(r, g, b, a)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.parse_hsl(params)
|
97
|
+
values = parse_hsl_values(params, 3)
|
98
|
+
return nil unless values
|
99
|
+
|
100
|
+
h, s, l = values
|
101
|
+
rgb = hsl_to_rgb(h, s, l)
|
102
|
+
Color.from_rgb(*rgb)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.parse_hsla(params)
|
106
|
+
values = parse_hsl_values(params, 4)
|
107
|
+
return nil unless values
|
108
|
+
|
109
|
+
h, s, l, a = values
|
110
|
+
rgb = hsl_to_rgb(h, s, l)
|
111
|
+
Color.from_rgb(*rgb, a)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.parse_hwb(params)
|
115
|
+
values = parse_hwb_values(params)
|
116
|
+
return nil unless values
|
117
|
+
|
118
|
+
h, w, b, a = values
|
119
|
+
rgb = hwb_to_rgb(h, w, b)
|
120
|
+
Color.from_rgb(*rgb, a)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.parse_lab(params)
|
124
|
+
values = parse_lab_values(params, 3)
|
125
|
+
return nil unless values
|
126
|
+
|
127
|
+
l, a, b = values
|
128
|
+
# Convert CIELAB to XYZ, then to sRGB
|
129
|
+
xyz = lab_to_xyz(l, a, b)
|
130
|
+
rgb = xyz_to_rgb(*xyz)
|
131
|
+
Color.from_rgb(*rgb)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.parse_lch(params)
|
135
|
+
values = parse_lch_values(params, 3)
|
136
|
+
return nil unless values
|
137
|
+
|
138
|
+
l, c, h = values
|
139
|
+
# Convert CIELCH to CIELAB, then to XYZ, then to sRGB
|
140
|
+
lab = lch_to_lab(l, c, h)
|
141
|
+
xyz = lab_to_xyz(*lab)
|
142
|
+
rgb = xyz_to_rgb(*xyz)
|
143
|
+
Color.from_rgb(*rgb)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.parse_oklab(params)
|
147
|
+
values = parse_oklab_values(params, 3)
|
148
|
+
return nil unless values
|
149
|
+
|
150
|
+
l, a, b = values
|
151
|
+
Color.from_oklab(l, a, b)
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.parse_oklch(params)
|
155
|
+
values = parse_oklch_values(params, 3)
|
156
|
+
return nil unless values
|
157
|
+
|
158
|
+
l, c, h = values
|
159
|
+
Color.from_oklch(l, c, h)
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.parse_color_function(params)
|
163
|
+
# Parse color(space values...)
|
164
|
+
parts = params.split(/\s+/, 2)
|
165
|
+
return nil unless parts.length == 2
|
166
|
+
|
167
|
+
space = parts[0]
|
168
|
+
values_str = parts[1]
|
169
|
+
|
170
|
+
case space
|
171
|
+
when "srgb"
|
172
|
+
values = parse_color_values(values_str, 3)
|
173
|
+
return nil unless values
|
174
|
+
r, g, b = values
|
175
|
+
Color.from_rgb(r, g, b)
|
176
|
+
when "srgb-linear"
|
177
|
+
values = parse_color_values(values_str, 3)
|
178
|
+
return nil unless values
|
179
|
+
r, g, b = values
|
180
|
+
Color.from_lrgb(r, g, b)
|
181
|
+
when "display-p3"
|
182
|
+
# For now, approximate as sRGB
|
183
|
+
values = parse_color_values(values_str, 3)
|
184
|
+
return nil unless values
|
185
|
+
r, g, b = values
|
186
|
+
Color.from_rgb(r, g, b)
|
187
|
+
when "a98-rgb"
|
188
|
+
# For now, approximate as sRGB
|
189
|
+
values = parse_color_values(values_str, 3)
|
190
|
+
return nil unless values
|
191
|
+
r, g, b = values
|
192
|
+
Color.from_rgb(r, g, b)
|
193
|
+
when "prophoto-rgb"
|
194
|
+
# For now, approximate as sRGB
|
195
|
+
values = parse_color_values(values_str, 3)
|
196
|
+
return nil unless values
|
197
|
+
r, g, b = values
|
198
|
+
Color.from_rgb(r, g, b)
|
199
|
+
when "rec2020"
|
200
|
+
# For now, approximate as sRGB
|
201
|
+
values = parse_color_values(values_str, 3)
|
202
|
+
return nil unless values
|
203
|
+
r, g, b = values
|
204
|
+
Color.from_rgb(r, g, b)
|
205
|
+
else
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Helper methods for parsing values
|
211
|
+
|
212
|
+
def self.parse_color_values(str, expected_count)
|
213
|
+
values = str.split(/\s*,\s*/).map(&:strip)
|
214
|
+
return nil unless values.length == expected_count
|
215
|
+
|
216
|
+
values.map do |v|
|
217
|
+
parse_numeric_value(v)
|
218
|
+
end.compact
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.parse_hsl_values(str, expected_count)
|
222
|
+
values = str.split(/\s*,\s*/).map(&:strip)
|
223
|
+
return nil unless values.length == expected_count
|
224
|
+
|
225
|
+
parsed = []
|
226
|
+
values.each_with_index do |v, i|
|
227
|
+
if i == 0 # Hue
|
228
|
+
val = parse_angle_value(v)
|
229
|
+
return nil unless val
|
230
|
+
parsed << val
|
231
|
+
else # Saturation, Lightness, Alpha
|
232
|
+
val = parse_percentage_or_number(v)
|
233
|
+
return nil unless val
|
234
|
+
parsed << val
|
235
|
+
end
|
236
|
+
end
|
237
|
+
parsed
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.parse_hwb_values(str)
|
241
|
+
values = str.split(/\s*,\s*/).map(&:strip)
|
242
|
+
return nil unless values.length >= 3
|
243
|
+
|
244
|
+
h = parse_angle_value(values[0])
|
245
|
+
w = parse_percentage_or_number(values[1])
|
246
|
+
b = parse_percentage_or_number(values[2])
|
247
|
+
a = values[3] ? parse_numeric_value(values[3]) : 1.0
|
248
|
+
|
249
|
+
return nil unless h && w && b && a
|
250
|
+
|
251
|
+
[h, w, b, a]
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.parse_lab_values(str, expected_count)
|
255
|
+
values = str.split(/\s+/, expected_count).map(&:strip)
|
256
|
+
return nil unless values.length == expected_count
|
257
|
+
|
258
|
+
l = parse_percentage_or_number(values[0])
|
259
|
+
a = parse_numeric_value(values[1])
|
260
|
+
b = parse_numeric_value(values[2])
|
261
|
+
|
262
|
+
return nil unless l && a && b
|
263
|
+
|
264
|
+
[l, a, b]
|
265
|
+
end
|
266
|
+
|
267
|
+
def self.parse_lch_values(str, expected_count)
|
268
|
+
values = str.split(/\s+/, expected_count).map(&:strip)
|
269
|
+
return nil unless values.length == expected_count
|
270
|
+
|
271
|
+
l = parse_percentage_or_number(values[0])
|
272
|
+
c = parse_numeric_value(values[1])
|
273
|
+
h = parse_angle_value(values[2])
|
274
|
+
|
275
|
+
return nil unless l && c && h
|
276
|
+
|
277
|
+
[l, c, h]
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.parse_oklab_values(str, expected_count)
|
281
|
+
values = str.split(/\s+/, expected_count).map(&:strip)
|
282
|
+
return nil unless values.length == expected_count
|
283
|
+
|
284
|
+
l = parse_percentage_or_number(values[0])
|
285
|
+
a = parse_numeric_value(values[1])
|
286
|
+
b = parse_numeric_value(values[2])
|
287
|
+
|
288
|
+
return nil unless l && a && b
|
289
|
+
|
290
|
+
[l, a, b]
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.parse_oklch_values(str, expected_count)
|
294
|
+
values = str.split(/\s+/, expected_count).map(&:strip)
|
295
|
+
return nil unless values.length == expected_count
|
296
|
+
|
297
|
+
l = parse_percentage_or_number(values[0])
|
298
|
+
c = parse_numeric_value(values[1])
|
299
|
+
h = parse_angle_value(values[2])
|
300
|
+
|
301
|
+
return nil unless l && c && h
|
302
|
+
|
303
|
+
[l, c, h]
|
304
|
+
end
|
305
|
+
|
306
|
+
def self.parse_numeric_value(str)
|
307
|
+
return nil unless str
|
308
|
+
|
309
|
+
if str.end_with?('%')
|
310
|
+
(str.chomp('%').to_f / 100.0)
|
311
|
+
else
|
312
|
+
str.to_f
|
313
|
+
end
|
314
|
+
rescue
|
315
|
+
nil
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.parse_percentage_or_number(str)
|
319
|
+
return nil unless str
|
320
|
+
|
321
|
+
if str.end_with?('%')
|
322
|
+
str.chomp('%').to_f / 100.0
|
323
|
+
else
|
324
|
+
str.to_f
|
325
|
+
end
|
326
|
+
rescue
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.parse_angle_value(str)
|
331
|
+
return nil unless str
|
332
|
+
|
333
|
+
if str.end_with?('deg')
|
334
|
+
str.chomp('deg').to_f
|
335
|
+
elsif str.end_with?('rad')
|
336
|
+
str.chomp('rad').to_f * 180.0 / Math::PI
|
337
|
+
elsif str.end_with?('grad')
|
338
|
+
str.chomp('grad').to_f * 0.9
|
339
|
+
elsif str.end_with?('turn')
|
340
|
+
str.chomp('turn').to_f * 360.0
|
341
|
+
else
|
342
|
+
str.to_f # Assume degrees
|
343
|
+
end
|
344
|
+
rescue
|
345
|
+
nil
|
346
|
+
end
|
347
|
+
|
348
|
+
# Color space conversion functions
|
349
|
+
|
350
|
+
def self.hsl_to_rgb(h, s, l)
|
351
|
+
h = h / 360.0 # Normalize hue to 0-1
|
352
|
+
|
353
|
+
c = (1 - (2 * l - 1).abs) * s
|
354
|
+
x = c * (1 - ((h * 6) % 2 - 1).abs)
|
355
|
+
m = l - c / 2
|
356
|
+
|
357
|
+
if h < 1.0/6
|
358
|
+
r, g, b = c, x, 0
|
359
|
+
elsif h < 2.0/6
|
360
|
+
r, g, b = x, c, 0
|
361
|
+
elsif h < 3.0/6
|
362
|
+
r, g, b = 0, c, x
|
363
|
+
elsif h < 4.0/6
|
364
|
+
r, g, b = 0, x, c
|
365
|
+
elsif h < 5.0/6
|
366
|
+
r, g, b = x, 0, c
|
367
|
+
else
|
368
|
+
r, g, b = c, 0, x
|
369
|
+
end
|
370
|
+
|
371
|
+
[r + m, g + m, b + m]
|
372
|
+
end
|
373
|
+
|
374
|
+
def self.hwb_to_rgb(h, w, b)
|
375
|
+
# Normalize values
|
376
|
+
h = h / 360.0
|
377
|
+
|
378
|
+
# Calculate RGB from HSL equivalent
|
379
|
+
if w + b >= 1
|
380
|
+
gray = w / (w + b)
|
381
|
+
[gray, gray, gray]
|
382
|
+
else
|
383
|
+
rgb = hsl_to_rgb(h * 360, 1, 0.5)
|
384
|
+
r, g, b_rgb = rgb
|
385
|
+
|
386
|
+
# Apply whiteness and blackness
|
387
|
+
r = r * (1 - w - b) + w
|
388
|
+
g = g * (1 - w - b) + w
|
389
|
+
b_rgb = b_rgb * (1 - w - b) + w
|
390
|
+
|
391
|
+
[r, g, b_rgb]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def self.lab_to_xyz(l, a, b)
|
396
|
+
# CIELAB to XYZ conversion (D65 white point)
|
397
|
+
y = (l + 16) / 116
|
398
|
+
x = a / 500 + y
|
399
|
+
z = y - b / 200
|
400
|
+
|
401
|
+
x = x**3 > 0.008856 ? x**3 : (x - 16/116) / 7.787
|
402
|
+
y = y**3 > 0.008856 ? y**3 : (y - 16/116) / 7.787
|
403
|
+
z = z**3 > 0.008856 ? z**3 : (z - 16/116) / 7.787
|
404
|
+
|
405
|
+
# D65 white point
|
406
|
+
x *= 0.95047
|
407
|
+
y *= 1.0
|
408
|
+
z *= 1.08883
|
409
|
+
|
410
|
+
[x, y, z]
|
411
|
+
end
|
412
|
+
|
413
|
+
def self.lch_to_lab(l, c, h)
|
414
|
+
h_rad = h * Math::PI / 180.0
|
415
|
+
a = c * Math.cos(h_rad)
|
416
|
+
b = c * Math.sin(h_rad)
|
417
|
+
[l, a, b]
|
418
|
+
end
|
419
|
+
|
420
|
+
def self.xyz_to_rgb(x, y, z)
|
421
|
+
# XYZ to linear RGB
|
422
|
+
r = x * 3.2406 + y * -1.5372 + z * -0.4986
|
423
|
+
g = x * -0.9689 + y * 1.8758 + z * 0.0415
|
424
|
+
b = x * 0.0557 + y * -0.2040 + z * 1.0570
|
425
|
+
|
426
|
+
# Linear RGB to sRGB
|
427
|
+
[r, g, b].map do |v|
|
428
|
+
if v > 0.0031308
|
429
|
+
1.055 * (v ** (1/2.4)) - 0.055
|
430
|
+
else
|
431
|
+
12.92 * v
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
data/lib/abachrome/version.rb
CHANGED
data/lib/abachrome.rb
CHANGED
@@ -129,7 +129,7 @@ module Abachrome
|
|
129
129
|
end
|
130
130
|
|
131
131
|
# Creates a color object from a CSS color name.
|
132
|
-
#
|
132
|
+
#
|
133
133
|
# @param color_name [String] The CSS color name (e.g., 'red', 'blue', 'cornflowerblue').
|
134
134
|
# Case-insensitive.
|
135
135
|
# @return [Abachrome::Color, nil] A color object in the RGB color space if the name is valid,
|
@@ -141,6 +141,15 @@ module Abachrome
|
|
141
141
|
from_rgb(*rgb_values.map { |v| v / 255.0 })
|
142
142
|
end
|
143
143
|
|
144
|
+
# Parses a CSS color string and returns a Color object.
|
145
|
+
#
|
146
|
+
# @param css_string [String] The CSS color string to parse (e.g., "#ff0000", "rgb(255, 0, 0)", "red")
|
147
|
+
# @return [Abachrome::Color, nil] A Color object if parsing succeeds, nil otherwise
|
148
|
+
def parse(css_string)
|
149
|
+
require_relative "abachrome/parsers/css"
|
150
|
+
Parsers::CSS.parse(css_string)
|
151
|
+
end
|
152
|
+
|
144
153
|
# Convert a color from its current color space to another color space.
|
145
154
|
#
|
146
155
|
# @param color [Abachrome::Color] The color object to convert
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Initial Security Assessment for Abachrome
|
2
|
+
|
3
|
+
## Assessment Date
|
4
|
+
2025-10-12
|
5
|
+
|
6
|
+
## Scope
|
7
|
+
This assessment covers the Abachrome Ruby gem, focusing on color manipulation and conversion functionality.
|
8
|
+
|
9
|
+
## Findings
|
10
|
+
|
11
|
+
### Positive Security Aspects
|
12
|
+
- **No External Dependencies**: Pure Ruby implementation with minimal dependencies
|
13
|
+
- **Immutable Objects**: Color objects are immutable, preventing accidental modification
|
14
|
+
- **Input Validation**: All parsing operations include validation
|
15
|
+
- **No Network Operations**: All computations are local
|
16
|
+
- **No File System Access**: No reading/writing of files
|
17
|
+
- **No System Calls**: Pure mathematical computations
|
18
|
+
|
19
|
+
### Potential Risks
|
20
|
+
- **Parsing Complex Inputs**: CSS color parsing could be vulnerable to malformed input
|
21
|
+
- **BigDecimal Precision**: High precision could lead to DoS via very large numbers
|
22
|
+
- **Memory Usage**: Large color palettes could consume significant memory
|
23
|
+
|
24
|
+
### Recommendations
|
25
|
+
1. Implement input length limits for parsing operations
|
26
|
+
2. Add timeout protections for complex computations
|
27
|
+
3. Validate coordinate ranges strictly
|
28
|
+
4. Consider rate limiting for palette operations
|
29
|
+
5. Regular dependency updates and security scans
|
30
|
+
|
31
|
+
## Threat Model
|
32
|
+
|
33
|
+
### Actors
|
34
|
+
- **Users**: Developers using the gem
|
35
|
+
- **Attackers**: Malicious users attempting to exploit parsing or computation
|
36
|
+
|
37
|
+
### Assets
|
38
|
+
- System resources (CPU, memory)
|
39
|
+
- User data integrity
|
40
|
+
|
41
|
+
### Threats
|
42
|
+
- DoS via computationally expensive inputs
|
43
|
+
- Memory exhaustion via large data structures
|
44
|
+
- Parsing exploits in CSS color functions
|
45
|
+
|
46
|
+
### Mitigations
|
47
|
+
- Input sanitization and validation
|
48
|
+
- Reasonable limits on data sizes
|
49
|
+
- Immutable data structures
|
50
|
+
- Pure functional operations where possible
|
51
|
+
|
52
|
+
## Conclusion
|
53
|
+
The gem has a strong security posture due to its pure computational nature and lack of external interfaces. Focus should be on input validation and resource limits for production use.
|
data/security/vex.json
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"document": {
|
3
|
+
"id": "abachrome-vex",
|
4
|
+
"author": "Durable Programming",
|
5
|
+
"timestamp": "2025-10-12T00:00:00Z",
|
6
|
+
"version": "1.0"
|
7
|
+
},
|
8
|
+
"product_tree": {
|
9
|
+
"branches": [
|
10
|
+
{
|
11
|
+
"type": "package-url",
|
12
|
+
"name": "pkg:gem/abachrome",
|
13
|
+
"product": {
|
14
|
+
"name": "Abachrome",
|
15
|
+
"version": "*"
|
16
|
+
}
|
17
|
+
}
|
18
|
+
]
|
19
|
+
},
|
20
|
+
"vulnerabilities": []
|
21
|
+
}
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abachrome
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Durable Programming
|
@@ -37,8 +37,13 @@ files:
|
|
37
37
|
- ".envrc"
|
38
38
|
- ".rubocop.yml"
|
39
39
|
- CHANGELOG.md
|
40
|
+
- CLA.md
|
41
|
+
- CODE-OF-CONDUCT.md
|
42
|
+
- LICENSE
|
40
43
|
- README.md
|
41
44
|
- Rakefile
|
45
|
+
- SECURITY.md
|
46
|
+
- abachrome.gemspec
|
42
47
|
- demos/ncurses/plasma.rb
|
43
48
|
- devenv.lock
|
44
49
|
- devenv.nix
|
@@ -97,11 +102,14 @@ files:
|
|
97
102
|
- lib/abachrome/palette_mixins/interpolate.rb
|
98
103
|
- lib/abachrome/palette_mixins/resample.rb
|
99
104
|
- lib/abachrome/palette_mixins/stretch_luminance.rb
|
105
|
+
- lib/abachrome/parsers/css.rb
|
100
106
|
- lib/abachrome/parsers/hex.rb
|
101
107
|
- lib/abachrome/to_abcd.rb
|
102
108
|
- lib/abachrome/version.rb
|
103
109
|
- logo.png
|
104
110
|
- logo.webp
|
111
|
+
- security/assesments/2025-10-12-SECURITY_ASSESSMENT.md
|
112
|
+
- security/vex.json
|
105
113
|
homepage: https://github.com/durableprogramming/abachrome
|
106
114
|
licenses:
|
107
115
|
- MIT
|