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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8c9aa122ff45945bef0a05d6fa6a5dcfd46252ffe5970bf288074db5d3514c9
4
- data.tar.gz: cdafcb1ca599a58449dc03c239e5b8891f4399629dbdf7ca4e3ef23fb7f6ab94
3
+ metadata.gz: bcb5043777c52ed2a3f4bcd96966dcbe00e0f7bcf5ac410398afc855d59407f5
4
+ data.tar.gz: 65fabbfff1bbc559a0998f5a412800987ed2921252cdd2d4a757c6b59041c66a
5
5
  SHA512:
6
- metadata.gz: 35bbffc26c5eb757a90935fd0caf362818d80cc33af0860c738061ca3d0047f6b6d5c8124087d8283c0bb93b26699990279f98af96215456099a493d1e4720ee
7
- data.tar.gz: 567ffe7ce56412c5913384ce8b6470b9d8d0e7e880463e2024a6d4cd6fca4bcb9b792ce0ca890aea1edf07ed218497a8369058f38b8b30ab94bee63694ef03e9
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: [jruby, truffleruby]
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
- Style/Documentation:
70
- Enabled: true
71
- Exclude:
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
- - 'benchmarks/**/*'
74
- - 'lib/cataract/pure/**/*.rb'
75
- - 'lib/cataract/pure.rb'
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/merge.rb'
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/merge.rb'
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/merge.rb'
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/merge.rb'
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/merge.rb'
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/merge.rb'
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/merge.rb'
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) | 63.2K i/s | 3.56K i/s | 15.49K i/s | 4.7K i/s | 6.39K i/s |
24
- | Medium CSS with @media (139 lines, 1.6KB) | 40.14K i/s | 2.07K i/s | 8.76K i/s | 2.81K i/s | 3.54K i/s |
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.58x faster (avg) |
31
- | Native vs Pure (YJIT) | 4.26x faster (avg) |
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.79K i/s | 442.2 i/s | 716.9 i/s | 38.5 i/s | 39.7 i/s |
43
- | Media Type Filtering (print only) | 282.98K i/s | 114.55K i/s | 167.51K i/s | 2.74K i/s | 4.32K i/s |
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.26x faster (avg) |
50
- | Native vs Pure (YJIT) | 1.69x faster (avg) |
51
- | YJIT impact on Pure Ruby | 1.46x faster (avg) |
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 | 8.31M i/s | 499.15K i/s | 2.59M i/s | 370.14K i/s | 344.42K i/s |
62
- | Compound Selectors | 6.57M i/s | 184.61K i/s | 403.41K i/s | 223.76K i/s | 204.19K i/s |
63
- | Combinators | 5.24M i/s | 144.7K i/s | 253.9K i/s | 188.05K i/s | 177.88K i/s |
64
- | Pseudo-classes & Pseudo-elements | 5.39M i/s | 115.46K i/s | 194.13K i/s | 116.96K i/s | 109.64K i/s |
65
- | :not() Pseudo-class (CSS3) | 3.31M i/s | 101.48K i/s | 160.62K i/s | 144.35K i/s | 141.24K i/s |
66
- | Complex Real-world Selectors | 4.09M i/s | 51.63K i/s | 77.37K i/s | 82.16K i/s | 80.6K i/s |
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) | 41.16x faster (avg) |
73
- | Native vs Pure (YJIT) | 8.94x faster (avg) |
74
- | YJIT impact on Pure Ruby | 3.36x faster (avg) |
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 Merging
78
+ ## CSS Flattening (Cascade)
79
79
 
80
- Time to merge multiple CSS rule sets with same selector
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) | 10.5K i/s | 3.46K i/s | 6.73K i/s | 1.51K i/s | 2.27K i/s |
85
- | Simple properties | 125.51K i/s | 77.36K i/s | 110.86K i/s | 27.92K i/s | 40.42K i/s |
86
- | Cascade with specificity | 140.46K i/s | 80.08K i/s | 116.35K i/s | 31.48K i/s | 45.99K i/s |
87
- | Important declarations | 139.59K i/s | 80.29K i/s | 116.39K i/s | 30.84K i/s | 44.87K i/s |
88
- | Shorthand expansion | 10.5K i/s | 3.46K i/s | 6.73K i/s | 1.51K i/s | 2.27K i/s |
89
- | Complex merging | 24.74K i/s | 16.62K i/s | 23.57K i/s | 11.59K i/s | 16.48K i/s |
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) | 1.87x faster (avg) |
96
- | Native vs Pure (YJIT) | 1.16x faster (avg) |
97
- | YJIT impact on Pure Ruby | 1.45x faster (avg) |
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:merging
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 2024.
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 2024
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. Load it explicitly to reduce memory footprint:
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, merging, serialization)
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:merging'].invoke
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 merging performance'
109
- task merging: :compile do
110
- puts 'Running merging benchmark...'
111
- ruby 'benchmarks/benchmark_merging.rb'
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
- desc 'Run fuzzer to test parser robustness (including color conversion)'
199
- task fuzz: :compile do
200
- iterations = ENV['ITERATIONS'] || '10000'
201
- puts "Running CSS parser fuzzer (#{iterations} iterations)..."
202
- # Use system with ENV.to_h to preserve environment variables like FUZZ_GC_STRESS
203
- system(ENV.to_h, RbConfig.ruby, '-Ilib', 'scripts/fuzzer/run.rb', iterations)
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
@@ -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, "merge", cataract_merge_new, 1);
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 merge constants (cached property strings)
1065
- init_merge_constants();
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();
@@ -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
- // Merge (merge_new.c)
144
- VALUE cataract_merge_new(VALUE self, VALUE rules_array);
145
- void init_merge_constants(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);