cataract 0.1.4 → 0.2.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/ci-manual-rubies.yml +18 -1
- data/.rubocop.yml +36 -6
- data/.rubocop_todo.yml +7 -7
- data/BENCHMARKS.md +30 -30
- data/CHANGELOG.md +10 -0
- data/RAGEL_MIGRATION.md +2 -2
- data/README.md +7 -2
- data/Rakefile +24 -11
- data/cataract.gemspec +1 -1
- data/ext/cataract/cataract.c +12 -3
- data/ext/cataract/cataract.h +5 -3
- data/ext/cataract/css_parser.c +156 -32
- data/ext/cataract/extconf.rb +2 -2
- data/ext/cataract/{merge.c → flatten.c} +520 -468
- data/ext/cataract/shorthand_expander.c +164 -115
- data/lib/cataract/import_resolver.rb +60 -39
- data/lib/cataract/import_statement.rb +49 -0
- data/lib/cataract/pure/{merge.rb → flatten.rb} +39 -40
- data/lib/cataract/pure/imports.rb +13 -0
- data/lib/cataract/pure/parser.rb +108 -4
- data/lib/cataract/pure.rb +32 -9
- data/lib/cataract/rule.rb +51 -6
- data/lib/cataract/stylesheet.rb +343 -41
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +28 -24
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bcb5043777c52ed2a3f4bcd96966dcbe00e0f7bcf5ac410398afc855d59407f5
|
|
4
|
+
data.tar.gz: 65fabbfff1bbc559a0998f5a412800987ed2921252cdd2d4a757c6b59041c66a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cee4076685ff5a5e6ae8dbe441a9e7cefc8f1ad109fa7179373cb0bb39e4118b570ddfef68aeef573b002a683b3ca66148293f460b52a562472596d4d1e11842
|
|
7
|
+
data.tar.gz: de9ae8a5f1d684c797706b48e10e7372cda9ae280698e67ea3dac25be81331a901c9a181dd8ca04d2cb9ce46b2447fc0709501b0fd53c848bcc6981a4d286597
|
|
@@ -4,12 +4,29 @@ on:
|
|
|
4
4
|
workflow_dispatch:
|
|
5
5
|
|
|
6
6
|
jobs:
|
|
7
|
+
test-jruby:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- name: Set up Ruby
|
|
13
|
+
uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: jruby
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
|
|
18
|
+
- name: Display Ruby version
|
|
19
|
+
run: ruby --version
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: bundle exec rake test:pure
|
|
23
|
+
|
|
7
24
|
test-alt-rubies:
|
|
8
25
|
runs-on: ubuntu-latest
|
|
9
26
|
strategy:
|
|
10
27
|
fail-fast: false
|
|
11
28
|
matrix:
|
|
12
|
-
ruby: [
|
|
29
|
+
ruby: [truffleruby, ruby-head]
|
|
13
30
|
|
|
14
31
|
steps:
|
|
15
32
|
- uses: actions/checkout@v4
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
inherit_from: .rubocop_todo.yml
|
|
2
2
|
|
|
3
|
+
require:
|
|
4
|
+
- ./.rubocop/cataract/ban_assert_includes.rb
|
|
5
|
+
|
|
3
6
|
plugins:
|
|
4
7
|
- rubocop-performance
|
|
5
8
|
- rubocop-minitest
|
|
@@ -66,13 +69,17 @@ Layout/LineLength:
|
|
|
66
69
|
Minitest/MultipleAssertions:
|
|
67
70
|
Max: 10
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
# Disable ambiguous block association warnings in tests
|
|
73
|
+
# Tests frequently use assert/refute with blocks that call methods with their own blocks,
|
|
74
|
+
# e.g., `assert sheet.rules.any? { |r| r == '.box { color: red; }' }`
|
|
75
|
+
# This compares Rule objects against CSS strings using our custom Rule#== implementation.
|
|
76
|
+
# While this triggers the ambiguous block warning, it's intentional and clear in context.
|
|
77
|
+
Lint/AmbiguousBlockAssociation:
|
|
78
|
+
Exclude:
|
|
72
79
|
- 'test/**/*'
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
|
|
81
|
+
Style/Documentation:
|
|
82
|
+
Enabled: false
|
|
76
83
|
|
|
77
84
|
# Disable modifier if/unless enforcement - use it when it's clearer, not because a cop says so
|
|
78
85
|
Style/IfUnlessModifier:
|
|
@@ -143,3 +150,26 @@ Style/MapIntoArray:
|
|
|
143
150
|
Style/OptionalBooleanParameter:
|
|
144
151
|
Exclude:
|
|
145
152
|
- 'lib/cataract/pure/**/*.rb'
|
|
153
|
+
|
|
154
|
+
Style/StderrPuts:
|
|
155
|
+
Exclude:
|
|
156
|
+
- 'scripts/**/*.rb'
|
|
157
|
+
|
|
158
|
+
# Disable all performance cops in tests - test clarity is more important than micro-optimizations
|
|
159
|
+
# Performance suggestions often hurt readability without meaningful benefit in test code
|
|
160
|
+
Performance:
|
|
161
|
+
Exclude:
|
|
162
|
+
- 'test/**/*'
|
|
163
|
+
|
|
164
|
+
Style/HashLikeCase:
|
|
165
|
+
Exclude:
|
|
166
|
+
- 'benchmarks/**/*'
|
|
167
|
+
|
|
168
|
+
# Custom Cataract cops
|
|
169
|
+
Cataract/BanAssertIncludes:
|
|
170
|
+
Enabled: true
|
|
171
|
+
Include:
|
|
172
|
+
- 'test/**/*'
|
|
173
|
+
Exclude:
|
|
174
|
+
- 'test/test_benchmark_doc_generator.rb'
|
|
175
|
+
- 'test/support/**/*' # Support files define assert_contains which uses assert_includes internally
|
data/.rubocop_todo.yml
CHANGED
|
@@ -34,13 +34,13 @@ Layout/MultilineOperationIndentation:
|
|
|
34
34
|
# This cop supports safe autocorrection (--autocorrect).
|
|
35
35
|
Lint/AmbiguousOperatorPrecedence:
|
|
36
36
|
Exclude:
|
|
37
|
-
- 'lib/cataract/pure/
|
|
37
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
38
38
|
|
|
39
39
|
# Offense count: 2
|
|
40
40
|
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
|
|
41
41
|
Lint/DuplicateBranch:
|
|
42
42
|
Exclude:
|
|
43
|
-
- 'lib/cataract/pure/
|
|
43
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
44
44
|
|
|
45
45
|
# Offense count: 1
|
|
46
46
|
Lint/IneffectiveAccessModifier:
|
|
@@ -74,7 +74,7 @@ Minitest/EmptyLineBeforeAssertionMethods:
|
|
|
74
74
|
Performance/UnfreezeString:
|
|
75
75
|
Exclude:
|
|
76
76
|
- 'lib/cataract/pure.rb'
|
|
77
|
-
- 'lib/cataract/pure/
|
|
77
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
78
78
|
- 'lib/cataract/pure/parser.rb'
|
|
79
79
|
|
|
80
80
|
# Offense count: 4
|
|
@@ -84,7 +84,7 @@ Performance/UnfreezeString:
|
|
|
84
84
|
Style/ConditionalAssignment:
|
|
85
85
|
Exclude:
|
|
86
86
|
- 'lib/cataract/pure/imports.rb'
|
|
87
|
-
- 'lib/cataract/pure/
|
|
87
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
88
88
|
|
|
89
89
|
# Offense count: 4
|
|
90
90
|
# Configuration parameters: AllowedConstants.
|
|
@@ -123,7 +123,7 @@ Style/IdenticalConditionalBranches:
|
|
|
123
123
|
# Configuration parameters: AllowIfModifier.
|
|
124
124
|
Style/IfInsideElse:
|
|
125
125
|
Exclude:
|
|
126
|
-
- 'lib/cataract/pure/
|
|
126
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
127
127
|
- 'lib/cataract/pure/serializer.rb'
|
|
128
128
|
- 'lib/cataract/pure/specificity.rb'
|
|
129
129
|
|
|
@@ -131,7 +131,7 @@ Style/IfInsideElse:
|
|
|
131
131
|
# This cop supports safe autocorrection (--autocorrect).
|
|
132
132
|
Style/NegatedIfElseCondition:
|
|
133
133
|
Exclude:
|
|
134
|
-
- 'lib/cataract/pure/
|
|
134
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
135
135
|
|
|
136
136
|
# Offense count: 1
|
|
137
137
|
# This cop supports safe autocorrection (--autocorrect).
|
|
@@ -157,7 +157,7 @@ Style/RedundantRegexpArgument:
|
|
|
157
157
|
# Configuration parameters: AllowModifier.
|
|
158
158
|
Style/SoleNestedConditional:
|
|
159
159
|
Exclude:
|
|
160
|
-
- 'lib/cataract/pure/
|
|
160
|
+
- 'lib/cataract/pure/flatten.rb'
|
|
161
161
|
- 'lib/cataract/pure/parser.rb'
|
|
162
162
|
|
|
163
163
|
# Offense count: 3
|
data/BENCHMARKS.md
CHANGED
|
@@ -20,15 +20,15 @@ Time to parse CSS into internal data structures
|
|
|
20
20
|
|
|
21
21
|
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
22
22
|
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
23
|
-
| Small CSS (64 lines, 1.0KB) |
|
|
24
|
-
| Medium CSS with @media (139 lines, 1.6KB) |
|
|
23
|
+
| Small CSS (64 lines, 1.0KB) | 62.47K i/s | 3.59K i/s | 15.67K i/s | 4.73K i/s | 6.17K i/s |
|
|
24
|
+
| Medium CSS with @media (139 lines, 1.6KB) | 39.7K i/s | 2.05K i/s | 8.64K i/s | 2.76K i/s | 3.52K i/s |
|
|
25
25
|
|
|
26
26
|
### Speedups
|
|
27
27
|
|
|
28
28
|
| Comparison | Speedup |
|
|
29
29
|
|------------|---------|
|
|
30
|
-
| Native vs Pure (no YJIT) | 18.
|
|
31
|
-
| Native vs Pure (YJIT) | 4.
|
|
30
|
+
| Native vs Pure (no YJIT) | 18.4x faster (avg) |
|
|
31
|
+
| Native vs Pure (YJIT) | 4.2x faster (avg) |
|
|
32
32
|
| YJIT impact on Pure Ruby | 4.31x faster (avg) |
|
|
33
33
|
|
|
34
34
|
---
|
|
@@ -39,16 +39,16 @@ Time to convert parsed CSS back to string format
|
|
|
39
39
|
|
|
40
40
|
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
41
41
|
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
42
|
-
| Full Serialization (Bootstrap CSS - 191KB) | 1.
|
|
43
|
-
| Media Type Filtering (print only) | 282.
|
|
42
|
+
| Full Serialization (Bootstrap CSS - 191KB) | 1.77K i/s | 444.3 i/s | 676.3 i/s | 37.1 i/s | 35.9 i/s |
|
|
43
|
+
| Media Type Filtering (print only) | 282.58K i/s | 114.27K i/s | 155.82K i/s | 2.67K i/s | 4.03K i/s |
|
|
44
44
|
|
|
45
45
|
### Speedups
|
|
46
46
|
|
|
47
47
|
| Comparison | Speedup |
|
|
48
48
|
|------------|---------|
|
|
49
|
-
| Native vs Pure (no YJIT) | 3.
|
|
50
|
-
| Native vs Pure (YJIT) | 1.
|
|
51
|
-
| YJIT impact on Pure Ruby | 1.
|
|
49
|
+
| Native vs Pure (no YJIT) | 3.23x faster (avg) |
|
|
50
|
+
| Native vs Pure (YJIT) | 1.82x faster (avg) |
|
|
51
|
+
| YJIT impact on Pure Ruby | 1.36x faster (avg) |
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -58,43 +58,43 @@ Time to calculate CSS selector specificity values
|
|
|
58
58
|
|
|
59
59
|
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
60
60
|
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
61
|
-
| Simple Selectors |
|
|
62
|
-
| Compound Selectors | 6.
|
|
63
|
-
| Combinators | 5.
|
|
64
|
-
| Pseudo-classes & Pseudo-elements | 5.
|
|
65
|
-
| :not() Pseudo-class (CSS3) | 3.
|
|
66
|
-
| Complex Real-world Selectors | 4.
|
|
61
|
+
| Simple Selectors | 7.35M i/s | 506.04K i/s | 2.59M i/s | 357.72K i/s | 353.42K i/s |
|
|
62
|
+
| Compound Selectors | 6.36M i/s | 184.96K i/s | 406.99K i/s | 216.59K i/s | 214.86K i/s |
|
|
63
|
+
| Combinators | 5.04M i/s | 146.91K i/s | 252.94K i/s | 185.44K i/s | 183.71K i/s |
|
|
64
|
+
| Pseudo-classes & Pseudo-elements | 5.1M i/s | 116.46K i/s | 197.06K i/s | 113.05K i/s | 113.99K i/s |
|
|
65
|
+
| :not() Pseudo-class (CSS3) | 3.23M i/s | 102.63K i/s | 161.55K i/s | 144.04K i/s | 144.53K i/s |
|
|
66
|
+
| Complex Real-world Selectors | 4.01M i/s | 52.03K i/s | 77.65K i/s | 83.91K i/s | 82.03K i/s |
|
|
67
67
|
|
|
68
68
|
### Speedups
|
|
69
69
|
|
|
70
70
|
| Comparison | Speedup |
|
|
71
71
|
|------------|---------|
|
|
72
|
-
| Native vs Pure (no YJIT) |
|
|
73
|
-
| Native vs Pure (YJIT) | 8.
|
|
74
|
-
| YJIT impact on Pure Ruby | 3.
|
|
72
|
+
| Native vs Pure (no YJIT) | 39.27x faster (avg) |
|
|
73
|
+
| Native vs Pure (YJIT) | 8.43x faster (avg) |
|
|
74
|
+
| YJIT impact on Pure Ruby | 3.32x faster (avg) |
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
-
## CSS
|
|
78
|
+
## CSS Flattening (Cascade)
|
|
79
79
|
|
|
80
|
-
Time to
|
|
80
|
+
Time to flatten multiple CSS rule sets with same selector
|
|
81
81
|
|
|
82
82
|
| Test Case | Native | Pure (no YJIT) | Pure (YJIT) | css_parser (no YJIT) | css_parser (YJIT) |
|
|
83
83
|
|-----------|--------|----------------|-------------|----------------------|-------------------|
|
|
84
|
-
| No shorthand properties (large) |
|
|
85
|
-
| Simple properties |
|
|
86
|
-
| Cascade with specificity |
|
|
87
|
-
| Important declarations |
|
|
88
|
-
| Shorthand expansion |
|
|
89
|
-
| Complex merging |
|
|
84
|
+
| No shorthand properties (large) | 21.34K i/s | 3.11K i/s | 5.23K i/s | 1.58K i/s | 2.3K i/s |
|
|
85
|
+
| Simple properties | 158.81K i/s | 75.72K i/s | 102.44K i/s | 28.29K i/s | 40.72K i/s |
|
|
86
|
+
| Cascade with specificity | 204.11K i/s | 77.93K i/s | 108.77K i/s | 31.92K i/s | 46.39K i/s |
|
|
87
|
+
| Important declarations | 203.43K i/s | 77.88K i/s | 109.38K i/s | 31.25K i/s | 45.25K i/s |
|
|
88
|
+
| Shorthand expansion | 21.34K i/s | 3.11K i/s | 5.23K i/s | 1.58K i/s | 2.3K i/s |
|
|
89
|
+
| Complex merging | 30.94K i/s | 16.09K i/s | 21.57K i/s | 11.6K i/s | 16.63K i/s |
|
|
90
90
|
|
|
91
91
|
### Speedups
|
|
92
92
|
|
|
93
93
|
| Comparison | Speedup |
|
|
94
94
|
|------------|---------|
|
|
95
|
-
| Native vs Pure (no YJIT) |
|
|
96
|
-
| Native vs Pure (YJIT) | 1.
|
|
97
|
-
| YJIT impact on Pure Ruby | 1.
|
|
95
|
+
| Native vs Pure (no YJIT) | 3.03x faster (avg) |
|
|
96
|
+
| Native vs Pure (YJIT) | 1.72x faster (avg) |
|
|
97
|
+
| YJIT impact on Pure Ruby | 1.38x faster (avg) |
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
@@ -108,7 +108,7 @@ rake benchmark
|
|
|
108
108
|
rake benchmark:parsing
|
|
109
109
|
rake benchmark:serialization
|
|
110
110
|
rake benchmark:specificity
|
|
111
|
-
rake benchmark:
|
|
111
|
+
rake benchmark:flattening
|
|
112
112
|
|
|
113
113
|
# Generate documentation
|
|
114
114
|
rake benchmark:generate_docs
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## [0.2.0] - 2025-11-14
|
|
2
|
+
|
|
3
|
+
- Major: CSS `@import` resolution refactored from string-concatenation to parsed-object architecture with proper charset handling, media query combining,
|
|
4
|
+
and circular import detection
|
|
5
|
+
- Major: Terminology change: all `merge` methods renamed to `flatten` to better represent CSS cascade behavior (old names deprecated with warnings)
|
|
6
|
+
- Major: Rule equality now considers shorthand/longhand property equivalence (e.g., `margin: 10px` equals `margin-top: 10px; margin-right: 10px; ...`)
|
|
7
|
+
- Performance: Flatten operation optimized with array-based property storage, pre-allocated frozen strings, and lazy specificity calculation
|
|
8
|
+
- Feature: New Stylesheet collection methods (`+`, `-`, `|`, `concat`, `take`, `take_while`) with cascade rules applied
|
|
9
|
+
- Feature: Added source order tracking for proper CSS cascade resolution
|
|
10
|
+
|
|
1
11
|
## [0.1.4] - 2025-11-12
|
|
2
12
|
- Major: Pure Ruby implementation added (#12)
|
|
3
13
|
- Fix: Media query serialization bugs - parentheses now preserved per CSS spec (min-width: 768px), fixed media query ordering
|
data/RAGEL_MIGRATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Why We Switched
|
|
4
4
|
|
|
5
|
-
Started with Ragel as an experiment for the CSS parser, but quickly moved to hand-written pure C in October
|
|
5
|
+
Started with Ragel as an experiment for the CSS parser, but quickly moved to hand-written pure C in October 2025.
|
|
6
6
|
|
|
7
7
|
### Performance
|
|
8
8
|
|
|
@@ -55,6 +55,6 @@ Pure C is more verbose than Ragel's DSL, but the performance gains and simpler b
|
|
|
55
55
|
|
|
56
56
|
## Details
|
|
57
57
|
|
|
58
|
-
- Swapped in October
|
|
58
|
+
- Swapped in October 2025
|
|
59
59
|
- Files: `css_parser.c`, `specificity.c`, `value_splitter.c`
|
|
60
60
|
- API unchanged, all tests pass
|
data/README.md
CHANGED
|
@@ -181,7 +181,10 @@ Cataract parses and preserves all standard CSS including:
|
|
|
181
181
|
|
|
182
182
|
Cataract supports converting colors between multiple CSS color formats with high precision.
|
|
183
183
|
|
|
184
|
-
**Note:** Color conversion is an optional extension
|
|
184
|
+
**Note:** Color conversion is an optional extension and _not_ loaded by default.
|
|
185
|
+
|
|
186
|
+
<details>
|
|
187
|
+
<summary>Color conversion examples and supported formats</summary>
|
|
185
188
|
|
|
186
189
|
```ruby
|
|
187
190
|
require 'cataract'
|
|
@@ -241,6 +244,8 @@ sheet.convert_colors!(to: :hex) # Converts all formats to hex
|
|
|
241
244
|
- CSS Color Level 5 features (`none`, `infinity`, relative color syntax with `from`) are preserved but not converted
|
|
242
245
|
- Unknown or future color functions are passed through unchanged
|
|
243
246
|
|
|
247
|
+
</details>
|
|
248
|
+
|
|
244
249
|
### `@import` Support
|
|
245
250
|
|
|
246
251
|
`@import` statements can be resolved with security controls:
|
|
@@ -301,7 +306,7 @@ Each `Rule` is a struct containing:
|
|
|
301
306
|
- `specificity`: Calculated CSS specificity (cached)
|
|
302
307
|
|
|
303
308
|
Implementation details:
|
|
304
|
-
- **C implementation**: Critical paths implemented in C (parsing,
|
|
309
|
+
- **C implementation**: Critical paths implemented in C (parsing, cascade/flatten, serialization)
|
|
305
310
|
- **Flat rule array**: All rules stored in a single array, preserving source order
|
|
306
311
|
- **Efficient media query handling**: O(1) lookup via internal media index
|
|
307
312
|
- **Memory efficient**: Minimal allocations, reuses string buffers where possible
|
data/Rakefile
CHANGED
|
@@ -79,7 +79,7 @@ task :benchmark do
|
|
|
79
79
|
Rake::Task['benchmark:parsing'].invoke
|
|
80
80
|
Rake::Task['benchmark:serialization'].invoke
|
|
81
81
|
Rake::Task['benchmark:specificity'].invoke
|
|
82
|
-
Rake::Task['benchmark:
|
|
82
|
+
Rake::Task['benchmark:flattening'].invoke
|
|
83
83
|
puts "\n#{'-' * 80}"
|
|
84
84
|
puts 'All benchmarks complete!'
|
|
85
85
|
puts 'Generate documentation with: rake benchmark:generate_docs'
|
|
@@ -105,10 +105,10 @@ namespace :benchmark do
|
|
|
105
105
|
ruby 'benchmarks/benchmark_specificity.rb'
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
desc 'Benchmark CSS
|
|
109
|
-
task
|
|
110
|
-
puts 'Running
|
|
111
|
-
ruby 'benchmarks/
|
|
108
|
+
desc 'Benchmark CSS flattening performance'
|
|
109
|
+
task flattening: :compile do
|
|
110
|
+
puts 'Running flattening benchmark...'
|
|
111
|
+
ruby 'benchmarks/benchmark_flattening.rb'
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
desc 'Benchmark string allocation optimization (buffer vs dynamic)'
|
|
@@ -195,14 +195,27 @@ task :lint do
|
|
|
195
195
|
end
|
|
196
196
|
|
|
197
197
|
# Fuzz testing
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
namespace :fuzz do
|
|
199
|
+
desc 'Run fuzzer with C extension'
|
|
200
|
+
task c: :compile do
|
|
201
|
+
iterations = ENV['ITERATIONS'] || '10000'
|
|
202
|
+
puts "Running CSS parser fuzzer with C extension (#{iterations} iterations)..."
|
|
203
|
+
system(ENV.to_h, RbConfig.ruby, '-Ilib', 'scripts/fuzzer/run.rb', iterations)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
desc 'Run fuzzer with pure Ruby implementation'
|
|
207
|
+
task :pure do
|
|
208
|
+
iterations = ENV['ITERATIONS'] || '10000'
|
|
209
|
+
debug_msg = ENV['FUZZ_DEBUG'] == '1' ? ' (debug mode)' : ''
|
|
210
|
+
puts "Running CSS parser fuzzer with pure Ruby (#{iterations} iterations#{debug_msg})..."
|
|
211
|
+
env = ENV.to_h.merge('CATARACT_PURE' => '1')
|
|
212
|
+
system(env, RbConfig.ruby, '-Ilib', 'scripts/fuzzer/run.rb', iterations)
|
|
213
|
+
end
|
|
204
214
|
end
|
|
205
215
|
|
|
216
|
+
desc 'Run fuzzer with both C extension and pure Ruby'
|
|
217
|
+
task fuzz: ['fuzz:c', 'fuzz:pure']
|
|
218
|
+
|
|
206
219
|
# Documentation generation with YARD
|
|
207
220
|
begin
|
|
208
221
|
require 'yard'
|
data/cataract.gemspec
CHANGED
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
# Specify which files should be added to the gem when it is released.
|
|
23
23
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
24
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
-
f.match(%r{^(test|spec|features|benchmarks|scripts)/}) ||
|
|
25
|
+
f.match(%r{^(test|spec|features|benchmarks|scripts|\.rubocop)/}) ||
|
|
26
26
|
f.match(/^(test|benchmark)_.*\.rb$/)
|
|
27
27
|
end
|
|
28
28
|
end
|
data/ext/cataract/cataract.c
CHANGED
|
@@ -7,6 +7,7 @@ VALUE cRule;
|
|
|
7
7
|
VALUE cDeclaration;
|
|
8
8
|
VALUE cAtRule;
|
|
9
9
|
VALUE cStylesheet;
|
|
10
|
+
VALUE cImportStatement;
|
|
10
11
|
|
|
11
12
|
// Error class definitions (shared with main extension)
|
|
12
13
|
VALUE eCataractError;
|
|
@@ -1044,6 +1045,12 @@ void Init_native_extension(void) {
|
|
|
1044
1045
|
rb_raise(rb_eLoadError, "Cataract::AtRule not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
|
|
1045
1046
|
}
|
|
1046
1047
|
|
|
1048
|
+
if (rb_const_defined(mCataract, rb_intern("ImportStatement"))) {
|
|
1049
|
+
cImportStatement = rb_const_get(mCataract, rb_intern("ImportStatement"));
|
|
1050
|
+
} else {
|
|
1051
|
+
rb_raise(rb_eLoadError, "Cataract::ImportStatement not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1047
1054
|
// Define Declarations class and add to_s method
|
|
1048
1055
|
VALUE cDeclarations = rb_define_class_under(mCataract, "Declarations", rb_cObject);
|
|
1049
1056
|
rb_define_method(cDeclarations, "to_s", new_declarations_to_s_method, 0);
|
|
@@ -1057,12 +1064,14 @@ void Init_native_extension(void) {
|
|
|
1057
1064
|
rb_define_module_function(mCataract, "_stylesheet_to_formatted_s", stylesheet_to_formatted_s_new, 4);
|
|
1058
1065
|
rb_define_module_function(mCataract, "parse_media_types", parse_media_types, 1);
|
|
1059
1066
|
rb_define_module_function(mCataract, "parse_declarations", new_parse_declarations, 1);
|
|
1060
|
-
rb_define_module_function(mCataract, "
|
|
1067
|
+
rb_define_module_function(mCataract, "flatten", cataract_flatten, 1);
|
|
1068
|
+
rb_define_module_function(mCataract, "merge", cataract_flatten, 1); // Deprecated alias for backwards compatibility
|
|
1061
1069
|
rb_define_module_function(mCataract, "extract_imports", extract_imports, 1);
|
|
1062
1070
|
rb_define_module_function(mCataract, "calculate_specificity", calculate_specificity, 1);
|
|
1071
|
+
rb_define_module_function(mCataract, "_expand_shorthand", cataract_expand_shorthand, 1);
|
|
1063
1072
|
|
|
1064
|
-
// Initialize
|
|
1065
|
-
|
|
1073
|
+
// Initialize flatten constants (cached property strings)
|
|
1074
|
+
init_flatten_constants();
|
|
1066
1075
|
|
|
1067
1076
|
// Export compile-time flags as a hash for runtime introspection
|
|
1068
1077
|
VALUE compile_flags = rb_hash_new();
|
data/ext/cataract/cataract.h
CHANGED
|
@@ -12,6 +12,7 @@ extern VALUE cRule;
|
|
|
12
12
|
extern VALUE cDeclaration;
|
|
13
13
|
extern VALUE cAtRule;
|
|
14
14
|
extern VALUE cStylesheet;
|
|
15
|
+
extern VALUE cImportStatement;
|
|
15
16
|
|
|
16
17
|
// Error class references
|
|
17
18
|
extern VALUE eCataractError;
|
|
@@ -140,9 +141,9 @@ VALUE parse_css_new(VALUE self, VALUE css_string);
|
|
|
140
141
|
VALUE parse_css_new_impl(VALUE css_string, int rule_id_offset);
|
|
141
142
|
VALUE parse_media_types(VALUE self, VALUE media_query_sym);
|
|
142
143
|
|
|
143
|
-
//
|
|
144
|
-
VALUE
|
|
145
|
-
void
|
|
144
|
+
// Flatten (flatten.c)
|
|
145
|
+
VALUE cataract_flatten(VALUE self, VALUE rules_array);
|
|
146
|
+
void init_flatten_constants(void);
|
|
146
147
|
|
|
147
148
|
// Specificity (specificity.c)
|
|
148
149
|
VALUE calculate_specificity(VALUE self, VALUE selector);
|
|
@@ -162,6 +163,7 @@ VALUE cataract_expand_border_side(VALUE self, VALUE side, VALUE value);
|
|
|
162
163
|
VALUE cataract_expand_font(VALUE self, VALUE value);
|
|
163
164
|
VALUE cataract_expand_list_style(VALUE self, VALUE value);
|
|
164
165
|
VALUE cataract_expand_background(VALUE self, VALUE value);
|
|
166
|
+
VALUE cataract_expand_shorthand(VALUE self, VALUE decl);
|
|
165
167
|
VALUE cataract_create_margin_shorthand(VALUE self, VALUE properties);
|
|
166
168
|
VALUE cataract_create_padding_shorthand(VALUE self, VALUE properties);
|
|
167
169
|
VALUE cataract_create_border_width_shorthand(VALUE self, VALUE properties);
|