cataract 0.2.5 → 0.4.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 -6
- data/.rubocop_todo.yml +11 -19
- data/BENCHMARKS.md +40 -40
- data/CHANGELOG.md +28 -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 +312 -550
- data/ext/cataract/cataract.h +126 -16
- data/ext/cataract/css_parser.c +782 -806
- 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 +26 -10
- data/lib/cataract/import_resolver.rb +36 -16
- data/lib/cataract/native.rb +30 -0
- data/lib/cataract/pure/byte_constants.rb +70 -67
- data/lib/cataract/pure/declarations.rb +125 -0
- data/lib/cataract/pure/flatten.rb +1195 -1182
- data/lib/cataract/pure/parser.rb +1721 -1652
- data/lib/cataract/pure/serializer.rb +575 -710
- data/lib/cataract/pure/specificity.rb +152 -175
- data/lib/cataract/pure.rb +73 -108
- data/lib/cataract/rule.rb +6 -3
- data/lib/cataract/stylesheet.rb +475 -415
- data/lib/cataract/version.rb +1 -1
- data/lib/cataract.rb +41 -30
- data/lib/tasks/profile.rake +6 -3
- metadata +4 -14
- 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
- data/lib/cataract/pure/helpers.rb +0 -35
data/ext/cataract/cataract.c
CHANGED
|
@@ -131,8 +131,10 @@ VALUE parse_css_new(int argc, VALUE *argv, VALUE self) {
|
|
|
131
131
|
* @param charset [String, nil] Optional @charset value
|
|
132
132
|
* @return [String] CSS string
|
|
133
133
|
*/
|
|
134
|
-
// Helper to serialize a
|
|
135
|
-
|
|
134
|
+
// Helper to serialize a rule's declarations - indent == NULL means compact
|
|
135
|
+
// (semicolon+space separated, single line); non-NULL means one declaration
|
|
136
|
+
// per line, prefixed with indent.
|
|
137
|
+
static void serialize_declarations(VALUE result, VALUE declarations, const char *indent) {
|
|
136
138
|
long decl_len = RARRAY_LEN(declarations);
|
|
137
139
|
for (long j = 0; j < decl_len; j++) {
|
|
138
140
|
VALUE decl = rb_ary_entry(declarations, j);
|
|
@@ -140,6 +142,7 @@ static void serialize_declarations(VALUE result, VALUE declarations) {
|
|
|
140
142
|
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
141
143
|
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
142
144
|
|
|
145
|
+
if (indent) rb_str_cat2(result, indent);
|
|
143
146
|
rb_str_append(result, property);
|
|
144
147
|
rb_str_cat2(result, ": ");
|
|
145
148
|
rb_str_append(result, value);
|
|
@@ -148,34 +151,15 @@ static void serialize_declarations(VALUE result, VALUE declarations) {
|
|
|
148
151
|
rb_str_cat2(result, " !important");
|
|
149
152
|
}
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Formatted version - each declaration on its own line with indentation
|
|
161
|
-
static void serialize_declarations_formatted(VALUE result, VALUE declarations, const char *indent) {
|
|
162
|
-
long decl_len = RARRAY_LEN(declarations);
|
|
163
|
-
for (long j = 0; j < decl_len; j++) {
|
|
164
|
-
VALUE decl = rb_ary_entry(declarations, j);
|
|
165
|
-
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
166
|
-
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
167
|
-
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
168
|
-
|
|
169
|
-
rb_str_cat2(result, indent);
|
|
170
|
-
rb_str_append(result, property);
|
|
171
|
-
rb_str_cat2(result, ": ");
|
|
172
|
-
rb_str_append(result, value);
|
|
173
|
-
|
|
174
|
-
if (RTEST(important)) {
|
|
175
|
-
rb_str_cat2(result, " !important");
|
|
154
|
+
if (indent) {
|
|
155
|
+
rb_str_cat2(result, ";\n");
|
|
156
|
+
} else {
|
|
157
|
+
rb_str_cat2(result, ";");
|
|
158
|
+
// Add space after semicolon except for last declaration
|
|
159
|
+
if (j < decl_len - 1) {
|
|
160
|
+
rb_str_cat2(result, " ");
|
|
161
|
+
}
|
|
176
162
|
}
|
|
177
|
-
|
|
178
|
-
rb_str_cat2(result, ";\n");
|
|
179
163
|
}
|
|
180
164
|
}
|
|
181
165
|
|
|
@@ -201,13 +185,13 @@ static void serialize_at_rule(VALUE result, VALUE at_rule) {
|
|
|
201
185
|
rb_str_cat2(result, " ");
|
|
202
186
|
rb_str_append(result, nested_selector);
|
|
203
187
|
rb_str_cat2(result, " { ");
|
|
204
|
-
serialize_declarations(result, nested_declarations);
|
|
188
|
+
serialize_declarations(result, nested_declarations, NULL);
|
|
205
189
|
rb_str_cat2(result, " }\n");
|
|
206
190
|
}
|
|
207
191
|
} else {
|
|
208
192
|
// Serialize as declarations (e.g., @font-face)
|
|
209
193
|
rb_str_cat2(result, " ");
|
|
210
|
-
serialize_declarations(result, content);
|
|
194
|
+
serialize_declarations(result, content, NULL);
|
|
211
195
|
rb_str_cat2(result, "\n");
|
|
212
196
|
}
|
|
213
197
|
}
|
|
@@ -284,7 +268,7 @@ static void serialize_rule(VALUE result, VALUE rule) {
|
|
|
284
268
|
|
|
285
269
|
rb_str_append(result, selector);
|
|
286
270
|
rb_str_cat2(result, " { ");
|
|
287
|
-
serialize_declarations(result, declarations);
|
|
271
|
+
serialize_declarations(result, declarations, NULL);
|
|
288
272
|
rb_str_cat2(result, " }\n");
|
|
289
273
|
}
|
|
290
274
|
|
|
@@ -318,7 +302,7 @@ static void serialize_at_rule_formatted(VALUE result, VALUE at_rule, const char
|
|
|
318
302
|
VALUE nested_indent = rb_str_new_cstr(indent);
|
|
319
303
|
rb_str_cat2(nested_indent, " ");
|
|
320
304
|
const char *nested_indent_ptr = RSTRING_PTR(nested_indent);
|
|
321
|
-
|
|
305
|
+
serialize_declarations(result, nested_declarations, nested_indent_ptr);
|
|
322
306
|
RB_GC_GUARD(nested_indent);
|
|
323
307
|
|
|
324
308
|
// Closing brace (2-space indent)
|
|
@@ -330,7 +314,7 @@ static void serialize_at_rule_formatted(VALUE result, VALUE at_rule, const char
|
|
|
330
314
|
VALUE content_indent = rb_str_new_cstr(indent);
|
|
331
315
|
rb_str_cat2(content_indent, " ");
|
|
332
316
|
const char *content_indent_ptr = RSTRING_PTR(content_indent);
|
|
333
|
-
|
|
317
|
+
serialize_declarations(result, content, content_indent_ptr);
|
|
334
318
|
RB_GC_GUARD(content_indent);
|
|
335
319
|
}
|
|
336
320
|
}
|
|
@@ -360,7 +344,7 @@ static void serialize_rule_formatted(VALUE result, VALUE rule, const char *inden
|
|
|
360
344
|
VALUE decl_indent = rb_str_new_cstr(indent);
|
|
361
345
|
rb_str_cat2(decl_indent, " ");
|
|
362
346
|
const char *decl_indent_ptr = RSTRING_PTR(decl_indent);
|
|
363
|
-
|
|
347
|
+
serialize_declarations(result, declarations, decl_indent_ptr);
|
|
364
348
|
RB_GC_GUARD(decl_indent);
|
|
365
349
|
|
|
366
350
|
// Closing brace - double newline for all except last rule
|
|
@@ -393,6 +377,54 @@ static int build_mq_reverse_map_callback(VALUE list_id, VALUE mq_ids, VALUE arg)
|
|
|
393
377
|
return ST_CONTINUE;
|
|
394
378
|
}
|
|
395
379
|
|
|
380
|
+
// Build the media_query_id => list_id reverse map used by both stylesheet
|
|
381
|
+
// serializers (grouping and nesting) to detect when a rule's media query is
|
|
382
|
+
// part of a comma-separated list (e.g. "@media screen, print").
|
|
383
|
+
static VALUE build_mq_id_to_list_id(VALUE media_query_lists) {
|
|
384
|
+
VALUE mq_id_to_list_id = rb_hash_new();
|
|
385
|
+
if (!NIL_P(media_query_lists) && TYPE(media_query_lists) == T_HASH) {
|
|
386
|
+
struct build_mq_reverse_map_ctx ctx = { mq_id_to_list_id };
|
|
387
|
+
rb_hash_foreach(media_query_lists, build_mq_reverse_map_callback, (VALUE)&ctx);
|
|
388
|
+
}
|
|
389
|
+
return mq_id_to_list_id;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Build a rule_id => rule lookup. rules_array is NOT guaranteed to satisfy
|
|
393
|
+
// rules[i].id == i here - that invariant only holds for the full,
|
|
394
|
+
// freshly-parsed rules array; Stylesheet#to_s(media: ...) passes a filtered
|
|
395
|
+
// subset whenever the media filter isn't :all, so a plain array index would
|
|
396
|
+
// silently fetch the wrong rule (or even an AtRule, which doesn't share
|
|
397
|
+
// Rule's struct layout) once any rule has been filtered out. Both Rule and
|
|
398
|
+
// AtRule keep id at field index 0, so a single RULE_ID read is safe for
|
|
399
|
+
// either struct type.
|
|
400
|
+
static VALUE build_rules_by_id(VALUE rules_array) {
|
|
401
|
+
VALUE rules_by_id = rb_hash_new();
|
|
402
|
+
long len = RARRAY_LEN(rules_array);
|
|
403
|
+
for (long i = 0; i < len; i++) {
|
|
404
|
+
VALUE rule = rb_ary_entry(rules_array, i);
|
|
405
|
+
VALUE rule_id = rb_struct_aref(rule, INT2FIX(RULE_ID));
|
|
406
|
+
rb_hash_aset(rules_by_id, rule_id, rule);
|
|
407
|
+
}
|
|
408
|
+
return rules_by_id;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Rule and AtRule don't share a member layout past their first few fields
|
|
412
|
+
// (see the AT_RULE_* comment in cataract.h), so reading parent_rule_id or
|
|
413
|
+
// media_query_id off a rule of unknown type needs to dispatch on which
|
|
414
|
+
// struct it actually is rather than always using Rule's index - otherwise,
|
|
415
|
+
// for an AtRule, RULE_PARENT_RULE_ID's index (4) actually reads its
|
|
416
|
+
// media_query_id field instead.
|
|
417
|
+
static inline VALUE rule_parent_id(VALUE rule) {
|
|
418
|
+
// AtRule instances are never nested via CSS nesting - only Rule can be.
|
|
419
|
+
return rb_obj_is_kind_of(rule, cAtRule) ? Qnil : rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
static inline VALUE rule_media_query_id_of(VALUE rule) {
|
|
423
|
+
return rb_obj_is_kind_of(rule, cAtRule)
|
|
424
|
+
? rb_struct_aref(rule, INT2FIX(AT_RULE_MEDIA_QUERY_ID))
|
|
425
|
+
: rb_struct_aref(rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
426
|
+
}
|
|
427
|
+
|
|
396
428
|
// Formatting options for stylesheet serialization
|
|
397
429
|
// Avoids mode flags and if/else branches - all behavior controlled by struct values
|
|
398
430
|
struct format_opts {
|
|
@@ -404,6 +436,126 @@ struct format_opts {
|
|
|
404
436
|
int add_blank_lines; // 0 (compact) vs 1 (formatted)
|
|
405
437
|
};
|
|
406
438
|
|
|
439
|
+
// Serialize a single rule, compact or formatted depending on whether
|
|
440
|
+
// decl_indent is NULL - mirrors the format_opts indent-as-mode-flag
|
|
441
|
+
// convention used throughout this file.
|
|
442
|
+
static inline void emit_rule(VALUE result, VALUE rule, const char *decl_indent, const char *rule_prefix) {
|
|
443
|
+
if (decl_indent) {
|
|
444
|
+
serialize_rule_formatted(result, rule, rule_prefix, 1);
|
|
445
|
+
} else {
|
|
446
|
+
serialize_rule(result, rule);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Scan rule_ids_in_list for other not-yet-processed rules that share
|
|
451
|
+
// rule_media_query_id and rule_declarations with the current rule, marking
|
|
452
|
+
// each match as processed. Returns an array of their selectors (does not
|
|
453
|
+
// include the current rule's own selector or mark it processed - the
|
|
454
|
+
// caller's rule_id is handled by serialize_rule_in_group).
|
|
455
|
+
static VALUE collect_matching_selectors(VALUE rule_ids_in_list, VALUE rule_media_query_id, VALUE rule_declarations, VALUE rules_by_id, VALUE processed_rule_ids) {
|
|
456
|
+
VALUE matching_selectors = rb_ary_new();
|
|
457
|
+
long list_len = RARRAY_LEN(rule_ids_in_list);
|
|
458
|
+
|
|
459
|
+
for (long j = 0; j < list_len; j++) {
|
|
460
|
+
VALUE other_rule_id = rb_ary_entry(rule_ids_in_list, j);
|
|
461
|
+
|
|
462
|
+
// Skip if already processed
|
|
463
|
+
if (RTEST(rb_hash_aref(processed_rule_ids, other_rule_id))) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Find the rule by ID
|
|
468
|
+
VALUE other_rule = rb_hash_aref(rules_by_id, other_rule_id);
|
|
469
|
+
if (NIL_P(other_rule)) continue;
|
|
470
|
+
|
|
471
|
+
// Check same media context (compare media_query_id directly)
|
|
472
|
+
VALUE other_rule_media_query_id = rb_struct_aref(other_rule, INT2FIX(RULE_MEDIA_QUERY_ID));
|
|
473
|
+
if (!rb_equal(rule_media_query_id, other_rule_media_query_id)) {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Check if declarations match
|
|
478
|
+
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
479
|
+
if (rb_equal(rule_declarations, other_declarations)) {
|
|
480
|
+
VALUE other_selector = rb_struct_aref(other_rule, INT2FIX(RULE_SELECTOR));
|
|
481
|
+
rb_ary_push(matching_selectors, other_selector);
|
|
482
|
+
rb_hash_aset(processed_rule_ids, other_rule_id, Qtrue);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return matching_selectors;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Emit a comma-joined group of matching selectors sharing one declaration
|
|
490
|
+
// block, or fall back to a single rule if grouping didn't find any matches.
|
|
491
|
+
// rule_prefix is written before the (possibly joined) selector line and
|
|
492
|
+
// before the closing brace - "" at the top level, opts->media_indent inside
|
|
493
|
+
// a media block (a no-op when "").
|
|
494
|
+
static void emit_grouped_rule(VALUE result, VALUE rule, VALUE matching_selectors, VALUE rule_declarations, const struct format_opts *opts, const char *decl_indent, const char *rule_prefix) {
|
|
495
|
+
if (RARRAY_LEN(matching_selectors) > 1) {
|
|
496
|
+
VALUE selector_str = rb_ary_join(matching_selectors, rb_str_new_cstr(", "));
|
|
497
|
+
rb_str_cat2(result, rule_prefix);
|
|
498
|
+
rb_str_append(result, selector_str);
|
|
499
|
+
rb_str_cat2(result, opts->opening_brace);
|
|
500
|
+
serialize_declarations(result, rule_declarations, decl_indent);
|
|
501
|
+
rb_str_cat2(result, rule_prefix);
|
|
502
|
+
rb_str_cat2(result, opts->closing_brace);
|
|
503
|
+
RB_GC_GUARD(selector_str);
|
|
504
|
+
} else {
|
|
505
|
+
emit_rule(result, rule, decl_indent, rule_prefix);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Loop-invariant state shared by every serialize_rule_in_group call within
|
|
510
|
+
// one serialize_stylesheet_with_grouping invocation - populated once instead
|
|
511
|
+
// of threading 5 params through on every call.
|
|
512
|
+
struct rule_group_ctx {
|
|
513
|
+
VALUE selector_lists;
|
|
514
|
+
int grouping_enabled;
|
|
515
|
+
VALUE rules_by_id;
|
|
516
|
+
VALUE processed_rule_ids;
|
|
517
|
+
const struct format_opts *opts;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
// Serialize one rule, applying selector-list grouping if enabled. Used
|
|
521
|
+
// identically for both the base (not-in-media) and in-media contexts -
|
|
522
|
+
// in_media selects the relevant format_opts fields ("" and
|
|
523
|
+
// opts->decl_indent_base at the base level; opts->media_indent and
|
|
524
|
+
// opts->decl_indent_media inside a media block).
|
|
525
|
+
static void serialize_rule_in_group(
|
|
526
|
+
VALUE result,
|
|
527
|
+
VALUE rule,
|
|
528
|
+
VALUE rule_id,
|
|
529
|
+
VALUE rule_media_query_id,
|
|
530
|
+
const struct rule_group_ctx *ctx,
|
|
531
|
+
int in_media
|
|
532
|
+
) {
|
|
533
|
+
const char *decl_indent = in_media ? ctx->opts->decl_indent_media : ctx->opts->decl_indent_base;
|
|
534
|
+
const char *rule_prefix = in_media ? ctx->opts->media_indent : "";
|
|
535
|
+
|
|
536
|
+
// Try to group with other rules from same selector list
|
|
537
|
+
// Check if this is a Rule (not AtRule) before accessing selector_list_id
|
|
538
|
+
if (ctx->grouping_enabled && rb_obj_is_kind_of(rule, cRule)) {
|
|
539
|
+
VALUE selector_list_id = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR_LIST_ID));
|
|
540
|
+
if (!NIL_P(selector_list_id)) {
|
|
541
|
+
// Get list of rule IDs in this selector list
|
|
542
|
+
VALUE rule_ids_in_list = rb_hash_aref(ctx->selector_lists, selector_list_id);
|
|
543
|
+
|
|
544
|
+
if (!NIL_P(rule_ids_in_list) && RARRAY_LEN(rule_ids_in_list) > 1) {
|
|
545
|
+
VALUE rule_declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
546
|
+
VALUE matching_selectors = collect_matching_selectors(rule_ids_in_list, rule_media_query_id, rule_declarations, ctx->rules_by_id, ctx->processed_rule_ids);
|
|
547
|
+
emit_grouped_rule(result, rule, matching_selectors, rule_declarations, ctx->opts, decl_indent, rule_prefix);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// No grouping applies (disabled, not a Rule, no selector_list_id, or a
|
|
554
|
+
// selector list of 1) - serialize normally
|
|
555
|
+
emit_rule(result, rule, decl_indent, rule_prefix);
|
|
556
|
+
rb_hash_aset(ctx->processed_rule_ids, rule_id, Qtrue);
|
|
557
|
+
}
|
|
558
|
+
|
|
407
559
|
// Private shared implementation for stylesheet serialization with optional selector list grouping
|
|
408
560
|
// All formatting behavior controlled by format_opts struct to avoid mode flags and if/else branches
|
|
409
561
|
static VALUE serialize_stylesheet_with_grouping(
|
|
@@ -421,15 +573,19 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
421
573
|
|
|
422
574
|
// Build reverse map: media_query_id => list_id
|
|
423
575
|
// This allows us to detect when multiple rules share a comma-separated media query list
|
|
424
|
-
VALUE mq_id_to_list_id =
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
576
|
+
VALUE mq_id_to_list_id = build_mq_id_to_list_id(media_query_lists);
|
|
577
|
+
|
|
578
|
+
// Only needed by the grouping path - rules_array here may already be
|
|
579
|
+
// filtered (Stylesheet#to_s(media: ...) for anything but :all), so
|
|
580
|
+
// looking up "other rules in this selector list" needs a real id => rule
|
|
581
|
+
// map rather than indexing directly into rules_array.
|
|
582
|
+
VALUE rules_by_id = grouping_enabled ? build_rules_by_id(rules_array) : Qnil;
|
|
429
583
|
|
|
430
584
|
// Track processed rules to avoid duplicates when grouping
|
|
431
585
|
VALUE processed_rule_ids = rb_hash_new();
|
|
432
586
|
|
|
587
|
+
struct rule_group_ctx group_ctx = { selector_lists, grouping_enabled, rules_by_id, processed_rule_ids, opts };
|
|
588
|
+
|
|
433
589
|
// Iterate through rules in insertion order, grouping consecutive media queries
|
|
434
590
|
VALUE current_media = Qnil;
|
|
435
591
|
int in_media_block = 0;
|
|
@@ -443,8 +599,8 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
443
599
|
continue;
|
|
444
600
|
}
|
|
445
601
|
|
|
446
|
-
// Get media_query_id and fetch MediaQuery object (nil for
|
|
447
|
-
VALUE rule_media_query_id =
|
|
602
|
+
// Get media_query_id and fetch MediaQuery object (nil for rules without media query)
|
|
603
|
+
VALUE rule_media_query_id = rule_media_query_id_of(rule);
|
|
448
604
|
VALUE rule_media = Qnil;
|
|
449
605
|
if (!NIL_P(rule_media_query_id)) {
|
|
450
606
|
rule_media = rb_ary_entry(media_queries, FIX2INT(rule_media_query_id));
|
|
@@ -464,95 +620,7 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
464
620
|
rb_str_cat2(result, "\n");
|
|
465
621
|
}
|
|
466
622
|
|
|
467
|
-
|
|
468
|
-
// Check if this is a Rule (not AtRule) before accessing selector_list_id
|
|
469
|
-
if (grouping_enabled && rb_obj_is_kind_of(rule, cRule)) {
|
|
470
|
-
VALUE selector_list_id = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR_LIST_ID));
|
|
471
|
-
if (!NIL_P(selector_list_id)) {
|
|
472
|
-
// Get list of rule IDs in this selector list
|
|
473
|
-
VALUE rule_ids_in_list = rb_hash_aref(selector_lists, selector_list_id);
|
|
474
|
-
|
|
475
|
-
if (NIL_P(rule_ids_in_list) || RARRAY_LEN(rule_ids_in_list) <= 1) {
|
|
476
|
-
// Just this rule, serialize normally
|
|
477
|
-
if (opts->decl_indent_base) {
|
|
478
|
-
serialize_rule_formatted(result, rule, "", 1);
|
|
479
|
-
} else {
|
|
480
|
-
serialize_rule(result, rule);
|
|
481
|
-
}
|
|
482
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
483
|
-
} else {
|
|
484
|
-
// Find all rules with matching declarations and same media context
|
|
485
|
-
VALUE matching_selectors = rb_ary_new();
|
|
486
|
-
VALUE rule_declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
487
|
-
|
|
488
|
-
long list_len = RARRAY_LEN(rule_ids_in_list);
|
|
489
|
-
for (long j = 0; j < list_len; j++) {
|
|
490
|
-
VALUE other_rule_id = rb_ary_entry(rule_ids_in_list, j);
|
|
491
|
-
|
|
492
|
-
// Skip if already processed
|
|
493
|
-
if (RTEST(rb_hash_aref(processed_rule_ids, other_rule_id))) {
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// Find the rule by ID
|
|
498
|
-
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
499
|
-
if (NIL_P(other_rule)) continue;
|
|
500
|
-
|
|
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)) {
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Check if declarations match
|
|
508
|
-
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
509
|
-
if (rb_equal(rule_declarations, other_declarations)) {
|
|
510
|
-
VALUE other_selector = rb_struct_aref(other_rule, INT2FIX(RULE_SELECTOR));
|
|
511
|
-
rb_ary_push(matching_selectors, other_selector);
|
|
512
|
-
rb_hash_aset(processed_rule_ids, other_rule_id, Qtrue);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Serialize grouped or single rule
|
|
517
|
-
if (RARRAY_LEN(matching_selectors) > 1) {
|
|
518
|
-
// Group selectors with comma-space separator
|
|
519
|
-
VALUE selector_str = rb_ary_join(matching_selectors, rb_str_new_cstr(", "));
|
|
520
|
-
rb_str_append(result, selector_str);
|
|
521
|
-
rb_str_cat2(result, opts->opening_brace);
|
|
522
|
-
if (opts->decl_indent_base) {
|
|
523
|
-
serialize_declarations_formatted(result, rule_declarations, opts->decl_indent_base);
|
|
524
|
-
} else {
|
|
525
|
-
serialize_declarations(result, rule_declarations);
|
|
526
|
-
}
|
|
527
|
-
rb_str_cat2(result, opts->closing_brace);
|
|
528
|
-
RB_GC_GUARD(selector_str);
|
|
529
|
-
} else {
|
|
530
|
-
// Just one rule, serialize normally
|
|
531
|
-
if (opts->decl_indent_base) {
|
|
532
|
-
serialize_rule_formatted(result, rule, "", 1);
|
|
533
|
-
} else {
|
|
534
|
-
serialize_rule(result, rule);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
} else {
|
|
539
|
-
// No selector_list_id, serialize normally
|
|
540
|
-
if (opts->decl_indent_base) {
|
|
541
|
-
serialize_rule_formatted(result, rule, "", 1);
|
|
542
|
-
} else {
|
|
543
|
-
serialize_rule(result, rule);
|
|
544
|
-
}
|
|
545
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
546
|
-
}
|
|
547
|
-
} else {
|
|
548
|
-
// Grouping disabled, serialize normally
|
|
549
|
-
if (opts->decl_indent_base) {
|
|
550
|
-
serialize_rule_formatted(result, rule, "", 1);
|
|
551
|
-
} else {
|
|
552
|
-
serialize_rule(result, rule);
|
|
553
|
-
}
|
|
554
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
555
|
-
}
|
|
623
|
+
serialize_rule_in_group(result, rule, rule_id, rule_media_query_id, &group_ctx, 0);
|
|
556
624
|
} else {
|
|
557
625
|
// This rule is in a media query
|
|
558
626
|
// Check if media query changed from previous rule (compare MediaQuery objects by value)
|
|
@@ -580,80 +648,7 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
580
648
|
in_media_block = 1;
|
|
581
649
|
}
|
|
582
650
|
|
|
583
|
-
|
|
584
|
-
// Check if this is a Rule (not AtRule) before accessing selector_list_id
|
|
585
|
-
if (grouping_enabled && rb_obj_is_kind_of(rule, cRule)) {
|
|
586
|
-
VALUE selector_list_id = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR_LIST_ID));
|
|
587
|
-
if (!NIL_P(selector_list_id)) {
|
|
588
|
-
VALUE rule_ids_in_list = rb_hash_aref(selector_lists, selector_list_id);
|
|
589
|
-
|
|
590
|
-
if (NIL_P(rule_ids_in_list) || RARRAY_LEN(rule_ids_in_list) <= 1) {
|
|
591
|
-
if (opts->decl_indent_media) {
|
|
592
|
-
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
593
|
-
} else {
|
|
594
|
-
serialize_rule(result, rule);
|
|
595
|
-
}
|
|
596
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
597
|
-
} else {
|
|
598
|
-
VALUE matching_selectors = rb_ary_new();
|
|
599
|
-
VALUE rule_declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
600
|
-
|
|
601
|
-
long list_len = RARRAY_LEN(rule_ids_in_list);
|
|
602
|
-
for (long j = 0; j < list_len; j++) {
|
|
603
|
-
VALUE other_rule_id = rb_ary_entry(rule_ids_in_list, j);
|
|
604
|
-
if (RTEST(rb_hash_aref(processed_rule_ids, other_rule_id))) continue;
|
|
605
|
-
|
|
606
|
-
VALUE other_rule = rb_ary_entry(rules_array, FIX2INT(other_rule_id));
|
|
607
|
-
if (NIL_P(other_rule)) continue;
|
|
608
|
-
|
|
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;
|
|
611
|
-
|
|
612
|
-
VALUE other_declarations = rb_struct_aref(other_rule, INT2FIX(RULE_DECLARATIONS));
|
|
613
|
-
if (rb_equal(rule_declarations, other_declarations)) {
|
|
614
|
-
VALUE other_selector = rb_struct_aref(other_rule, INT2FIX(RULE_SELECTOR));
|
|
615
|
-
rb_ary_push(matching_selectors, other_selector);
|
|
616
|
-
rb_hash_aset(processed_rule_ids, other_rule_id, Qtrue);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (RARRAY_LEN(matching_selectors) > 1) {
|
|
621
|
-
VALUE selector_str = rb_ary_join(matching_selectors, rb_str_new_cstr(", "));
|
|
622
|
-
rb_str_cat2(result, opts->media_indent);
|
|
623
|
-
rb_str_append(result, selector_str);
|
|
624
|
-
rb_str_cat2(result, opts->opening_brace);
|
|
625
|
-
if (opts->decl_indent_media) {
|
|
626
|
-
serialize_declarations_formatted(result, rule_declarations, opts->decl_indent_media);
|
|
627
|
-
} else {
|
|
628
|
-
serialize_declarations(result, rule_declarations);
|
|
629
|
-
}
|
|
630
|
-
rb_str_cat2(result, opts->media_indent);
|
|
631
|
-
rb_str_cat2(result, opts->closing_brace);
|
|
632
|
-
RB_GC_GUARD(selector_str);
|
|
633
|
-
} else {
|
|
634
|
-
if (opts->decl_indent_media) {
|
|
635
|
-
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
636
|
-
} else {
|
|
637
|
-
serialize_rule(result, rule);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
} else {
|
|
642
|
-
if (opts->decl_indent_media) {
|
|
643
|
-
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
644
|
-
} else {
|
|
645
|
-
serialize_rule(result, rule);
|
|
646
|
-
}
|
|
647
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
648
|
-
}
|
|
649
|
-
} else {
|
|
650
|
-
if (opts->decl_indent_media) {
|
|
651
|
-
serialize_rule_formatted(result, rule, opts->media_indent, 1);
|
|
652
|
-
} else {
|
|
653
|
-
serialize_rule(result, rule);
|
|
654
|
-
}
|
|
655
|
-
rb_hash_aset(processed_rule_ids, rule_id, Qtrue);
|
|
656
|
-
}
|
|
651
|
+
serialize_rule_in_group(result, rule, rule_id, rule_media_query_id, &group_ctx, 1);
|
|
657
652
|
}
|
|
658
653
|
}
|
|
659
654
|
|
|
@@ -664,37 +659,11 @@ static VALUE serialize_stylesheet_with_grouping(
|
|
|
664
659
|
|
|
665
660
|
// Guard hash objects we created and used throughout
|
|
666
661
|
RB_GC_GUARD(mq_id_to_list_id);
|
|
662
|
+
RB_GC_GUARD(rules_by_id);
|
|
667
663
|
RB_GC_GUARD(processed_rule_ids);
|
|
668
664
|
return result;
|
|
669
665
|
}
|
|
670
666
|
|
|
671
|
-
// Original stylesheet serialization (no nesting support) - compact format
|
|
672
|
-
static VALUE stylesheet_to_s_without_nesting(VALUE rules_array, VALUE media_queries, VALUE media_query_lists, VALUE charset, VALUE selector_lists) {
|
|
673
|
-
Check_Type(rules_array, T_ARRAY);
|
|
674
|
-
Check_Type(media_queries, T_ARRAY);
|
|
675
|
-
|
|
676
|
-
VALUE result = rb_str_new_cstr("");
|
|
677
|
-
|
|
678
|
-
// Add charset if present
|
|
679
|
-
if (!NIL_P(charset)) {
|
|
680
|
-
rb_str_cat2(result, "@charset \"");
|
|
681
|
-
rb_str_append(result, charset);
|
|
682
|
-
rb_str_cat2(result, "\";\n");
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// Compact formatting options
|
|
686
|
-
struct format_opts opts = {
|
|
687
|
-
.opening_brace = " { ",
|
|
688
|
-
.closing_brace = " }\n",
|
|
689
|
-
.media_indent = "",
|
|
690
|
-
.decl_indent_base = NULL,
|
|
691
|
-
.decl_indent_media = NULL,
|
|
692
|
-
.add_blank_lines = 0
|
|
693
|
-
};
|
|
694
|
-
|
|
695
|
-
return serialize_stylesheet_with_grouping(rules_array, media_queries, media_query_lists, result, selector_lists, &opts);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
667
|
// Forward declarations
|
|
699
668
|
static void serialize_children_only(VALUE result, VALUE rules_array, long rule_idx,
|
|
700
669
|
VALUE parent_to_children, VALUE parent_selector,
|
|
@@ -766,7 +735,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
766
735
|
rb_str_cat2(child_indent, " ");
|
|
767
736
|
}
|
|
768
737
|
const char *child_indent_ptr = RSTRING_PTR(child_indent);
|
|
769
|
-
|
|
738
|
+
serialize_declarations(result, child_declarations, child_indent_ptr);
|
|
770
739
|
RB_GC_GUARD(child_indent);
|
|
771
740
|
}
|
|
772
741
|
|
|
@@ -787,7 +756,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
787
756
|
|
|
788
757
|
// Serialize child declarations
|
|
789
758
|
VALUE child_declarations = rb_struct_aref(child, INT2FIX(RULE_DECLARATIONS));
|
|
790
|
-
serialize_declarations(result, child_declarations);
|
|
759
|
+
serialize_declarations(result, child_declarations, NULL);
|
|
791
760
|
|
|
792
761
|
// Recursively serialize grandchildren
|
|
793
762
|
serialize_children_only(result, rules_array, child_idx, parent_to_children,
|
|
@@ -823,7 +792,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
823
792
|
rb_str_cat2(child_indent, " ");
|
|
824
793
|
}
|
|
825
794
|
const char *child_indent_ptr = RSTRING_PTR(child_indent);
|
|
826
|
-
|
|
795
|
+
serialize_declarations(result, child_declarations, child_indent_ptr);
|
|
827
796
|
RB_GC_GUARD(child_indent);
|
|
828
797
|
}
|
|
829
798
|
|
|
@@ -835,7 +804,7 @@ static void serialize_children_only(VALUE result, VALUE rules_array, long rule_i
|
|
|
835
804
|
rb_str_cat2(result, " { ");
|
|
836
805
|
|
|
837
806
|
VALUE child_declarations = rb_struct_aref(child, INT2FIX(RULE_DECLARATIONS));
|
|
838
|
-
serialize_declarations(result, child_declarations);
|
|
807
|
+
serialize_declarations(result, child_declarations, NULL);
|
|
839
808
|
|
|
840
809
|
rb_str_cat2(result, " }");
|
|
841
810
|
}
|
|
@@ -876,7 +845,7 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
876
845
|
if (!NIL_P(declarations) && RARRAY_LEN(declarations) > 0) {
|
|
877
846
|
DEBUG_PRINTF("[SERIALIZE_RULE] Serializing %ld declarations with indent='%s' (%d spaces)\n",
|
|
878
847
|
RARRAY_LEN(declarations), decl_indent, decl_spaces);
|
|
879
|
-
|
|
848
|
+
serialize_declarations(result, declarations, decl_indent);
|
|
880
849
|
}
|
|
881
850
|
|
|
882
851
|
// Serialize nested children
|
|
@@ -891,7 +860,7 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
891
860
|
rb_str_cat2(result, " { ");
|
|
892
861
|
|
|
893
862
|
// Serialize own declarations
|
|
894
|
-
serialize_declarations(result, declarations);
|
|
863
|
+
serialize_declarations(result, declarations, NULL);
|
|
895
864
|
|
|
896
865
|
// Serialize nested children
|
|
897
866
|
serialize_children_only(result, rules_array, rule_idx, parent_to_children,
|
|
@@ -904,50 +873,31 @@ static void serialize_rule_with_children(VALUE result, VALUE rules_array, long r
|
|
|
904
873
|
RB_GC_GUARD(rule);
|
|
905
874
|
}
|
|
906
875
|
|
|
907
|
-
//
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
Check_Type(rules_array, T_ARRAY);
|
|
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");
|
|
924
|
-
// TODO: Phase 2 - use selector_lists for grouping
|
|
925
|
-
(void)selector_lists; // Suppress unused parameter warning
|
|
926
|
-
|
|
927
|
-
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
928
|
-
if (!RTEST(has_nesting)) {
|
|
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);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
DEBUG_PRINTF("[STYLESHEET_TO_S] Taking slow path (has nesting)\n");
|
|
934
|
-
// SLOW PATH: Has nesting - use lookahead approach
|
|
876
|
+
// Shared implementation for stylesheet serialization with nesting support.
|
|
877
|
+
// Handles both compact (to_s) and formatted (to_formatted_s) output via the
|
|
878
|
+
// format_opts struct, mirroring the pattern serialize_stylesheet_with_grouping
|
|
879
|
+
// uses for the no-nesting fast path.
|
|
880
|
+
static VALUE serialize_stylesheet_with_nesting(
|
|
881
|
+
VALUE rules_array,
|
|
882
|
+
VALUE media_queries,
|
|
883
|
+
VALUE media_query_lists,
|
|
884
|
+
VALUE result,
|
|
885
|
+
const struct format_opts *opts
|
|
886
|
+
) {
|
|
887
|
+
int formatted = (opts->decl_indent_base != NULL);
|
|
935
888
|
long total_rules = RARRAY_LEN(rules_array);
|
|
936
|
-
VALUE result = rb_str_new_cstr("");
|
|
937
889
|
|
|
938
|
-
//
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
rb_str_cat2(result, "\";\n");
|
|
943
|
-
}
|
|
890
|
+
// Reverse map: media_query_id => list_id, so a rule whose media query is
|
|
891
|
+
// part of a comma-separated list (e.g. "@media screen, print") renders
|
|
892
|
+
// the full list, not just its own single media query.
|
|
893
|
+
VALUE mq_id_to_list_id = build_mq_id_to_list_id(media_query_lists);
|
|
944
894
|
|
|
945
895
|
// Build parent_to_children map (parent_rule_id -> array of child indices)
|
|
946
896
|
// This allows O(1) lookup of children when serializing each parent
|
|
947
897
|
VALUE parent_to_children = rb_hash_new();
|
|
948
898
|
for (long i = 0; i < total_rules; i++) {
|
|
949
899
|
VALUE rule = rb_ary_entry(rules_array, i);
|
|
950
|
-
VALUE parent_id =
|
|
900
|
+
VALUE parent_id = rule_parent_id(rule);
|
|
951
901
|
|
|
952
902
|
if (!NIL_P(parent_id)) {
|
|
953
903
|
DEBUG_PRINTF("[MAP] Rule %ld has parent_id=%s, adding to map\n", i,
|
|
@@ -973,7 +923,7 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
973
923
|
DEBUG_PRINTF("[SERIALIZE] Starting serialization, total_rules=%ld\n", total_rules);
|
|
974
924
|
for (long i = 0; i < total_rules; i++) {
|
|
975
925
|
VALUE rule = rb_ary_entry(rules_array, i);
|
|
976
|
-
VALUE parent_id =
|
|
926
|
+
VALUE parent_id = rule_parent_id(rule);
|
|
977
927
|
|
|
978
928
|
DEBUG_PRINTF("[SERIALIZE] Rule %ld: selector=%s, parent_id=%s\n", i,
|
|
979
929
|
RSTRING_PTR(rb_struct_aref(rule, INT2FIX(RULE_SELECTOR))),
|
|
@@ -986,7 +936,7 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
986
936
|
}
|
|
987
937
|
|
|
988
938
|
// Get media_query_id for this rule and fetch the MediaQuery object
|
|
989
|
-
VALUE rule_media_query_id =
|
|
939
|
+
VALUE rule_media_query_id = rule_media_query_id_of(rule);
|
|
990
940
|
VALUE rule_media = Qnil;
|
|
991
941
|
if (!NIL_P(rule_media_query_id)) {
|
|
992
942
|
rule_media = rb_ary_entry(media_queries, FIX2INT(rule_media_query_id));
|
|
@@ -1002,6 +952,10 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
1002
952
|
rb_str_cat2(result, "}\n");
|
|
1003
953
|
in_media_block = 0;
|
|
1004
954
|
current_media = Qnil;
|
|
955
|
+
|
|
956
|
+
if (opts->add_blank_lines) {
|
|
957
|
+
rb_str_cat2(result, "\n");
|
|
958
|
+
}
|
|
1005
959
|
}
|
|
1006
960
|
} else {
|
|
1007
961
|
// In media - check if we need to open/change block (compare MediaQuery objects by value)
|
|
@@ -1009,11 +963,14 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
1009
963
|
// Close previous media block if open
|
|
1010
964
|
if (in_media_block) {
|
|
1011
965
|
rb_str_cat2(result, "}\n");
|
|
966
|
+
} else if (opts->add_blank_lines && RSTRING_LEN(result) > 0) {
|
|
967
|
+
// Add blank line before new media block (except at start)
|
|
968
|
+
rb_str_cat2(result, "\n");
|
|
1012
969
|
}
|
|
1013
970
|
// Open new media block - store the MediaQuery object for comparison
|
|
1014
971
|
current_media = rule_media;
|
|
1015
972
|
rb_str_cat2(result, "@media ");
|
|
1016
|
-
|
|
973
|
+
append_media_query_string(result, rule_media_query_id, mq_id_to_list_id, media_query_lists, media_queries);
|
|
1017
974
|
rb_str_cat2(result, " {\n");
|
|
1018
975
|
in_media_block = 1;
|
|
1019
976
|
}
|
|
@@ -1025,11 +982,18 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
1025
982
|
continue;
|
|
1026
983
|
}
|
|
1027
984
|
|
|
985
|
+
// Add indent if inside media block (no-op in compact mode, where media_indent is "")
|
|
986
|
+
if (in_media_block) {
|
|
987
|
+
DEBUG_PRINTF("[FORMATTED] Adding base indent for media block\n");
|
|
988
|
+
rb_str_cat2(result, opts->media_indent);
|
|
989
|
+
}
|
|
990
|
+
|
|
1028
991
|
// Serialize rule with nested children
|
|
992
|
+
DEBUG_PRINTF("[FORMATTED] Calling serialize_rule_with_children, in_media_block=%d\n", in_media_block);
|
|
1029
993
|
serialize_rule_with_children(
|
|
1030
994
|
result, rules_array, i, parent_to_children, media_queries,
|
|
1031
|
-
|
|
1032
|
-
0 // indent_level (
|
|
995
|
+
formatted,
|
|
996
|
+
in_media_block ? 1 : 0 // indent_level (unused when !formatted)
|
|
1033
997
|
);
|
|
1034
998
|
}
|
|
1035
999
|
|
|
@@ -1042,47 +1006,16 @@ static VALUE stylesheet_to_s(VALUE self, VALUE rules_array, VALUE charset, VALUE
|
|
|
1042
1006
|
return result;
|
|
1043
1007
|
}
|
|
1044
1008
|
|
|
1045
|
-
//
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
VALUE result = rb_str_new_cstr("");
|
|
1051
|
-
|
|
1052
|
-
// Add charset if present
|
|
1053
|
-
if (!NIL_P(charset)) {
|
|
1054
|
-
rb_str_cat2(result, "@charset \"");
|
|
1055
|
-
rb_str_append(result, charset);
|
|
1056
|
-
rb_str_cat2(result, "\";\n");
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// Formatted output options
|
|
1060
|
-
struct format_opts opts = {
|
|
1061
|
-
.opening_brace = " {\n",
|
|
1062
|
-
.closing_brace = "}\n",
|
|
1063
|
-
.media_indent = " ",
|
|
1064
|
-
.decl_indent_base = " ",
|
|
1065
|
-
.decl_indent_media = " ",
|
|
1066
|
-
.add_blank_lines = 1
|
|
1067
|
-
};
|
|
1068
|
-
|
|
1069
|
-
return serialize_stylesheet_with_grouping(rules_array, media_queries, media_query_lists, result, selector_lists, &opts);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// Formatted version with indentation and newlines (with nesting support)
|
|
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) {
|
|
1009
|
+
// Shared implementation for stylesheet_to_s/stylesheet_to_formatted_s - both wrap
|
|
1010
|
+
// this, differing only in which format_opts they select. Dispatches on has_nesting
|
|
1011
|
+
// to pick the selector-list-grouping serializer (no nesting - zero overhead) or the
|
|
1012
|
+
// parent/child lookahead serializer (has nesting).
|
|
1013
|
+
static VALUE stylesheet_serialize_impl(VALUE rules_array, VALUE charset, VALUE has_nesting, VALUE selector_lists, VALUE media_queries, VALUE media_query_lists, int formatted) {
|
|
1074
1014
|
Check_Type(rules_array, T_ARRAY);
|
|
1075
1015
|
Check_Type(media_queries, T_ARRAY);
|
|
1076
1016
|
if (!NIL_P(media_query_lists)) Check_Type(media_query_lists, T_HASH);
|
|
1077
1017
|
if (!NIL_P(selector_lists)) Check_Type(selector_lists, T_HASH);
|
|
1078
1018
|
|
|
1079
|
-
// Fast path: if no nesting, use original implementation (zero overhead)
|
|
1080
|
-
if (!RTEST(has_nesting)) {
|
|
1081
|
-
return stylesheet_to_formatted_s_without_nesting(rules_array, media_queries, media_query_lists, charset, selector_lists);
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
// SLOW PATH: Has nesting - use parameterized serialization with formatted=1
|
|
1085
|
-
long total_rules = RARRAY_LEN(rules_array);
|
|
1086
1019
|
VALUE result = rb_str_new_cstr("");
|
|
1087
1020
|
|
|
1088
1021
|
// Add charset if present
|
|
@@ -1092,101 +1025,45 @@ static VALUE stylesheet_to_formatted_s(VALUE self, VALUE rules_array, VALUE char
|
|
|
1092
1025
|
rb_str_cat2(result, "\";\n");
|
|
1093
1026
|
}
|
|
1094
1027
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1028
|
+
struct format_opts opts;
|
|
1029
|
+
if (formatted) {
|
|
1030
|
+
opts = (struct format_opts){
|
|
1031
|
+
.opening_brace = " {\n",
|
|
1032
|
+
.closing_brace = "}\n",
|
|
1033
|
+
.media_indent = " ",
|
|
1034
|
+
.decl_indent_base = " ",
|
|
1035
|
+
.decl_indent_media = " ",
|
|
1036
|
+
.add_blank_lines = 1
|
|
1037
|
+
};
|
|
1038
|
+
} else {
|
|
1039
|
+
opts = (struct format_opts){
|
|
1040
|
+
.opening_brace = " { ",
|
|
1041
|
+
.closing_brace = " }\n",
|
|
1042
|
+
.media_indent = "",
|
|
1043
|
+
.decl_indent_base = NULL,
|
|
1044
|
+
.decl_indent_media = NULL,
|
|
1045
|
+
.add_blank_lines = 0
|
|
1046
|
+
};
|
|
1109
1047
|
}
|
|
1110
1048
|
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
// Serialize only top-level rules (parent_rule_id == nil)
|
|
1116
|
-
for (long i = 0; i < total_rules; i++) {
|
|
1117
|
-
VALUE rule = rb_ary_entry(rules_array, i);
|
|
1118
|
-
VALUE parent_id = rb_struct_aref(rule, INT2FIX(RULE_PARENT_RULE_ID));
|
|
1119
|
-
|
|
1120
|
-
// Skip child rules - they're serialized when we hit their parent
|
|
1121
|
-
if (!NIL_P(parent_id)) {
|
|
1122
|
-
continue;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
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
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// Handle media block transitions
|
|
1133
|
-
if (NIL_P(rule_media)) {
|
|
1134
|
-
// Not in media - close any open media block
|
|
1135
|
-
if (in_media_block) {
|
|
1136
|
-
rb_str_cat2(result, "}\n");
|
|
1137
|
-
in_media_block = 0;
|
|
1138
|
-
current_media = Qnil;
|
|
1139
|
-
|
|
1140
|
-
// Add blank line after closing media block
|
|
1141
|
-
rb_str_cat2(result, "\n");
|
|
1142
|
-
}
|
|
1143
|
-
} else {
|
|
1144
|
-
// In media - check if we need to open/change block (compare MediaQuery objects by value)
|
|
1145
|
-
if (NIL_P(current_media) || !rb_equal(current_media, rule_media)) {
|
|
1146
|
-
// Close previous media block if open
|
|
1147
|
-
if (in_media_block) {
|
|
1148
|
-
rb_str_cat2(result, "}\n");
|
|
1149
|
-
} else if (RSTRING_LEN(result) > 0) {
|
|
1150
|
-
// Add blank line before new media block (except at start)
|
|
1151
|
-
rb_str_cat2(result, "\n");
|
|
1152
|
-
}
|
|
1153
|
-
// Open new media block - store the MediaQuery object for comparison
|
|
1154
|
-
current_media = rule_media;
|
|
1155
|
-
rb_str_cat2(result, "@media ");
|
|
1156
|
-
append_media_query_text(result, rule_media);
|
|
1157
|
-
rb_str_cat2(result, " {\n");
|
|
1158
|
-
in_media_block = 1;
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Check if this is an AtRule
|
|
1163
|
-
if (rb_obj_is_kind_of(rule, cAtRule)) {
|
|
1164
|
-
serialize_at_rule(result, rule);
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
// Add indent if inside media block
|
|
1169
|
-
if (in_media_block) {
|
|
1170
|
-
DEBUG_PRINTF("[FORMATTED] Adding base indent for media block\n");
|
|
1171
|
-
rb_str_cat2(result, " ");
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
// Serialize rule with nested children
|
|
1175
|
-
DEBUG_PRINTF("[FORMATTED] Calling serialize_rule_with_children, in_media_block=%d\n", in_media_block);
|
|
1176
|
-
serialize_rule_with_children(
|
|
1177
|
-
result, rules_array, i, parent_to_children, media_queries,
|
|
1178
|
-
1, // formatted (with indentation)
|
|
1179
|
-
in_media_block ? 1 : 0 // indent_level (1 if inside media block, 0 otherwise)
|
|
1180
|
-
);
|
|
1049
|
+
// Fast path: if no nesting, use the selector-list-grouping serializer (zero overhead)
|
|
1050
|
+
if (!RTEST(has_nesting)) {
|
|
1051
|
+
return serialize_stylesheet_with_grouping(rules_array, media_queries, media_query_lists, result, selector_lists, &opts);
|
|
1181
1052
|
}
|
|
1182
1053
|
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1054
|
+
// Has nesting - use the parent/child lookahead serializer
|
|
1055
|
+
// TODO: Phase 2 - use selector_lists for grouping on the nesting path too
|
|
1056
|
+
return serialize_stylesheet_with_nesting(rules_array, media_queries, media_query_lists, result, &opts);
|
|
1057
|
+
}
|
|
1187
1058
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1059
|
+
// New stylesheet serialization entry point - checks for nesting and delegates
|
|
1060
|
+
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) {
|
|
1061
|
+
return stylesheet_serialize_impl(rules_array, charset, has_nesting, selector_lists, media_queries, media_query_lists, 0);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Formatted version with indentation and newlines (with nesting support)
|
|
1065
|
+
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) {
|
|
1066
|
+
return stylesheet_serialize_impl(rules_array, charset, has_nesting, selector_lists, media_queries, media_query_lists, 1);
|
|
1190
1067
|
}
|
|
1191
1068
|
|
|
1192
1069
|
/*
|
|
@@ -1207,62 +1084,18 @@ static VALUE new_parse_declarations_string(const char *start, const char *end) {
|
|
|
1207
1084
|
while (pos < end && (IS_WHITESPACE(*pos) || *pos == ';')) pos++;
|
|
1208
1085
|
if (pos >= end) break;
|
|
1209
1086
|
|
|
1210
|
-
//
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (pos
|
|
1214
|
-
|
|
1215
|
-
const char *prop_end = pos;
|
|
1216
|
-
// Trim trailing whitespace
|
|
1217
|
-
while (prop_end > prop_start && IS_WHITESPACE(*(prop_end-1))) prop_end--;
|
|
1218
|
-
// Trim leading whitespace
|
|
1219
|
-
while (prop_start < prop_end && IS_WHITESPACE(*prop_start)) prop_start++;
|
|
1220
|
-
|
|
1221
|
-
pos++; // Skip colon
|
|
1222
|
-
// Trim leading whitespace
|
|
1223
|
-
while (pos < end && IS_WHITESPACE(*pos)) pos++;
|
|
1224
|
-
|
|
1225
|
-
// Find value (up to semicolon or end), handling parentheses
|
|
1226
|
-
const char *val_start = pos;
|
|
1227
|
-
int paren_depth = 0;
|
|
1228
|
-
while (pos < end) {
|
|
1229
|
-
if (*pos == '(') paren_depth++;
|
|
1230
|
-
else if (*pos == ')') paren_depth--;
|
|
1231
|
-
else if (*pos == ';' && paren_depth == 0) break;
|
|
1232
|
-
pos++;
|
|
1233
|
-
}
|
|
1234
|
-
const char *val_end = pos;
|
|
1235
|
-
// Trim trailing whitespace
|
|
1236
|
-
while (val_end > val_start && IS_WHITESPACE(*(val_end-1))) val_end--;
|
|
1237
|
-
|
|
1238
|
-
// Check for !important
|
|
1239
|
-
int is_important = 0;
|
|
1240
|
-
if (val_end - val_start >= 10) { // strlen("!important") = 10
|
|
1241
|
-
const char *check = val_end - 10;
|
|
1242
|
-
while (check < val_end && IS_WHITESPACE(*check)) check++;
|
|
1243
|
-
if (check < val_end && *check == '!') {
|
|
1244
|
-
check++;
|
|
1245
|
-
while (check < val_end && IS_WHITESPACE(*check)) check++;
|
|
1246
|
-
if ((val_end - check) >= 9 && strncmp(check, "important", 9) == 0) {
|
|
1247
|
-
is_important = 1;
|
|
1248
|
-
const char *important_pos = check - 1;
|
|
1249
|
-
while (important_pos > val_start && (IS_WHITESPACE(*(important_pos-1)) || *(important_pos-1) == '!')) {
|
|
1250
|
-
important_pos--;
|
|
1251
|
-
}
|
|
1252
|
-
val_end = important_pos;
|
|
1253
|
-
// Trim trailing whitespace again
|
|
1254
|
-
while (val_end > val_start && IS_WHITESPACE(*(val_end-1))) val_end--;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1087
|
+
// Standalone declaration-list input never contains braces, so only
|
|
1088
|
+
// ':' terminates the property scan (stop_prop_scan_early=0).
|
|
1089
|
+
struct declaration_span span;
|
|
1090
|
+
if (!parse_one_declaration(&pos, end, 0, &span)) break; // No colon found
|
|
1258
1091
|
|
|
1259
1092
|
// Skip if value is empty
|
|
1260
|
-
if (val_end > val_start) {
|
|
1261
|
-
long prop_len = prop_end - prop_start;
|
|
1262
|
-
long val_len = val_end - val_start;
|
|
1093
|
+
if (span.val_end > span.val_start) {
|
|
1094
|
+
long prop_len = span.prop_end - span.prop_start;
|
|
1095
|
+
long val_len = span.val_end - span.val_start;
|
|
1263
1096
|
|
|
1264
1097
|
// Create property string (US-ASCII, lowercased)
|
|
1265
|
-
VALUE property = rb_usascii_str_new(prop_start, prop_len);
|
|
1098
|
+
VALUE property = rb_usascii_str_new(span.prop_start, prop_len);
|
|
1266
1099
|
// Lowercase it inline
|
|
1267
1100
|
char *prop_ptr = RSTRING_PTR(property);
|
|
1268
1101
|
for (long i = 0; i < prop_len; i++) {
|
|
@@ -1271,11 +1104,11 @@ static VALUE new_parse_declarations_string(const char *start, const char *end) {
|
|
|
1271
1104
|
}
|
|
1272
1105
|
}
|
|
1273
1106
|
|
|
1274
|
-
VALUE value = rb_utf8_str_new(val_start, val_len);
|
|
1107
|
+
VALUE value = rb_utf8_str_new(span.val_start, val_len);
|
|
1275
1108
|
|
|
1276
1109
|
// Create Declaration struct
|
|
1277
1110
|
VALUE decl = rb_struct_new(cDeclaration,
|
|
1278
|
-
property, value, is_important ? Qtrue : Qfalse);
|
|
1111
|
+
property, value, span.is_important ? Qtrue : Qfalse);
|
|
1279
1112
|
|
|
1280
1113
|
rb_ary_push(declarations, decl);
|
|
1281
1114
|
}
|
|
@@ -1284,78 +1117,6 @@ static VALUE new_parse_declarations_string(const char *start, const char *end) {
|
|
|
1284
1117
|
return declarations;
|
|
1285
1118
|
}
|
|
1286
1119
|
|
|
1287
|
-
/*
|
|
1288
|
-
* Convert array of Declaration structs to CSS string
|
|
1289
|
-
* Format: "prop: value; prop2: value2 !important; "
|
|
1290
|
-
*
|
|
1291
|
-
* This is a copy of declarations_array_to_s from cataract.c,
|
|
1292
|
-
* but works with Declaration structs instead of Declaration structs
|
|
1293
|
-
*/
|
|
1294
|
-
static VALUE new_declarations_array_to_s(VALUE declarations_array) {
|
|
1295
|
-
Check_Type(declarations_array, T_ARRAY);
|
|
1296
|
-
|
|
1297
|
-
long len = RARRAY_LEN(declarations_array);
|
|
1298
|
-
if (len == 0) {
|
|
1299
|
-
return rb_str_new_cstr("");
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// Use rb_str_buf_new for efficient string building
|
|
1303
|
-
VALUE result = rb_str_buf_new(len * 32); // Estimate 32 chars per declaration
|
|
1304
|
-
|
|
1305
|
-
for (long i = 0; i < len; i++) {
|
|
1306
|
-
VALUE decl = rb_ary_entry(declarations_array, i);
|
|
1307
|
-
|
|
1308
|
-
// Validate this is a Declaration struct
|
|
1309
|
-
if (!RB_TYPE_P(decl, T_STRUCT) || rb_obj_class(decl) != cDeclaration) {
|
|
1310
|
-
rb_raise(rb_eTypeError,
|
|
1311
|
-
"Expected array of Declaration structs, got %s at index %ld",
|
|
1312
|
-
rb_obj_classname(decl), i);
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
// Extract struct fields
|
|
1316
|
-
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
1317
|
-
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
1318
|
-
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
1319
|
-
|
|
1320
|
-
// Append: "property: value"
|
|
1321
|
-
rb_str_buf_append(result, property);
|
|
1322
|
-
rb_str_buf_cat2(result, ": ");
|
|
1323
|
-
rb_str_buf_append(result, value);
|
|
1324
|
-
|
|
1325
|
-
// Append " !important" if needed
|
|
1326
|
-
if (RTEST(important)) {
|
|
1327
|
-
rb_str_buf_cat2(result, " !important");
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
rb_str_buf_cat2(result, "; ");
|
|
1331
|
-
|
|
1332
|
-
RB_GC_GUARD(decl);
|
|
1333
|
-
RB_GC_GUARD(property);
|
|
1334
|
-
RB_GC_GUARD(value);
|
|
1335
|
-
RB_GC_GUARD(important);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
// Strip trailing space
|
|
1339
|
-
rb_str_set_len(result, RSTRING_LEN(result) - 1);
|
|
1340
|
-
|
|
1341
|
-
RB_GC_GUARD(result);
|
|
1342
|
-
return result;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
/*
|
|
1346
|
-
* Instance method: Declarations#to_s
|
|
1347
|
-
* Converts declarations to CSS string
|
|
1348
|
-
*
|
|
1349
|
-
* @return [String] CSS declarations like "color: red; margin: 10px !important;"
|
|
1350
|
-
*/
|
|
1351
|
-
static VALUE new_declarations_to_s_method(VALUE self) {
|
|
1352
|
-
// Get @values instance variable (array of Declaration structs)
|
|
1353
|
-
VALUE values = rb_ivar_get(self, rb_intern("@values"));
|
|
1354
|
-
|
|
1355
|
-
// Call core serialization function
|
|
1356
|
-
return new_declarations_array_to_s(values);
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
1120
|
/*
|
|
1360
1121
|
* Ruby-facing wrapper for new_parse_declarations
|
|
1361
1122
|
*
|
|
@@ -1446,23 +1207,24 @@ void Init_native_extension(void) {
|
|
|
1446
1207
|
rb_raise(rb_eLoadError, "Cataract::MediaQuery not defined. Do not require 'cataract/native_extension' directly, use require 'cataract'");
|
|
1447
1208
|
}
|
|
1448
1209
|
|
|
1449
|
-
// Define
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
// Define Stylesheet class (Ruby will add instance methods like each_selector)
|
|
1210
|
+
// Define Stylesheet class (Ruby will add instance methods like each_selector).
|
|
1211
|
+
// Declarations is left alone here - it's a plain Ruby class (declarations.rb)
|
|
1212
|
+
// with its own #to_s, not something this backend attaches methods to.
|
|
1454
1213
|
cStylesheet = rb_define_class_under(mCataract, "Stylesheet", rb_cObject);
|
|
1455
1214
|
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
rb_define_module_function(
|
|
1463
|
-
rb_define_module_function(
|
|
1464
|
-
rb_define_module_function(
|
|
1465
|
-
rb_define_module_function(
|
|
1215
|
+
// Every native-specific entry point lives under Cataract::Backends::Native,
|
|
1216
|
+
// never directly on Cataract itself, so the pure Ruby backend can be loaded
|
|
1217
|
+
// in the same process without either backend clobbering the other's methods.
|
|
1218
|
+
VALUE mBackends = rb_define_module_under(mCataract, "Backends");
|
|
1219
|
+
VALUE mNative = rb_define_module_under(mBackends, "Native");
|
|
1220
|
+
|
|
1221
|
+
rb_define_module_function(mNative, "parse", parse_css_new, -1);
|
|
1222
|
+
rb_define_module_function(mNative, "stylesheet_to_s", stylesheet_to_s, 6);
|
|
1223
|
+
rb_define_module_function(mNative, "stylesheet_to_formatted_s", stylesheet_to_formatted_s, 6);
|
|
1224
|
+
rb_define_module_function(mNative, "parse_declarations", new_parse_declarations, 1);
|
|
1225
|
+
rb_define_module_function(mNative, "flatten", cataract_flatten, 1);
|
|
1226
|
+
rb_define_module_function(mNative, "calculate_specificity", calculate_specificity, 1);
|
|
1227
|
+
rb_define_module_function(mNative, "expand_shorthand", cataract_expand_shorthand, 1);
|
|
1466
1228
|
|
|
1467
1229
|
// Initialize flatten constants (cached property strings)
|
|
1468
1230
|
init_flatten_constants();
|
|
@@ -1482,11 +1244,11 @@ void Init_native_extension(void) {
|
|
|
1482
1244
|
rb_hash_aset(compile_flags, ID2SYM(rb_intern("str_buf_optimization")), Qtrue);
|
|
1483
1245
|
#endif
|
|
1484
1246
|
|
|
1485
|
-
rb_define_const(
|
|
1247
|
+
rb_define_const(mNative, "COMPILE_FLAGS", compile_flags);
|
|
1486
1248
|
|
|
1487
|
-
// Flag to indicate native
|
|
1488
|
-
rb_define_const(
|
|
1249
|
+
// Flag to indicate the native backend is loaded (for pure Ruby fallback detection)
|
|
1250
|
+
rb_define_const(mNative, "NATIVE_EXTENSION_LOADED", Qtrue);
|
|
1489
1251
|
|
|
1490
1252
|
// Implementation type constant
|
|
1491
|
-
rb_define_const(
|
|
1253
|
+
rb_define_const(mNative, "IMPLEMENTATION", ID2SYM(rb_intern("native")));
|
|
1492
1254
|
}
|