cataract 0.2.0 → 0.2.1

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: bcb5043777c52ed2a3f4bcd96966dcbe00e0f7bcf5ac410398afc855d59407f5
4
- data.tar.gz: 65fabbfff1bbc559a0998f5a412800987ed2921252cdd2d4a757c6b59041c66a
3
+ metadata.gz: a1c4727f2f1b3c3aafa117ca105ee0d55a6d6f33b0fed789533af10c24173918
4
+ data.tar.gz: 86c0e96b1a953fb817fa235517e00215b4b363baea7d1225c5fcb2b9643b29c9
5
5
  SHA512:
6
- metadata.gz: cee4076685ff5a5e6ae8dbe441a9e7cefc8f1ad109fa7179373cb0bb39e4118b570ddfef68aeef573b002a683b3ca66148293f460b52a562472596d4d1e11842
7
- data.tar.gz: de9ae8a5f1d684c797706b48e10e7372cda9ae280698e67ea3dac25be81331a901c9a181dd8ca04d2cb9ce46b2447fc0709501b0fd53c848bcc6981a4d286597
6
+ metadata.gz: 7282bde75fb5cd81ad2b7f2d7134a6fde2941a8f5320a4de2d463114d25bfc709e80f1639e9479cb69b7f22d685d203a7097fa0cf8c2e952041b733420991080
7
+ data.tar.gz: f8af1d50b389d64c36c5138f431b5b09f95ec3ca8f68b6c342ec280e7e1c84a8fca95ec6c95a81d97f9bced8d0f295acfbf2849ca47939e16cbf1b408544cdbe
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.2.1] - 2025-11-14
2
+
3
+ - Fix serializer bug related to media queries
4
+
1
5
  ## [0.2.0] - 2025-11-14
2
6
 
3
7
  - Major: CSS `@import` resolution refactored from string-concatenation to parsed-object architecture with proper charset handling, media query combining,
@@ -549,18 +549,34 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
549
549
 
550
550
  if (formatted) {
551
551
  // Formatted output with indentation
552
+ DEBUG_PRINTF("[SERIALIZE_RULE] Formatted mode, indent_level=%d, selector=%s\n", indent_level, RSTRING_PTR(selector));
552
553
  rb_str_append(result, selector);
553
554
  rb_str_cat2(result, " {\n");
554
555
 
556
+ // Build indent strings based on indent_level
557
+ // Declarations are inside the rule, so add 1 level (2 spaces per level)
558
+ // Closing brace matches the opening selector level
559
+ char decl_indent[MAX_INDENT_BUFFER];
560
+ char closing_indent[MAX_INDENT_BUFFER];
561
+ int decl_spaces = (indent_level + 1) * 2;
562
+ int closing_spaces = indent_level * 2;
563
+ memset(decl_indent, ' ', decl_spaces);
564
+ decl_indent[decl_spaces] = '\0';
565
+ memset(closing_indent, ' ', closing_spaces);
566
+ closing_indent[closing_spaces] = '\0';
567
+
555
568
  // Serialize own declarations with indentation (each on its own line)
556
569
  if (!NIL_P(declarations) && RARRAY_LEN(declarations) > 0) {
557
- serialize_declarations_formatted(result, declarations, " ");
570
+ DEBUG_PRINTF("[SERIALIZE_RULE] Serializing %ld declarations with indent='%s' (%d spaces)\n",
571
+ RARRAY_LEN(declarations), decl_indent, decl_spaces);
572
+ serialize_declarations_formatted(result, declarations, decl_indent);
558
573
  }
559
574
 
560
575
  // Serialize nested children
561
576
  serialize_children_only(result, rules_array, rule_idx, rule_to_media, parent_to_children,
562
577
  selector, declarations, formatted, indent_level + 1);
563
578
 
579
+ rb_str_cat2(result, closing_indent);
564
580
  rb_str_cat2(result, "}\n");
565
581
  } else {
566
582
  // Compact output
@@ -576,6 +592,9 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
576
592
 
577
593
  rb_str_cat2(result, " }\n");
578
594
  }
595
+
596
+ // Prevent compiler from optimizing away 'rule' before we're done with selector/declarations
597
+ RB_GC_GUARD(rule);
579
598
  }
580
599
 
581
600
  // New stylesheet serialization entry point - checks for nesting and delegates
@@ -626,6 +645,10 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
626
645
 
627
646
  DEBUG_PRINTF("[MAP] parent_to_children map: %s\n", RSTRING_PTR(rb_inspect(parent_to_children)));
628
647
 
648
+ // Track media block state for proper opening/closing
649
+ VALUE current_media = Qnil;
650
+ int in_media_block = 0;
651
+
629
652
  // Serialize only top-level rules (parent_rule_id == nil)
630
653
  // Children are serialized recursively
631
654
  DEBUG_PRINTF("[SERIALIZE] Starting serialization, total_rules=%ld\n", total_rules);
@@ -643,6 +666,34 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
643
666
  continue;
644
667
  }
645
668
 
669
+ // Get media for this rule
670
+ VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
671
+ VALUE rule_media = rb_hash_aref(rule_to_media, rule_id);
672
+
673
+ // Handle media block transitions
674
+ if (NIL_P(rule_media)) {
675
+ // Not in media - close any open media block
676
+ if (in_media_block) {
677
+ rb_str_cat2(result, "}\n");
678
+ in_media_block = 0;
679
+ current_media = Qnil;
680
+ }
681
+ } else {
682
+ // In media - check if we need to open/change block
683
+ if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
684
+ // Close previous media block if open
685
+ if (in_media_block) {
686
+ rb_str_cat2(result, "}\n");
687
+ }
688
+ // Open new media block
689
+ current_media = rule_media;
690
+ rb_str_cat2(result, "@media ");
691
+ rb_str_append(result, rb_sym2str(rule_media));
692
+ rb_str_cat2(result, " {\n");
693
+ in_media_block = 1;
694
+ }
695
+ }
696
+
646
697
  // Check if this is an AtRule
647
698
  if (rb_obj_is_kind_of(rule, cAtRule)) {
648
699
  serialize_at_rule(result, rule);
@@ -657,6 +708,11 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
657
708
  );
658
709
  }
659
710
 
711
+ // Close final media block if still open
712
+ if (in_media_block) {
713
+ rb_str_cat2(result, "}\n");
714
+ }
715
+
660
716
  return result;
661
717
  }
662
718
 
@@ -779,6 +835,10 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
779
835
  }
780
836
  }
781
837
 
838
+ // Track media block state for proper opening/closing
839
+ VALUE current_media = Qnil;
840
+ int in_media_block = 0;
841
+
782
842
  // Serialize only top-level rules (parent_rule_id == nil)
783
843
  for (long i = 0; i < total_rules; i++) {
784
844
  VALUE rule = rb_ary_entry(rules_array, i);
@@ -789,20 +849,66 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
789
849
  continue;
790
850
  }
791
851
 
852
+ // Get media for this rule
853
+ VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
854
+ VALUE rule_media = rb_hash_aref(rule_to_media, rule_id);
855
+
856
+ // Handle media block transitions
857
+ if (NIL_P(rule_media)) {
858
+ // Not in media - close any open media block
859
+ if (in_media_block) {
860
+ rb_str_cat2(result, "}\n");
861
+ in_media_block = 0;
862
+ current_media = Qnil;
863
+
864
+ // Add blank line after closing media block
865
+ rb_str_cat2(result, "\n");
866
+ }
867
+ } else {
868
+ // In media - check if we need to open/change block
869
+ if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
870
+ // Close previous media block if open
871
+ if (in_media_block) {
872
+ rb_str_cat2(result, "}\n");
873
+ } else if (RSTRING_LEN(result) > 0) {
874
+ // Add blank line before new media block (except at start)
875
+ rb_str_cat2(result, "\n");
876
+ }
877
+ // Open new media block
878
+ current_media = rule_media;
879
+ rb_str_cat2(result, "@media ");
880
+ rb_str_append(result, rb_sym2str(rule_media));
881
+ rb_str_cat2(result, " {\n");
882
+ in_media_block = 1;
883
+ }
884
+ }
885
+
792
886
  // Check if this is an AtRule
793
887
  if (rb_obj_is_kind_of(rule, cAtRule)) {
794
888
  serialize_at_rule(result, rule);
795
889
  continue;
796
890
  }
797
891
 
892
+ // Add indent if inside media block
893
+ if (in_media_block) {
894
+ DEBUG_PRINTF("[FORMATTED] Adding base indent for media block\n");
895
+ rb_str_cat2(result, " ");
896
+ }
897
+
798
898
  // Serialize rule with nested children
899
+ DEBUG_PRINTF("[FORMATTED] Calling serialize_rule_with_children, in_media_block=%d\n", in_media_block);
799
900
  serialize_rule_with_children(
800
901
  result, rules_array, i, rule_to_media, parent_to_children,
801
902
  1, // formatted (with indentation)
802
- 0 // indent_level (top-level)
903
+ in_media_block ? 1 : 0 // indent_level (1 if inside media block, 0 otherwise)
803
904
  );
804
905
  }
805
906
 
907
+ // Close final media block if still open
908
+ if (in_media_block) {
909
+ rb_str_cat2(result, "}\n");
910
+ }
911
+
806
912
  return result;
807
913
  }
808
914
 
@@ -120,6 +120,10 @@ static inline VALUE strip_string(const char *str, long len) {
120
120
  #define MAX_PARSE_DEPTH 10 // Max recursion depth for nested @media/@supports blocks and CSS nesting
121
121
  #endif
122
122
 
123
+ // Max buffer size for indent strings in serialization
124
+ // (MAX_PARSE_DEPTH + 1) * 2 spaces + null terminator, rounded up for safety
125
+ #define MAX_INDENT_BUFFER ((MAX_PARSE_DEPTH + 2) * 2 + 1)
126
+
123
127
  #ifndef MAX_PROPERTY_NAME_LENGTH
124
128
  #define MAX_PROPERTY_NAME_LENGTH 256 // Max length of CSS property name
125
129
  #endif
@@ -355,6 +355,9 @@ module Cataract
355
355
  current_media = nil
356
356
  end
357
357
 
358
+ # Add blank line before base rule if we just closed a media block (ends with "}\n")
359
+ result << "\n" if result.length > 1 && result.getbyte(-1) == BYTE_NEWLINE && result.getbyte(-2) == BYTE_RBRACE
360
+
358
361
  serialize_rule_with_nesting_formatted(result, rule, rule_children, rule_to_media, '')
359
362
  else
360
363
  if current_media.nil? || current_media != rule_media
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cataract
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cataract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Cook