{"id":33916396,"url":"https://github.com/jamescook/cataract","last_synced_at":"2026-04-07T15:31:33.358Z","repository":{"id":323403689,"uuid":"1072591919","full_name":"jamescook/cataract","owner":"jamescook","description":"CSS Parser","archived":false,"fork":false,"pushed_at":"2026-01-10T19:50:23.000Z","size":1439,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-20T14:16:40.181Z","etag":null,"topics":["c","css","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamescook.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-09T00:09:29.000Z","updated_at":"2026-01-10T19:50:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamescook/cataract","commit_stats":null,"previous_names":["jamescook/cataract"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/jamescook/cataract","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamescook%2Fcataract","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamescook%2Fcataract/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamescook%2Fcataract/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamescook%2Fcataract/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamescook","download_url":"https://codeload.github.com/jamescook/cataract/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamescook%2Fcataract/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31518427,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","css","ruby"],"created_at":"2025-12-12T07:19:26.772Z","updated_at":"2026-04-07T15:31:33.352Z","avatar_url":"https://github.com/jamescook.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cataract\n\nA performant CSS parser for accurate parsing of complex CSS structures.\n\n[![codecov](https://codecov.io/github/jamescook/cataract/graph/badge.svg?token=1PTVV1QTV5)](https://codecov.io/github/jamescook/cataract)\n\n**[API Documentation](https://jamescook.github.io/cataract/)**\n\n## Features\n\n- **C Extension**: Performance-focused C implementation for parsing and serialization\n- **CSS2 Support**: Selectors, combinators, pseudo-classes, pseudo-elements, @media queries\n- **CSS3 Support**: Attribute selectors (`^=`, `$=`, `*=`)\n- **CSS Color Level 4**: Parses and preserves modern color formats (hex, rgb, hsl, hwb, oklab, oklch, lab, lch, named colors). Optional color conversion utility for transforming between formats.\n- **Specificity Calculation**: Automatic CSS specificity computation\n- **Media Query Filtering**: Query rules by media type\n- **Zero Runtime Dependencies**: Pure C extension with no runtime gem dependencies\n\n## Installation\n\nAdd this line to your Gemfile:\n\n```ruby\ngem 'cataract'\n```\n\nOr install directly:\n\n```bash\ngem install cataract\n```\n\n### Requirements\n\n- Ruby \u003e= 3.1.0\n\n### Pure Ruby Implementation\n\nCataract includes a pure Ruby implementation alongside the C extension. This is useful for:\n- Platforms where C extensions cannot be compiled\n- Development/debugging without needing to recompile C code\n- Environments with restricted native code execution\n\n**In your Gemfile:**\n```ruby\ngem 'cataract', require: 'cataract/pure'\n```\n\n**Or set environment variable:**\n```ruby\n# For CI, testing, or one-off usage\nENV['CATARACT_PURE'] = '1'\nrequire 'cataract'\n```\n\n**Check which implementation is loaded:**\n```ruby\nCataract::IMPLEMENTATION  # =\u003e :native or :ruby\n```\n\n**Note:** The pure Ruby implementation does not include color conversion functionality (`convert_colors!`), which is only available in the C extension.\n\n## Usage\n\n### Basic Parsing\n\n```ruby\nrequire 'cataract'\n\n# Parse CSS\nsheet = Cataract::Stylesheet.parse(\u003c\u003c~CSS)\n  body { margin: 0; padding: 0 }\n\n  @media screen and (min-width: 768px) {\n    .container { width: 750px }\n  }\n\n  div.header \u003e h1:hover { color: blue }\nCSS\n\n# Get all selectors\nsheet.selectors\n# =\u003e [\"body\", \".container\", \"div.header \u003e h1:hover\"]\n\n# Get all rules\nsheet.rules.each do |rule|\n  puts \"#{rule.selector}: #{rule.declarations.length} declarations\"\nend\n\n# Access specific rule\nbody_rule = sheet.rules.first\nbody_rule.selector       # =\u003e \"body\"\nbody_rule.specificity    # =\u003e 1\nbody_rule.declarations   # =\u003e [#\u003cDeclaration property=\"margin\" value=\"0\"\u003e, ...]\n\n# Count rules\nsheet.rules_count\n# =\u003e 3\n\n# Serialize back to CSS\nsheet.to_s\n# =\u003e \"body { margin: 0; padding: 0; } @media screen and (min-width: 768px) { .container { width: 750px; } } ...\"\n```\n\n### Advanced Filtering with Enumerable\n\n`Cataracy::Stylesheet` implements `Enumerable`, providing standard Ruby collection methods plus chainable scopes:\n\n```ruby\nsheet = Cataract::Stylesheet.parse(css)\n\n# Basic Enumerable methods work\nsheet.map(\u0026:selector)                    # =\u003e [\"body\", \".container\", \"div.header \u003e h1:hover\"]\nsheet.select(\u0026:selector?).count          # =\u003e Count only selector-based rules (excludes @keyframes, etc.)\nsheet.find { |r| r.selector == 'body' }  # =\u003e First rule matching selector\n\n# Filter to selector-based rules only (excludes at-rules like @keyframes, @font-face)\nsheet.select(\u0026:selector?).each do |rule|\n  puts \"#{rule.selector}: specificity #{rule.specificity}\"\nend\n\n# Filter by media query (returns chainable scope)\nsheet.with_media(:print).each do |rule|\n  puts \"Print rule: #{rule.selector}\"\nend\n\n# Filter by selector (returns chainable scope)\nsheet.with_selector('body').each do |rule|\n  puts \"Body rule has #{rule.declarations.length} declarations\"\nend\n\n# Filter by specificity (returns chainable scope)\nsheet.with_specificity(100..).each do |rule|\n  puts \"High specificity: #{rule.selector} (#{rule.specificity})\"\nend\n\n# Filter by property (returns chainable scope)\nsheet.with_property('margin')                       # Exact match: finds only 'margin' property\nsheet.with_property('margin', prefix_match: true)   # Prefix match: finds margin, margin-top, margin-left, etc.\nsheet.with_property('background', prefix_match: true)  # Finds ALL background-* properties\nsheet.with_property('margin', '10px')               # Filter by value too\n\n# Chain filters together\nsheet.with_media(:screen)\n     .with_specificity(50..200)\n     .select(\u0026:selector?)\n     .map(\u0026:selector)\n# =\u003e [\"#header .nav\", \".sidebar \u003e ul li\"]\n\n# Find all rules with any margin-related property\nsheet.with_property('margin', prefix_match: true).each do |rule|\n  puts \"#{rule.selector} uses margin\"\nend\n\n# Find high-specificity selectors (potential refactoring targets)\nsheet.with_specificity(100..).select(\u0026:selector?).each do |rule|\n  puts \"Refactor candidate: #{rule.selector} (specificity: #{rule.specificity})\"\nend\n\n# Find positioned elements in screen media\nsheet.with_media(:screen).select do |rule|\n  rule.selector? \u0026\u0026 rule.declarations.any? do |d|\n    d.property == 'position' \u0026\u0026 d.value == 'relative'\n  end\nend\n\n# Terminal operations force evaluation\nsheet.with_media(:print).to_a         # =\u003e Array of rules\nsheet.with_selector('.header').size   # =\u003e 3\nsheet.with_specificity(10..50).empty? # =\u003e false\n```\n\nSee [BENCHMARKS.md](BENCHMARKS.md) for detailed performance comparisons.\n\n## CSS Support\n\nCataract parses and preserves all standard CSS including:\n- **Selectors**: All CSS2/CSS3 selectors (type, class, ID, attribute, pseudo-classes, pseudo-elements, combinators)\n- **At-rules**:\n  - **`@media`**: Special handling with indexing and filtering API (`with_media(:print)`, `with_media(:all)`)\n  - **Others** (`@font-face`, `@keyframes`, `@supports`, `@page`, `@layer`, `@container`, `@property`, `@scope`, `@counter-style`): Parsed and preserved as-is (pass-through)\n- **Media Queries**: Full support including nested queries and media features\n- **Special syntax**: Data URIs, `calc()`, `url()`, CSS functions with parentheses\n- **!important**: Full support with correct cascade behavior\n\n### Color Conversion\n\nCataract supports converting colors between multiple CSS color formats with high precision.\n\n**Note:** Color conversion is an optional extension and _not_ loaded by default.\n\n\u003cdetails\u003e\n\u003csummary\u003eColor conversion examples and supported formats\u003c/summary\u003e\n\n```ruby\nrequire 'cataract'\nrequire 'cataract/color_conversion'\n\n# Convert hex to RGB\nsheet = Cataract::Stylesheet.parse('.button { color: #ff0000; background: #00ff00; }')\nsheet.convert_colors!(from: :hex, to: :rgb)\nsheet.to_s\n# =\u003e \".button { color: rgb(255 0 0); background: rgb(0 255 0); }\"\n\n# Convert RGB to HSL for easier color manipulation\nsheet = Cataract::Stylesheet.parse('.card { color: rgb(255, 128, 0); }')\nsheet.convert_colors!(from: :rgb, to: :hsl)\nsheet.to_s\n# =\u003e \".card { color: hsl(30, 100%, 50%); }\"\n\n# Convert to Oklab for perceptually uniform colors\nsheet = Cataract::Stylesheet.parse('.gradient { background: linear-gradient(#ff0000, #0000ff); }')\nsheet.convert_colors!(to: :oklab)\nsheet.to_s\n# =\u003e \".gradient { background: linear-gradient(oklab(0.6280 0.2249 0.1258), oklab(0.4520 -0.0325 -0.3115)); }\"\n\n# Auto-detect source format and convert all colors\nsheet = Cataract::Stylesheet.parse(\u003c\u003c~CSS)\n  .mixed {\n    color: #ff0000;\n    background: rgb(0, 255, 0);\n    border-color: hsl(240, 100%, 50%);\n  }\nCSS\nsheet.convert_colors!(to: :hex)  # Converts all formats to hex\n```\n\n#### Supported Color Formats\n\n| Format | From | To | Alpha | Example | Notes |\n|--------|------|-----|-------|---------|-------|\n| **hex** | ✓ | ✓ | ✓ | `#ff0000`, `#f00`, `#ff000080` | 3, 6, or 8 digit hex |\n| **rgb** | ✓ | ✓ | ✓ | `rgb(255 0 0)`, `rgb(255, 0, 0)` | Modern \u0026 legacy syntax |\n| **hsl** | ✓ | ✓ | ✓ | `hsl(0, 100%, 50%)` | Hue, saturation, lightness |\n| **hwb** | ✓ | ✓ | ✓ | `hwb(0 0% 0%)` | Hue, whiteness, blackness |\n| **oklab** | ✓ | ✓ | ✓ | `oklab(0.628 0.225 0.126)` | Perceptually uniform color space |\n| **oklch** | ✓ | ✓ | ✓ | `oklch(0.628 0.258 29.2)` | Cylindrical Oklab (LCh) |\n| **lab** | ✓ | ✓ | ✓ | `lab(53.2% 80.1 67.2)` | CIE L\\*a\\*b\\* color space (D50) |\n| **lch** | ✓ | ✓ | ✓ | `lch(53.2% 104.5 40)` | Cylindrical Lab (polar coordinates) |\n| **named** | ✓ | ✓ | – | `red`, `blue`, `rebeccapurple` | 147 CSS named colors |\n| **color()** | – | – | – | `color(display-p3 1 0 0)` | Absolute color spaces (planned) |\n\n**Format aliases:**\n- `:rgba` → uses `rgb()` syntax with alpha\n- `:hsla` → uses `hsl()` syntax with alpha\n- `:hwba` → uses `hwb()` syntax with alpha\n\n**Limitations:**\n- Math functions (`calc()`, `min()`, `max()`, `clamp()`) are not evaluated and will be preserved unchanged\n- CSS Color Level 5 features (`none`, `infinity`, relative color syntax with `from`) are preserved but not converted\n- Unknown or future color functions are passed through unchanged\n\n\u003c/details\u003e\n\n### `@import` Support\n\n`@import` statements can be resolved with security controls:\n\n```ruby\n# Disabled by default\nsheet = Cataract::Stylesheet.parse(css)  # @import statements are ignored\n\n# Enable with safe defaults (HTTPS only, .css files only, max depth 5)\nsheet = Cataract::Stylesheet.parse(css, import: true)\n\n# Custom options for full control\nsheet = Cataract::Stylesheet.parse(css, import: {\n  allowed_schemes: ['https', 'file'],   # Default: ['https']\n  extensions: ['css'],                   # Default: ['css']\n  max_depth: 3,                          # Default: 5\n  timeout: 10,                           # Default: 10 seconds\n  follow_redirects: true                 # Default: true\n})\n```\n\n**Security note**: Import resolution includes protections against:\n- Unauthorized schemes (file://, data://, etc.)\n- Non-CSS file extensions\n- Circular references\n- Excessive nesting depth\n\n## Development\n\n```bash\n# Install dependencies\nbundle install\n\n# Compile the C extension\nrake compile\n\n# Run tests\nrake test\n\n# Run benchmarks\nrake benchmark\n\n# Run fuzzer to test parser robustness\nrake fuzz                      # 10,000 iterations (default)\nrake fuzz ITERATIONS=100000    # Custom iteration count\n```\n\n**Fuzzer**: Generates random CSS input to test parser robustness against malformed or edge-case CSS. Helps catch crashes, memory leaks, and parsing edge cases.\n\n## How It Works\n\nCataract uses a high-performance C implementation for CSS parsing and serialization.\n\nEach `Rule` is a struct containing:\n- `id`: Integer ID (position in rules array)\n- `selector`: The CSS selector string\n- `declarations`: Array of `Declaration` structs (property, value, important flag)\n- `specificity`: Calculated CSS specificity (cached)\n\nImplementation details:\n- **C implementation**: Critical paths implemented in C (parsing, cascade/flatten, serialization)\n- **Flat rule array**: All rules stored in a single array, preserving source order\n- **Efficient media query handling**: O(1) lookup via internal media index\n- **Memory efficient**: Minimal allocations, reuses string buffers where possible\n- **Comprehensive parsing**: Preserves complex CSS structures including nested media queries, nested selectors, data URIs, CSS functions (calc(), var(), etc.)\n\n## Development Notes\n\nSignificant portions of this codebase were generated with assistance from [Claude Code](https://claude.com/claude-code), including the benchmark infrastructure, test suite, and documentation generation system.\n\n## License\n\nMIT\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/jamescook/cataract.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamescook%2Fcataract","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamescook%2Fcataract","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamescook%2Fcataract/lists"}