cataract 0.2.4 → 0.3.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/test.yml +6 -6
- data/.gitignore +17 -3
- data/.rubocop.yml +1 -0
- data/BENCHMARKS.md +24 -6
- data/CHANGELOG.md +29 -1
- data/Gemfile +2 -2
- data/examples/css_analyzer/analyzer.rb +9 -3
- data/examples/css_analyzer/analyzers/base.rb +1 -1
- data/ext/cataract/cataract.c +251 -458
- data/ext/cataract/cataract.h +127 -16
- data/ext/cataract/css_parser.c +1029 -700
- data/ext/cataract/extconf.rb +1 -2
- data/ext/cataract/flatten.c +279 -991
- data/ext/cataract/shorthand_expander.c +80 -102
- data/ext/cataract_color/color_conversion.c +1 -1
- data/lib/cataract/declarations.rb +13 -7
- data/lib/cataract/error.rb +49 -0
- data/lib/cataract/import_resolver.rb +36 -16
- data/lib/cataract/pure/byte_constants.rb +4 -1
- data/lib/cataract/pure/flatten.rb +143 -145
- data/lib/cataract/pure/parser.rb +623 -388
- data/lib/cataract/pure/serializer.rb +109 -104
- data/lib/cataract/pure/specificity.rb +112 -156
- data/lib/cataract/pure.rb +1 -6
- data/lib/cataract/stylesheet.rb +480 -410
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +1 -0
- metadata +3 -13
- data/ext/cataract/import_scanner.c +0 -174
- data/ext/cataract_old/cataract.c +0 -393
- data/ext/cataract_old/cataract.h +0 -250
- data/ext/cataract_old/css_parser.c +0 -933
- data/ext/cataract_old/extconf.rb +0 -67
- data/ext/cataract_old/import_scanner.c +0 -174
- data/ext/cataract_old/merge.c +0 -776
- data/ext/cataract_old/shorthand_expander.c +0 -902
- data/ext/cataract_old/specificity.c +0 -213
- data/ext/cataract_old/stylesheet.c +0 -290
- data/ext/cataract_old/value_splitter.c +0 -116
|
@@ -72,45 +72,45 @@ static VALUE expand_dimensions(VALUE parts, const char *property, const char *su
|
|
|
72
72
|
* Expand margin shorthand: "10px 20px 30px 40px"
|
|
73
73
|
* Returns array of Declaration structs
|
|
74
74
|
*/
|
|
75
|
-
VALUE cataract_expand_margin(VALUE self, VALUE value) {
|
|
75
|
+
VALUE cataract_expand_margin(VALUE self, VALUE value, VALUE important) {
|
|
76
76
|
VALUE parts = cataract_split_value(self, value);
|
|
77
|
-
return expand_dimensions(parts, "margin", NULL,
|
|
77
|
+
return expand_dimensions(parts, "margin", NULL, important);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/*
|
|
81
81
|
* Expand padding shorthand: "10px 20px 30px 40px"
|
|
82
82
|
* Returns array of Declaration structs
|
|
83
83
|
*/
|
|
84
|
-
VALUE cataract_expand_padding(VALUE self, VALUE value) {
|
|
84
|
+
VALUE cataract_expand_padding(VALUE self, VALUE value, VALUE important) {
|
|
85
85
|
VALUE parts = cataract_split_value(self, value);
|
|
86
|
-
return expand_dimensions(parts, "padding", NULL,
|
|
86
|
+
return expand_dimensions(parts, "padding", NULL, important);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/*
|
|
90
90
|
* Expand border-color shorthand: "red green blue yellow"
|
|
91
91
|
* Returns array of Declaration structs
|
|
92
92
|
*/
|
|
93
|
-
VALUE cataract_expand_border_color(VALUE self, VALUE value) {
|
|
93
|
+
VALUE cataract_expand_border_color(VALUE self, VALUE value, VALUE important) {
|
|
94
94
|
VALUE parts = cataract_split_value(self, value);
|
|
95
|
-
return expand_dimensions(parts, "border", "color",
|
|
95
|
+
return expand_dimensions(parts, "border", "color", important);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/*
|
|
99
99
|
* Expand border-style shorthand: "solid dashed dotted double"
|
|
100
100
|
* Returns array of Declaration structs
|
|
101
101
|
*/
|
|
102
|
-
VALUE cataract_expand_border_style(VALUE self, VALUE value) {
|
|
102
|
+
VALUE cataract_expand_border_style(VALUE self, VALUE value, VALUE important) {
|
|
103
103
|
VALUE parts = cataract_split_value(self, value);
|
|
104
|
-
return expand_dimensions(parts, "border", "style",
|
|
104
|
+
return expand_dimensions(parts, "border", "style", important);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/*
|
|
108
108
|
* Expand border-width shorthand: "1px 2px 3px 4px"
|
|
109
109
|
* Returns array of Declaration structs
|
|
110
110
|
*/
|
|
111
|
-
VALUE cataract_expand_border_width(VALUE self, VALUE value) {
|
|
111
|
+
VALUE cataract_expand_border_width(VALUE self, VALUE value, VALUE important) {
|
|
112
112
|
VALUE parts = cataract_split_value(self, value);
|
|
113
|
-
return expand_dimensions(parts, "border", "width",
|
|
113
|
+
return expand_dimensions(parts, "border", "width", important);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/*
|
|
@@ -140,7 +140,7 @@ static int is_border_style(const char *str) {
|
|
|
140
140
|
* Expand border shorthand: "1px solid red"
|
|
141
141
|
* Returns array of Declaration structs (up to 12: 4 sides × 3 properties)
|
|
142
142
|
*/
|
|
143
|
-
VALUE cataract_expand_border(VALUE self, VALUE value) {
|
|
143
|
+
VALUE cataract_expand_border(VALUE self, VALUE value, VALUE important) {
|
|
144
144
|
VALUE parts = cataract_split_value(self, value);
|
|
145
145
|
long len = RARRAY_LEN(parts);
|
|
146
146
|
|
|
@@ -169,19 +169,19 @@ VALUE cataract_expand_border(VALUE self, VALUE value) {
|
|
|
169
169
|
if (width != Qnil) {
|
|
170
170
|
char prop[64];
|
|
171
171
|
snprintf(prop, sizeof(prop), "border-%s-width", sides[i]);
|
|
172
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), width,
|
|
172
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), width, important);
|
|
173
173
|
rb_ary_push(result, decl);
|
|
174
174
|
}
|
|
175
175
|
if (style != Qnil) {
|
|
176
176
|
char prop[64];
|
|
177
177
|
snprintf(prop, sizeof(prop), "border-%s-style", sides[i]);
|
|
178
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), style,
|
|
178
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), style, important);
|
|
179
179
|
rb_ary_push(result, decl);
|
|
180
180
|
}
|
|
181
181
|
if (color != Qnil) {
|
|
182
182
|
char prop[64];
|
|
183
183
|
snprintf(prop, sizeof(prop), "border-%s-color", sides[i]);
|
|
184
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), color,
|
|
184
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), color, important);
|
|
185
185
|
rb_ary_push(result, decl);
|
|
186
186
|
}
|
|
187
187
|
}
|
|
@@ -193,7 +193,7 @@ VALUE cataract_expand_border(VALUE self, VALUE value) {
|
|
|
193
193
|
* Expand border-{side} shorthand: "2px dashed blue"
|
|
194
194
|
* Returns array of Declaration structs (up to 3: width, style, color)
|
|
195
195
|
*/
|
|
196
|
-
VALUE cataract_expand_border_side(VALUE self, VALUE side, VALUE value) {
|
|
196
|
+
VALUE cataract_expand_border_side(VALUE self, VALUE side, VALUE value, VALUE important) {
|
|
197
197
|
VALUE parts = cataract_split_value(self, value);
|
|
198
198
|
long len = RARRAY_LEN(parts);
|
|
199
199
|
const char *side_str = StringValueCStr(side);
|
|
@@ -234,19 +234,19 @@ VALUE cataract_expand_border_side(VALUE self, VALUE side, VALUE value) {
|
|
|
234
234
|
if (width != Qnil) {
|
|
235
235
|
char prop[64];
|
|
236
236
|
snprintf(prop, sizeof(prop), "border-%s-width", side_str);
|
|
237
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), width,
|
|
237
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), width, important);
|
|
238
238
|
rb_ary_push(result, decl);
|
|
239
239
|
}
|
|
240
240
|
if (style != Qnil) {
|
|
241
241
|
char prop[64];
|
|
242
242
|
snprintf(prop, sizeof(prop), "border-%s-style", side_str);
|
|
243
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), style,
|
|
243
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), style, important);
|
|
244
244
|
rb_ary_push(result, decl);
|
|
245
245
|
}
|
|
246
246
|
if (color != Qnil) {
|
|
247
247
|
char prop[64];
|
|
248
248
|
snprintf(prop, sizeof(prop), "border-%s-color", side_str);
|
|
249
|
-
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), color,
|
|
249
|
+
VALUE decl = rb_struct_new(cDeclaration, STR_NEW_CSTR(prop), color, important);
|
|
250
250
|
rb_ary_push(result, decl);
|
|
251
251
|
}
|
|
252
252
|
|
|
@@ -258,7 +258,7 @@ VALUE cataract_expand_border_side(VALUE self, VALUE side, VALUE value) {
|
|
|
258
258
|
* Font syntax: [style] [variant] [weight] [size]/[line-height] [family]
|
|
259
259
|
* Only size and family are required
|
|
260
260
|
*/
|
|
261
|
-
VALUE cataract_expand_font(VALUE self, VALUE value) {
|
|
261
|
+
VALUE cataract_expand_font(VALUE self, VALUE value, VALUE important) {
|
|
262
262
|
// Font is complex - need to handle / separator for line-height
|
|
263
263
|
// Split on / first to separate size from line-height
|
|
264
264
|
const char *str = StringValueCStr(value);
|
|
@@ -398,15 +398,15 @@ VALUE cataract_expand_font(VALUE self, VALUE value) {
|
|
|
398
398
|
|
|
399
399
|
// Create array of Declaration structs
|
|
400
400
|
VALUE result = rb_ary_new_capa(6);
|
|
401
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-style"), style,
|
|
402
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-variant"), variant,
|
|
403
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-weight"), weight,
|
|
401
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-style"), style, important));
|
|
402
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-variant"), variant, important));
|
|
403
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-weight"), weight, important));
|
|
404
404
|
if (size != Qnil) {
|
|
405
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-size"), size,
|
|
405
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-size"), size, important));
|
|
406
406
|
}
|
|
407
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("line-height"), line_height,
|
|
407
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("line-height"), line_height, important));
|
|
408
408
|
if (family != Qnil) {
|
|
409
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-family"), family,
|
|
409
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("font-family"), family, important));
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
return result;
|
|
@@ -415,7 +415,7 @@ VALUE cataract_expand_font(VALUE self, VALUE value) {
|
|
|
415
415
|
/*
|
|
416
416
|
* Expand list-style shorthand: "square inside"
|
|
417
417
|
*/
|
|
418
|
-
VALUE cataract_expand_list_style(VALUE self, VALUE value) {
|
|
418
|
+
VALUE cataract_expand_list_style(VALUE self, VALUE value, VALUE important) {
|
|
419
419
|
VALUE parts = cataract_split_value(self, value);
|
|
420
420
|
long len = RARRAY_LEN(parts);
|
|
421
421
|
|
|
@@ -459,13 +459,13 @@ VALUE cataract_expand_list_style(VALUE self, VALUE value) {
|
|
|
459
459
|
// Create array of Declaration structs
|
|
460
460
|
VALUE result = rb_ary_new_capa(3);
|
|
461
461
|
if (type != Qnil) {
|
|
462
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-type"), type,
|
|
462
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-type"), type, important));
|
|
463
463
|
}
|
|
464
464
|
if (position != Qnil) {
|
|
465
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-position"), position,
|
|
465
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-position"), position, important));
|
|
466
466
|
}
|
|
467
467
|
if (image != Qnil) {
|
|
468
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-image"), image,
|
|
468
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("list-style-image"), image, important));
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
return result;
|
|
@@ -475,7 +475,7 @@ VALUE cataract_expand_list_style(VALUE self, VALUE value) {
|
|
|
475
475
|
* Expand background shorthand: "url(img.png) no-repeat center / cover"
|
|
476
476
|
* This is complex - background has many sub-properties and / separator for size
|
|
477
477
|
*/
|
|
478
|
-
VALUE cataract_expand_background(VALUE self, VALUE value) {
|
|
478
|
+
VALUE cataract_expand_background(VALUE self, VALUE value, VALUE important) {
|
|
479
479
|
DEBUG_PRINTF("[cataract_expand_background] input value: '%s'\n", RSTRING_PTR(value));
|
|
480
480
|
|
|
481
481
|
// First, check if there's a / separator for background-size
|
|
@@ -596,17 +596,17 @@ VALUE cataract_expand_background(VALUE self, VALUE value) {
|
|
|
596
596
|
// Create array of Declaration structs
|
|
597
597
|
VALUE result = rb_ary_new_capa(6);
|
|
598
598
|
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-color"),
|
|
599
|
-
color != Qnil ? color : STR_NEW_CSTR("transparent"),
|
|
599
|
+
color != Qnil ? color : STR_NEW_CSTR("transparent"), important));
|
|
600
600
|
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-image"),
|
|
601
|
-
image != Qnil ? image : STR_NEW_CSTR("none"),
|
|
601
|
+
image != Qnil ? image : STR_NEW_CSTR("none"), important));
|
|
602
602
|
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-repeat"),
|
|
603
|
-
repeat != Qnil ? repeat : STR_NEW_CSTR("repeat"),
|
|
603
|
+
repeat != Qnil ? repeat : STR_NEW_CSTR("repeat"), important));
|
|
604
604
|
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-attachment"),
|
|
605
|
-
attachment != Qnil ? attachment : STR_NEW_CSTR("scroll"),
|
|
605
|
+
attachment != Qnil ? attachment : STR_NEW_CSTR("scroll"), important));
|
|
606
606
|
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-position"),
|
|
607
|
-
position != Qnil ? position : STR_NEW_CSTR("0% 0%"),
|
|
607
|
+
position != Qnil ? position : STR_NEW_CSTR("0% 0%"), important));
|
|
608
608
|
if (size != Qnil) {
|
|
609
|
-
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-size"), size,
|
|
609
|
+
rb_ary_push(result, rb_struct_new(cDeclaration, STR_NEW_CSTR("background-size"), size, important));
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
return result;
|
|
@@ -616,26 +616,10 @@ VALUE cataract_expand_background(VALUE self, VALUE value) {
|
|
|
616
616
|
// SHORTHAND CREATION (Inverse of expansion)
|
|
617
617
|
// ============================================================================
|
|
618
618
|
|
|
619
|
-
// Helper:
|
|
620
|
-
//
|
|
621
|
-
//
|
|
622
|
-
static VALUE
|
|
623
|
-
char key_top[32], key_right[32], key_bottom[32], key_left[32];
|
|
624
|
-
snprintf(key_top, sizeof(key_top), "%s-top", base);
|
|
625
|
-
snprintf(key_right, sizeof(key_right), "%s-right", base);
|
|
626
|
-
snprintf(key_bottom, sizeof(key_bottom), "%s-bottom", base);
|
|
627
|
-
snprintf(key_left, sizeof(key_left), "%s-left", base);
|
|
628
|
-
|
|
629
|
-
VALUE top = rb_hash_aref(properties, STR_NEW_CSTR(key_top));
|
|
630
|
-
VALUE right = rb_hash_aref(properties, STR_NEW_CSTR(key_right));
|
|
631
|
-
VALUE bottom = rb_hash_aref(properties, STR_NEW_CSTR(key_bottom));
|
|
632
|
-
VALUE left = rb_hash_aref(properties, STR_NEW_CSTR(key_left));
|
|
633
|
-
|
|
634
|
-
// All four sides must be present
|
|
635
|
-
if (NIL_P(top) || NIL_P(right) || NIL_P(bottom) || NIL_P(left)) {
|
|
636
|
-
return Qnil;
|
|
637
|
-
}
|
|
638
|
-
|
|
619
|
+
// Helper: Collapse 4 box-model side values into optimized CSS shorthand
|
|
620
|
+
// syntax (1, 2, 3, or 4 values), per the standard top/right/bottom/left
|
|
621
|
+
// collapsing rules shared by margin/padding/border-{width,style,color}.
|
|
622
|
+
static VALUE collapse_box_values(VALUE top, VALUE right, VALUE bottom, VALUE left) {
|
|
639
623
|
const char *top_str = StringValueCStr(top);
|
|
640
624
|
const char *right_str = StringValueCStr(right);
|
|
641
625
|
const char *bottom_str = StringValueCStr(bottom);
|
|
@@ -662,6 +646,29 @@ static VALUE create_dimension_shorthand(VALUE properties, const char *base) {
|
|
|
662
646
|
return rb_sprintf("%s %s %s %s", top_str, right_str, bottom_str, left_str);
|
|
663
647
|
}
|
|
664
648
|
|
|
649
|
+
// Helper: Create dimension shorthand (margin or padding)
|
|
650
|
+
// Input: hash with "#{base}-top", "#{base}-right", "#{base}-bottom", "#{base}-left"
|
|
651
|
+
// Output: optimized shorthand string, or Qnil if not all sides present
|
|
652
|
+
static VALUE create_dimension_shorthand(VALUE properties, const char *base) {
|
|
653
|
+
char key_top[32], key_right[32], key_bottom[32], key_left[32];
|
|
654
|
+
snprintf(key_top, sizeof(key_top), "%s-top", base);
|
|
655
|
+
snprintf(key_right, sizeof(key_right), "%s-right", base);
|
|
656
|
+
snprintf(key_bottom, sizeof(key_bottom), "%s-bottom", base);
|
|
657
|
+
snprintf(key_left, sizeof(key_left), "%s-left", base);
|
|
658
|
+
|
|
659
|
+
VALUE top = rb_hash_aref(properties, STR_NEW_CSTR(key_top));
|
|
660
|
+
VALUE right = rb_hash_aref(properties, STR_NEW_CSTR(key_right));
|
|
661
|
+
VALUE bottom = rb_hash_aref(properties, STR_NEW_CSTR(key_bottom));
|
|
662
|
+
VALUE left = rb_hash_aref(properties, STR_NEW_CSTR(key_left));
|
|
663
|
+
|
|
664
|
+
// All four sides must be present
|
|
665
|
+
if (NIL_P(top) || NIL_P(right) || NIL_P(bottom) || NIL_P(left)) {
|
|
666
|
+
return Qnil;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return collapse_box_values(top, right, bottom, left);
|
|
670
|
+
}
|
|
671
|
+
|
|
665
672
|
// Create margin shorthand from longhand properties
|
|
666
673
|
// Input: hash with "margin-top", "margin-right", "margin-bottom", "margin-left"
|
|
667
674
|
// Output: optimized shorthand string, or Qnil if not all sides present
|
|
@@ -699,31 +706,7 @@ static VALUE create_border_dimension_shorthand(VALUE properties, const char *suf
|
|
|
699
706
|
return Qnil;
|
|
700
707
|
}
|
|
701
708
|
|
|
702
|
-
|
|
703
|
-
const char *top_str = StringValueCStr(top);
|
|
704
|
-
const char *right_str = StringValueCStr(right);
|
|
705
|
-
const char *bottom_str = StringValueCStr(bottom);
|
|
706
|
-
const char *left_str = StringValueCStr(left);
|
|
707
|
-
|
|
708
|
-
// Optimize: if all same, return single value
|
|
709
|
-
if (strcmp(top_str, right_str) == 0 &&
|
|
710
|
-
strcmp(top_str, bottom_str) == 0 &&
|
|
711
|
-
strcmp(top_str, left_str) == 0) {
|
|
712
|
-
return rb_str_dup(top);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
// Optimize: if top==bottom and left==right, use two values
|
|
716
|
-
if (strcmp(top_str, bottom_str) == 0 && strcmp(left_str, right_str) == 0) {
|
|
717
|
-
return rb_sprintf("%s %s", top_str, right_str);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Optimize: if left==right, use three values
|
|
721
|
-
if (strcmp(left_str, right_str) == 0) {
|
|
722
|
-
return rb_sprintf("%s %s %s", top_str, right_str, bottom_str);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// All different: use four values
|
|
726
|
-
return rb_sprintf("%s %s %s %s", top_str, right_str, bottom_str, left_str);
|
|
709
|
+
return collapse_box_values(top, right, bottom, left);
|
|
727
710
|
}
|
|
728
711
|
|
|
729
712
|
// Create border-width shorthand from individual sides
|
|
@@ -1023,42 +1006,37 @@ VALUE cataract_expand_shorthand(VALUE self, VALUE decl) {
|
|
|
1023
1006
|
return result;
|
|
1024
1007
|
}
|
|
1025
1008
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
//
|
|
1009
|
+
// Try to expand based on property name, calling the same public
|
|
1010
|
+
// cataract_expand_* functions used directly by callers (and by
|
|
1011
|
+
// flatten.c) - return array of Declarations directly
|
|
1029
1012
|
VALUE result = Qnil;
|
|
1030
1013
|
|
|
1031
1014
|
if (strcmp(prop, "margin") == 0) {
|
|
1032
|
-
|
|
1033
|
-
result = expand_dimensions(parts, "margin", NULL, important);
|
|
1015
|
+
result = cataract_expand_margin(Qnil, value, important);
|
|
1034
1016
|
} else if (strcmp(prop, "padding") == 0) {
|
|
1035
|
-
|
|
1036
|
-
result = expand_dimensions(parts, "padding", NULL, important);
|
|
1017
|
+
result = cataract_expand_padding(Qnil, value, important);
|
|
1037
1018
|
} else if (strcmp(prop, "border-color") == 0) {
|
|
1038
|
-
|
|
1039
|
-
result = expand_dimensions(parts, "border", "color", important);
|
|
1019
|
+
result = cataract_expand_border_color(Qnil, value, important);
|
|
1040
1020
|
} else if (strcmp(prop, "border-style") == 0) {
|
|
1041
|
-
|
|
1042
|
-
result = expand_dimensions(parts, "border", "style", important);
|
|
1021
|
+
result = cataract_expand_border_style(Qnil, value, important);
|
|
1043
1022
|
} else if (strcmp(prop, "border-width") == 0) {
|
|
1044
|
-
|
|
1045
|
-
result = expand_dimensions(parts, "border", "width", important);
|
|
1023
|
+
result = cataract_expand_border_width(Qnil, value, important);
|
|
1046
1024
|
} else if (strcmp(prop, "border") == 0) {
|
|
1047
|
-
result = cataract_expand_border(Qnil, value);
|
|
1025
|
+
result = cataract_expand_border(Qnil, value, important);
|
|
1048
1026
|
} else if (strcmp(prop, "border-top") == 0) {
|
|
1049
|
-
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value);
|
|
1027
|
+
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("top"), value, important);
|
|
1050
1028
|
} else if (strcmp(prop, "border-right") == 0) {
|
|
1051
|
-
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value);
|
|
1029
|
+
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("right"), value, important);
|
|
1052
1030
|
} else if (strcmp(prop, "border-bottom") == 0) {
|
|
1053
|
-
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value);
|
|
1031
|
+
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("bottom"), value, important);
|
|
1054
1032
|
} else if (strcmp(prop, "border-left") == 0) {
|
|
1055
|
-
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value);
|
|
1033
|
+
result = cataract_expand_border_side(Qnil, STR_NEW_CSTR("left"), value, important);
|
|
1056
1034
|
} else if (strcmp(prop, "font") == 0) {
|
|
1057
|
-
result = cataract_expand_font(Qnil, value);
|
|
1035
|
+
result = cataract_expand_font(Qnil, value, important);
|
|
1058
1036
|
} else if (strcmp(prop, "background") == 0) {
|
|
1059
|
-
result = cataract_expand_background(Qnil, value);
|
|
1037
|
+
result = cataract_expand_background(Qnil, value, important);
|
|
1060
1038
|
} else if (strcmp(prop, "list-style") == 0) {
|
|
1061
|
-
result = cataract_expand_list_style(Qnil, value);
|
|
1039
|
+
result = cataract_expand_list_style(Qnil, value, important);
|
|
1062
1040
|
}
|
|
1063
1041
|
|
|
1064
1042
|
// If not a shorthand (or expansion failed), return array with original declaration
|
|
@@ -1456,7 +1456,7 @@ static VALUE expand_property_if_needed(VALUE property_name, VALUE value) {
|
|
|
1456
1456
|
|
|
1457
1457
|
// Check if this is a shorthand property that needs expansion
|
|
1458
1458
|
if (strcmp(prop, "background") == 0) {
|
|
1459
|
-
return cataract_expand_background(Qnil, value);
|
|
1459
|
+
return cataract_expand_background(Qnil, value, Qfalse);
|
|
1460
1460
|
}
|
|
1461
1461
|
// Add other shorthands if needed (margin, padding, border, font, list-style)
|
|
1462
1462
|
|
|
@@ -278,13 +278,19 @@ module Cataract
|
|
|
278
278
|
dup.merge!(other)
|
|
279
279
|
end
|
|
280
280
|
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
# Ensure dup/clone create a proper independent copy, duplicating the
|
|
282
|
+
# internal declarations array so mutations (via []=, delete, merge!, etc.)
|
|
283
|
+
# on the copy don't affect the source. Declaration structs themselves are
|
|
284
|
+
# treated as immutable value objects throughout this class (always
|
|
285
|
+
# replaced wholesale, never mutated in place), so a shallow copy of the
|
|
286
|
+
# array is sufficient - defining this via initialize_copy (rather than
|
|
287
|
+
# overriding #dup directly) ensures #clone gets the same fix, since both
|
|
288
|
+
# go through initialize_copy.
|
|
289
|
+
#
|
|
290
|
+
# @param source [Declarations] Source Declarations being copied
|
|
291
|
+
def initialize_copy(source)
|
|
292
|
+
super
|
|
293
|
+
@values = source.instance_variable_get(:@values).dup
|
|
288
294
|
end
|
|
289
295
|
|
|
290
296
|
# Compare this Declarations with another object.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cataract
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
# Error raised during import resolution
|
|
7
|
+
class ImportError < Error; end
|
|
8
|
+
|
|
9
|
+
# Parsing errors
|
|
10
|
+
class DepthError < Error; end
|
|
11
|
+
class SizeError < Error; end
|
|
12
|
+
# Internal parser consistency errors
|
|
13
|
+
|
|
14
|
+
# Error raised when invalid CSS is encountered in strict mode
|
|
15
|
+
class ParseError < Error
|
|
16
|
+
attr_reader :line, :column, :error_type
|
|
17
|
+
|
|
18
|
+
# @param message [String] Error message (without position info)
|
|
19
|
+
# @param css [String, nil] Full CSS string for calculating position
|
|
20
|
+
# @param pos [Integer, nil] Byte position in CSS where error occurred
|
|
21
|
+
# @param line [Integer, nil] Line number (if already calculated)
|
|
22
|
+
# @param column [Integer, nil] Column number (if already calculated)
|
|
23
|
+
# @param type [Symbol, nil] Type of parse error (:empty_value, :malformed_declaration, etc.)
|
|
24
|
+
def initialize(message, css: nil, pos: nil, line: nil, column: nil, type: nil)
|
|
25
|
+
# Calculate line/column from css and pos if provided
|
|
26
|
+
if css && pos
|
|
27
|
+
@line = css.byteslice(0, pos).count("\n") + 1
|
|
28
|
+
line_start = css.rindex("\n", pos - 1)
|
|
29
|
+
@column = line_start ? pos - line_start : pos + 1
|
|
30
|
+
else
|
|
31
|
+
@line = line
|
|
32
|
+
@column = column
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@error_type = type
|
|
36
|
+
|
|
37
|
+
# Build message with position info
|
|
38
|
+
full_message = if @line && @column
|
|
39
|
+
"#{message} at line #{@line}, column #{@column}"
|
|
40
|
+
elsif @line
|
|
41
|
+
"#{message} at line #{@line}"
|
|
42
|
+
else
|
|
43
|
+
message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
super(full_message)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -57,7 +57,8 @@ module Cataract
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Default options for safe import resolution
|
|
60
|
+
# Default options for safe import resolution (@import statements
|
|
61
|
+
# discovered while parsing a stylesheet - untrusted by default)
|
|
61
62
|
SAFE_DEFAULTS = {
|
|
62
63
|
max_depth: 5, # Prevent infinite recursion
|
|
63
64
|
allowed_schemes: ['https'], # Only HTTPS by default
|
|
@@ -66,17 +67,33 @@ module Cataract
|
|
|
66
67
|
follow_redirects: true, # Follow redirects
|
|
67
68
|
base_path: nil, # Base path for resolving relative file imports
|
|
68
69
|
base_uri: nil, # Base URI for resolving relative HTTP imports
|
|
69
|
-
fetcher: nil
|
|
70
|
+
fetcher: nil, # Custom fetcher (defaults to DefaultFetcher)
|
|
71
|
+
dangerous_path_prefixes: ['/etc/', '/proc/', '/sys/', '/dev/'] # Blocked file:// path prefixes
|
|
70
72
|
}.freeze
|
|
71
73
|
|
|
74
|
+
# Default options for an explicitly caller-invoked load (Stylesheet#load_uri
|
|
75
|
+
# / #load_file). The caller chose this exact URI/path themselves - a
|
|
76
|
+
# different trust model than @import content discovered inside a
|
|
77
|
+
# stylesheet - so this permits the schemes/extensions load_uri has always
|
|
78
|
+
# supported, while still keeping the dangerous_path_prefixes protection
|
|
79
|
+
# from SAFE_DEFAULTS as a default (callers can still override it).
|
|
80
|
+
LOAD_DEFAULTS = SAFE_DEFAULTS.merge(
|
|
81
|
+
allowed_schemes: %w[http https file],
|
|
82
|
+
extensions: :any # skip extension validation entirely - see validate_url
|
|
83
|
+
).freeze
|
|
84
|
+
|
|
72
85
|
# Normalize options with safe defaults
|
|
73
|
-
|
|
86
|
+
#
|
|
87
|
+
# @param options [true, Hash] true for defaults as-is, or a Hash to merge over them
|
|
88
|
+
# @param defaults [Hash] Which default profile to merge over (SAFE_DEFAULTS
|
|
89
|
+
# for @import resolution, LOAD_DEFAULTS for an explicit load_uri/load_file call)
|
|
90
|
+
def self.normalize_options(options, defaults: SAFE_DEFAULTS)
|
|
74
91
|
if options == true
|
|
75
|
-
# imports: true -> use
|
|
76
|
-
|
|
92
|
+
# imports: true -> use defaults as-is
|
|
93
|
+
defaults.dup
|
|
77
94
|
elsif options.is_a?(Hash)
|
|
78
|
-
# imports: { ... } -> merge with
|
|
79
|
-
|
|
95
|
+
# imports: { ... } -> merge with defaults
|
|
96
|
+
defaults.merge(options)
|
|
80
97
|
else
|
|
81
98
|
raise ArgumentError, 'imports option must be true or a Hash'
|
|
82
99
|
end
|
|
@@ -127,13 +144,16 @@ module Cataract
|
|
|
127
144
|
"Import scheme '#{uri.scheme}' not allowed. Allowed schemes: #{options[:allowed_schemes].join(', ')}"
|
|
128
145
|
end
|
|
129
146
|
|
|
130
|
-
# Check extension
|
|
131
|
-
|
|
132
|
-
|
|
147
|
+
# Check extension (extensions: :any skips this check entirely - used by
|
|
148
|
+
# LOAD_DEFAULTS, since a directly-invoked load isn't restricted to .css)
|
|
149
|
+
unless options[:extensions] == :any
|
|
150
|
+
path = uri.path || ''
|
|
151
|
+
ext = File.extname(path).delete_prefix('.')
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
unless ext.empty? || options[:extensions].include?(ext)
|
|
154
|
+
raise ImportError,
|
|
155
|
+
"Import extension '.#{ext}' not allowed. Allowed extensions: #{options[:extensions].join(', ')}"
|
|
156
|
+
end
|
|
137
157
|
end
|
|
138
158
|
|
|
139
159
|
# Additional security checks for file:// scheme
|
|
@@ -146,9 +166,9 @@ module Cataract
|
|
|
146
166
|
raise ImportError, "Import file not found or not readable: #{file_path}"
|
|
147
167
|
end
|
|
148
168
|
|
|
149
|
-
# Prevent reading sensitive files (basic check
|
|
150
|
-
|
|
151
|
-
if
|
|
169
|
+
# Prevent reading sensitive files (basic check, configurable via
|
|
170
|
+
# options[:dangerous_path_prefixes] - pass [] to disable)
|
|
171
|
+
if options[:dangerous_path_prefixes]&.any? { |prefix| file_path.start_with?(prefix) }
|
|
152
172
|
raise ImportError, "Import of sensitive system files not allowed: #{file_path}"
|
|
153
173
|
end
|
|
154
174
|
end
|
|
@@ -45,7 +45,10 @@ module Cataract
|
|
|
45
45
|
BYTE_BACKSLASH = 92 # '\\'
|
|
46
46
|
BYTE_BANG = 33 # '!'
|
|
47
47
|
BYTE_PERCENT = 37 # '%'
|
|
48
|
-
|
|
48
|
+
BYTE_EQUALS = 61 # '='
|
|
49
|
+
BYTE_CARET = 94 # '^'
|
|
50
|
+
BYTE_DOLLAR = 36 # '$'
|
|
51
|
+
BYTE_PIPE = 124 # '|'
|
|
49
52
|
|
|
50
53
|
# Specific lowercase letters (for keyword matching)
|
|
51
54
|
BYTE_LOWER_U = 117 # 'u'
|