cataract 0.2.1 → 0.2.2
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/ci.yml +1 -1
- data/.rubocop.yml +2 -0
- data/BENCHMARKS.md +41 -38
- data/CHANGELOG.md +13 -0
- data/README.md +9 -3
- data/ext/cataract/cataract.c +273 -92
- data/ext/cataract/cataract.h +4 -3
- data/ext/cataract/css_parser.c +125 -11
- data/ext/cataract/flatten.c +271 -16
- data/lib/cataract/declaration.rb +19 -0
- data/lib/cataract/pure/flatten.rb +103 -8
- data/lib/cataract/pure/parser.rb +203 -139
- data/lib/cataract/pure/serializer.rb +217 -115
- data/lib/cataract/pure.rb +4 -2
- data/lib/cataract/rule.rb +39 -3
- data/lib/cataract/stylesheet.rb +137 -14
- data/lib/cataract/stylesheet_scope.rb +11 -4
- data/lib/cataract/version.rb +1 -1
- metadata +1 -1
data/ext/cataract/cataract.c
CHANGED
|
@@ -32,10 +32,21 @@ VALUE eSizeError;
|
|
|
32
32
|
* This matches the old parse_css API
|
|
33
33
|
*
|
|
34
34
|
* @param css_string [String] CSS to parse
|
|
35
|
+
* @param parser_options [Hash] Parser options (optional, defaults to {})
|
|
35
36
|
* @return [Hash] { rules: [...], media_index: {...}, charset: "..." }
|
|
36
37
|
*/
|
|
37
|
-
VALUE parse_css_new(VALUE
|
|
38
|
-
|
|
38
|
+
VALUE parse_css_new(int argc, VALUE *argv, VALUE self) {
|
|
39
|
+
VALUE css_string, parser_options;
|
|
40
|
+
|
|
41
|
+
// Parse arguments: required css_string, optional parser_options hash
|
|
42
|
+
rb_scan_args(argc, argv, "11", &css_string, &parser_options);
|
|
43
|
+
|
|
44
|
+
// Default to empty hash if not provided
|
|
45
|
+
if (NIL_P(parser_options)) {
|
|
46
|
+
parser_options = rb_hash_new();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return parse_css_new_impl(css_string, parser_options, 0);
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/*
|
|
@@ -292,6 +303,17 @@ struct build_rule_map_ctx {
|
|
|
292
303
|
VALUE rule_to_media;
|
|
293
304
|
};
|
|
294
305
|
|
|
306
|
+
// Formatting options for stylesheet serialization
|
|
307
|
+
// Avoids mode flags and if/else branches - all behavior controlled by struct values
|
|
308
|
+
struct format_opts {
|
|
309
|
+
const char *opening_brace; // " { " (compact) vs " {\n" (formatted)
|
|
310
|
+
const char *closing_brace; // " }\n" (compact) vs "}\n" (formatted)
|
|
311
|
+
const char *media_indent; // "" (compact) vs " " (formatted)
|
|
312
|
+
const char *decl_indent_base; // NULL (compact) vs " " (formatted base rules)
|
|
313
|
+
const char *decl_indent_media; // NULL (compact) vs " " (formatted media rules)
|
|
314
|
+
int add_blank_lines; // 0 (compact) vs 1 (formatted)
|
|
315
|
+
};
|
|
316
|
+
|
|
295
317
|
// Callback to build reverse map from rule_id to media_sym
|
|
296
318
|
static int build_rule_map_callback(VALUE media_sym, VALUE rule_ids, VALUE arg) {
|
|
297
319
|
struct build_rule_map_ctx *ctx = (struct build_rule_map_ctx *)arg;
|
|
@@ -318,27 +340,28 @@ static int build_rule_map_callback(VALUE media_sym, VALUE rule_ids, VALUE arg) {
|
|
|
318
340
|
return ST_CONTINUE;
|
|
319
341
|
}
|
|
320
342
|
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
VALUE result
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
rb_str_cat2(result, "@charset \"");
|
|
331
|
-
rb_str_append(result, charset);
|
|
332
|
-
rb_str_cat2(result, "\";\n");
|
|
333
|
-
}
|
|
334
|
-
|
|
343
|
+
// Private shared implementation for stylesheet serialization with optional selector list grouping
|
|
344
|
+
// All formatting behavior controlled by format_opts struct to avoid mode flags and if/else branches
|
|
345
|
+
static VALUE serialize_stylesheet_with_grouping(
|
|
346
|
+
VALUE rules_array,
|
|
347
|
+
VALUE media_index,
|
|
348
|
+
VALUE result,
|
|
349
|
+
VALUE selector_lists,
|
|
350
|
+
const struct format_opts *opts
|
|
351
|
+
) {
|
|
335
352
|
long total_rules = RARRAY_LEN(rules_array);
|
|
336
353
|
|
|
354
|
+
// Check if selector list grouping is enabled (non-empty hash)
|
|
355
|
+
int grouping_enabled = (!NIL_P(selector_lists) && TYPE(selector_lists) == T_HASH && RHASH_SIZE(selector_lists) > 0);
|
|
356
|
+
|
|
337
357
|
// Build a map from rule_id to media query symbol using rb_hash_foreach
|
|
338
358
|
VALUE rule_to_media = rb_hash_new();
|
|
339
359
|
struct build_rule_map_ctx map_ctx = { rule_to_media };
|
|
340
360
|
rb_hash_foreach(media_index, build_rule_map_callback, (VALUE)&map_ctx);
|
|
341
361
|
|
|
362
|
+
// Track processed rules to avoid duplicates when grouping
|
|
363
|
+
VALUE processed_rule_ids = rb_hash_new();
|
|
364
|
+
|
|
342
365
|
// Iterate through rules in insertion order, grouping consecutive media queries
|
|
343
366
|
VALUE current_media = Qnil;
|
|
344
367
|
int in_media_block = 0;
|
|
@@ -346,7 +369,14 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
346
369
|
for (long i = 0; i < total_rules; i++) {
|
|
347
370
|
VALUE rule = rb_ary_entry(rules_array, i);
|
|
348
371
|
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
372
|
+
|
|
373
|
+
// Skip if already processed (when grouped)
|
|
374
|
+
if (RTEST(rb_hash_aref(processed_rule_ids, rule_id))) {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
349
378
|
VALUE rule_media = rb_hash_aref(rule_to_media, rule_id);
|
|
379
|
+
int is_first_rule = (i == 0);
|
|
350
380
|
|
|
351
381
|
if (NIL_P(rule_media)) {
|
|
352
382
|
// Not in any media query - close any open media block first
|
|
@@ -356,8 +386,100 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
356
386
|
current_media = Qnil;
|
|
357
387
|
}
|
|
358
388
|
|
|
359
|
-
//
|
|
360
|
-
|
|
389
|
+
// Add blank line prefix for non-first rules (formatted only)
|
|
390
|
+
if (opts->add_blank_lines && !is_first_rule) {
|
|
391
|
+
rb_str_cat2(result, "\n");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Try to group with other rules from same selector list
|
|
395
|
+
// Check if this is a Rule (not AtRule) before accessing selector_list_id
|
|
396
|
+
if (grouping_enabled && rb_obj_is_kind_of(rule, cRule)) {
|
|
397
|
+
VALUE selector_list_id = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR_LIST_ID));
|
|
398
|
+
if (!NIL_P(selector_list_id)) {
|
|
399
|
+
// Get list of rule IDs in this selector list
|
|
400
|
+
VALUE rule_ids_in_list = rb_hash_aref(selector_lists, selector_list_id);
|
|
401
|
+
|
|
402
|
+
if (NIL_P(rule_ids_in_list) || RARRAY_LEN(rule_ids_in_list) <= 1) {
|
|
403
|
+
// Just this rule, serialize normally
|
|
404
|
+
if (opts->decl_indent_base) {
|
|
405
|
+
serialize_rule_formatted(result, rule, "", 1);
|
|
406
|
+
} else {
|
|
407
|
+
serialize_rule(result, rule);
|
|
408
|
+
}
|
|
409
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
410
|
+
} else {
|
|
411
|
+
// Find all rules with matching declarations and same media context
|
|
412
|
+
VALUE matching_selectors = rb_ary_new();
|
|
413
|
+
VALUE rule_declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
414
|
+
|
|
415
|
+
long list_len = RARRAY_LEN(rule_ids_in_list);
|
|
416
|
+
for (long j = 0; j < list_len; j++) {
|
|
417
|
+
VALUE other_rule_id = rb_ary_entry(rule_ids_in_list, j);
|
|
418
|
+
|
|
419
|
+
// Skip if already processed
|
|
420
|
+
if (RTEST(rb_hash_aref(processed_rule_ids, other_rule_id))) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Find the rule by ID
|
|
425
|
+
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
426
|
+
if (NIL_P(other_rule)) continue;
|
|
427
|
+
|
|
428
|
+
// Check same media context (both should be nil for base rules)
|
|
429
|
+
VALUE other_rule_media = rb_hash_aref(rule_to_media, other_rule_id);
|
|
430
|
+
if (!rb_equal(rule_media, other_rule_media)) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check if declarations match
|
|
435
|
+
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
436
|
+
if (rb_equal(rule_declarations, other_declarations)) {
|
|
437
|
+
VALUE other_selector = rb_struct_aref(other_rule, INT2FIX(RULE_SELECTOR));
|
|
438
|
+
rb_ary_push(matching_selectors, other_selector);
|
|
439
|
+
rb_hash_aset(processed_rule_ids, other_rule_id, Qtrue);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Serialize grouped or single rule
|
|
444
|
+
if (RARRAY_LEN(matching_selectors) > 1) {
|
|
445
|
+
// Group selectors with comma-space separator
|
|
446
|
+
VALUE selector_str = rb_ary_join(matching_selectors, rb_str_new_cstr(", "));
|
|
447
|
+
rb_str_append(result, selector_str);
|
|
448
|
+
rb_str_cat2(result, opts->opening_brace);
|
|
449
|
+
if (opts->decl_indent_base) {
|
|
450
|
+
serialize_declarations_formatted(result, rule_declarations, opts->decl_indent_base);
|
|
451
|
+
} else {
|
|
452
|
+
serialize_declarations(result, rule_declarations);
|
|
453
|
+
}
|
|
454
|
+
rb_str_cat2(result, opts->closing_brace);
|
|
455
|
+
RB_GC_GUARD(selector_str);
|
|
456
|
+
} else {
|
|
457
|
+
// Just one rule, serialize normally
|
|
458
|
+
if (opts->decl_indent_base) {
|
|
459
|
+
serialize_rule_formatted(result, rule, "", 1);
|
|
460
|
+
} else {
|
|
461
|
+
serialize_rule(result, rule);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
// No selector_list_id, serialize normally
|
|
467
|
+
if (opts->decl_indent_base) {
|
|
468
|
+
serialize_rule_formatted(result, rule, "", 1);
|
|
469
|
+
} else {
|
|
470
|
+
serialize_rule(result, rule);
|
|
471
|
+
}
|
|
472
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
// Grouping disabled, serialize normally
|
|
476
|
+
if (opts->decl_indent_base) {
|
|
477
|
+
serialize_rule_formatted(result, rule, "", 1);
|
|
478
|
+
} else {
|
|
479
|
+
serialize_rule(result, rule);
|
|
480
|
+
}
|
|
481
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
482
|
+
}
|
|
361
483
|
} else {
|
|
362
484
|
// This rule is in a media query
|
|
363
485
|
// Check if media query changed from previous rule
|
|
@@ -367,6 +489,11 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
367
489
|
rb_str_cat2(result, "}\n");
|
|
368
490
|
}
|
|
369
491
|
|
|
492
|
+
// Add blank line prefix for non-first rules (formatted only)
|
|
493
|
+
if (opts->add_blank_lines && !is_first_rule) {
|
|
494
|
+
rb_str_cat2(result, "\n");
|
|
495
|
+
}
|
|
496
|
+
|
|
370
497
|
// Open new media block
|
|
371
498
|
current_media = rule_media;
|
|
372
499
|
rb_str_cat2(result, "@media ");
|
|
@@ -375,8 +502,80 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
375
502
|
in_media_block = 1;
|
|
376
503
|
}
|
|
377
504
|
|
|
378
|
-
// Serialize rule inside media block
|
|
379
|
-
|
|
505
|
+
// Serialize rule inside media block (with grouping if enabled)
|
|
506
|
+
// Check if this is a Rule (not AtRule) before accessing selector_list_id
|
|
507
|
+
if (grouping_enabled && rb_obj_is_kind_of(rule, cRule)) {
|
|
508
|
+
VALUE selector_list_id = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR_LIST_ID));
|
|
509
|
+
if (!NIL_P(selector_list_id)) {
|
|
510
|
+
VALUE rule_ids_in_list = rb_hash_aref(selector_lists, selector_list_id);
|
|
511
|
+
|
|
512
|
+
if (NIL_P(rule_ids_in_list) || RARRAY_LEN(rule_ids_in_list) <= 1) {
|
|
513
|
+
if (opts->decl_indent_media) {
|
|
514
|
+
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
515
|
+
} else {
|
|
516
|
+
serialize_rule(result, rule);
|
|
517
|
+
}
|
|
518
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
519
|
+
} else {
|
|
520
|
+
VALUE matching_selectors = rb_ary_new();
|
|
521
|
+
VALUE rule_declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
522
|
+
|
|
523
|
+
long list_len = RARRAY_LEN(rule_ids_in_list);
|
|
524
|
+
for (long j = 0; j < list_len; j++) {
|
|
525
|
+
VALUE other_rule_id = rb_ary_entry(rule_ids_in_list, j);
|
|
526
|
+
if (RTEST(rb_hash_aref(processed_rule_ids, other_rule_id))) continue;
|
|
527
|
+
|
|
528
|
+
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
529
|
+
if (NIL_P(other_rule)) continue;
|
|
530
|
+
|
|
531
|
+
VALUE other_rule_media = rb_hash_aref(rule_to_media, other_rule_id);
|
|
532
|
+
if (!rb_equal(rule_media, other_rule_media)) continue;
|
|
533
|
+
|
|
534
|
+
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
535
|
+
if (rb_equal(rule_declarations, other_declarations)) {
|
|
536
|
+
VALUE other_selector = rb_struct_aref(other_rule, INT2FIX(RULE_SELECTOR));
|
|
537
|
+
rb_ary_push(matching_selectors, other_selector);
|
|
538
|
+
rb_hash_aset(processed_rule_ids, other_rule_id, Qtrue);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (RARRAY_LEN(matching_selectors) > 1) {
|
|
543
|
+
VALUE selector_str = rb_ary_join(matching_selectors, rb_str_new_cstr(", "));
|
|
544
|
+
rb_str_cat2(result, opts->media_indent);
|
|
545
|
+
rb_str_append(result, selector_str);
|
|
546
|
+
rb_str_cat2(result, opts->opening_brace);
|
|
547
|
+
if (opts->decl_indent_media) {
|
|
548
|
+
serialize_declarations_formatted(result, rule_declarations, opts->decl_indent_media);
|
|
549
|
+
} else {
|
|
550
|
+
serialize_declarations(result, rule_declarations);
|
|
551
|
+
}
|
|
552
|
+
rb_str_cat2(result, opts->media_indent);
|
|
553
|
+
rb_str_cat2(result, opts->closing_brace);
|
|
554
|
+
RB_GC_GUARD(selector_str);
|
|
555
|
+
} else {
|
|
556
|
+
if (opts->decl_indent_media) {
|
|
557
|
+
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
558
|
+
} else {
|
|
559
|
+
serialize_rule(result, rule);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
if (opts->decl_indent_media) {
|
|
565
|
+
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
566
|
+
} else {
|
|
567
|
+
serialize_rule(result, rule);
|
|
568
|
+
}
|
|
569
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
if (opts->decl_indent_media) {
|
|
573
|
+
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
574
|
+
} else {
|
|
575
|
+
serialize_rule(result, rule);
|
|
576
|
+
}
|
|
577
|
+
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
578
|
+
}
|
|
380
579
|
}
|
|
381
580
|
}
|
|
382
581
|
|
|
@@ -385,9 +584,38 @@ static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALU
|
|
|
385
584
|
rb_str_cat2(result, "}\n");
|
|
386
585
|
}
|
|
387
586
|
|
|
587
|
+
RB_GC_GUARD(rule_to_media);
|
|
588
|
+
RB_GC_GUARD(processed_rule_ids);
|
|
388
589
|
return result;
|
|
389
590
|
}
|
|
390
591
|
|
|
592
|
+
// Original stylesheet serialization (no nesting support) - compact format
|
|
593
|
+
static VALUE stylesheet_to_s_original(VALUE rules_array, VALUE media_index, VALUE charset, VALUE selector_lists) {
|
|
594
|
+
Check_Type(rules_array, T_ARRAY);
|
|
595
|
+
Check_Type(media_index, T_HASH);
|
|
596
|
+
|
|
597
|
+
VALUE result = rb_str_new_cstr("");
|
|
598
|
+
|
|
599
|
+
// Add charset if present
|
|
600
|
+
if (!NIL_P(charset)) {
|
|
601
|
+
rb_str_cat2(result, "@charset \"");
|
|
602
|
+
rb_str_append(result, charset);
|
|
603
|
+
rb_str_cat2(result, "\";\n");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Compact formatting options
|
|
607
|
+
struct format_opts opts = {
|
|
608
|
+
.opening_brace = " { ",
|
|
609
|
+
.closing_brace = " }\n",
|
|
610
|
+
.media_indent = "",
|
|
611
|
+
.decl_indent_base = NULL,
|
|
612
|
+
.decl_indent_media = NULL,
|
|
613
|
+
.add_blank_lines = 0
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
return serialize_stylesheet_with_grouping(rules_array, media_index, result, selector_lists, &opts);
|
|
617
|
+
}
|
|
618
|
+
|
|
391
619
|
// Forward declarations
|
|
392
620
|
static void serialize_children_only(VALUE result, VALUE rules_array, long rule_idx,
|
|
393
621
|
VALUE rule_to_media, VALUE parent_to_children, VALUE parent_selector,
|
|
@@ -598,13 +826,15 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
598
826
|
}
|
|
599
827
|
|
|
600
828
|
// New stylesheet serialization entry point - checks for nesting and delegates
|
|
601
|
-
static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_index, VALUE charset, VALUE has_nesting) {
|
|
829
|
+
static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_index, VALUE charset, VALUE has_nesting, VALUE selector_lists) {
|
|
602
830
|
Check_Type(rules_array, T_ARRAY);
|
|
603
831
|
Check_Type(media_index, T_HASH);
|
|
832
|
+
// TODO: Phase 2 - use selector_lists for grouping
|
|
833
|
+
(void)selector_lists; // Suppress unused parameter warning
|
|
604
834
|
|
|
605
835
|
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
606
836
|
if (!RTEST(has_nesting)) {
|
|
607
|
-
return stylesheet_to_s_original(rules_array, media_index, charset);
|
|
837
|
+
return stylesheet_to_s_original(rules_array, media_index, charset, selector_lists);
|
|
608
838
|
}
|
|
609
839
|
|
|
610
840
|
// SLOW PATH: Has nesting - use lookahead approach
|
|
@@ -713,12 +943,13 @@ static VALUE stylesheet_to_s_new(VALUE self, VALUE rules_array, VALUE media_inde
|
|
|
713
943
|
rb_str_cat2(result, "}\n");
|
|
714
944
|
}
|
|
715
945
|
|
|
946
|
+
RB_GC_GUARD(rule_to_media);
|
|
947
|
+
RB_GC_GUARD(parent_to_children);
|
|
716
948
|
return result;
|
|
717
949
|
}
|
|
718
950
|
|
|
719
951
|
// Original formatted serialization (no nesting support)
|
|
720
|
-
static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_index, VALUE charset) {
|
|
721
|
-
long total_rules = RARRAY_LEN(rules_array);
|
|
952
|
+
static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_index, VALUE charset, VALUE selector_lists) {
|
|
722
953
|
VALUE result = rb_str_new_cstr("");
|
|
723
954
|
|
|
724
955
|
// Add charset if present
|
|
@@ -728,79 +959,27 @@ static VALUE stylesheet_to_formatted_s_original(VALUE rules_array, VALUE media_i
|
|
|
728
959
|
rb_str_cat2(result, "\";\n");
|
|
729
960
|
}
|
|
730
961
|
|
|
731
|
-
//
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
743
|
-
VALUE rule_media = rb_hash_aref(rule_to_media, rule_id);
|
|
744
|
-
int is_first_rule = (i == 0);
|
|
745
|
-
|
|
746
|
-
if (NIL_P(rule_media)) {
|
|
747
|
-
// Not in any media query - close any open media block first
|
|
748
|
-
if (in_media_block) {
|
|
749
|
-
rb_str_cat2(result, "}\n");
|
|
750
|
-
in_media_block = 0;
|
|
751
|
-
current_media = Qnil;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Add blank line prefix for non-first rules
|
|
755
|
-
if (!is_first_rule) {
|
|
756
|
-
rb_str_cat2(result, "\n");
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Output rule with no indentation (always single newline suffix)
|
|
760
|
-
serialize_rule_formatted(result, rule, "", 1);
|
|
761
|
-
} else {
|
|
762
|
-
// This rule is in a media query
|
|
763
|
-
if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
|
|
764
|
-
// Close previous media block if open
|
|
765
|
-
if (in_media_block) {
|
|
766
|
-
rb_str_cat2(result, "}\n");
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// Add blank line prefix for non-first rules
|
|
770
|
-
if (!is_first_rule) {
|
|
771
|
-
rb_str_cat2(result, "\n");
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// Open new media block
|
|
775
|
-
current_media = rule_media;
|
|
776
|
-
rb_str_cat2(result, "@media ");
|
|
777
|
-
rb_str_append(result, rb_sym2str(rule_media));
|
|
778
|
-
rb_str_cat2(result, " {\n");
|
|
779
|
-
in_media_block = 1;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
// Serialize rule inside media block with 2-space indentation
|
|
783
|
-
// Rules inside media blocks always get single newline (is_last=1)
|
|
784
|
-
serialize_rule_formatted(result, rule, " ", 1);
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Close final media block if still open
|
|
789
|
-
if (in_media_block) {
|
|
790
|
-
rb_str_cat2(result, "}\n");
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
return result;
|
|
962
|
+
// Formatted output options
|
|
963
|
+
struct format_opts opts = {
|
|
964
|
+
.opening_brace = " {\n",
|
|
965
|
+
.closing_brace = "}\n",
|
|
966
|
+
.media_indent = " ",
|
|
967
|
+
.decl_indent_base = " ",
|
|
968
|
+
.decl_indent_media = " ",
|
|
969
|
+
.add_blank_lines = 1
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
return serialize_stylesheet_with_grouping(rules_array, media_index, result, selector_lists, &opts);
|
|
794
973
|
}
|
|
795
974
|
|
|
796
975
|
// Formatted version with indentation and newlines (with nesting support)
|
|
797
|
-
static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE media_index, VALUE charset, VALUE has_nesting) {
|
|
976
|
+
static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE media_index, VALUE charset, VALUE has_nesting, VALUE selector_lists) {
|
|
798
977
|
Check_Type(rules_array, T_ARRAY);
|
|
799
978
|
Check_Type(media_index, T_HASH);
|
|
800
979
|
|
|
801
980
|
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
802
981
|
if (!RTEST(has_nesting)) {
|
|
803
|
-
return stylesheet_to_formatted_s_original(rules_array, media_index, charset);
|
|
982
|
+
return stylesheet_to_formatted_s_original(rules_array, media_index, charset, selector_lists);
|
|
804
983
|
}
|
|
805
984
|
|
|
806
985
|
// SLOW PATH: Has nesting - use parameterized serialization with formatted=1
|
|
@@ -909,6 +1088,8 @@ static VALUE stylesheet_to_formatted_s_new(VALUE self, VALUE rules_array, VALUE
|
|
|
909
1088
|
rb_str_cat2(result, "}\n");
|
|
910
1089
|
}
|
|
911
1090
|
|
|
1091
|
+
RB_GC_GUARD(rule_to_media);
|
|
1092
|
+
RB_GC_GUARD(parent_to_children);
|
|
912
1093
|
return result;
|
|
913
1094
|
}
|
|
914
1095
|
|
|
@@ -1165,9 +1346,9 @@ void Init_native_extension(void) {
|
|
|
1165
1346
|
cStylesheet = rb_define_class_under(mCataract, "Stylesheet", rb_cObject);
|
|
1166
1347
|
|
|
1167
1348
|
// Define module functions
|
|
1168
|
-
rb_define_module_function(mCataract, "_parse_css", parse_css_new, 1);
|
|
1169
|
-
rb_define_module_function(mCataract, "_stylesheet_to_s", stylesheet_to_s_new,
|
|
1170
|
-
rb_define_module_function(mCataract, "_stylesheet_to_formatted_s", stylesheet_to_formatted_s_new,
|
|
1349
|
+
rb_define_module_function(mCataract, "_parse_css", parse_css_new, -1);
|
|
1350
|
+
rb_define_module_function(mCataract, "_stylesheet_to_s", stylesheet_to_s_new, 5);
|
|
1351
|
+
rb_define_module_function(mCataract, "_stylesheet_to_formatted_s", stylesheet_to_formatted_s_new, 5);
|
|
1171
1352
|
rb_define_module_function(mCataract, "parse_media_types", parse_media_types, 1);
|
|
1172
1353
|
rb_define_module_function(mCataract, "parse_declarations", new_parse_declarations, 1);
|
|
1173
1354
|
rb_define_module_function(mCataract, "flatten", cataract_flatten, 1);
|
data/ext/cataract/cataract.h
CHANGED
|
@@ -23,13 +23,14 @@ extern VALUE eSizeError;
|
|
|
23
23
|
// Struct field indices
|
|
24
24
|
// ============================================================================
|
|
25
25
|
|
|
26
|
-
// Rule struct field indices (id, selector, declarations, specificity, parent_rule_id, nesting_style)
|
|
26
|
+
// Rule struct field indices (id, selector, declarations, specificity, parent_rule_id, nesting_style, selector_list_id)
|
|
27
27
|
#define RULE_ID 0
|
|
28
28
|
#define RULE_SELECTOR 1
|
|
29
29
|
#define RULE_DECLARATIONS 2
|
|
30
30
|
#define RULE_SPECIFICITY 3
|
|
31
31
|
#define RULE_PARENT_RULE_ID 4
|
|
32
32
|
#define RULE_NESTING_STYLE 5
|
|
33
|
+
#define RULE_SELECTOR_LIST_ID 6
|
|
33
34
|
|
|
34
35
|
// Nesting style constants
|
|
35
36
|
#define NESTING_STYLE_IMPLICIT 0 // .parent { .child { } } - no &
|
|
@@ -141,8 +142,8 @@ static inline VALUE strip_string(const char *str, long len) {
|
|
|
141
142
|
// ============================================================================
|
|
142
143
|
|
|
143
144
|
// CSS parser (css_parser_new.c)
|
|
144
|
-
VALUE parse_css_new(VALUE
|
|
145
|
-
VALUE parse_css_new_impl(VALUE css_string, int rule_id_offset);
|
|
145
|
+
VALUE parse_css_new(int argc, VALUE *argv, VALUE self);
|
|
146
|
+
VALUE parse_css_new_impl(VALUE css_string, VALUE parser_options, int rule_id_offset);
|
|
146
147
|
VALUE parse_media_types(VALUE self, VALUE media_query_sym);
|
|
147
148
|
|
|
148
149
|
// Flatten (flatten.c)
|