cataract 0.2.3 → 0.2.5
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/.rubocop.yml +8 -3
- data/BENCHMARKS.md +50 -32
- data/CHANGELOG.md +21 -1
- data/Gemfile +3 -0
- data/ext/cataract/cataract.c +219 -112
- data/ext/cataract/cataract.h +5 -1
- data/ext/cataract/css_parser.c +875 -50
- data/ext/cataract/flatten.c +233 -91
- data/ext/cataract/shorthand_expander.c +7 -0
- data/lib/cataract/at_rule.rb +2 -1
- data/lib/cataract/constants.rb +10 -0
- data/lib/cataract/error.rb +49 -0
- data/lib/cataract/import_resolver.rb +18 -87
- data/lib/cataract/import_statement.rb +29 -5
- data/lib/cataract/media_query.rb +98 -0
- data/lib/cataract/pure/byte_constants.rb +15 -0
- data/lib/cataract/pure/flatten.rb +127 -15
- data/lib/cataract/pure/parser.rb +800 -271
- data/lib/cataract/pure/serializer.rb +216 -115
- data/lib/cataract/pure.rb +8 -7
- data/lib/cataract/rule.rb +9 -5
- data/lib/cataract/stylesheet.rb +345 -101
- data/lib/cataract/stylesheet_scope.rb +10 -7
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +5 -8
- data/lib/tasks/profile.rake +210 -0
- metadata +5 -2
- data/lib/cataract/pure/imports.rb +0 -268
data/ext/cataract/cataract.c
CHANGED
|
@@ -8,11 +8,85 @@ VALUE cDeclaration;
|
|
|
8
8
|
VALUE cAtRule;
|
|
9
9
|
VALUE cStylesheet;
|
|
10
10
|
VALUE cImportStatement;
|
|
11
|
+
VALUE cMediaQuery;
|
|
11
12
|
|
|
12
13
|
// Error class definitions (shared with main extension)
|
|
13
14
|
VALUE eCataractError;
|
|
14
15
|
VALUE eDepthError;
|
|
15
16
|
VALUE eSizeError;
|
|
17
|
+
VALUE eParseError;
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Helper Functions
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Build media query text from MediaQuery struct
|
|
25
|
+
* Implements the logic from MediaQuery#text in Ruby
|
|
26
|
+
*/
|
|
27
|
+
static void append_media_query_text(VALUE result, VALUE media_query) {
|
|
28
|
+
DEBUG_PRINTF("[APPEND_MQ] Called with media_query=%s (class: %s)\n",
|
|
29
|
+
RSTRING_PTR(rb_inspect(media_query)),
|
|
30
|
+
rb_obj_classname(media_query));
|
|
31
|
+
VALUE media_type = rb_struct_aref(media_query, INT2FIX(1)); // type field
|
|
32
|
+
VALUE media_conditions = rb_struct_aref(media_query, INT2FIX(2)); // conditions field
|
|
33
|
+
|
|
34
|
+
if (!NIL_P(media_conditions)) {
|
|
35
|
+
// Has conditions
|
|
36
|
+
ID all_id = rb_intern("all");
|
|
37
|
+
if (SYM2ID(media_type) == all_id) {
|
|
38
|
+
// Type is :all - just output conditions (don't say "all and ...")
|
|
39
|
+
rb_str_append(result, media_conditions);
|
|
40
|
+
} else {
|
|
41
|
+
// Output "type and conditions"
|
|
42
|
+
rb_str_append(result, rb_sym2str(media_type));
|
|
43
|
+
rb_str_cat2(result, " and ");
|
|
44
|
+
rb_str_append(result, media_conditions);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// No conditions - just output type
|
|
48
|
+
rb_str_append(result, rb_sym2str(media_type));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Build media query string from MediaQuery object, handling comma-separated lists
|
|
53
|
+
// Matches pure Ruby's build_media_query_string method
|
|
54
|
+
// @param result [String] String to append to
|
|
55
|
+
// @param media_query_id [VALUE] The media query ID from the rule (Fixnum)
|
|
56
|
+
// @param mq_id_to_list_id [Hash] Reverse map: media_query_id => list_id
|
|
57
|
+
// @param media_query_lists [Hash] Hash mapping list_id => array of MediaQuery IDs
|
|
58
|
+
// @param media_queries [Array] Array of all MediaQuery objects
|
|
59
|
+
static void append_media_query_string(VALUE result, VALUE media_query_id, VALUE mq_id_to_list_id, VALUE media_query_lists, VALUE media_queries) {
|
|
60
|
+
// Check if this media_query_id is part of a comma-separated list
|
|
61
|
+
VALUE list_id = rb_hash_aref(mq_id_to_list_id, media_query_id);
|
|
62
|
+
|
|
63
|
+
if (!NIL_P(list_id)) {
|
|
64
|
+
// Part of a list - serialize all media queries in the list with commas
|
|
65
|
+
VALUE mq_ids = rb_hash_aref(media_query_lists, list_id);
|
|
66
|
+
if (!NIL_P(mq_ids) && TYPE(mq_ids) == T_ARRAY) {
|
|
67
|
+
long list_len = RARRAY_LEN(mq_ids);
|
|
68
|
+
for (long i = 0; i < list_len; i++) {
|
|
69
|
+
if (i > 0) {
|
|
70
|
+
rb_str_cat2(result, ", ");
|
|
71
|
+
}
|
|
72
|
+
VALUE mq_id = rb_ary_entry(mq_ids, i);
|
|
73
|
+
int mq_id_int = FIX2INT(mq_id);
|
|
74
|
+
VALUE mq = rb_ary_entry(media_queries, mq_id_int);
|
|
75
|
+
if (!NIL_P(mq)) {
|
|
76
|
+
append_media_query_text(result, mq);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Single media query - just append it
|
|
82
|
+
int mq_id_int = FIX2INT(media_query_id);
|
|
83
|
+
VALUE mq = rb_ary_entry(media_queries, mq_id_int);
|
|
84
|
+
if (!NIL_P(mq)) {
|
|
85
|
+
append_media_query_text(result, mq);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// No GC guards needed - we don't extract pointers from VALUEs, just pass them to functions
|
|
89
|
+
}
|
|
16
90
|
|
|
17
91
|
// ============================================================================
|
|
18
92
|
// Stubbed Implementation - Phase 1
|
|
@@ -298,11 +372,27 @@ static void serialize_rule_formatted(VALUE result, VALUE rule, const char *inden
|
|
|
298
372
|
}
|
|
299
373
|
}
|
|
300
374
|
|
|
301
|
-
// Context for building
|
|
302
|
-
struct
|
|
303
|
-
VALUE
|
|
375
|
+
// Context for building mq_id_to_list_id reverse map
|
|
376
|
+
struct build_mq_reverse_map_ctx {
|
|
377
|
+
VALUE mq_id_to_list_id;
|
|
304
378
|
};
|
|
305
379
|
|
|
380
|
+
// Callback to build reverse map: media_query_id => list_id
|
|
381
|
+
// Iterates through media_query_lists hash: list_id => [mq_id1, mq_id2, ...]
|
|
382
|
+
static int build_mq_reverse_map_callback(VALUE list_id, VALUE mq_ids, VALUE arg) {
|
|
383
|
+
struct build_mq_reverse_map_ctx *ctx = (struct build_mq_reverse_map_ctx *)arg;
|
|
384
|
+
|
|
385
|
+
if (!NIL_P(mq_ids) && TYPE(mq_ids) == T_ARRAY) {
|
|
386
|
+
long num_mq_ids = RARRAY_LEN(mq_ids);
|
|
387
|
+
for (long i = 0; i < num_mq_ids; i++) {
|
|
388
|
+
VALUE mq_id = rb_ary_entry(mq_ids, i);
|
|
389
|
+
rb_hash_aset(ctx->mq_id_to_list_id, mq_id, list_id);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return ST_CONTINUE;
|
|
394
|
+
}
|
|
395
|
+
|
|
306
396
|
// Formatting options for stylesheet serialization
|
|
307
397
|
// Avoids mode flags and if/else branches - all behavior controlled by struct values
|
|
308
398
|
struct format_opts {
|
|
@@ -314,37 +404,12 @@ struct format_opts {
|
|
|
314
404
|
int add_blank_lines; // 0 (compact) vs 1 (formatted)
|
|
315
405
|
};
|
|
316
406
|
|
|
317
|
-
// Callback to build reverse map from rule_id to media_sym
|
|
318
|
-
static int build_rule_map_callback(VALUE media_sym, VALUE rule_ids, VALUE arg) {
|
|
319
|
-
struct build_rule_map_ctx *ctx = (struct build_rule_map_ctx *)arg;
|
|
320
|
-
|
|
321
|
-
Check_Type(rule_ids, T_ARRAY);
|
|
322
|
-
long ids_len = RARRAY_LEN(rule_ids);
|
|
323
|
-
|
|
324
|
-
for (long i = 0; i < ids_len; i++) {
|
|
325
|
-
VALUE id = rb_ary_entry(rule_ids, i);
|
|
326
|
-
VALUE existing = rb_hash_aref(ctx->rule_to_media, id);
|
|
327
|
-
|
|
328
|
-
if (NIL_P(existing)) {
|
|
329
|
-
rb_hash_aset(ctx->rule_to_media, id, media_sym);
|
|
330
|
-
} else {
|
|
331
|
-
// Keep the longer/more specific media query
|
|
332
|
-
VALUE existing_str = rb_sym2str(existing);
|
|
333
|
-
VALUE new_str = rb_sym2str(media_sym);
|
|
334
|
-
if (RSTRING_LEN(new_str) > RSTRING_LEN(existing_str)) {
|
|
335
|
-
rb_hash_aset(ctx->rule_to_media, id, media_sym);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return ST_CONTINUE;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
407
|
// Private shared implementation for stylesheet serialization with optional selector list grouping
|
|
344
408
|
// All formatting behavior controlled by format_opts struct to avoid mode flags and if/else branches
|
|
345
409
|
static VALUE serialize_stylesheet_with_grouping(
|
|
346
410
|
VALUE rules_array,
|
|
347
|
-
VALUE
|
|
411
|
+
VALUE media_queries,
|
|
412
|
+
VALUE media_query_lists,
|
|
348
413
|
VALUE result,
|
|
349
414
|
VALUE selector_lists,
|
|
350
415
|
const struct format_opts *opts
|
|
@@ -354,10 +419,13 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
354
419
|
// Check if selector list grouping is enabled (non-empty hash)
|
|
355
420
|
int grouping_enabled = (!NIL_P(selector_lists) && TYPE(selector_lists) == T_HASH && RHASH_SIZE(selector_lists) > 0);
|
|
356
421
|
|
|
357
|
-
// Build
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
422
|
+
// Build reverse map: media_query_id => list_id
|
|
423
|
+
// This allows us to detect when multiple rules share a comma-separated media query list
|
|
424
|
+
VALUE mq_id_to_list_id = rb_hash_new();
|
|
425
|
+
if (!NIL_P(media_query_lists) && TYPE(media_query_lists) == T_HASH) {
|
|
426
|
+
struct build_mq_reverse_map_ctx ctx = { mq_id_to_list_id };
|
|
427
|
+
rb_hash_foreach(media_query_lists, build_mq_reverse_map_callback, (VALUE)&ctx);
|
|
428
|
+
}
|
|
361
429
|
|
|
362
430
|
// Track processed rules to avoid duplicates when grouping
|
|
363
431
|
VALUE processed_rule_ids = rb_hash_new();
|
|
@@ -375,7 +443,12 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
375
443
|
continue;
|
|
376
444
|
}
|
|
377
445
|
|
|
378
|
-
|
|
446
|
+
// Get media_query_id and fetch MediaQuery object (nil for AtRule or rules without media query)
|
|
447
|
+
VALUE rule_media_query_id = rb_obj_is_kind_of(rule, cAtRule) ? Qnil : rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
448
|
+
VALUE rule_media = Qnil;
|
|
449
|
+
if (!NIL_P(rule_media_query_id)) {
|
|
450
|
+
rule_media = rb_ary_entry(media_queries, FIX2INT(rule_media_query_id));
|
|
451
|
+
}
|
|
379
452
|
int is_first_rule = (i == 0);
|
|
380
453
|
|
|
381
454
|
if (NIL_P(rule_media)) {
|
|
@@ -425,9 +498,9 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
425
498
|
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
426
499
|
if (NIL_P(other_rule)) continue;
|
|
427
500
|
|
|
428
|
-
// Check same media context (
|
|
429
|
-
VALUE
|
|
430
|
-
if (!rb_equal(
|
|
501
|
+
// Check same media context (compare media_query_id directly)
|
|
502
|
+
VALUE other_rule_media_query_id = rb_struct_aref(other_rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
503
|
+
if (!rb_equal(rule_media_query_id, other_rule_media_query_id)) {
|
|
431
504
|
continue;
|
|
432
505
|
}
|
|
433
506
|
|
|
@@ -482,7 +555,7 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
482
555
|
}
|
|
483
556
|
} else {
|
|
484
557
|
// This rule is in a media query
|
|
485
|
-
// Check if media query changed from previous rule
|
|
558
|
+
// Check if media query changed from previous rule (compare MediaQuery objects by value)
|
|
486
559
|
if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
|
|
487
560
|
// Close previous media block if open
|
|
488
561
|
if (in_media_block) {
|
|
@@ -494,10 +567,15 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
494
567
|
rb_str_cat2(result, "\n");
|
|
495
568
|
}
|
|
496
569
|
|
|
497
|
-
// Open new media block
|
|
570
|
+
// Open new media block - store MediaQuery object for comparison
|
|
498
571
|
current_media = rule_media;
|
|
499
572
|
rb_str_cat2(result, "@media ");
|
|
500
|
-
|
|
573
|
+
|
|
574
|
+
// Serialize the media query (handles comma-separated lists)
|
|
575
|
+
if (!NIL_P(rule_media_query_id)) {
|
|
576
|
+
append_media_query_string(result, rule_media_query_id, mq_id_to_list_id, media_query_lists, media_queries);
|
|
577
|
+
}
|
|
578
|
+
|
|
501
579
|
rb_str_cat2(result, " {\n");
|
|
502
580
|
in_media_block = 1;
|
|
503
581
|
}
|
|
@@ -528,8 +606,8 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
528
606
|
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
529
607
|
if (NIL_P(other_rule)) continue;
|
|
530
608
|
|
|
531
|
-
VALUE
|
|
532
|
-
if (!rb_equal(
|
|
609
|
+
VALUE other_rule_media_query_id = rb_struct_aref(other_rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
610
|
+
if (!rb_equal(rule_media_query_id, other_rule_media_query_id)) continue;
|
|
533
611
|
|
|
534
612
|
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
535
613
|
if (rb_equal(rule_declarations, other_declarations)) {
|
|
@@ -584,15 +662,16 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
584
662
|
rb_str_cat2(result, "}\n");
|
|
585
663
|
}
|
|
586
664
|
|
|
587
|
-
|
|
665
|
+
// Guard hash objects we created and used throughout
|
|
666
|
+
RB_GC_GUARD(mq_id_to_list_id);
|
|
588
667
|
RB_GC_GUARD(processed_rule_ids);
|
|
589
668
|
return result;
|
|
590
669
|
}
|
|
591
670
|
|
|
592
671
|
// Original stylesheet serialization (no nesting support) - compact format
|
|
593
|
-
static VALUE
|
|
672
|
+
static VALUE stylesheet_to_s_without_nesting(VALUE rules_array, VALUE media_queries, VALUE media_query_lists, VALUE charset, VALUE selector_lists) {
|
|
594
673
|
Check_Type(rules_array, T_ARRAY);
|
|
595
|
-
Check_Type(
|
|
674
|
+
Check_Type(media_queries, T_ARRAY);
|
|
596
675
|
|
|
597
676
|
VALUE result = rb_str_new_cstr("");
|
|
598
677
|
|
|
@@ -613,24 +692,24 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
613
692
|
.add_blank_lines = 0
|
|
614
693
|
};
|
|
615
694
|
|
|
616
|
-
return serialize_stylesheet_with_grouping(rules_array,
|
|
695
|
+
return serialize_stylesheet_with_grouping(rules_array, media_queries, media_query_lists, result, selector_lists, &opts);
|
|
617
696
|
}
|
|
618
697
|
|
|
619
698
|
// Forward declarations
|
|
620
699
|
static void serialize_children_only(VALUE result, VALUE rules_array, long rule_idx,
|
|
621
|
-
VALUE
|
|
622
|
-
VALUE parent_declarations, int formatted, int indent_level);
|
|
700
|
+
VALUE parent_to_children, VALUE parent_selector,
|
|
701
|
+
VALUE parent_declarations, VALUE media_queries, int formatted, int indent_level);
|
|
623
702
|
static void serialize_rule_with_children(VALUE result, VALUE rules_array, long rule_idx,
|
|
624
|
-
VALUE
|
|
703
|
+
VALUE parent_to_children, VALUE media_queries,
|
|
625
704
|
int formatted, int indent_level);
|
|
626
705
|
|
|
627
706
|
// Helper: Only serialize children of a rule (not the rule itself)
|
|
628
707
|
static void serialize_children_only(VALUE result, VALUE rules_array, long rule_idx,
|
|
629
|
-
VALUE
|
|
630
|
-
VALUE parent_declarations, int formatted, int indent_level) {
|
|
708
|
+
VALUE parent_to_children, VALUE parent_selector,
|
|
709
|
+
VALUE parent_declarations, VALUE media_queries, int formatted, int indent_level) {
|
|
631
710
|
VALUE rule = rb_ary_entry(rules_array, rule_idx);
|
|
632
711
|
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
633
|
-
VALUE
|
|
712
|
+
VALUE rule_media_query_id = rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
634
713
|
int parent_has_declarations = !NIL_P(parent_declarations) && RARRAY_LEN(parent_declarations) > 0;
|
|
635
714
|
|
|
636
715
|
// Build indentation string for this level (only if formatted)
|
|
@@ -657,15 +736,14 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
657
736
|
for (long i = 0; i < num_children; i++) {
|
|
658
737
|
long child_idx = FIX2LONG(rb_ary_entry(children_indices, i));
|
|
659
738
|
VALUE child = rb_ary_entry(rules_array, child_idx);
|
|
660
|
-
VALUE
|
|
661
|
-
VALUE child_media = rb_hash_aref(rule_to_media, child_id); // Look up by rule ID
|
|
739
|
+
VALUE child_media_query_id = rb_struct_aref(child, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
662
740
|
|
|
663
|
-
DEBUG_PRINTF("[SERIALIZE] Child %ld:
|
|
664
|
-
NIL_P(
|
|
665
|
-
NIL_P(
|
|
741
|
+
DEBUG_PRINTF("[SERIALIZE] Child %ld: child_media_query_id=%s, rule_media_query_id=%s\n", child_idx,
|
|
742
|
+
NIL_P(child_media_query_id) ? "nil" : RSTRING_PTR(rb_inspect(child_media_query_id)),
|
|
743
|
+
NIL_P(rule_media_query_id) ? "nil" : RSTRING_PTR(rb_inspect(rule_media_query_id)));
|
|
666
744
|
|
|
667
745
|
// Only serialize selector-nested children here (not @media nested)
|
|
668
|
-
if (NIL_P(
|
|
746
|
+
if (NIL_P(child_media_query_id) || rb_equal(child_media_query_id, rule_media_query_id)) {
|
|
669
747
|
DEBUG_PRINTF("[SERIALIZE] -> Serializing as selector-nested child\n");
|
|
670
748
|
VALUE child_selector = rb_struct_aref(child, INT2FIX(RULE_SELECTOR));
|
|
671
749
|
VALUE child_nesting_style = rb_struct_aref(child, INT2FIX(RULE_NESTING_STYLE));
|
|
@@ -693,8 +771,8 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
693
771
|
}
|
|
694
772
|
|
|
695
773
|
// Recursively serialize grandchildren
|
|
696
|
-
serialize_children_only(result, rules_array, child_idx,
|
|
697
|
-
child_selector, child_declarations, formatted, indent_level + 1);
|
|
774
|
+
serialize_children_only(result, rules_array, child_idx, parent_to_children,
|
|
775
|
+
child_selector, child_declarations, media_queries, formatted, indent_level + 1);
|
|
698
776
|
|
|
699
777
|
// Closing brace with indentation and newline
|
|
700
778
|
rb_str_append(result, indent_str);
|
|
@@ -712,8 +790,8 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
712
790
|
serialize_declarations(result, child_declarations);
|
|
713
791
|
|
|
714
792
|
// Recursively serialize grandchildren
|
|
715
|
-
serialize_children_only(result, rules_array, child_idx,
|
|
716
|
-
child_selector, child_declarations, formatted, indent_level);
|
|
793
|
+
serialize_children_only(result, rules_array, child_idx, parent_to_children,
|
|
794
|
+
child_selector, child_declarations, media_queries, formatted, indent_level);
|
|
717
795
|
|
|
718
796
|
rb_str_cat2(result, " }");
|
|
719
797
|
}
|
|
@@ -724,16 +802,17 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
724
802
|
for (long i = 0; i < num_children; i++) {
|
|
725
803
|
long child_idx = FIX2LONG(rb_ary_entry(children_indices, i));
|
|
726
804
|
VALUE child = rb_ary_entry(rules_array, child_idx);
|
|
727
|
-
VALUE
|
|
728
|
-
VALUE child_media = rb_hash_aref(rule_to_media, child_id); // Look up by rule ID
|
|
805
|
+
VALUE child_media_query_id = rb_struct_aref(child, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
729
806
|
|
|
730
807
|
// Check if this is a different media than parent
|
|
731
|
-
if (!NIL_P(
|
|
732
|
-
// Nested @media!
|
|
808
|
+
if (!NIL_P(child_media_query_id) && !rb_equal(rule_media_query_id, child_media_query_id)) {
|
|
809
|
+
// Nested @media! Get the MediaQuery object for serialization
|
|
810
|
+
VALUE child_media = rb_ary_entry(media_queries, FIX2INT(child_media_query_id));
|
|
811
|
+
|
|
733
812
|
if (formatted) {
|
|
734
813
|
rb_str_append(result, indent_str);
|
|
735
814
|
rb_str_cat2(result, "@media ");
|
|
736
|
-
|
|
815
|
+
append_media_query_text(result, child_media);
|
|
737
816
|
rb_str_cat2(result, " {\n");
|
|
738
817
|
|
|
739
818
|
VALUE child_declarations = rb_struct_aref(child, INT2FIX(RULE_DECLARATIONS));
|
|
@@ -752,7 +831,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
752
831
|
rb_str_cat2(result, "}\n");
|
|
753
832
|
} else {
|
|
754
833
|
rb_str_cat2(result, " @media ");
|
|
755
|
-
|
|
834
|
+
append_media_query_text(result, child_media);
|
|
756
835
|
rb_str_cat2(result, " { ");
|
|
757
836
|
|
|
758
837
|
VALUE child_declarations = rb_struct_aref(child, INT2FIX(RULE_DECLARATIONS));
|
|
@@ -767,7 +846,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
767
846
|
|
|
768
847
|
// Recursive serializer for a rule and its nested children
|
|
769
848
|
static void serialize_rule_with_children(VALUE result, VALUE rules_array, long rule_idx,
|
|
770
|
-
VALUE
|
|
849
|
+
VALUE parent_to_children, VALUE media_queries,
|
|
771
850
|
int formatted, int indent_level) {
|
|
772
851
|
VALUE rule = rb_ary_entry(rules_array, rule_idx);
|
|
773
852
|
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
@@ -801,8 +880,8 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
801
880
|
}
|
|
802
881
|
|
|
803
882
|
// Serialize nested children
|
|
804
|
-
serialize_children_only(result, rules_array, rule_idx,
|
|
805
|
-
selector, declarations, formatted, indent_level + 1);
|
|
883
|
+
serialize_children_only(result, rules_array, rule_idx, parent_to_children,
|
|
884
|
+
selector, declarations, media_queries, formatted, indent_level + 1);
|
|
806
885
|
|
|
807
886
|
rb_str_cat2(result, closing_indent);
|
|
808
887
|
rb_str_cat2(result, "}\n");
|
|
@@ -815,8 +894,8 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
815
894
|
serialize_declarations(result, declarations);
|
|
816
895
|
|
|
817
896
|
// Serialize nested children
|
|
818
|
-
serialize_children_only(result, rules_array, rule_idx,
|
|
819
|
-
selector, declarations, formatted, indent_level);
|
|
897
|
+
serialize_children_only(result, rules_array, rule_idx, parent_to_children,
|
|
898
|
+
selector, declarations, media_queries, formatted, indent_level);
|
|
820
899
|
|
|
821
900
|
rb_str_cat2(result, " }\n");
|
|
822
901
|
}
|
|
@@ -826,17 +905,32 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
826
905
|
}
|
|
827
906
|
|
|
828
907
|
// New stylesheet serialization entry point - checks for nesting and delegates
|
|
829
|
-
static VALUE
|
|
908
|
+
static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE has_nesting, VALUE selector_lists, VALUE media_queries, VALUE media_query_lists) {
|
|
909
|
+
DEBUG_PRINTF("[STYLESHEET_TO_S] Called with:\n");
|
|
910
|
+
DEBUG_PRINTF(" rules_array length: %ld\n", RARRAY_LEN(rules_array));
|
|
911
|
+
DEBUG_PRINTF(" media_queries type: %s, length: %ld\n",
|
|
912
|
+
rb_obj_classname(media_queries),
|
|
913
|
+
TYPE(media_queries) == T_ARRAY ? RARRAY_LEN(media_queries) : -1);
|
|
914
|
+
DEBUG_PRINTF(" media_queries inspect: %s\n", RSTRING_PTR(rb_inspect(media_queries)));
|
|
915
|
+
DEBUG_PRINTF(" media_query_lists class: %s\n", rb_obj_classname(media_query_lists));
|
|
916
|
+
DEBUG_PRINTF(" selector_lists class: %s\n", rb_obj_classname(selector_lists));
|
|
917
|
+
|
|
918
|
+
DEBUG_PRINTF("[STYLESHEET_TO_S] About to Check_Type\n");
|
|
830
919
|
Check_Type(rules_array, T_ARRAY);
|
|
831
|
-
Check_Type(
|
|
920
|
+
Check_Type(media_queries, T_ARRAY);
|
|
921
|
+
if (!NIL_P(media_query_lists)) Check_Type(media_query_lists, T_HASH);
|
|
922
|
+
if (!NIL_P(selector_lists)) Check_Type(selector_lists, T_HASH);
|
|
923
|
+
DEBUG_PRINTF("[STYLESHEET_TO_S] Check_Type passed\n");
|
|
832
924
|
// TODO: Phase 2 - use selector_lists for grouping
|
|
833
925
|
(void)selector_lists; // Suppress unused parameter warning
|
|
834
926
|
|
|
835
927
|
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
836
928
|
if (!RTEST(has_nesting)) {
|
|
837
|
-
|
|
929
|
+
DEBUG_PRINTF("[STYLESHEET_TO_S] Taking fast path (no nesting)\n");
|
|
930
|
+
return stylesheet_to_s_without_nesting(rules_array, media_queries, media_query_lists, charset, selector_lists);
|
|
838
931
|
}
|
|
839
932
|
|
|
933
|
+
DEBUG_PRINTF("[STYLESHEET_TO_S] Taking slow path (has nesting)\n");
|
|
840
934
|
// SLOW PATH: Has nesting - use lookahead approach
|
|
841
935
|
long total_rules = RARRAY_LEN(rules_array);
|
|
842
936
|
VALUE result = rb_str_new_cstr("");
|
|
@@ -848,11 +942,6 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
848
942
|
rb_str_cat2(result, "\";\n");
|
|
849
943
|
}
|
|
850
944
|
|
|
851
|
-
// Build rule_to_media map
|
|
852
|
-
VALUE rule_to_media = rb_hash_new();
|
|
853
|
-
struct build_rule_map_ctx map_ctx = { rule_to_media };
|
|
854
|
-
rb_hash_foreach(media_index, build_rule_map_callback, (VALUE)&map_ctx);
|
|
855
|
-
|
|
856
945
|
// Build parent_to_children map (parent_rule_id -> array of child indices)
|
|
857
946
|
// This allows O(1) lookup of children when serializing each parent
|
|
858
947
|
VALUE parent_to_children = rb_hash_new();
|
|
@@ -896,9 +985,15 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
896
985
|
continue;
|
|
897
986
|
}
|
|
898
987
|
|
|
899
|
-
// Get
|
|
900
|
-
VALUE
|
|
901
|
-
VALUE rule_media =
|
|
988
|
+
// Get media_query_id for this rule and fetch the MediaQuery object
|
|
989
|
+
VALUE rule_media_query_id = rb_obj_is_kind_of(rule, cAtRule) ? Qnil : rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
990
|
+
VALUE rule_media = Qnil;
|
|
991
|
+
if (!NIL_P(rule_media_query_id)) {
|
|
992
|
+
rule_media = rb_ary_entry(media_queries, FIX2INT(rule_media_query_id));
|
|
993
|
+
}
|
|
994
|
+
DEBUG_PRINTF("[SERIALIZE] rule_media_query_id=%s, rule_media=%s\n",
|
|
995
|
+
NIL_P(rule_media_query_id) ? "nil" : RSTRING_PTR(rb_inspect(rule_media_query_id)),
|
|
996
|
+
NIL_P(rule_media) ? "nil" : RSTRING_PTR(rb_inspect(rule_media)));
|
|
902
997
|
|
|
903
998
|
// Handle media block transitions
|
|
904
999
|
if (NIL_P(rule_media)) {
|
|
@@ -909,16 +1004,16 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
909
1004
|
current_media = Qnil;
|
|
910
1005
|
}
|
|
911
1006
|
} else {
|
|
912
|
-
// In media - check if we need to open/change block
|
|
1007
|
+
// In media - check if we need to open/change block (compare MediaQuery objects by value)
|
|
913
1008
|
if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
|
|
914
1009
|
// Close previous media block if open
|
|
915
1010
|
if (in_media_block) {
|
|
916
1011
|
rb_str_cat2(result, "}\n");
|
|
917
1012
|
}
|
|
918
|
-
// Open new media block
|
|
1013
|
+
// Open new media block - store the MediaQuery object for comparison
|
|
919
1014
|
current_media = rule_media;
|
|
920
1015
|
rb_str_cat2(result, "@media ");
|
|
921
|
-
|
|
1016
|
+
append_media_query_text(result, rule_media);
|
|
922
1017
|
rb_str_cat2(result, " {\n");
|
|
923
1018
|
in_media_block = 1;
|
|
924
1019
|
}
|
|
@@ -932,7 +1027,7 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
932
1027
|
|
|
933
1028
|
// Serialize rule with nested children
|
|
934
1029
|
serialize_rule_with_children(
|
|
935
|
-
result, rules_array, i,
|
|
1030
|
+
result, rules_array, i, parent_to_children, media_queries,
|
|
936
1031
|
0, // formatted (compact)
|
|
937
1032
|
0 // indent_level (top-level)
|
|
938
1033
|
);
|
|
@@ -943,13 +1038,15 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
943
1038
|
rb_str_cat2(result, "}\n");
|
|
944
1039
|
}
|
|
945
1040
|
|
|
946
|
-
RB_GC_GUARD(rule_to_media);
|
|
947
1041
|
RB_GC_GUARD(parent_to_children);
|
|
948
1042
|
return result;
|
|
949
1043
|
}
|
|
950
1044
|
|
|
951
1045
|
// Original formatted serialization (no nesting support)
|
|
952
|
-
static VALUE
|
|
1046
|
+
static VALUE stylesheet_to_formatted_s_without_nesting(VALUE rules_array, VALUE media_queries, VALUE media_query_lists, VALUE charset, VALUE selector_lists) {
|
|
1047
|
+
Check_Type(rules_array, T_ARRAY);
|
|
1048
|
+
Check_Type(media_queries, T_ARRAY);
|
|
1049
|
+
|
|
953
1050
|
VALUE result = rb_str_new_cstr("");
|
|
954
1051
|
|
|
955
1052
|
// Add charset if present
|
|
@@ -969,17 +1066,19 @@ static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_i
|
|
|
969
1066
|
.add_blank_lines = 1
|
|
970
1067
|
};
|
|
971
1068
|
|
|
972
|
-
return serialize_stylesheet_with_grouping(rules_array,
|
|
1069
|
+
return serialize_stylesheet_with_grouping(rules_array, media_queries, media_query_lists, result, selector_lists, &opts);
|
|
973
1070
|
}
|
|
974
1071
|
|
|
975
1072
|
// Formatted version with indentation and newlines (with nesting support)
|
|
976
|
-
static VALUE
|
|
1073
|
+
static VALUE stylesheet_to_formatted_s(VALUE self, VALUE rules_array, VALUE charset, VALUE has_nesting, VALUE selector_lists, VALUE media_queries, VALUE media_query_lists) {
|
|
977
1074
|
Check_Type(rules_array, T_ARRAY);
|
|
978
|
-
Check_Type(
|
|
1075
|
+
Check_Type(media_queries, T_ARRAY);
|
|
1076
|
+
if (!NIL_P(media_query_lists)) Check_Type(media_query_lists, T_HASH);
|
|
1077
|
+
if (!NIL_P(selector_lists)) Check_Type(selector_lists, T_HASH);
|
|
979
1078
|
|
|
980
1079
|
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
981
1080
|
if (!RTEST(has_nesting)) {
|
|
982
|
-
return
|
|
1081
|
+
return stylesheet_to_formatted_s_without_nesting(rules_array, media_queries, media_query_lists, charset, selector_lists);
|
|
983
1082
|
}
|
|
984
1083
|
|
|
985
1084
|
// SLOW PATH: Has nesting - use parameterized serialization with formatted=1
|
|
@@ -993,11 +1092,6 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
993
1092
|
rb_str_cat2(result, "\";\n");
|
|
994
1093
|
}
|
|
995
1094
|
|
|
996
|
-
// Build rule_to_media map
|
|
997
|
-
VALUE rule_to_media = rb_hash_new();
|
|
998
|
-
struct build_rule_map_ctx map_ctx = { rule_to_media };
|
|
999
|
-
rb_hash_foreach(media_index, build_rule_map_callback, (VALUE)&map_ctx);
|
|
1000
|
-
|
|
1001
1095
|
// Build parent_to_children map (parent_rule_id -> array of child indices)
|
|
1002
1096
|
VALUE parent_to_children = rb_hash_new();
|
|
1003
1097
|
for (long i = 0; i < total_rules; i++) {
|
|
@@ -1028,9 +1122,12 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
1028
1122
|
continue;
|
|
1029
1123
|
}
|
|
1030
1124
|
|
|
1031
|
-
// Get
|
|
1032
|
-
VALUE
|
|
1033
|
-
VALUE rule_media =
|
|
1125
|
+
// Get media_query_id for this rule and fetch the MediaQuery object
|
|
1126
|
+
VALUE rule_media_query_id = rb_obj_is_kind_of(rule, cAtRule) ? Qnil : rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
1127
|
+
VALUE rule_media = Qnil;
|
|
1128
|
+
if (!NIL_P(rule_media_query_id)) {
|
|
1129
|
+
rule_media = rb_ary_entry(media_queries, FIX2INT(rule_media_query_id));
|
|
1130
|
+
}
|
|
1034
1131
|
|
|
1035
1132
|
// Handle media block transitions
|
|
1036
1133
|
if (NIL_P(rule_media)) {
|
|
@@ -1044,7 +1141,7 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
1044
1141
|
rb_str_cat2(result, "\n");
|
|
1045
1142
|
}
|
|
1046
1143
|
} else {
|
|
1047
|
-
// In media - check if we need to open/change block
|
|
1144
|
+
// In media - check if we need to open/change block (compare MediaQuery objects by value)
|
|
1048
1145
|
if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
|
|
1049
1146
|
// Close previous media block if open
|
|
1050
1147
|
if (in_media_block) {
|
|
@@ -1053,10 +1150,10 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
1053
1150
|
// Add blank line before new media block (except at start)
|
|
1054
1151
|
rb_str_cat2(result, "\n");
|
|
1055
1152
|
}
|
|
1056
|
-
// Open new media block
|
|
1153
|
+
// Open new media block - store the MediaQuery object for comparison
|
|
1057
1154
|
current_media = rule_media;
|
|
1058
1155
|
rb_str_cat2(result, "@media ");
|
|
1059
|
-
|
|
1156
|
+
append_media_query_text(result, rule_media);
|
|
1060
1157
|
rb_str_cat2(result, " {\n");
|
|
1061
1158
|
in_media_block = 1;
|
|
1062
1159
|
}
|
|
@@ -1077,7 +1174,7 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
1077
1174
|
// Serialize rule with nested children
|
|
1078
1175
|
DEBUG_PRINTF("[FORMATTED] Calling serialize_rule_with_children, in_media_block=%d\n", in_media_block);
|
|
1079
1176
|
serialize_rule_with_children(
|
|
1080
|
-
result, rules_array, i,
|
|
1177
|
+
result, rules_array, i, parent_to_children, media_queries,
|
|
1081
1178
|
1, // formatted (with indentation)
|
|
1082
1179
|
in_media_block ? 1 : 0 // indent_level (1 if inside media block, 0 otherwise)
|
|
1083
1180
|
);
|
|
@@ -1088,7 +1185,6 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
1088
1185
|
rb_str_cat2(result, "}\n");
|
|
1089
1186
|
}
|
|
1090
1187
|
|
|
1091
|
-
RB_GC_GUARD(rule_to_media);
|
|
1092
1188
|
RB_GC_GUARD(parent_to_children);
|
|
1093
1189
|
return result;
|
|
1094
1190
|
}
|
|
@@ -1312,6 +1408,12 @@ void Init_native_extension(void) {
|
|
|
1312
1408
|
eSizeError = rb_define_class_under(mCataract, "SizeError", eCataractError);
|
|
1313
1409
|
}
|
|
1314
1410
|
|
|
1411
|
+
if (rb_const_defined(mCataract, rb_intern("ParseError"))) {
|
|
1412
|
+
eParseError = rb_const_get(mCataract, rb_intern("ParseError"));
|
|
1413
|
+
} else {
|
|
1414
|
+
eParseError = rb_define_class_under(mCataract, "ParseError", eCataractError);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1315
1417
|
// Reuse Ruby-defined structs (they must be defined before loading this extension)
|
|
1316
1418
|
// If they don't exist, someone required the extension directly instead of via lib/cataract.rb
|
|
1317
1419
|
if (rb_const_defined(mCataract, rb_intern("Rule"))) {
|
|
@@ -1338,6 +1440,12 @@ void Init_native_extension(void) {
|
|
|
1338
1440
|
rb_raise(rb_eLoadError, "Cataract::ImportStatement not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
|
|
1339
1441
|
}
|
|
1340
1442
|
|
|
1443
|
+
if (rb_const_defined(mCataract, rb_intern("MediaQuery"))) {
|
|
1444
|
+
cMediaQuery = rb_const_get(mCataract, rb_intern("MediaQuery"));
|
|
1445
|
+
} else {
|
|
1446
|
+
rb_raise(rb_eLoadError, "Cataract::MediaQuery not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1341
1449
|
// Define Declarations class and add to_s method
|
|
1342
1450
|
VALUE cDeclarations = rb_define_class_under(mCataract, "Declarations", rb_cObject);
|
|
1343
1451
|
rb_define_method(cDeclarations, "to_s", new_declarations_to_s_method, 0);
|
|
@@ -1347,15 +1455,14 @@ void Init_native_extension(void) {
|
|
|
1347
1455
|
|
|
1348
1456
|
// Define module functions
|
|
1349
1457
|
rb_define_module_function(mCataract, "_parse_css", parse_css_new, -1);
|
|
1350
|
-
rb_define_module_function(mCataract, "
|
|
1351
|
-
rb_define_module_function(mCataract, "
|
|
1458
|
+
rb_define_module_function(mCataract, "stylesheet_to_s", stylesheet_to_s, 6);
|
|
1459
|
+
rb_define_module_function(mCataract, "stylesheet_to_formatted_s", stylesheet_to_formatted_s, 6);
|
|
1352
1460
|
rb_define_module_function(mCataract, "parse_media_types", parse_media_types, 1);
|
|
1353
1461
|
rb_define_module_function(mCataract, "parse_declarations", new_parse_declarations, 1);
|
|
1354
1462
|
rb_define_module_function(mCataract, "flatten", cataract_flatten, 1);
|
|
1355
1463
|
rb_define_module_function(mCataract, "merge", cataract_flatten, 1); // Deprecated alias for backwards compatibility
|
|
1356
|
-
rb_define_module_function(mCataract, "extract_imports", extract_imports, 1);
|
|
1357
1464
|
rb_define_module_function(mCataract, "calculate_specificity", calculate_specificity, 1);
|
|
1358
|
-
rb_define_module_function(mCataract, "
|
|
1465
|
+
rb_define_module_function(mCataract, "expand_shorthand", cataract_expand_shorthand, 1);
|
|
1359
1466
|
|
|
1360
1467
|
// Initialize flatten constants (cached property strings)
|
|
1361
1468
|
init_flatten_constants();
|