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 +4 -4
- data/.gitignore +0 -3
- data/.rubocop_todo.yml +11 -19
- data/.yardopts +9 -0
- data/BENCHMARKS.md +40 -40
- data/CHANGELOG.md +17 -0
- data/ext/cataract/cataract.c +193 -111
- data/ext/cataract/cataract.h +13 -3
- data/ext/cataract/css_parser.c +298 -54
- data/ext/cataract/flatten.c +6 -2
- data/ext/cataract/specificity.c +13 -2
- data/lib/cataract/at_rule.rb +4 -2
- data/lib/cataract/conditional_group.rb +72 -0
- data/lib/cataract/declarations.rb +14 -4
- data/lib/cataract/native.rb +31 -0
- data/lib/cataract/pure/byte_constants.rb +70 -66
- data/lib/cataract/pure/declarations.rb +125 -0
- data/lib/cataract/pure/flatten.rb +1203 -1182
- data/lib/cataract/pure/parser.rb +1953 -1715
- data/lib/cataract/pure/serializer.rb +653 -706
- data/lib/cataract/pure/specificity.rb +173 -144
- data/lib/cataract/pure.rb +74 -101
- data/lib/cataract/rule.rb +14 -7
- data/lib/cataract/stylesheet.rb +84 -25
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +41 -30
- data/lib/tasks/profile.rake +6 -3
- metadata +5 -2
- data/lib/cataract/pure/helpers.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f107809adb744598338be592626fc5ebbfdb81c2813957c7e5ed1652f37a607
|
|
4
|
+
data.tar.gz: 810a6aadbe0a8c2ce4eff7c307c17c9de354f1a3bc0d9509ae4e83f1d7e1c8b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b7e85ed2f9ad0b9154e78c795bc0548ca1eaeb55f8f24b2b644852394a506173afb64042f55a3c02c13c21e1b539738695a83c8f774aaf0311bdda8f00da42b9
|
|
7
|
+
data.tar.gz: f6ea7361ad3d180aaf9e4faa72bf686906a0f81345df59f70856734e50f17b79d4dc20823d0fae520279767cd289fb8e42bbd619230f095d1d26f9ed95aa1094
|
data/.gitignore
CHANGED
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
147
|
+
# Offense count: 6
|
|
156
148
|
# This cop supports safe autocorrection (--autocorrect).
|
|
157
149
|
# Configuration parameters: AllowModifier.
|
|
158
150
|
Style/SoleNestedConditional:
|
data/.yardopts
ADDED
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
|
|
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
|
|
15
|
-
- **Generated**:
|
|
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) |
|
|
24
|
-
| Medium CSS with @media (139 lines, 1.6KB) |
|
|
25
|
-
| Selector lists (3500 lines, 62.5KB, 500 lists) |
|
|
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) |
|
|
32
|
-
| Native vs Pure (YJIT) | 3.
|
|
33
|
-
| YJIT impact on Pure Ruby | 4.
|
|
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 |
|
|
42
|
-
| Medium CSS (139 lines) - with error checking |
|
|
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 |
|
|
50
|
-
| Pure (no YJIT) |
|
|
51
|
-
| Pure (YJIT) | 10.
|
|
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.
|
|
62
|
-
| to_s (Compact utilities - 2.9KB) | 1.
|
|
63
|
-
| to_formatted_s (Nested CSS - 1.3KB) |
|
|
64
|
-
| to_s with selector_lists (3.4KB) |
|
|
65
|
-
| Media filtering (Bootstrap print only) |
|
|
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) |
|
|
72
|
-
| Native vs Pure (YJIT) | 1.
|
|
73
|
-
| YJIT impact on Pure Ruby | 1.
|
|
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 |
|
|
84
|
-
| Compound Selectors |
|
|
85
|
-
| Combinators |
|
|
86
|
-
| Pseudo-classes & Pseudo-elements |
|
|
87
|
-
| :not() Pseudo-class (CSS3) |
|
|
88
|
-
| Complex Real-world Selectors |
|
|
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) |
|
|
95
|
-
| Native vs Pure (YJIT) |
|
|
96
|
-
| YJIT impact on Pure Ruby | 2.
|
|
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) |
|
|
107
|
-
| Simple properties |
|
|
108
|
-
| Cascade with specificity |
|
|
109
|
-
| Important declarations |
|
|
110
|
-
| Shorthand expansion |
|
|
111
|
-
| Complex flattening |
|
|
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.
|
|
118
|
-
| Native vs Pure (YJIT) | 1.
|
|
119
|
-
| YJIT impact on Pure Ruby | 1.
|
|
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.
|