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/lib/cataract/version.rb
CHANGED
data/lib/cataract.rb
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'cataract/version'
|
|
4
|
-
require_relative 'cataract/error'
|
|
5
|
-
require_relative 'cataract/constants'
|
|
6
|
-
|
|
7
|
-
# Load struct definitions first (before C extension or pure Ruby)
|
|
8
|
-
require_relative 'cataract/declaration'
|
|
9
|
-
require_relative 'cataract/rule'
|
|
10
|
-
require_relative 'cataract/at_rule'
|
|
11
|
-
require_relative 'cataract/media_query'
|
|
12
|
-
require_relative 'cataract/import_statement'
|
|
13
|
-
|
|
14
|
-
# Load pure Ruby or C extension based on ENV var
|
|
15
|
-
if %w[1 true].include?(ENV.fetch('CATARACT_PURE', nil)) || RUBY_ENGINE == 'jruby'
|
|
16
|
-
require_relative 'cataract/pure'
|
|
17
|
-
else
|
|
18
|
-
require_relative 'cataract/native_extension'
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Load supporting Ruby files (used by both implementations)
|
|
22
|
-
require_relative 'cataract/stylesheet_scope'
|
|
23
|
-
require_relative 'cataract/stylesheet'
|
|
24
|
-
require_relative 'cataract/declarations'
|
|
25
|
-
require_relative 'cataract/import_resolver'
|
|
26
|
-
|
|
27
3
|
# Cataract is a high-performance CSS parser written in C with a Ruby interface.
|
|
28
4
|
#
|
|
29
5
|
# It provides fast CSS parsing, rule querying, cascade merging, and serialization.
|
|
30
6
|
# Designed for performance-critical applications that need to process large amounts of CSS.
|
|
31
7
|
#
|
|
8
|
+
# Pure Ruby and native each live entirely under their own Backends::Pure /
|
|
9
|
+
# Backends::Native namespace (no shared method/constant is ever defined by
|
|
10
|
+
# a backend directly on Cataract or on a shared value type), so both can be
|
|
11
|
+
# required in the same process - e.g. to compare their output directly.
|
|
12
|
+
# Backends.active is the one this process picked by default (below); it's
|
|
13
|
+
# what shared value types (Stylesheet, Declarations, Rule) fall back to
|
|
14
|
+
# when they aren't otherwise told which backend produced them.
|
|
15
|
+
#
|
|
32
16
|
# @example Basic usage
|
|
33
17
|
# require 'cataract'
|
|
34
18
|
#
|
|
@@ -44,6 +28,33 @@ require_relative 'cataract/import_resolver'
|
|
|
44
28
|
# @see Stylesheet Main class for working with parsed CSS
|
|
45
29
|
# @see Rule Represents individual CSS rules
|
|
46
30
|
module Cataract
|
|
31
|
+
module Backends
|
|
32
|
+
class << self
|
|
33
|
+
attr_accessor :active
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if %w[1 true].include?(ENV.fetch('CATARACT_PURE', nil)) || RUBY_ENGINE == 'jruby'
|
|
39
|
+
require_relative 'cataract/pure'
|
|
40
|
+
Cataract::Backends.active = Cataract::Backends::Pure
|
|
41
|
+
else
|
|
42
|
+
require_relative 'cataract/native'
|
|
43
|
+
Cataract::Backends.active = Cataract::Backends::Native
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module Cataract
|
|
47
|
+
# Mirror the active backend's identity constants at the top level.
|
|
48
|
+
#
|
|
49
|
+
# Native is a Module (IMPLEMENTATION/COMPILE_FLAGS live on it directly);
|
|
50
|
+
# Pure is a frozen instance (they live on its class instead), so reach
|
|
51
|
+
# through .class only when active isn't already a Module itself.
|
|
52
|
+
backend_const_holder = Backends.active.is_a?(Module) ? Backends.active : Backends.active.class
|
|
53
|
+
IMPLEMENTATION = backend_const_holder::IMPLEMENTATION
|
|
54
|
+
COMPILE_FLAGS = backend_const_holder::COMPILE_FLAGS
|
|
55
|
+
NATIVE_EXTENSION_LOADED = true if defined?(Backends::Native) && Backends.active == Backends::Native
|
|
56
|
+
PURE_RUBY_LOADED = true if defined?(Backends::Pure) && Backends.active == Backends::Pure
|
|
57
|
+
|
|
47
58
|
class << self
|
|
48
59
|
# Parse a CSS string into a Stylesheet object.
|
|
49
60
|
#
|
|
@@ -75,10 +86,8 @@ module Cataract
|
|
|
75
86
|
#
|
|
76
87
|
# @see Stylesheet#parse
|
|
77
88
|
# @see Stylesheet.parse
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
Stylesheet.parse(css, **options)
|
|
81
|
-
end
|
|
89
|
+
def parse_css(css, **options)
|
|
90
|
+
Stylesheet.parse(css, **options)
|
|
82
91
|
end
|
|
83
92
|
|
|
84
93
|
# Flatten CSS rules according to CSS cascade rules.
|
|
@@ -113,9 +122,11 @@ module Cataract
|
|
|
113
122
|
#
|
|
114
123
|
# @note This is a module-level convenience method. The same functionality is available
|
|
115
124
|
# as an instance method: `stylesheet.flatten`
|
|
116
|
-
# @note Implemented in C (see ext/cataract/flatten.c)
|
|
117
125
|
#
|
|
118
126
|
# @see Stylesheet#flatten
|
|
119
|
-
|
|
127
|
+
def flatten(stylesheet_or_css)
|
|
128
|
+
stylesheet_or_css = Stylesheet.parse(stylesheet_or_css) if stylesheet_or_css.is_a?(String)
|
|
129
|
+
stylesheet_or_css.flatten
|
|
130
|
+
end
|
|
120
131
|
end
|
|
121
132
|
end
|
data/lib/tasks/profile.rake
CHANGED
|
@@ -35,7 +35,7 @@ namespace :profile do
|
|
|
35
35
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
36
36
|
puts
|
|
37
37
|
|
|
38
|
-
require_relative '../../lib/cataract
|
|
38
|
+
require_relative '../../lib/cataract'
|
|
39
39
|
require 'json'
|
|
40
40
|
|
|
41
41
|
# Use higher sampling rate (interval in microseconds, default is 1000)
|
|
@@ -74,6 +74,9 @@ namespace :profile do
|
|
|
74
74
|
abort('stackprof gem not found. Install with: gem install stackprof')
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Ensure we're using pure Ruby implementation
|
|
78
|
+
ENV['CATARACT_PURE'] = '1'
|
|
79
|
+
|
|
77
80
|
fixture_path = File.expand_path('../../test/fixtures/bootstrap.css', __dir__)
|
|
78
81
|
unless File.exist?(fixture_path)
|
|
79
82
|
abort("Fixture not found: #{fixture_path}")
|
|
@@ -94,7 +97,7 @@ namespace :profile do
|
|
|
94
97
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
95
98
|
puts
|
|
96
99
|
|
|
97
|
-
require_relative '../../lib/cataract
|
|
100
|
+
require_relative '../../lib/cataract'
|
|
98
101
|
require 'json'
|
|
99
102
|
|
|
100
103
|
# Parse once outside profiling to get stylesheet
|
|
@@ -162,7 +165,7 @@ namespace :profile do
|
|
|
162
165
|
puts "CSS size: #{css_content.bytesize} bytes"
|
|
163
166
|
puts
|
|
164
167
|
|
|
165
|
-
require_relative '../../lib/cataract
|
|
168
|
+
require_relative '../../lib/cataract'
|
|
166
169
|
require 'json'
|
|
167
170
|
|
|
168
171
|
# Parse once outside profiling to get stylesheet
|
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.4.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,16 +66,6 @@ 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
|
|
@@ -87,10 +76,11 @@ files:
|
|
|
87
76
|
- lib/cataract/import_resolver.rb
|
|
88
77
|
- lib/cataract/import_statement.rb
|
|
89
78
|
- lib/cataract/media_query.rb
|
|
79
|
+
- lib/cataract/native.rb
|
|
90
80
|
- lib/cataract/pure.rb
|
|
91
81
|
- lib/cataract/pure/byte_constants.rb
|
|
82
|
+
- lib/cataract/pure/declarations.rb
|
|
92
83
|
- lib/cataract/pure/flatten.rb
|
|
93
|
-
- lib/cataract/pure/helpers.rb
|
|
94
84
|
- lib/cataract/pure/parser.rb
|
|
95
85
|
- lib/cataract/pure/serializer.rb
|
|
96
86
|
- lib/cataract/pure/specificity.rb
|
|
@@ -122,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
112
|
- !ruby/object:Gem::Version
|
|
123
113
|
version: '0'
|
|
124
114
|
requirements: []
|
|
125
|
-
rubygems_version:
|
|
115
|
+
rubygems_version: 4.0.10
|
|
126
116
|
specification_version: 4
|
|
127
117
|
summary: High-performance CSS parser with C extensions
|
|
128
118
|
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
|
-
}
|