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
data/lib/cataract/version.rb
CHANGED
data/lib/cataract.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cataract
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Cook
|
|
@@ -56,7 +56,6 @@ files:
|
|
|
56
56
|
- ext/cataract/css_parser.c
|
|
57
57
|
- ext/cataract/extconf.rb
|
|
58
58
|
- ext/cataract/flatten.c
|
|
59
|
-
- ext/cataract/import_scanner.c
|
|
60
59
|
- ext/cataract/shorthand_expander.c
|
|
61
60
|
- ext/cataract/specificity.c
|
|
62
61
|
- ext/cataract/value_splitter.c
|
|
@@ -67,22 +66,13 @@ files:
|
|
|
67
66
|
- ext/cataract_color/color_conversion_named.c
|
|
68
67
|
- ext/cataract_color/color_conversion_oklab.c
|
|
69
68
|
- ext/cataract_color/extconf.rb
|
|
70
|
-
- ext/cataract_old/cataract.c
|
|
71
|
-
- ext/cataract_old/cataract.h
|
|
72
|
-
- ext/cataract_old/css_parser.c
|
|
73
|
-
- ext/cataract_old/extconf.rb
|
|
74
|
-
- ext/cataract_old/import_scanner.c
|
|
75
|
-
- ext/cataract_old/merge.c
|
|
76
|
-
- ext/cataract_old/shorthand_expander.c
|
|
77
|
-
- ext/cataract_old/specificity.c
|
|
78
|
-
- ext/cataract_old/stylesheet.c
|
|
79
|
-
- ext/cataract_old/value_splitter.c
|
|
80
69
|
- lib/cataract.rb
|
|
81
70
|
- lib/cataract/at_rule.rb
|
|
82
71
|
- lib/cataract/color_conversion.rb
|
|
83
72
|
- lib/cataract/constants.rb
|
|
84
73
|
- lib/cataract/declaration.rb
|
|
85
74
|
- lib/cataract/declarations.rb
|
|
75
|
+
- lib/cataract/error.rb
|
|
86
76
|
- lib/cataract/import_resolver.rb
|
|
87
77
|
- lib/cataract/import_statement.rb
|
|
88
78
|
- lib/cataract/media_query.rb
|
|
@@ -121,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
121
111
|
- !ruby/object:Gem::Version
|
|
122
112
|
version: '0'
|
|
123
113
|
requirements: []
|
|
124
|
-
rubygems_version:
|
|
114
|
+
rubygems_version: 4.0.10
|
|
125
115
|
specification_version: 4
|
|
126
116
|
summary: High-performance CSS parser with C extensions
|
|
127
117
|
test_files: []
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#include <ruby.h>
|
|
2
|
-
#include <ctype.h>
|
|
3
|
-
#include <string.h>
|
|
4
|
-
#include "cataract.h"
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* Scan CSS for @import statements
|
|
8
|
-
*
|
|
9
|
-
* Matches patterns:
|
|
10
|
-
* @import url("path");
|
|
11
|
-
* @import url('path');
|
|
12
|
-
* @import "path";
|
|
13
|
-
* @import 'path';
|
|
14
|
-
* @import url("path") print; (with media query)
|
|
15
|
-
*
|
|
16
|
-
* Returns array of hashes: [{url: "...", media: "...", full_match: "..."}]
|
|
17
|
-
*/
|
|
18
|
-
VALUE extract_imports(VALUE self, VALUE css_string) {
|
|
19
|
-
Check_Type(css_string, T_STRING);
|
|
20
|
-
|
|
21
|
-
const char *css = RSTRING_PTR(css_string);
|
|
22
|
-
long css_len = RSTRING_LEN(css_string);
|
|
23
|
-
|
|
24
|
-
VALUE imports = rb_ary_new();
|
|
25
|
-
|
|
26
|
-
const char *p = css;
|
|
27
|
-
const char *end = css + css_len;
|
|
28
|
-
|
|
29
|
-
while (p < end) {
|
|
30
|
-
// Skip whitespace and comments
|
|
31
|
-
while (p < end) {
|
|
32
|
-
if (IS_WHITESPACE(*p)) {
|
|
33
|
-
p++;
|
|
34
|
-
} else if (p + 2 <= end && p[0] == '/' && p[1] == '*') {
|
|
35
|
-
// Skip /* */ comment
|
|
36
|
-
p += 2;
|
|
37
|
-
while (p + 1 < end && !(p[0] == '*' && p[1] == '/')) {
|
|
38
|
-
p++;
|
|
39
|
-
}
|
|
40
|
-
if (p + 1 < end) p += 2; // Skip */
|
|
41
|
-
} else {
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Check for @import
|
|
47
|
-
if (p + 7 <= end && strncasecmp(p, "@import", 7) == 0) {
|
|
48
|
-
const char *import_start = p;
|
|
49
|
-
p += 7;
|
|
50
|
-
|
|
51
|
-
// Skip whitespace after @import
|
|
52
|
-
while (p < end && IS_WHITESPACE(*p)) p++;
|
|
53
|
-
|
|
54
|
-
// Check for optional url(
|
|
55
|
-
int has_url_function = 0;
|
|
56
|
-
if (p + 4 <= end && strncasecmp(p, "url(", 4) == 0) {
|
|
57
|
-
has_url_function = 1;
|
|
58
|
-
p += 4;
|
|
59
|
-
while (p < end && IS_WHITESPACE(*p)) p++;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Find opening quote
|
|
63
|
-
if (p >= end || (*p != '"' && *p != '\'')) {
|
|
64
|
-
// Invalid @import, skip to next semicolon
|
|
65
|
-
while (p < end && *p != ';') p++;
|
|
66
|
-
if (p < end) p++; // Skip semicolon
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
char quote_char = *p;
|
|
71
|
-
p++; // Skip opening quote
|
|
72
|
-
|
|
73
|
-
const char *url_start = p;
|
|
74
|
-
|
|
75
|
-
// Find closing quote (handle escaped quotes)
|
|
76
|
-
while (p < end && *p != quote_char) {
|
|
77
|
-
if (*p == '\\' && p + 1 < end) {
|
|
78
|
-
p += 2; // Skip escaped character
|
|
79
|
-
} else {
|
|
80
|
-
p++;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (p >= end) {
|
|
85
|
-
// Unterminated string
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const char *url_end = p;
|
|
90
|
-
p++; // Skip closing quote
|
|
91
|
-
|
|
92
|
-
// Skip closing paren if we had url(
|
|
93
|
-
if (has_url_function) {
|
|
94
|
-
while (p < end && IS_WHITESPACE(*p)) p++;
|
|
95
|
-
if (p < end && *p == ')') {
|
|
96
|
-
p++;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Skip whitespace before optional media query or semicolon
|
|
101
|
-
while (p < end && IS_WHITESPACE(*p)) p++;
|
|
102
|
-
|
|
103
|
-
// Check for optional media query (everything until semicolon)
|
|
104
|
-
const char *media_start = NULL;
|
|
105
|
-
const char *media_end = NULL;
|
|
106
|
-
|
|
107
|
-
if (p < end && *p != ';') {
|
|
108
|
-
media_start = p;
|
|
109
|
-
|
|
110
|
-
// Find semicolon
|
|
111
|
-
while (p < end && *p != ';') p++;
|
|
112
|
-
|
|
113
|
-
media_end = p;
|
|
114
|
-
|
|
115
|
-
// Trim trailing whitespace from media query
|
|
116
|
-
while (media_end > media_start && IS_WHITESPACE(*(media_end - 1))) {
|
|
117
|
-
media_end--;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Skip semicolon
|
|
122
|
-
if (p < end && *p == ';') p++;
|
|
123
|
-
|
|
124
|
-
const char *import_end = p;
|
|
125
|
-
|
|
126
|
-
// Build result hash
|
|
127
|
-
VALUE import_hash = rb_hash_new();
|
|
128
|
-
|
|
129
|
-
// Extract URL
|
|
130
|
-
VALUE url = rb_str_new(url_start, url_end - url_start);
|
|
131
|
-
rb_hash_aset(import_hash, ID2SYM(rb_intern("url")), url);
|
|
132
|
-
|
|
133
|
-
// Extract media query (or nil)
|
|
134
|
-
VALUE media = Qnil;
|
|
135
|
-
if (media_start && media_end > media_start) {
|
|
136
|
-
media = rb_str_new(media_start, media_end - media_start);
|
|
137
|
-
}
|
|
138
|
-
rb_hash_aset(import_hash, ID2SYM(rb_intern("media")), media);
|
|
139
|
-
|
|
140
|
-
// Extract full match
|
|
141
|
-
VALUE full_match = rb_str_new(import_start, import_end - import_start);
|
|
142
|
-
rb_hash_aset(import_hash, ID2SYM(rb_intern("full_match")), full_match);
|
|
143
|
-
|
|
144
|
-
rb_ary_push(imports, import_hash);
|
|
145
|
-
|
|
146
|
-
RB_GC_GUARD(url);
|
|
147
|
-
RB_GC_GUARD(media);
|
|
148
|
-
RB_GC_GUARD(full_match);
|
|
149
|
-
RB_GC_GUARD(import_hash);
|
|
150
|
-
} else {
|
|
151
|
-
// Not an @import, skip to next line or rule
|
|
152
|
-
// Once we hit a non-@import rule (except @charset), stop looking
|
|
153
|
-
// Per CSS spec, @import must be at the top
|
|
154
|
-
|
|
155
|
-
// Skip @charset if present
|
|
156
|
-
if (p + 8 <= end && strncasecmp(p, "@charset", 8) == 0) {
|
|
157
|
-
// Skip to semicolon
|
|
158
|
-
while (p < end && *p != ';') p++;
|
|
159
|
-
if (p < end) p++; // Skip semicolon
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// If we hit any other content, stop scanning for imports
|
|
164
|
-
if (p < end && !IS_WHITESPACE(*p)) {
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
p++;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
RB_GC_GUARD(imports);
|
|
173
|
-
return imports;
|
|
174
|
-
}
|
data/ext/cataract_old/cataract.c
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
#include <ruby.h>
|
|
2
|
-
#include <stdio.h>
|
|
3
|
-
#include "cataract.h"
|
|
4
|
-
|
|
5
|
-
// Global struct class definitions (declared extern in cataract.h)
|
|
6
|
-
VALUE cDeclaration;
|
|
7
|
-
VALUE cRule;
|
|
8
|
-
|
|
9
|
-
// Error class definitions (declared extern in cataract.h)
|
|
10
|
-
VALUE eCataractError;
|
|
11
|
-
VALUE eParseError;
|
|
12
|
-
VALUE eDepthError;
|
|
13
|
-
VALUE eSizeError;
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Ruby Bindings and Public API
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
static VALUE parse_css_internal(VALUE self, VALUE css_string, int depth) {
|
|
20
|
-
// Check recursion depth to prevent stack overflow and memory exhaustion
|
|
21
|
-
if (depth > MAX_PARSE_DEPTH) {
|
|
22
|
-
rb_raise(eDepthError,
|
|
23
|
-
"CSS nesting too deep: exceeded maximum depth of %d",
|
|
24
|
-
MAX_PARSE_DEPTH);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
Check_Type(css_string, T_STRING);
|
|
28
|
-
|
|
29
|
-
// Extract @charset if present (must be at very start per W3C spec)
|
|
30
|
-
// Handled separately because @charset must be at the absolute start
|
|
31
|
-
// and can be processed with simple string operations
|
|
32
|
-
VALUE charset = Qnil;
|
|
33
|
-
const char *css_start = RSTRING_PTR(css_string);
|
|
34
|
-
long css_len = RSTRING_LEN(css_string);
|
|
35
|
-
|
|
36
|
-
// Check for @charset at very start: @charset "UTF-8";
|
|
37
|
-
// Per spec: exact syntax with double quotes required
|
|
38
|
-
if (css_len > 10 && strncmp(css_start, "@charset ", 9) == 0) {
|
|
39
|
-
// Find opening quote
|
|
40
|
-
char *quote_start = strchr(css_start + 9, '"');
|
|
41
|
-
if (quote_start != NULL) {
|
|
42
|
-
// Find closing quote and semicolon
|
|
43
|
-
char *quote_end = strchr(quote_start + 1, '"');
|
|
44
|
-
if (quote_end != NULL) {
|
|
45
|
-
char *semicolon = quote_end + 1;
|
|
46
|
-
// Skip whitespace between quote and semicolon
|
|
47
|
-
while (semicolon < css_start + css_len && IS_WHITESPACE(*semicolon)) {
|
|
48
|
-
semicolon++;
|
|
49
|
-
}
|
|
50
|
-
if (semicolon < css_start + css_len && *semicolon == ';') {
|
|
51
|
-
// Valid @charset rule found
|
|
52
|
-
charset = rb_str_new(quote_start + 1, quote_end - quote_start - 1);
|
|
53
|
-
DEBUG_PRINTF("[@charset] Extracted: '%s'\n", RSTRING_PTR(charset));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Parse CSS using our C parser implementation
|
|
60
|
-
// Returns hash: {query_string => [rules]} already grouped
|
|
61
|
-
VALUE rules_by_media = parse_css_impl(css_string, depth, Qnil);
|
|
62
|
-
|
|
63
|
-
// GC Guard: Protect Ruby objects from garbage collection
|
|
64
|
-
RB_GC_GUARD(css_string);
|
|
65
|
-
RB_GC_GUARD(rules_by_media);
|
|
66
|
-
RB_GC_GUARD(charset);
|
|
67
|
-
|
|
68
|
-
// At depth 0 (top-level parse), return hash with rules and charset
|
|
69
|
-
// Nested parses (depth > 0) return the hash directly
|
|
70
|
-
if (depth == 0) {
|
|
71
|
-
VALUE result = rb_hash_new();
|
|
72
|
-
rb_hash_aset(result, ID2SYM(rb_intern("rules")), rules_by_media);
|
|
73
|
-
rb_hash_aset(result, ID2SYM(rb_intern("charset")), charset);
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
return rules_by_media;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/*
|
|
80
|
-
* Ruby-facing wrapper for parse_declarations
|
|
81
|
-
*
|
|
82
|
-
* @param declarations_string [String] CSS declarations like "color: red; margin: 10px"
|
|
83
|
-
* @return [Array<Declaration>] Array of parsed declaration structs
|
|
84
|
-
*/
|
|
85
|
-
static VALUE parse_declarations(VALUE self, VALUE declarations_string) {
|
|
86
|
-
Check_Type(declarations_string, T_STRING);
|
|
87
|
-
|
|
88
|
-
const char *input = RSTRING_PTR(declarations_string);
|
|
89
|
-
long input_len = RSTRING_LEN(declarations_string);
|
|
90
|
-
|
|
91
|
-
// Strip outer braces and whitespace (css_parser compatibility)
|
|
92
|
-
const char *start = input;
|
|
93
|
-
const char *end = input + input_len;
|
|
94
|
-
|
|
95
|
-
while (start < end && (IS_WHITESPACE(*start) || *start == '{')) start++;
|
|
96
|
-
while (end > start && (IS_WHITESPACE(*(end-1)) || *(end-1) == '}')) end--;
|
|
97
|
-
|
|
98
|
-
VALUE result = parse_declarations_string(start, end);
|
|
99
|
-
|
|
100
|
-
RB_GC_GUARD(result);
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Public wrapper for Ruby - starts at depth 0
|
|
105
|
-
static VALUE parse_css(VALUE self, VALUE css_string) {
|
|
106
|
-
// Verify that cRule was initialized in Init_cataract
|
|
107
|
-
if (cRule == Qnil || cRule == 0) {
|
|
108
|
-
rb_raise(rb_eRuntimeError, "cRule struct class not initialized - Init_cataract may have failed");
|
|
109
|
-
}
|
|
110
|
-
return parse_css_internal(self, css_string, 0);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/*
|
|
114
|
-
* Convert array of Rule structs to full CSS string
|
|
115
|
-
* Format: "selector { prop: value; }\nselector2 { prop: value; }"
|
|
116
|
-
*/
|
|
117
|
-
static VALUE rules_to_s(VALUE self, VALUE rules_array) {
|
|
118
|
-
Check_Type(rules_array, T_ARRAY);
|
|
119
|
-
|
|
120
|
-
long len = RARRAY_LEN(rules_array);
|
|
121
|
-
if (len == 0) {
|
|
122
|
-
return rb_str_new_cstr("");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Estimate: ~100 chars per rule (selector + declarations)
|
|
126
|
-
VALUE result = rb_str_buf_new(len * 100);
|
|
127
|
-
|
|
128
|
-
for (long i = 0; i < len; i++) {
|
|
129
|
-
VALUE rule = rb_ary_entry(rules_array, i);
|
|
130
|
-
|
|
131
|
-
// Validate this is a Rule struct
|
|
132
|
-
if (!RB_TYPE_P(rule, T_STRUCT)) {
|
|
133
|
-
rb_raise(rb_eTypeError,
|
|
134
|
-
"Expected array of Rule structs, got %s at index %ld",
|
|
135
|
-
rb_obj_classname(rule), i);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Extract: selector, declarations, specificity, media_query
|
|
139
|
-
VALUE selector = rb_struct_aref(rule, INT2FIX(RULE_SELECTOR));
|
|
140
|
-
VALUE declarations = rb_struct_aref(rule, INT2FIX(RULE_DECLARATIONS));
|
|
141
|
-
|
|
142
|
-
// Append selector
|
|
143
|
-
rb_str_buf_append(result, selector);
|
|
144
|
-
rb_str_buf_cat2(result, " { ");
|
|
145
|
-
|
|
146
|
-
// Serialize each declaration
|
|
147
|
-
long decl_len = RARRAY_LEN(declarations);
|
|
148
|
-
for (long j = 0; j < decl_len; j++) {
|
|
149
|
-
VALUE decl = rb_ary_entry(declarations, j);
|
|
150
|
-
|
|
151
|
-
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
152
|
-
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
153
|
-
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
154
|
-
|
|
155
|
-
rb_str_buf_append(result, property);
|
|
156
|
-
rb_str_buf_cat2(result, ": ");
|
|
157
|
-
rb_str_buf_append(result, value);
|
|
158
|
-
|
|
159
|
-
if (RTEST(important)) {
|
|
160
|
-
rb_str_buf_cat2(result, " !important");
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
rb_str_buf_cat2(result, "; ");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
rb_str_buf_cat2(result, "}\n");
|
|
167
|
-
|
|
168
|
-
RB_GC_GUARD(rule);
|
|
169
|
-
RB_GC_GUARD(selector);
|
|
170
|
-
RB_GC_GUARD(declarations);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
RB_GC_GUARD(result);
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/*
|
|
178
|
-
* Convert array of Declaration structs to CSS string
|
|
179
|
-
* Format: "prop: value; prop2: value2 !important; "
|
|
180
|
-
*
|
|
181
|
-
* This is the core serialization logic used by both:
|
|
182
|
-
* - Declarations#to_s (instance method)
|
|
183
|
-
* - Internal C serialization (stylesheet.c)
|
|
184
|
-
*
|
|
185
|
-
* Exported (non-static) so stylesheet.c can call it
|
|
186
|
-
*/
|
|
187
|
-
VALUE declarations_array_to_s(VALUE declarations_array) {
|
|
188
|
-
Check_Type(declarations_array, T_ARRAY);
|
|
189
|
-
|
|
190
|
-
long len = RARRAY_LEN(declarations_array);
|
|
191
|
-
if (len == 0) {
|
|
192
|
-
return rb_str_new_cstr("");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Use rb_str_buf_new for efficient string building
|
|
196
|
-
VALUE result = rb_str_buf_new(len * 32); // Estimate 32 chars per declaration
|
|
197
|
-
|
|
198
|
-
for (long i = 0; i < len; i++) {
|
|
199
|
-
VALUE decl = rb_ary_entry(declarations_array, i);
|
|
200
|
-
|
|
201
|
-
// Validate this is a Declaration struct
|
|
202
|
-
if (!RB_TYPE_P(decl, T_STRUCT) || rb_obj_class(decl) != cDeclaration) {
|
|
203
|
-
rb_raise(rb_eTypeError,
|
|
204
|
-
"Expected array of Declaration structs, got %s at index %ld",
|
|
205
|
-
rb_obj_classname(decl), i);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Extract struct fields
|
|
209
|
-
VALUE property = rb_struct_aref(decl, INT2FIX(DECL_PROPERTY));
|
|
210
|
-
VALUE value = rb_struct_aref(decl, INT2FIX(DECL_VALUE));
|
|
211
|
-
VALUE important = rb_struct_aref(decl, INT2FIX(DECL_IMPORTANT));
|
|
212
|
-
|
|
213
|
-
// Append: "property: value"
|
|
214
|
-
rb_str_buf_append(result, property);
|
|
215
|
-
rb_str_buf_cat2(result, ": ");
|
|
216
|
-
rb_str_buf_append(result, value);
|
|
217
|
-
|
|
218
|
-
// Append " !important" if needed
|
|
219
|
-
if (RTEST(important)) {
|
|
220
|
-
rb_str_buf_cat2(result, " !important");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
rb_str_buf_cat2(result, "; ");
|
|
224
|
-
|
|
225
|
-
RB_GC_GUARD(decl);
|
|
226
|
-
RB_GC_GUARD(property);
|
|
227
|
-
RB_GC_GUARD(value);
|
|
228
|
-
RB_GC_GUARD(important);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Strip trailing space
|
|
232
|
-
rb_str_set_len(result, RSTRING_LEN(result) - 1);
|
|
233
|
-
|
|
234
|
-
RB_GC_GUARD(result);
|
|
235
|
-
return result;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/*
|
|
239
|
-
* Instance method: Declarations#to_s
|
|
240
|
-
* Converts declarations to CSS string
|
|
241
|
-
*
|
|
242
|
-
* @return [String] CSS declarations like "color: red; margin: 10px !important;"
|
|
243
|
-
*/
|
|
244
|
-
static VALUE declarations_to_s_method(VALUE self) {
|
|
245
|
-
// Get @values instance variable (array of Declaration structs)
|
|
246
|
-
VALUE values = rb_ivar_get(self, rb_intern("@values"));
|
|
247
|
-
|
|
248
|
-
// Call core serialization function
|
|
249
|
-
return declarations_array_to_s(values);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
void Init_cataract() {
|
|
253
|
-
VALUE module = rb_define_module("Cataract");
|
|
254
|
-
|
|
255
|
-
// Initialize merge constants (cached strings and symbol IDs)
|
|
256
|
-
init_merge_constants();
|
|
257
|
-
|
|
258
|
-
// Define error class hierarchy
|
|
259
|
-
eCataractError = rb_define_class_under(module, "Error", rb_eStandardError);
|
|
260
|
-
eParseError = rb_define_class_under(module, "ParseError", eCataractError);
|
|
261
|
-
eDepthError = rb_define_class_under(module, "DepthError", eCataractError);
|
|
262
|
-
eSizeError = rb_define_class_under(module, "SizeError", eCataractError);
|
|
263
|
-
|
|
264
|
-
// Define Cataract::Declarations class (Ruby side will add methods)
|
|
265
|
-
VALUE cDeclarations = rb_define_class_under(module, "Declarations", rb_cObject);
|
|
266
|
-
|
|
267
|
-
// Define Cataract::Declaration = Struct.new(:property, :value, :important)
|
|
268
|
-
cDeclaration = rb_struct_define_under(
|
|
269
|
-
module,
|
|
270
|
-
"Declaration",
|
|
271
|
-
"property",
|
|
272
|
-
"value",
|
|
273
|
-
"important",
|
|
274
|
-
NULL
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
// Add methods to Declarations class
|
|
278
|
-
rb_define_method(cDeclarations, "to_s", declarations_to_s_method, 0);
|
|
279
|
-
|
|
280
|
-
// Define Cataract::Rule = Struct.new(:selector, :declarations, :specificity)
|
|
281
|
-
// Note: media_query removed - media info now stored at group level in hash structure
|
|
282
|
-
cRule = rb_struct_define_under(
|
|
283
|
-
module,
|
|
284
|
-
"Rule",
|
|
285
|
-
"selector",
|
|
286
|
-
"declarations",
|
|
287
|
-
"specificity",
|
|
288
|
-
NULL
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
// Define Cataract::Stylesheet class (Ruby side will reopen and add methods)
|
|
292
|
-
rb_define_class_under(module, "Stylesheet", rb_cObject);
|
|
293
|
-
|
|
294
|
-
rb_define_module_function(module, "parse_css", parse_css, 1);
|
|
295
|
-
rb_define_module_function(module, "parse_declarations", parse_declarations, 1);
|
|
296
|
-
rb_define_module_function(module, "calculate_specificity", calculate_specificity, 1);
|
|
297
|
-
rb_define_module_function(module, "merge_rules", cataract_merge_wrapper, 1);
|
|
298
|
-
rb_define_module_function(module, "apply_cascade", cataract_merge_wrapper, 1); // Alias with better name
|
|
299
|
-
/* @api private */
|
|
300
|
-
rb_define_module_function(module, "_rules_to_s", rules_to_s, 1);
|
|
301
|
-
|
|
302
|
-
/* @api private */
|
|
303
|
-
rb_define_module_function(module, "_split_value", cataract_split_value, 1);
|
|
304
|
-
/* @api private */
|
|
305
|
-
rb_define_module_function(module, "_expand_margin", cataract_expand_margin, 1);
|
|
306
|
-
/* @api private */
|
|
307
|
-
rb_define_module_function(module, "_expand_padding", cataract_expand_padding, 1);
|
|
308
|
-
/* @api private */
|
|
309
|
-
rb_define_module_function(module, "_expand_border_color", cataract_expand_border_color, 1);
|
|
310
|
-
/* @api private */
|
|
311
|
-
rb_define_module_function(module, "_expand_border_style", cataract_expand_border_style, 1);
|
|
312
|
-
/* @api private */
|
|
313
|
-
rb_define_module_function(module, "_expand_border_width", cataract_expand_border_width, 1);
|
|
314
|
-
/* @api private */
|
|
315
|
-
rb_define_module_function(module, "_expand_border", cataract_expand_border, 1);
|
|
316
|
-
/* @api private */
|
|
317
|
-
rb_define_module_function(module, "_expand_border_side", cataract_expand_border_side, 2);
|
|
318
|
-
/* @api private */
|
|
319
|
-
rb_define_module_function(module, "_expand_font", cataract_expand_font, 1);
|
|
320
|
-
/* @api private */
|
|
321
|
-
rb_define_module_function(module, "_expand_list_style", cataract_expand_list_style, 1);
|
|
322
|
-
/* @api private */
|
|
323
|
-
rb_define_module_function(module, "_expand_background", cataract_expand_background, 1);
|
|
324
|
-
|
|
325
|
-
// Shorthand creation (inverse of expansion)
|
|
326
|
-
/* @api private */
|
|
327
|
-
rb_define_module_function(module, "_create_margin_shorthand", cataract_create_margin_shorthand, 1);
|
|
328
|
-
/* @api private */
|
|
329
|
-
rb_define_module_function(module, "_create_padding_shorthand", cataract_create_padding_shorthand, 1);
|
|
330
|
-
/* @api private */
|
|
331
|
-
rb_define_module_function(module, "_create_border_width_shorthand", cataract_create_border_width_shorthand, 1);
|
|
332
|
-
/* @api private */
|
|
333
|
-
rb_define_module_function(module, "_create_border_style_shorthand", cataract_create_border_style_shorthand, 1);
|
|
334
|
-
/* @api private */
|
|
335
|
-
rb_define_module_function(module, "_create_border_color_shorthand", cataract_create_border_color_shorthand, 1);
|
|
336
|
-
/* @api private */
|
|
337
|
-
rb_define_module_function(module, "_create_border_shorthand", cataract_create_border_shorthand, 1);
|
|
338
|
-
/* @api private */
|
|
339
|
-
rb_define_module_function(module, "_create_background_shorthand", cataract_create_background_shorthand, 1);
|
|
340
|
-
/* @api private */
|
|
341
|
-
rb_define_module_function(module, "_create_font_shorthand", cataract_create_font_shorthand, 1);
|
|
342
|
-
/* @api private */
|
|
343
|
-
rb_define_module_function(module, "_create_list_style_shorthand", cataract_create_list_style_shorthand, 1);
|
|
344
|
-
|
|
345
|
-
// Serialization
|
|
346
|
-
/* @api private */
|
|
347
|
-
rb_define_module_function(module, "_stylesheet_to_s_c", stylesheet_to_s_c, 2);
|
|
348
|
-
/* @api private */
|
|
349
|
-
rb_define_module_function(module, "_stylesheet_to_formatted_s_c", stylesheet_to_formatted_s_c, 2);
|
|
350
|
-
|
|
351
|
-
// Import scanning
|
|
352
|
-
rb_define_module_function(module, "extract_imports", extract_imports, 1);
|
|
353
|
-
|
|
354
|
-
// Export string allocation mode as a constant for verification in benchmarks
|
|
355
|
-
#ifdef DISABLE_STR_BUF_OPTIMIZATION
|
|
356
|
-
rb_define_const(module, "STRING_ALLOC_MODE", ID2SYM(rb_intern("dynamic")));
|
|
357
|
-
#else
|
|
358
|
-
rb_define_const(module, "STRING_ALLOC_MODE", ID2SYM(rb_intern("buffer")));
|
|
359
|
-
#endif
|
|
360
|
-
|
|
361
|
-
// Export compile-time optimization flags as a hash for runtime introspection
|
|
362
|
-
VALUE compile_flags = rb_hash_new();
|
|
363
|
-
|
|
364
|
-
#ifdef DISABLE_STR_BUF_OPTIMIZATION
|
|
365
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("str_buf_optimization")), Qfalse);
|
|
366
|
-
#else
|
|
367
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("str_buf_optimization")), Qtrue);
|
|
368
|
-
#endif
|
|
369
|
-
|
|
370
|
-
#ifdef CATARACT_DEBUG
|
|
371
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("debug")), Qtrue);
|
|
372
|
-
#else
|
|
373
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("debug")), Qfalse);
|
|
374
|
-
#endif
|
|
375
|
-
|
|
376
|
-
#ifdef DISABLE_LOOP_UNROLL
|
|
377
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("loop_unroll")), Qfalse);
|
|
378
|
-
#else
|
|
379
|
-
rb_hash_aset(compile_flags, ID2SYM(rb_intern("loop_unroll")), Qtrue);
|
|
380
|
-
#endif
|
|
381
|
-
|
|
382
|
-
// Note: Compiler flags like -O3, -march=native, -funroll-loops don't have
|
|
383
|
-
// preprocessor defines, so we can't detect them at runtime. They're purely
|
|
384
|
-
// compiler optimizations that affect the generated code.
|
|
385
|
-
|
|
386
|
-
rb_define_const(module, "COMPILE_FLAGS", compile_flags);
|
|
387
|
-
|
|
388
|
-
// NOTE: Color conversion is now a separate extension (cataract_color)
|
|
389
|
-
// It's initialized when you require 'cataract/color_conversion'
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// NOTE: shorthand_expander.c and value_splitter.c are now compiled separately (not included)
|
|
393
|
-
|