cataract 0.3.0 → 0.5.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a6424d26e77e0dd4e0167324dc6190f03a9ca041e51fa9e61a78163f6fe312b
4
- data.tar.gz: 7c538f41ebd8e93a53caed4a5f066eea076277c26174bff7d22837730f03d90d
3
+ metadata.gz: 4f107809adb744598338be592626fc5ebbfdb81c2813957c7e5ed1652f37a607
4
+ data.tar.gz: 810a6aadbe0a8c2ce4eff7c307c17c9de354f1a3bc0d9509ae4e83f1d7e1c8b0
5
5
  SHA512:
6
- metadata.gz: d2297f4139e8025ac39b9467eaa4bd0541683af7e4870813159c557df813f411d389b59f5b28adb9c46f5390aba73405b937e946d9097b943e0ba70f6cb37963
7
- data.tar.gz: 0b4d9b6ec564fd674ad8558b7b4f309494c0ca12628947a8501b17ccb3567e1a2667dd0ea89f88ad82f688ac580122ec7e9505b8149433504ef40d923228b75b
6
+ metadata.gz: b7e85ed2f9ad0b9154e78c795bc0548ca1eaeb55f8f24b2b644852394a506173afb64042f55a3c02c13c21e1b539738695a83c8f774aaf0311bdda8f00da42b9
7
+ data.tar.gz: f6ea7361ad3d180aaf9e4faa72bf686906a0f81345df59f70856734e50f17b79d4dc20823d0fae520279767cd289fb8e42bbd619230f095d1d26f9ed95aa1094
data/.gitignore CHANGED
@@ -4,9 +4,6 @@ tmp/
4
4
  *.so
5
5
  *.o
6
6
 
7
- # Generated C code from Ragel
8
- ext/**/cataract.c
9
-
10
7
  # Compiled extensions
11
8
  lib/**/*.so
12
9
  lib/**/*.bundle
data/.rubocop_todo.yml CHANGED
@@ -22,21 +22,13 @@ Layout/EmptyLinesAfterModuleInclusion:
22
22
  Exclude:
23
23
  - 'benchmarks/parsing_tests.rb'
24
24
 
25
- # Offense count: 1
26
- # This cop supports safe autocorrection (--autocorrect).
27
- # Configuration parameters: EnforcedStyle, IndentationWidth.
28
- # SupportedStyles: aligned, indented
29
- Layout/MultilineOperationIndentation:
30
- Exclude:
31
- - 'lib/cataract/pure/helpers.rb'
32
-
33
- # Offense count: 1
25
+ # Offense count: 2
34
26
  # This cop supports safe autocorrection (--autocorrect).
35
27
  Lint/AmbiguousOperatorPrecedence:
36
28
  Exclude:
37
29
  - 'lib/cataract/pure/flatten.rb'
38
30
 
39
- # Offense count: 2
31
+ # Offense count: 4
40
32
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
41
33
  Lint/DuplicateBranch:
42
34
  Exclude:
@@ -69,7 +61,7 @@ Minitest/EmptyLineBeforeAssertionMethods:
69
61
  Exclude:
70
62
  - 'test/test_stylesheet.rb'
71
63
 
72
- # Offense count: 7
64
+ # Offense count: 14
73
65
  # This cop supports unsafe autocorrection (--autocorrect-all).
74
66
  Performance/UnfreezeString:
75
67
  Exclude:
@@ -77,7 +69,7 @@ Performance/UnfreezeString:
77
69
  - 'lib/cataract/pure/flatten.rb'
78
70
  - 'lib/cataract/pure/parser.rb'
79
71
 
80
- # Offense count: 4
72
+ # Offense count: 8
81
73
  # This cop supports safe autocorrection (--autocorrect).
82
74
  # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
83
75
  # SupportedStyles: assign_to_condition, assign_inside_condition
@@ -86,7 +78,7 @@ Style/ConditionalAssignment:
86
78
  - 'lib/cataract/pure/imports.rb'
87
79
  - 'lib/cataract/pure/flatten.rb'
88
80
 
89
- # Offense count: 4
81
+ # Offense count: 8
90
82
  # Configuration parameters: AllowedConstants.
91
83
  Style/Documentation:
92
84
  Exclude:
@@ -107,7 +99,7 @@ Style/HashLikeCase:
107
99
  - 'benchmarks/serialization_tests.rb'
108
100
  - 'benchmarks/specificity_tests.rb'
109
101
 
110
- # Offense count: 14
102
+ # Offense count: 28
111
103
  # This cop supports unsafe autocorrection (--autocorrect-all).
112
104
  Style/IdenticalConditionalBranches:
113
105
  Exclude:
@@ -118,7 +110,7 @@ Style/IdenticalConditionalBranches:
118
110
  - 'lib/cataract/pure/parser.rb'
119
111
  - 'lib/cataract/pure/serializer.rb'
120
112
 
121
- # Offense count: 5
113
+ # Offense count: 10
122
114
  # This cop supports safe autocorrection (--autocorrect).
123
115
  # Configuration parameters: AllowIfModifier.
124
116
  Style/IfInsideElse:
@@ -127,19 +119,19 @@ Style/IfInsideElse:
127
119
  - 'lib/cataract/pure/serializer.rb'
128
120
  - 'lib/cataract/pure/specificity.rb'
129
121
 
130
- # Offense count: 1
122
+ # Offense count: 2
131
123
  # This cop supports safe autocorrection (--autocorrect).
132
124
  Style/NegatedIfElseCondition:
133
125
  Exclude:
134
126
  - 'lib/cataract/pure/flatten.rb'
135
127
 
136
- # Offense count: 1
128
+ # Offense count: 2
137
129
  # This cop supports safe autocorrection (--autocorrect).
138
130
  Style/RedundantAssignment:
139
131
  Exclude:
140
132
  - 'lib/cataract/pure/specificity.rb'
141
133
 
142
- # Offense count: 2
134
+ # Offense count: 4
143
135
  # This cop supports safe autocorrection (--autocorrect).
144
136
  Style/RedundantParentheses:
145
137
  Exclude:
@@ -152,7 +144,7 @@ Style/RedundantRegexpArgument:
152
144
  Exclude:
153
145
  - 'test/test_stylesheet.rb'
154
146
 
155
- # Offense count: 3
147
+ # Offense count: 6
156
148
  # This cop supports safe autocorrection (--autocorrect).
157
149
  # Configuration parameters: AllowModifier.
158
150
  Style/SoleNestedConditional:
data/.yardopts ADDED
@@ -0,0 +1,9 @@
1
+ --output-dir docs
2
+ --readme README.md
3
+ --title 'Cataract - Fast CSS Parser'
4
+ --exclude lib/cataract/pure\.rb
5
+ --exclude lib/cataract/pure/
6
+ lib/**/*.rb
7
+ ext/**/*.c
8
+ -
9
+ docs/files/EXAMPLE.md
data/BENCHMARKS.md CHANGED
@@ -8,11 +8,11 @@ Performance comparison between Cataract's C extension and pure Ruby implementati
8
8
 
9
9
  ## Test Environment
10
10
 
11
- - **Ruby**: ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +YJIT +PRISM [arm64-darwin23]
11
+ - **Ruby**: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin25]
12
12
  - **CPU**: Apple M1 Pro
13
13
  - **Memory**: 32GB
14
- - **OS**: macOS 14.5
15
- - **Generated**: 2025-11-11T16:02:08-06:00
14
+ - **OS**: macOS 26.3.1
15
+ - **Generated**: 2026-07-08T10:35:16-05:00
16
16
 
17
17
  ## CSS Parsing
18
18
 
@@ -20,17 +20,17 @@ Time to parse CSS into internal data structures
20
20
 
21
21
  | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
22
22
  |-----------|--------|----------------|-------------|
23
- | Small CSS (64 lines, 1.0KB) | 37.27K i/s | 3.32K i/s | 13.48K i/s |
24
- | Medium CSS with @media (139 lines, 1.6KB) | 34.66K i/s | 2.06K i/s | 8.94K i/s |
25
- | Selector lists (3500 lines, 62.5KB, 500 lists) | 485.7 i/s | 55.7 i/s | 213.6 i/s |
23
+ | Small CSS (64 lines, 1.0KB) | 36.76K i/s | 3.33K i/s | 13.66K i/s |
24
+ | Medium CSS with @media (139 lines, 1.6KB) | 29.98K i/s | 2.11K i/s | 8.98K i/s |
25
+ | Selector lists (3500 lines, 62.5KB, 500 lists) | 447.4 i/s | 58.0 i/s | 219.8 i/s |
26
26
 
27
27
  ### Speedups
28
28
 
29
29
  | Comparison | Speedup |
30
30
  |------------|---------|
31
- | Native vs Pure (no YJIT) | 14.4x faster (avg) |
32
- | Native vs Pure (YJIT) | 3.38x faster (avg) |
33
- | YJIT impact on Pure Ruby | 4.23x faster (avg) |
31
+ | Native vs Pure (no YJIT) | 13.03x faster (avg) |
32
+ | Native vs Pure (YJIT) | 3.12x faster (avg) |
33
+ | YJIT impact on Pure Ruby | 4.15x faster (avg) |
34
34
 
35
35
  ### Parse Error Checking Overhead
36
36
 
@@ -38,17 +38,17 @@ Parse error detection can be enabled with `raise_parse_errors: true`. This compa
38
38
 
39
39
  | Configuration | Native | Pure (no YJIT) | Pure (YJIT) |
40
40
  |---------------|--------|----------------|-------------|
41
- | Medium CSS (139 lines) - no error checking | 35.18K i/s | 2.07K i/s | 9.05K i/s |
42
- | Medium CSS (139 lines) - with error checking | 34.84K i/s | 1.9K i/s | 8.19K i/s |
41
+ | Medium CSS (139 lines) - no error checking | 33.57K i/s | 2.12K i/s | 9.08K i/s |
42
+ | Medium CSS (139 lines) - with error checking | 33.09K i/s | 1.94K i/s | 8.19K i/s |
43
43
 
44
44
  **Overhead Analysis:**
45
45
 
46
46
 
47
47
  | Implementation | Overhead |
48
48
  |----------------|----------|
49
- | Native | ~0% (within noise) |
50
- | Pure (no YJIT) | 8.9% slower |
51
- | Pure (YJIT) | 10.4% slower |
49
+ | Native | 1.5% slower |
50
+ | Pure (no YJIT) | 9.2% slower |
51
+ | Pure (YJIT) | 10.9% slower |
52
52
 
53
53
  ---
54
54
 
@@ -58,19 +58,19 @@ Time to convert parsed CSS back to string format
58
58
 
59
59
  | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
60
60
  |-----------|--------|----------------|-------------|
61
- | to_s (Bootstrap - 191KB) | 1.32K i/s | 360.2 i/s | 656.6 i/s |
62
- | to_s (Compact utilities - 2.9KB) | 1.32K i/s | 360.2 i/s | 656.6 i/s |
63
- | to_formatted_s (Nested CSS - 1.3KB) | 176.13K i/s | 60.97K i/s | 121.5K i/s |
64
- | to_s with selector_lists (3.4KB) | 19.03K i/s | 8.01K i/s | 14.2K i/s |
65
- | Media filtering (Bootstrap print only) | 283.24K i/s | 97.28K i/s | 161.56K i/s |
61
+ | to_s (Bootstrap - 191KB) | 1.05K i/s | 368.6 i/s | 555.6 i/s |
62
+ | to_s (Compact utilities - 2.9KB) | 1.05K i/s | 368.6 i/s | 555.6 i/s |
63
+ | to_formatted_s (Nested CSS - 1.3KB) | 156.05K i/s | 53.98K i/s | 90.18K i/s |
64
+ | to_s with selector_lists (3.4KB) | 15.37K i/s | 7.71K i/s | 12.09K i/s |
65
+ | Media filtering (Bootstrap print only) | 1.18K i/s | 547.1 i/s | 769.2 i/s |
66
66
 
67
67
  ### Speedups
68
68
 
69
69
  | Comparison | Speedup |
70
70
  |------------|---------|
71
- | Native vs Pure (no YJIT) | 3.24x faster (avg) |
72
- | Native vs Pure (YJIT) | 1.73x faster (avg) |
73
- | YJIT impact on Pure Ruby | 1.86x faster (avg) |
71
+ | Native vs Pure (no YJIT) | 2.71x faster (avg) |
72
+ | Native vs Pure (YJIT) | 1.75x faster (avg) |
73
+ | YJIT impact on Pure Ruby | 1.55x faster (avg) |
74
74
 
75
75
  ---
76
76
 
@@ -80,20 +80,20 @@ Time to calculate CSS selector specificity values
80
80
 
81
81
  | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
82
82
  |-----------|--------|----------------|-------------|
83
- | Simple Selectors | 7.28M i/s | 506.61K i/s | 2.45M i/s |
84
- | Compound Selectors | 6.4M i/s | 186.62K i/s | 405.15K i/s |
85
- | Combinators | 5.04M i/s | 147.07K i/s | 255.92K i/s |
86
- | Pseudo-classes & Pseudo-elements | 5.17M i/s | 117.16K i/s | 195.12K i/s |
87
- | :not() Pseudo-class (CSS3) | 3.48M i/s | 103.47K i/s | 161.34K i/s |
88
- | Complex Real-world Selectors | 4.01M i/s | 52.41K i/s | 77.48K i/s |
83
+ | Simple Selectors | 1.97M i/s | 419.98K i/s | 1.44M i/s |
84
+ | Compound Selectors | 1.89M i/s | 186.59K i/s | 523.08K i/s |
85
+ | Combinators | 1.43M i/s | 142.61K i/s | 285.41K i/s |
86
+ | Pseudo-classes & Pseudo-elements | 1.41M i/s | 114.7K i/s | 235.07K i/s |
87
+ | :not() Pseudo-class (CSS3) | 1.52M i/s | 110.64K i/s | 247.84K i/s |
88
+ | Complex Real-world Selectors | 1.62M i/s | 56.2K i/s | 126.65K i/s |
89
89
 
90
90
  ### Speedups
91
91
 
92
92
  | Comparison | Speedup |
93
93
  |------------|---------|
94
- | Native vs Pure (no YJIT) | 39.54x faster (avg) |
95
- | Native vs Pure (YJIT) | 23.05x faster (avg) |
96
- | YJIT impact on Pure Ruby | 2.24x faster (avg) |
94
+ | Native vs Pure (no YJIT) | 13.28x faster (avg) |
95
+ | Native vs Pure (YJIT) | 5.82x faster (avg) |
96
+ | YJIT impact on Pure Ruby | 2.46x faster (avg) |
97
97
 
98
98
  ---
99
99
 
@@ -103,20 +103,20 @@ Time to flatten multiple CSS rule sets with same selector
103
103
 
104
104
  | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
105
105
  |-----------|--------|----------------|-------------|
106
- | No shorthand properties (large) | 13.91K i/s | 3.23K i/s | 6.39K i/s |
107
- | Simple properties | 144.19K i/s | 71.11K i/s | 101.38K i/s |
108
- | Cascade with specificity | 172.96K i/s | 69.16K i/s | 99.23K i/s |
109
- | Important declarations | 173.35K i/s | 69.12K i/s | 100.16K i/s |
110
- | Shorthand expansion | 13.91K i/s | 3.23K i/s | 6.39K i/s |
111
- | Complex flattening | 29.86K i/s | 15.77K i/s | 22.09K i/s |
106
+ | No shorthand properties (large) | 12.75K i/s | 3.69K i/s | 6.41K i/s |
107
+ | Simple properties | 142.64K i/s | 69.09K i/s | 100.71K i/s |
108
+ | Cascade with specificity | 178.29K i/s | 67.87K i/s | 101.77K i/s |
109
+ | Important declarations | 177.88K i/s | 68.12K i/s | 101.75K i/s |
110
+ | Shorthand expansion | 12.75K i/s | 3.69K i/s | 6.41K i/s |
111
+ | Complex flattening | 31.73K i/s | 16.04K i/s | 23.28K i/s |
112
112
 
113
113
  ### Speedups
114
114
 
115
115
  | Comparison | Speedup |
116
116
  |------------|---------|
117
- | Native vs Pure (no YJIT) | 2.54x faster (avg) |
118
- | Native vs Pure (YJIT) | 1.63x faster (avg) |
119
- | YJIT impact on Pure Ruby | 1.53x faster (avg) |
117
+ | Native vs Pure (no YJIT) | 2.45x faster (avg) |
118
+ | Native vs Pure (YJIT) | 1.6x faster (avg) |
119
+ | YJIT impact on Pure Ruby | 1.52x faster (avg) |
120
120
 
121
121
  ---
122
122
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [0.5.0] - 2026-07-09
2
+
3
+ - Feature: `@supports (condition) { ... }` now preserves its condition and the rules it wraps through parse -> serialize, in both backends - previously the condition was discarded entirely and the wrapped rules were silently flattened into the surrounding document with no trace of ever being conditional.
4
+ - Feature: `@container name (condition) { ... }` (container queries) now preserves its name, condition, and wrapped rules the same way - handles named, anonymous, and name-only forms, and nests correctly with `@media`/`@supports`/itself.
5
+ - Feature: `@layer` (cascade layers) now preserves its name and wrapped rules, both in block form (`@layer name { ... }`, including anonymous and dotted/nested names like `framework.layout`) and statement form (`@layer a, b;`, which declares layer order with no wrapped rules at all - previously silently corrupted parsing of whatever rule came right after it).
6
+ - Fix: `@media`/`@supports`/`@container`/`@scope`/`@layer` with no whitespace before a following `(` (e.g. minified `@supports(display:grid){...}`) no longer misparses the at-rule name, which silently corrupted the rest of the block. Present in both backends since `@media`/`@supports` shipped.
7
+ - Feature: `@namespace` (default and prefixed, e.g. `@namespace svg url(http://www.w3.org/2000/svg);`) is now preserved through parse -> serialize, in both backends - previously it wasn't modelled at all. Namespaced selectors (`ns|E`, `*|E`, `|E`, and the attribute-selector equivalents) already parsed as opaque text, but their specificity was miscounted - a namespace prefix was counted as its own type selector instead of contributing nothing, e.g. `svg|rect` computed as specificity 2 instead of 1. Both are fixed now.
8
+
9
+ ## [0.4.0] - 2026-07-08
10
+
11
+ - Fix: `Declarations.new(some_string)` (standalone CSS declaration-block parsing) now works under the pure Ruby backend (`CATARACT_PURE=1`) - previously raised `NoMethodError`.
12
+ - Fix: a compound `@media` list (e.g. `@media screen, print { ... }`) no longer silently collapses to just its first member when the document also contains CSS nesting elsewhere.
13
+ - Fix: `to_s`/`to_formatted_s` with a specific `media:` filter (anything but `:all`) could crash while grouping comma-separated selector lists (e.g. `h1, h2 { ... }`), if an unrelated rule elsewhere in the document was excluded by the filter. Present in both backends since at least 0.3.0.
14
+ - Fix: `Stylesheet#flatten` (non-destructive) could silently expand shorthand properties (e.g. `margin: 0`) on the *original* stylesheet as a side effect, under the pure Ruby backend. Present since at least 0.3.0; the C extension was unaffected.
15
+ - Breaking (minor): removed `Cataract.merge` (a long-deprecated alias for `.flatten`) and `Cataract.parse_media_types` (undocumented, no real callers) from the C extension's public surface. Use `Cataract.flatten`/`Stylesheet#flatten` instead of `.merge`.
16
+ - Internal: `Cataract`'s own directly-callable methods are now just `.flatten` and `.parse_css`. Previously `Cataract` also exposed `calculate_specificity`, `expand_shorthand`, `parse_declarations`, `stylesheet_to_s`, `stylesheet_to_formatted_s`, and `_parse_css` directly - undocumented implementation leakage, not intended for direct use. The documented ways to reach this functionality (`Rule#specificity`, `Rule#expanded_declarations`, `Declarations.new(some_string)`, `Stylesheet#to_s`/`#to_formatted_s`) are unchanged. The native C extension and pure Ruby backend also now live under `Cataract::Backends::Native` / `Cataract::Backends::Pure`, resolved once per process and held per-`Stylesheet` - this lets both backends coexist in the same process (e.g. for direct comparison). Existing callers relying only on the documented public API should notice nothing.
17
+
1
18
  ## [0.3.0] - 2026-07-06
2
19
 
3
20
  - Security/Breaking: `Stylesheet#load_uri` and `#load_file` now reject `file://` paths under `/etc/`, `/proc/`, `/sys/`, and `/dev/` by default (override with `dangerous_path_prefixes: []`). Previously these methods used a separate, unvalidated fetch implementation; they now reuse `ImportResolver` (the same validation and fetching `@import` resolution already uses), which also fixes `load_uri` not following HTTP redirects.