cataract 0.2.5 → 0.4.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/.github/workflows/test.yml +6 -6
- data/.gitignore +17 -6
- data/.rubocop_todo.yml +11 -19
- data/BENCHMARKS.md +40 -40
- data/CHANGELOG.md +28 -1
- data/Gemfile +2 -2
- data/examples/css_analyzer/analyzer.rb +9 -3
- data/examples/css_analyzer/analyzers/base.rb +1 -1
- data/ext/cataract/cataract.c +312 -550
- data/ext/cataract/cataract.h +126 -16
- data/ext/cataract/css_parser.c +782 -806
- data/ext/cataract/extconf.rb +1 -2
- data/ext/cataract/flatten.c +279 -991
- data/ext/cataract/shorthand_expander.c +80 -102
- data/ext/cataract_color/color_conversion.c +1 -1
- data/lib/cataract/declarations.rb +26 -10
- data/lib/cataract/import_resolver.rb +36 -16
- data/lib/cataract/native.rb +30 -0
- data/lib/cataract/pure/byte_constants.rb +70 -67
- data/lib/cataract/pure/declarations.rb +125 -0
- data/lib/cataract/pure/flatten.rb +1195 -1182
- data/lib/cataract/pure/parser.rb +1721 -1652
- data/lib/cataract/pure/serializer.rb +575 -710
- data/lib/cataract/pure/specificity.rb +152 -175
- data/lib/cataract/pure.rb +73 -108
- data/lib/cataract/rule.rb +6 -3
- data/lib/cataract/stylesheet.rb +475 -415
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +41 -30
- data/lib/tasks/profile.rake +6 -3
- metadata +4 -14
- data/ext/cataract/import_scanner.c +0 -174
- data/ext/cataract_old/cataract.c +0 -393
- data/ext/cataract_old/cataract.h +0 -250
- data/ext/cataract_old/css_parser.c +0 -933
- data/ext/cataract_old/extconf.rb +0 -67
- data/ext/cataract_old/import_scanner.c +0 -174
- data/ext/cataract_old/merge.c +0 -776
- data/ext/cataract_old/shorthand_expander.c +0 -902
- data/ext/cataract_old/specificity.c +0 -213
- data/ext/cataract_old/stylesheet.c +0 -290
- data/ext/cataract_old/value_splitter.c +0 -116
- 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: 67046f0d371d7b0b077971e8634ff4054e64fe23a55c80b1a9f80e3dee6d804b
|
|
4
|
+
data.tar.gz: 776865b7490e22c326c0bab93a3da32f088177817b3e237c68b66e233df39021
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21f5ccd9b4ed3e0831fa8723d02493b7b4a3582c359a3e3bae927e554d207422a0139592b23889d273ca1ed3a1888679c729d9ad0f4aa2bf4c90d18b2e16fe0c
|
|
7
|
+
data.tar.gz: f149256218337a47a1b27cd945857dd358199ceb47f70274c0c7db5cb3fa196d84a55281528c0f22d12da4bbbaff3b92c6e315210906a75aee2d304a9b91c275
|
data/.github/workflows/test.yml
CHANGED
|
@@ -18,7 +18,7 @@ jobs:
|
|
|
18
18
|
strategy:
|
|
19
19
|
fail-fast: false
|
|
20
20
|
matrix:
|
|
21
|
-
ruby: ['3.1', '3.2', '3.3', '3.4']
|
|
21
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
|
|
22
22
|
|
|
23
23
|
steps:
|
|
24
24
|
- uses: actions/checkout@v4
|
|
@@ -37,8 +37,8 @@ jobs:
|
|
|
37
37
|
env:
|
|
38
38
|
COVERAGE: 1
|
|
39
39
|
|
|
40
|
-
- name: Run debug test (Ruby
|
|
41
|
-
if: matrix.ruby == '
|
|
40
|
+
- name: Run debug test (Ruby 4.0 only)
|
|
41
|
+
if: matrix.ruby == '4.0'
|
|
42
42
|
run: |
|
|
43
43
|
bundle exec rake clean compile
|
|
44
44
|
bundle exec ruby -Ilib:test test/test_stylesheet.rb
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
CONFIGURE_ARGS: --enable-debug
|
|
47
47
|
|
|
48
48
|
- name: Upload coverage to Codecov
|
|
49
|
-
if: runner.os == 'Linux' && matrix.ruby == '
|
|
49
|
+
if: runner.os == 'Linux' && matrix.ruby == '4.0'
|
|
50
50
|
uses: codecov/codecov-action@v5
|
|
51
51
|
with:
|
|
52
52
|
fail_ci_if_error: false
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
verbose: true
|
|
55
55
|
|
|
56
56
|
- name: Upload coverage report artifact
|
|
57
|
-
if: runner.os == 'Linux' && matrix.ruby == '
|
|
57
|
+
if: runner.os == 'Linux' && matrix.ruby == '4.0'
|
|
58
58
|
uses: actions/upload-artifact@v4
|
|
59
59
|
with:
|
|
60
60
|
name: coverage-report
|
|
@@ -69,7 +69,7 @@ jobs:
|
|
|
69
69
|
- name: Set up Ruby
|
|
70
70
|
uses: ruby/setup-ruby@v1
|
|
71
71
|
with:
|
|
72
|
-
ruby-version: '
|
|
72
|
+
ruby-version: '4.0'
|
|
73
73
|
bundler-cache: true
|
|
74
74
|
|
|
75
75
|
- name: Run rubocop
|
data/.gitignore
CHANGED
|
@@ -4,12 +4,11 @@ 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
|
|
10
|
+
ext/**/*.bundle
|
|
11
|
+
ext/**/*.dSYM/
|
|
13
12
|
|
|
14
13
|
# Makefile generated by extconf.rb
|
|
15
14
|
ext/**/Makefile
|
|
@@ -29,19 +28,31 @@ Thumbs.db
|
|
|
29
28
|
vendor/
|
|
30
29
|
.bundle/
|
|
31
30
|
|
|
31
|
+
# Node (this is a Ruby gem; no JS toolchain is tracked, but guard against stray npm installs)
|
|
32
|
+
node_modules/
|
|
33
|
+
|
|
32
34
|
# Test artifacts
|
|
33
35
|
coverage/
|
|
34
36
|
test/.benchmark_results/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
# Fuzzer output (crash inputs, logs, recent-input dumps)
|
|
39
|
+
scripts/fuzzer/output/
|
|
38
40
|
|
|
39
41
|
# Benchmark results
|
|
40
42
|
benchmarks/.results/
|
|
41
43
|
benchmarks/.benchmark_results/
|
|
42
44
|
|
|
45
|
+
# CSS analyzer example debug output
|
|
46
|
+
examples/css_analyzer/output/
|
|
47
|
+
|
|
43
48
|
# Documentation (generated by YARD)
|
|
44
49
|
docs/
|
|
45
50
|
!docs/files/
|
|
46
51
|
!docs/files/EXAMPLE.md
|
|
47
52
|
.yardoc/
|
|
53
|
+
|
|
54
|
+
# Claude Code local project instructions (personal, not shared)
|
|
55
|
+
.claude/CLAUDE.md
|
|
56
|
+
|
|
57
|
+
.cache/
|
|
58
|
+
.cocci/
|
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/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,4 +1,31 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.4.0] - 2026-07-08
|
|
2
|
+
|
|
3
|
+
- Fix: `Declarations.new(some_string)` (standalone CSS declaration-block parsing) now works under the pure Ruby backend (`CATARACT_PURE=1`) - previously raised `NoMethodError`.
|
|
4
|
+
- 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.
|
|
5
|
+
- 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.
|
|
6
|
+
- 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.
|
|
7
|
+
- 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`.
|
|
8
|
+
- 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.
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - 2026-07-06
|
|
11
|
+
|
|
12
|
+
- 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.
|
|
13
|
+
- Fix: rules nested 3+ levels deep, or nested inside a top-level `@media`/`@supports`/`@container`/`@scope` block, could serialize incorrectly (silently flattened, or printed as an unrelated top-level rule) in the pure Ruby backend.
|
|
14
|
+
- Fix: a bare type selector nested without `&` (invalid CSS nesting) no longer corrupts serialized output with unbalanced braces, in both the C and pure Ruby parsers.
|
|
15
|
+
- Fix: `@keyframes` parsing no longer pollutes the outer rule list with its own inner selectors.
|
|
16
|
+
- Fix: at-rules (`@keyframes`, `@font-face`, etc.) nested inside `@media` blocks now correctly keep their media context across parsing, merging (`add_block`, `concat`/`+`), `@import` resolution, and serialization - previously this could be lost, misattributed, or (combined with CSS nesting) raise `NoMethodError` in the pure Ruby serializer.
|
|
17
|
+
- Fix: `remove_rules!` and the `-` operator no longer desync media query ids from the rules that reference them.
|
|
18
|
+
- Fix: resolving `@import`s no longer leaves duplicate/colliding rule ids.
|
|
19
|
+
- Fix: a plain `@import "file.css";` (no media qualifier) no longer loses that file's own `@media` rules.
|
|
20
|
+
- Fix: `Stylesheet#concat`/`+` now merges the other stylesheet's media queries and selector-list groupings too - previously this silently dropped `h1, h2`-style grouping and corrupted media attribution.
|
|
21
|
+
- Fix: `@import` media queries with a type keyword (e.g. `"screen and (min-width: 500px)"`) no longer keep a stray `"and"` in the parsed conditions.
|
|
22
|
+
- Fix: `to_s`/`to_formatted_s` media filtering is now consistent between the two - both include base (non-media) rules when filtering to a specific media type; previously `to_s` excluded them while `to_formatted_s` included them.
|
|
23
|
+
- Fix: values containing `url(...;...)` no longer lose everything after the first semicolon when the rule also uses CSS nesting.
|
|
24
|
+
- Fix: `!important` now parses correctly with whitespace between `!` and `important`, per the CSS grammar.
|
|
25
|
+
- Fix: expanding `border`, `border-{top,right,bottom,left}`, `font`, `background`, and `list-style` shorthands (used when comparing rules for equality) no longer silently drops `!important`.
|
|
26
|
+
- Fix: `Declarations`/`Stylesheet` `#dup`/`#clone` now produce fully independent copies; previously mutating a copy could mutate the original.
|
|
27
|
+
- Performance: CSS selector specificity calculation is ~30% faster (no longer re-allocates an internal lookup array on every call).
|
|
28
|
+
- Performance: native (C extension) CSS flattening is ~1-5% faster (fewer string allocations when recreating shorthand properties).
|
|
2
29
|
|
|
3
30
|
## [0.2.5 - 2025-11-25]
|
|
4
31
|
|
data/Gemfile
CHANGED
|
@@ -16,10 +16,10 @@ gem 'css_parser', '~> 1.0' # for benchmarking against
|
|
|
16
16
|
gem 'minitest'
|
|
17
17
|
gem 'minitest-spec'
|
|
18
18
|
gem 'nokogiri' # for docs
|
|
19
|
-
gem 'ruby-prof', require: false # for profiling
|
|
19
|
+
gem 'ruby-prof', require: false, platforms: :ruby # for profiling (MRI only)
|
|
20
20
|
gem 'simplecov', require: false
|
|
21
21
|
gem 'simplecov-cobertura', require: false
|
|
22
|
-
gem 'stackprof', require: false # for profiling
|
|
22
|
+
gem 'stackprof', require: false, platforms: :ruby # for profiling (MRI only)
|
|
23
23
|
gem 'webmock', '~> 3.0' # for testing URL loading
|
|
24
24
|
|
|
25
25
|
gem 'overcommit', '~> 0.64', group: :development
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'erb'
|
|
4
|
+
require 'fileutils'
|
|
4
5
|
require 'uri'
|
|
5
6
|
require_relative '../../lib/cataract'
|
|
6
7
|
require_relative 'analyzers/properties'
|
|
@@ -71,13 +72,18 @@ module CSSAnalyzer
|
|
|
71
72
|
def save_parsed_css
|
|
72
73
|
# Generate a unique filename based on source
|
|
73
74
|
source_slug = @source.gsub(%r{[:/]}, '_').gsub(/[^a-zA-Z0-9_.-]/, '')
|
|
74
|
-
|
|
75
|
+
|
|
76
|
+
# Write next to this script (not the caller's cwd) so running the
|
|
77
|
+
# analyzer from repo root doesn't scatter debug output there.
|
|
78
|
+
output_dir = File.join(__dir__, 'output')
|
|
79
|
+
FileUtils.mkdir_p(output_dir)
|
|
80
|
+
path = File.join(output_dir, "parsed-css-#{source_slug}.css")
|
|
75
81
|
|
|
76
82
|
# Serialize stylesheet to CSS
|
|
77
83
|
css_output = @stylesheet.to_s
|
|
78
84
|
|
|
79
|
-
File.write(
|
|
80
|
-
warn "Parsed CSS saved to #{
|
|
85
|
+
File.write(path, css_output)
|
|
86
|
+
warn "Parsed CSS saved to #{path} (#{@stylesheet.size} rules, #{css_output.length} bytes)"
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
private
|
|
@@ -26,7 +26,7 @@ module CSSAnalyzer
|
|
|
26
26
|
# @param rule [Cataract::Rule] The rule to check
|
|
27
27
|
# @return [Array<Symbol>] Array of media query symbols this rule appears in
|
|
28
28
|
def media_queries_for_rule(rule)
|
|
29
|
-
stylesheet.
|
|
29
|
+
stylesheet.media_index.select { |_media, ids| ids.include?(rule.id) }.keys
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|