cataract 0.1.3 → 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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci-manual-rubies.yml +44 -0
  3. data/.overcommit.yml +1 -1
  4. data/.rubocop.yml +96 -4
  5. data/.rubocop_todo.yml +186 -0
  6. data/BENCHMARKS.md +62 -141
  7. data/CHANGELOG.md +20 -0
  8. data/RAGEL_MIGRATION.md +2 -2
  9. data/README.md +37 -4
  10. data/Rakefile +72 -32
  11. data/cataract.gemspec +4 -1
  12. data/ext/cataract/cataract.c +59 -50
  13. data/ext/cataract/cataract.h +5 -3
  14. data/ext/cataract/css_parser.c +173 -65
  15. data/ext/cataract/extconf.rb +2 -2
  16. data/ext/cataract/{merge.c → flatten.c} +526 -468
  17. data/ext/cataract/shorthand_expander.c +164 -115
  18. data/lib/cataract/at_rule.rb +8 -9
  19. data/lib/cataract/declaration.rb +18 -0
  20. data/lib/cataract/import_resolver.rb +63 -43
  21. data/lib/cataract/import_statement.rb +49 -0
  22. data/lib/cataract/pure/byte_constants.rb +69 -0
  23. data/lib/cataract/pure/flatten.rb +1145 -0
  24. data/lib/cataract/pure/helpers.rb +35 -0
  25. data/lib/cataract/pure/imports.rb +268 -0
  26. data/lib/cataract/pure/parser.rb +1340 -0
  27. data/lib/cataract/pure/serializer.rb +590 -0
  28. data/lib/cataract/pure/specificity.rb +206 -0
  29. data/lib/cataract/pure.rb +153 -0
  30. data/lib/cataract/rule.rb +69 -15
  31. data/lib/cataract/stylesheet.rb +356 -49
  32. data/lib/cataract/version.rb +1 -1
  33. data/lib/cataract.rb +43 -26
  34. metadata +14 -26
  35. data/benchmarks/benchmark_harness.rb +0 -193
  36. data/benchmarks/benchmark_merging.rb +0 -121
  37. data/benchmarks/benchmark_optimization_comparison.rb +0 -168
  38. data/benchmarks/benchmark_parsing.rb +0 -153
  39. data/benchmarks/benchmark_ragel_removal.rb +0 -56
  40. data/benchmarks/benchmark_runner.rb +0 -70
  41. data/benchmarks/benchmark_serialization.rb +0 -180
  42. data/benchmarks/benchmark_shorthand.rb +0 -109
  43. data/benchmarks/benchmark_shorthand_expansion.rb +0 -176
  44. data/benchmarks/benchmark_specificity.rb +0 -124
  45. data/benchmarks/benchmark_string_allocation.rb +0 -151
  46. data/benchmarks/benchmark_stylesheet_to_s.rb +0 -62
  47. data/benchmarks/benchmark_to_s_cached.rb +0 -55
  48. data/benchmarks/benchmark_value_splitter.rb +0 -54
  49. data/benchmarks/benchmark_yjit.rb +0 -158
  50. data/benchmarks/benchmark_yjit_workers.rb +0 -61
  51. data/benchmarks/profile_to_s.rb +0 -23
  52. data/benchmarks/speedup_calculator.rb +0 -83
  53. data/benchmarks/system_metadata.rb +0 -81
  54. data/benchmarks/templates/benchmarks.md.erb +0 -221
  55. data/benchmarks/yjit_tests.rb +0 -141
  56. data/scripts/fuzzer/run.rb +0 -828
  57. data/scripts/fuzzer/worker.rb +0 -99
  58. data/scripts/generate_benchmarks_md.rb +0 -155
data/README.md CHANGED
@@ -34,6 +34,32 @@ gem install cataract
34
34
 
35
35
  - Ruby >= 3.1.0
36
36
 
37
+ ### Pure Ruby Implementation
38
+
39
+ Cataract includes a pure Ruby implementation alongside the C extension. This is useful for:
40
+ - Platforms where C extensions cannot be compiled
41
+ - Development/debugging without needing to recompile C code
42
+ - Environments with restricted native code execution
43
+
44
+ **In your Gemfile:**
45
+ ```ruby
46
+ gem 'cataract', require: 'cataract/pure'
47
+ ```
48
+
49
+ **Or set environment variable:**
50
+ ```ruby
51
+ # For CI, testing, or one-off usage
52
+ ENV['CATARACT_PURE'] = '1'
53
+ require 'cataract'
54
+ ```
55
+
56
+ **Check which implementation is loaded:**
57
+ ```ruby
58
+ Cataract::IMPLEMENTATION # => :native or :ruby
59
+ ```
60
+
61
+ **Note:** The pure Ruby implementation does not include color conversion functionality (`convert_colors!`), which is only available in the C extension.
62
+
37
63
  ## Usage
38
64
 
39
65
  ### Basic Parsing
@@ -142,9 +168,11 @@ See [BENCHMARKS.md](BENCHMARKS.md) for detailed performance comparisons.
142
168
 
143
169
  ## CSS Support
144
170
 
145
- Cataract aims to support all CSS specifications including:
171
+ Cataract parses and preserves all standard CSS including:
146
172
  - **Selectors**: All CSS2/CSS3 selectors (type, class, ID, attribute, pseudo-classes, pseudo-elements, combinators)
147
- - **At-rules**: `@media`, `@font-face`, `@keyframes`, `@supports`, `@page`, `@layer`, `@container`, `@property`, `@scope`, `@counter-style`
173
+ - **At-rules**:
174
+ - **`@media`**: Special handling with indexing and filtering API (`with_media(:print)`, `with_media(:all)`)
175
+ - **Others** (`@font-face`, `@keyframes`, `@supports`, `@page`, `@layer`, `@container`, `@property`, `@scope`, `@counter-style`): Parsed and preserved as-is (pass-through)
148
176
  - **Media Queries**: Full support including nested queries and media features
149
177
  - **Special syntax**: Data URIs, `calc()`, `url()`, CSS functions with parentheses
150
178
  - **!important**: Full support with correct cascade behavior
@@ -153,7 +181,10 @@ Cataract aims to support all CSS specifications including:
153
181
 
154
182
  Cataract supports converting colors between multiple CSS color formats with high precision.
155
183
 
156
- **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>
157
188
 
158
189
  ```ruby
159
190
  require 'cataract'
@@ -213,6 +244,8 @@ sheet.convert_colors!(to: :hex) # Converts all formats to hex
213
244
  - CSS Color Level 5 features (`none`, `infinity`, relative color syntax with `from`) are preserved but not converted
214
245
  - Unknown or future color functions are passed through unchanged
215
246
 
247
+ </details>
248
+
216
249
  ### `@import` Support
217
250
 
218
251
  `@import` statements can be resolved with security controls:
@@ -273,7 +306,7 @@ Each `Rule` is a struct containing:
273
306
  - `specificity`: Calculated CSS specificity (cached)
274
307
 
275
308
  Implementation details:
276
- - **C implementation**: Critical paths implemented in C (parsing, merging, serialization)
309
+ - **C implementation**: Critical paths implemented in C (parsing, cascade/flatten, serialization)
277
310
  - **Flat rule array**: All rules stored in a single array, preserving source order
278
311
  - **Efficient media query handling**: O(1) lookup via internal media index
279
312
  - **Memory efficient**: Minimal allocations, reuses string buffers where possible
data/Rakefile CHANGED
@@ -28,23 +28,58 @@ end
28
28
  # rake-compiler already adds: tmp/, lib/**/*.{so,bundle}, etc.
29
29
  CLEAN.include('ext/**/Makefile', 'ext/**/*.o')
30
30
 
31
- Rake::TestTask.new(:test) do |t|
32
- t.libs << 'test'
33
- t.libs << 'lib'
34
- # Load test_helper before running tests (handles SimpleCov setup)
35
- t.ruby_opts << '-rtest_helper'
36
- # Exclude css_parser_compat directory (reference tests only, not run)
37
- t.test_files = FileList['test/**/test_*.rb'].exclude('test/css_parser_compat/**/*')
31
+ namespace :test do
32
+ desc 'Run tests with C extension (default)'
33
+ Rake::TestTask.new(:c) do |t|
34
+ t.libs << 'test'
35
+ t.libs << 'lib'
36
+ t.ruby_opts << '-rtest_helper'
37
+ t.test_files = FileList['test/**/test_*.rb'].exclude('test/css_parser_compat/**/*', 'test/color/**/*')
38
+ end
39
+
40
+ desc 'Run tests with pure Ruby implementation'
41
+ task :pure do
42
+ # Run in subprocess to avoid conflicts with C extension
43
+ success = system({ 'CATARACT_PURE' => '1' }, 'rake', 'test:c')
44
+ abort('Pure Ruby tests failed!') unless success
45
+ end
46
+
47
+ desc 'Run tests for both C extension and pure Ruby'
48
+ task :all do
49
+ impls = ['pure ruby']
50
+
51
+ unless RUBY_ENGINE == 'jruby'
52
+ puts "\n#{'=' * 80}"
53
+ puts 'Running tests for C EXTENSION'
54
+ puts '=' * 80
55
+ impls << 'C extension'
56
+
57
+ Rake::Task['test:c'].invoke
58
+ end
59
+
60
+ puts "\n#{'=' * 80}"
61
+ puts 'Running tests for PURE RUBY implementation'
62
+ puts '=' * 80
63
+
64
+ Rake::Task['test:pure'].invoke
65
+
66
+ puts "\n#{'=' * 80}"
67
+ puts "✓ All tests passed for #{impls.join(' and ')}"
68
+ puts '=' * 80
69
+ end
38
70
  end
39
71
 
72
+ # Default test task runs both implementations
73
+ desc 'Run tests for both C extension and pure Ruby (default)'
74
+ task test: 'test:all'
75
+
40
76
  desc 'Run all benchmarks'
41
77
  task :benchmark do
42
78
  Rake::Task[:compile].invoke
43
79
  Rake::Task['benchmark:parsing'].invoke
44
80
  Rake::Task['benchmark:serialization'].invoke
45
81
  Rake::Task['benchmark:specificity'].invoke
46
- Rake::Task['benchmark:merging'].invoke
47
- Rake::Task['benchmark:yjit'].invoke
82
+ Rake::Task['benchmark:flattening'].invoke
48
83
  puts "\n#{'-' * 80}"
49
84
  puts 'All benchmarks complete!'
50
85
  puts 'Generate documentation with: rake benchmark:generate_docs'
@@ -53,33 +88,27 @@ end
53
88
 
54
89
  namespace :benchmark do
55
90
  desc 'Benchmark CSS parsing performance'
56
- task :parsing do
91
+ task parsing: :compile do
57
92
  puts 'Running parsing benchmark...'
58
93
  ruby 'benchmarks/benchmark_parsing.rb'
59
94
  end
60
95
 
61
96
  desc 'Benchmark CSS serialization (to_s) performance'
62
- task :serialization do
97
+ task serialization: :compile do
63
98
  puts 'Running serialization benchmark...'
64
99
  ruby 'benchmarks/benchmark_serialization.rb'
65
100
  end
66
101
 
67
102
  desc 'Benchmark specificity calculation performance'
68
- task :specificity do
103
+ task specificity: :compile do
69
104
  puts 'Running specificity benchmark...'
70
105
  ruby 'benchmarks/benchmark_specificity.rb'
71
106
  end
72
107
 
73
- desc 'Benchmark CSS merging performance'
74
- task :merging do
75
- puts 'Running merging benchmark...'
76
- ruby 'benchmarks/benchmark_merging.rb'
77
- end
78
-
79
- desc 'Benchmark Ruby-side operations with YJIT on vs off'
80
- task :yjit do
81
- puts 'Running YJIT benchmark...'
82
- ruby 'benchmarks/benchmark_yjit.rb'
108
+ desc 'Benchmark CSS flattening performance'
109
+ task flattening: :compile do
110
+ puts 'Running flattening benchmark...'
111
+ ruby 'benchmarks/benchmark_flattening.rb'
83
112
  end
84
113
 
85
114
  desc 'Benchmark string allocation optimization (buffer vs dynamic)'
@@ -166,14 +195,27 @@ task :lint do
166
195
  end
167
196
 
168
197
  # Fuzz testing
169
- desc 'Run fuzzer to test parser robustness (including color conversion)'
170
- task fuzz: :compile do
171
- iterations = ENV['ITERATIONS'] || '10000'
172
- puts "Running CSS parser fuzzer (#{iterations} iterations)..."
173
- # Use system with ENV.to_h to preserve environment variables like FUZZ_GC_STRESS
174
- 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
175
214
  end
176
215
 
216
+ desc 'Run fuzzer with both C extension and pure Ruby'
217
+ task fuzz: ['fuzz:c', 'fuzz:pure']
218
+
177
219
  # Documentation generation with YARD
178
220
  begin
179
221
  require 'yard'
@@ -189,10 +231,8 @@ begin
189
231
 
190
232
  desc 'Generate documentation and open in browser'
191
233
  task docs: :generate_example do
192
- # Generate YARD documentation
193
- YARD::CLI::Yardoc.run('--output-dir', 'docs', '--readme', 'README.md',
194
- '--title', 'Cataract - Fast CSS Parser',
195
- 'lib/**/*.rb', 'ext/**/*.c', '-', 'docs/files/EXAMPLE.md')
234
+ # Generate YARD documentation (uses .yardopts for configuration)
235
+ YARD::CLI::Yardoc.run
196
236
 
197
237
  # Open in browser (skip in CI)
198
238
  unless ENV['CI']
data/cataract.gemspec CHANGED
@@ -21,7 +21,10 @@ Gem::Specification.new do |spec|
21
21
 
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
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.match(/^test_.*\.rb$/) }
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features|benchmarks|scripts|\.rubocop)/}) ||
26
+ f.match(/^(test|benchmark)_.*\.rb$/)
27
+ end
25
28
  end
26
29
 
27
30
  spec.bindir = 'exe'
@@ -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;
@@ -254,7 +255,7 @@ static void serialize_at_rule_formatted(VALUE result, VALUE at_rule, const char
254
255
  }
255
256
 
256
257
  // Helper to serialize a single rule with formatting (indented, multi-line)
257
- static void serialize_rule_formatted(VALUE result, VALUE rule, const char *indent) {
258
+ static void serialize_rule_formatted(VALUE result, VALUE rule, const char *indent, int is_last) {
258
259
  // Check if this is an AtRule
259
260
  if (rb_obj_is_kind_of(rule, cAtRule)) {
260
261
  serialize_at_rule_formatted(result, rule, indent);
@@ -277,9 +278,13 @@ static void serialize_rule_formatted(VALUE result, VALUE rule, const char *inden
277
278
  serialize_declarations_formatted(result, declarations, decl_indent_ptr);
278
279
  RB_GC_GUARD(decl_indent);
279
280
 
280
- // Closing brace
281
+ // Closing brace - double newline for all except last rule
281
282
  rb_str_cat2(result, indent);
282
- rb_str_cat2(result, "}\n");
283
+ if (is_last) {
284
+ rb_str_cat2(result, "}\n");
285
+ } else {
286
+ rb_str_cat2(result, "}\n\n");
287
+ }
283
288
  }
284
289
 
285
290
  // Context for building rule_to_media map
@@ -680,6 +685,7 @@ static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_i
680
685
  VALUE rule = rb_ary_entry(rules_array, i);
681
686
  VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
682
687
  VALUE rule_media = rb_hash_aref(rule_to_media, rule_id);
688
+ int is_first_rule = (i == 0);
683
689
 
684
690
  if (NIL_P(rule_media)) {
685
691
  // Not in any media query - close any open media block first
@@ -689,19 +695,24 @@ static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_i
689
695
  current_media = Qnil;
690
696
  }
691
697
 
692
- // Output rule with no indentation
693
- serialize_rule_formatted(result, rule, "");
698
+ // Add blank line prefix for non-first rules
699
+ if (!is_first_rule) {
700
+ rb_str_cat2(result, "\n");
701
+ }
702
+
703
+ // Output rule with no indentation (always single newline suffix)
704
+ serialize_rule_formatted(result, rule, "", 1);
694
705
  } else {
695
706
  // This rule is in a media query
696
707
  if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
697
708
  // Close previous media block if open
698
709
  if (in_media_block) {
699
710
  rb_str_cat2(result, "}\n");
700
- } else {
701
- // Add blank line before @media if transitioning from non-media rules
702
- if (RSTRING_LEN(result) > 0) {
703
- rb_str_cat2(result, "\n");
704
- }
711
+ }
712
+
713
+ // Add blank line prefix for non-first rules
714
+ if (!is_first_rule) {
715
+ rb_str_cat2(result, "\n");
705
716
  }
706
717
 
707
718
  // Open new media block
@@ -713,7 +724,8 @@ static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_i
713
724
  }
714
725
 
715
726
  // Serialize rule inside media block with 2-space indentation
716
- serialize_rule_formatted(result, rule, " ");
727
+ // Rules inside media blocks always get single newline (is_last=1)
728
+ serialize_rule_formatted(result, rule, " ", 1);
717
729
  }
718
730
  }
719
731
 
@@ -1013,42 +1025,31 @@ void Init_native_extension(void) {
1013
1025
  eSizeError = rb_define_class_under(mCataract, "SizeError", eCataractError);
1014
1026
  }
1015
1027
 
1016
- // Define Rule struct: (id, selector, declarations, specificity, parent_rule_id, nesting_style)
1017
- cRule = rb_struct_define_under(
1018
- mCataract,
1019
- "Rule",
1020
- "id", // Integer (0-indexed position in @rules array)
1021
- "selector", // String (fully resolved/flattened selector)
1022
- "declarations", // Array of Declaration
1023
- "specificity", // Integer (nil = not calculated yet)
1024
- "parent_rule_id", // Integer | nil (parent rule ID for nested rules)
1025
- "nesting_style", // Integer | nil (0=implicit, 1=explicit, nil=not nested)
1026
- NULL
1027
- );
1028
-
1029
- // Define Declaration struct: (property, value, important)
1030
- cDeclaration = rb_struct_define_under(
1031
- mCataract,
1032
- "Declaration",
1033
- "property", // String
1034
- "value", // String
1035
- "important", // Boolean
1036
- NULL
1037
- );
1038
-
1039
- // Define AtRule struct: (id, selector, content, specificity)
1040
- // Matches Rule interface for duck-typing
1041
- // - For @keyframes: content is Array of Rule (keyframe blocks)
1042
- // - For @font-face: content is Array of Declaration
1043
- cAtRule = rb_struct_define_under(
1044
- mCataract,
1045
- "AtRule",
1046
- "id", // Integer (0-indexed position in @rules array)
1047
- "selector", // String (e.g., "@keyframes fade", "@font-face")
1048
- "content", // Array of Rule or Declaration
1049
- "specificity", // Always nil for at-rules
1050
- NULL
1051
- );
1028
+ // Reuse Ruby-defined structs (they must be defined before loading this extension)
1029
+ // If they don't exist, someone required the extension directly instead of via lib/cataract.rb
1030
+ if (rb_const_defined(mCataract, rb_intern("Rule"))) {
1031
+ cRule = rb_const_get(mCataract, rb_intern("Rule"));
1032
+ } else {
1033
+ rb_raise(rb_eLoadError, "Cataract::Rule not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
1034
+ }
1035
+
1036
+ if (rb_const_defined(mCataract, rb_intern("Declaration"))) {
1037
+ cDeclaration = rb_const_get(mCataract, rb_intern("Declaration"));
1038
+ } else {
1039
+ rb_raise(rb_eLoadError, "Cataract::Declaration not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
1040
+ }
1041
+
1042
+ if (rb_const_defined(mCataract, rb_intern("AtRule"))) {
1043
+ cAtRule = rb_const_get(mCataract, rb_intern("AtRule"));
1044
+ } else {
1045
+ rb_raise(rb_eLoadError, "Cataract::AtRule not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
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
+ }
1052
1053
 
1053
1054
  // Define Declarations class and add to_s method
1054
1055
  VALUE cDeclarations = rb_define_class_under(mCataract, "Declarations", rb_cObject);
@@ -1063,12 +1064,14 @@ void Init_native_extension(void) {
1063
1064
  rb_define_module_function(mCataract, "_stylesheet_to_formatted_s", stylesheet_to_formatted_s_new, 4);
1064
1065
  rb_define_module_function(mCataract, "parse_media_types", parse_media_types, 1);
1065
1066
  rb_define_module_function(mCataract, "parse_declarations", new_parse_declarations, 1);
1066
- 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
1067
1069
  rb_define_module_function(mCataract, "extract_imports", extract_imports, 1);
1068
1070
  rb_define_module_function(mCataract, "calculate_specificity", calculate_specificity, 1);
1071
+ rb_define_module_function(mCataract, "_expand_shorthand", cataract_expand_shorthand, 1);
1069
1072
 
1070
- // Initialize merge constants (cached property strings)
1071
- init_merge_constants();
1073
+ // Initialize flatten constants (cached property strings)
1074
+ init_flatten_constants();
1072
1075
 
1073
1076
  // Export compile-time flags as a hash for runtime introspection
1074
1077
  VALUE compile_flags = rb_hash_new();
@@ -1086,4 +1089,10 @@ void Init_native_extension(void) {
1086
1089
  #endif
1087
1090
 
1088
1091
  rb_define_const(mCataract, "COMPILE_FLAGS", compile_flags);
1092
+
1093
+ // Flag to indicate native extension is loaded (for pure Ruby fallback detection)
1094
+ rb_define_const(mCataract, "NATIVE_EXTENSION_LOADED", Qtrue);
1095
+
1096
+ // Implementation type constant
1097
+ rb_define_const(mCataract, "IMPLEMENTATION", ID2SYM(rb_intern("native")));
1089
1098
  }
@@ -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);