cataract 0.6.0 → 0.6.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2245bc15a5bd5029bcf6d0cb056a97a896d2855ef247dcc34a3764bba009e89
4
- data.tar.gz: 170bd30c28c2f1c5405ea742a9398dad5aaba2115cd424c73e17572bed8399d8
3
+ metadata.gz: 9e71b572a92ec74936764b374fa8846d340715a04e08f870ace3212037e4fdc5
4
+ data.tar.gz: 0bf71b6b6bed0ddbcecf8041daa6fdd25cead81ff136243378389fe6fa9954a7
5
5
  SHA512:
6
- metadata.gz: 329b054911204add270ac0f2853f0d79c0d12c0b549a34acb672fe6b71416b521f500b36403567f65afa620d165b980f6bc0068c661defe75226602b394c4791
7
- data.tar.gz: b252e03c315de4885224f2bfdd9841ca94e7294b43a80c3c1e27a77d236d451b3ed1a779ad95d956a0445a90d6733d11c8d1ca45ad93d5a693304e7410673d72
6
+ metadata.gz: 7e91e3991a7ed47e542ba6bece13da4aaf990259848f3eb3f1505747b808847df1c738c956687f6349898cf7584e3d510775c55100ba26e726dbec1b11a2d3d7
7
+ data.tar.gz: cbdadf47b0853006532a37be7d08418edfdb90398a4373e84d038f833ef3e9f2722997ce8bb097e2a757a3f0cdb3c47082347e68252e3d233910e7ce0c4b4a90
@@ -18,7 +18,7 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  matrix:
21
- ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
21
+ ruby: ['3.3', '3.4', '4.0']
22
22
 
23
23
  steps:
24
24
  - uses: actions/checkout@v4
data/.rubocop.yml CHANGED
@@ -8,7 +8,7 @@ plugins:
8
8
  - rubocop-minitest
9
9
 
10
10
  AllCops:
11
- TargetRubyVersion: 3.1
11
+ TargetRubyVersion: 3.3
12
12
  NewCops: enable
13
13
  SuggestExtensions: false
14
14
  Exclude:
@@ -178,5 +178,12 @@ Cataract/BanAssertIncludes:
178
178
  - 'test/color/**/*.rb'
179
179
  - 'test/test_benchmark_doc_generator.rb'
180
180
  - 'test/test_speedup_calculator.rb'
181
+ # Benchmark harness tests assert on generated markdown and on the wording
182
+ # of mismatch errors, not on parsed CSS.
183
+ - 'test/test_ruby_mode.rb'
184
+ - 'test/test_backend.rb'
185
+ - 'test/test_benchmark_implementation.rb'
186
+ - 'test/test_result_table.rb'
187
+ - 'test/test_benchmark_plumbing.rb'
181
188
  - 'test/test_parse_errors.rb' # Parse error tests check error messages with assert_match
182
189
  - 'test/support/**/*' # Support files define assert_contains which uses assert_includes internally
data/BENCHMARKS.md CHANGED
@@ -4,51 +4,54 @@
4
4
 
5
5
  # Performance Benchmarks
6
6
 
7
- Performance comparison between Cataract's C extension and pure Ruby implementations.
7
+ Performance comparison between Cataract's C extension and pure Ruby implementations, including pure Ruby under the interpreter, YJIT, and ZJIT.
8
8
 
9
9
  ## Test Environment
10
10
 
11
- - **Ruby**: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin25]
11
+ - **Ruby**: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +ZJIT +PRISM [arm64-darwin25]
12
12
  - **CPU**: Apple M1 Pro
13
13
  - **Memory**: 32GB
14
14
  - **OS**: macOS 26.3.1
15
- - **Generated**: 2026-07-08T10:35:16-05:00
15
+ - **Generated**: 2026-07-30T21:02:54-05:00
16
16
 
17
17
  ## CSS Parsing
18
18
 
19
19
  Time to parse CSS into internal data structures
20
20
 
21
- | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
22
- |-----------|--------|----------------|-------------|
23
- | Small CSS (64 lines, 1.0KB) | 36.76K i/s | 3.33K i/s | 13.66K i/s |
24
- | Medium CSS with @media (139 lines, 1.6KB) | 29.98K i/s | 2.11K i/s | 8.98K i/s |
25
- | Selector lists (3500 lines, 62.5KB, 500 lists) | 447.4 i/s | 58.0 i/s | 219.8 i/s |
21
+ | Test Case | Native | Pure (no JIT) | Pure (YJIT) | Pure (ZJIT) |
22
+ | --------- | ------ | ------------- | ----------- | ----------- |
23
+ | Small CSS (64 lines, 1.0KB) | 31.29K i/s | 3.3K i/s | 13.76K i/s | 7.53K i/s |
24
+ | Medium CSS with @media (139 lines, 1.6KB) | 28.46K i/s | 2.06K i/s | 8.88K i/s | 4.78K i/s |
25
+ | Selector lists (3500 lines, 62.5KB, 500 lists) | 369.3 i/s | 57.5 i/s | 216.9 i/s | 125.5 i/s |
26
26
 
27
27
  ### Speedups
28
28
 
29
29
  | Comparison | Speedup |
30
30
  |------------|---------|
31
- | Native vs Pure (no YJIT) | 13.03x faster (avg) |
32
- | Native vs Pure (YJIT) | 3.12x faster (avg) |
31
+ | Native vs Pure (no JIT) | 11.42x faster (avg) |
32
+ | Native vs Pure (YJIT) | 2.74x faster (avg) |
33
+ | Native vs Pure (ZJIT) | 4.92x faster (avg) |
33
34
  | YJIT impact on Pure Ruby | 4.15x faster (avg) |
35
+ | ZJIT impact on Pure Ruby | 2.31x faster (avg) |
36
+ | ZJIT vs YJIT | 1.79x slower (avg) |
34
37
 
35
38
  ### Parse Error Checking Overhead
36
39
 
37
40
  Parse error detection can be enabled with `raise_parse_errors: true`. This compares performance impact:
38
41
 
39
- | Configuration | Native | Pure (no YJIT) | Pure (YJIT) |
40
- |---------------|--------|----------------|-------------|
41
- | Medium CSS (139 lines) - no error checking | 33.57K i/s | 2.12K i/s | 9.08K i/s |
42
- | Medium CSS (139 lines) - with error checking | 33.09K i/s | 1.94K i/s | 8.19K i/s |
42
+ | Configuration | Native | Pure (no JIT) | Pure (YJIT) | Pure (ZJIT) |
43
+ | ------------- | ------ | ------------- | ----------- | ----------- |
44
+ | Medium CSS (139 lines) - no error checking | 27.71K i/s | 2.09K i/s | 8.62K i/s | 4.84K i/s |
45
+ | Medium CSS (139 lines) - with error checking | 27.36K i/s | 1.92K i/s | 7.97K i/s | 4.55K i/s |
43
46
 
44
47
  **Overhead Analysis:**
45
48
 
46
-
47
49
  | Implementation | Overhead |
48
50
  |----------------|----------|
49
- | Native | 1.5% slower |
50
- | Pure (no YJIT) | 9.2% slower |
51
- | Pure (YJIT) | 10.9% slower |
51
+ | Native | 1.3% slower |
52
+ | Pure (no JIT) | 9.0% slower |
53
+ | Pure (YJIT) | 8.2% slower |
54
+ | Pure (ZJIT) | 6.3% slower |
52
55
 
53
56
  ---
54
57
 
@@ -56,21 +59,24 @@ Parse error detection can be enabled with `raise_parse_errors: true`. This compa
56
59
 
57
60
  Time to convert parsed CSS back to string format
58
61
 
59
- | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
60
- |-----------|--------|----------------|-------------|
61
- | to_s (Bootstrap - 191KB) | 1.05K i/s | 368.6 i/s | 555.6 i/s |
62
- | to_s (Compact utilities - 2.9KB) | 1.05K i/s | 368.6 i/s | 555.6 i/s |
63
- | to_formatted_s (Nested CSS - 1.3KB) | 156.05K i/s | 53.98K i/s | 90.18K i/s |
64
- | to_s with selector_lists (3.4KB) | 15.37K i/s | 7.71K i/s | 12.09K i/s |
65
- | Media filtering (Bootstrap print only) | 1.18K i/s | 547.1 i/s | 769.2 i/s |
62
+ | Test Case | Native | Pure (no JIT) | Pure (YJIT) | Pure (ZJIT) |
63
+ | --------- | ------ | ------------- | ----------- | ----------- |
64
+ | to_s (Bootstrap - 191KB) | 984.2 i/s | 301.6 i/s | 484.0 i/s | 338.1 i/s |
65
+ | to_s (Compact utilities - 2.9KB) | 50.4K i/s | 11.02K i/s | 18.96K i/s | 10.9K i/s |
66
+ | to_formatted_s (Nested CSS - 1.3KB) | 151.49K i/s | 54.68K i/s | 87.22K i/s | 64.4K i/s |
67
+ | to_s with selector_lists (3.4KB) | 14.99K i/s | 6.72K i/s | 10.69K i/s | 7.87K i/s |
68
+ | Media filtering (Bootstrap print only) | 1.1K i/s | 464.3 i/s | 694.0 i/s | 528.0 i/s |
66
69
 
67
70
  ### Speedups
68
71
 
69
72
  | Comparison | Speedup |
70
73
  |------------|---------|
71
- | Native vs Pure (no YJIT) | 2.71x faster (avg) |
72
- | Native vs Pure (YJIT) | 1.75x faster (avg) |
73
- | YJIT impact on Pure Ruby | 1.55x faster (avg) |
74
+ | Native vs Pure (no JIT) | 3.04x faster (avg) |
75
+ | Native vs Pure (YJIT) | 1.88x faster (avg) |
76
+ | Native vs Pure (ZJIT) | 2.77x faster (avg) |
77
+ | YJIT impact on Pure Ruby | 1.6x faster (avg) |
78
+ | ZJIT impact on Pure Ruby | 1.12x faster (avg) |
79
+ | ZJIT vs YJIT | 1.43x slower (avg) |
74
80
 
75
81
  ---
76
82
 
@@ -78,22 +84,25 @@ Time to convert parsed CSS back to string format
78
84
 
79
85
  Time to calculate CSS selector specificity values
80
86
 
81
- | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
82
- |-----------|--------|----------------|-------------|
83
- | Simple Selectors | 1.97M i/s | 419.98K i/s | 1.44M i/s |
84
- | Compound Selectors | 1.89M i/s | 186.59K i/s | 523.08K i/s |
85
- | Combinators | 1.43M i/s | 142.61K i/s | 285.41K i/s |
86
- | Pseudo-classes & Pseudo-elements | 1.41M i/s | 114.7K i/s | 235.07K i/s |
87
- | :not() Pseudo-class (CSS3) | 1.52M i/s | 110.64K i/s | 247.84K i/s |
88
- | Complex Real-world Selectors | 1.62M i/s | 56.2K i/s | 126.65K i/s |
87
+ | Test Case | Native | Pure (no JIT) | Pure (YJIT) | Pure (ZJIT) |
88
+ | --------- | ------ | ------------- | ----------- | ----------- |
89
+ | Simple Selectors | 1.02M i/s | 413.96K i/s | 1.43M i/s | 749.57K i/s |
90
+ | Compound Selectors | 997.92K i/s | 183.13K i/s | 498.8K i/s | 404.43K i/s |
91
+ | Combinators | 757.46K i/s | 136.54K i/s | 267.01K i/s | 260.74K i/s |
92
+ | Pseudo-classes & Pseudo-elements | 757.92K i/s | 113.58K i/s | 224.99K i/s | 209.63K i/s |
93
+ | :not() Pseudo-class (CSS3) | 883.15K i/s | 110.53K i/s | 237.8K i/s | 189.57K i/s |
94
+ | Complex Real-world Selectors | 913.36K i/s | 55.32K i/s | 120.83K i/s | 116.13K i/s |
89
95
 
90
96
  ### Speedups
91
97
 
92
98
  | Comparison | Speedup |
93
99
  |------------|---------|
94
- | Native vs Pure (no YJIT) | 13.28x faster (avg) |
95
- | Native vs Pure (YJIT) | 5.82x faster (avg) |
96
- | YJIT impact on Pure Ruby | 2.46x faster (avg) |
100
+ | Native vs Pure (no JIT) | 7.44x faster (avg) |
101
+ | Native vs Pure (YJIT) | 3.37x faster (avg) |
102
+ | Native vs Pure (ZJIT) | 3.81x faster (avg) |
103
+ | YJIT impact on Pure Ruby | 2.41x faster (avg) |
104
+ | ZJIT impact on Pure Ruby | 1.93x faster (avg) |
105
+ | ZJIT vs YJIT | 1.2x slower (avg) |
97
106
 
98
107
  ---
99
108
 
@@ -101,22 +110,25 @@ Time to calculate CSS selector specificity values
101
110
 
102
111
  Time to flatten multiple CSS rule sets with same selector
103
112
 
104
- | Test Case | Native | Pure (no YJIT) | Pure (YJIT) |
105
- |-----------|--------|----------------|-------------|
106
- | No shorthand properties (large) | 12.75K i/s | 3.69K i/s | 6.41K i/s |
107
- | Simple properties | 142.64K i/s | 69.09K i/s | 100.71K i/s |
108
- | Cascade with specificity | 178.29K i/s | 67.87K i/s | 101.77K i/s |
109
- | Important declarations | 177.88K i/s | 68.12K i/s | 101.75K i/s |
110
- | Shorthand expansion | 12.75K i/s | 3.69K i/s | 6.41K i/s |
111
- | Complex flattening | 31.73K i/s | 16.04K i/s | 23.28K i/s |
113
+ | Test Case | Native | Pure (no JIT) | Pure (YJIT) | Pure (ZJIT) |
114
+ | --------- | ------ | ------------- | ----------- | ----------- |
115
+ | No shorthand properties (large) | 13.28K i/s | 2.85K i/s | 5.37K i/s | 3.44K i/s |
116
+ | Simple properties | 138.9K i/s | 54.39K i/s | 84.42K i/s | 65.18K i/s |
117
+ | Cascade with specificity | 168.14K i/s | 52.88K i/s | 86.97K i/s | 65.61K i/s |
118
+ | Important declarations | 167.4K i/s | 53.61K i/s | 87.29K i/s | 65.6K i/s |
119
+ | Shorthand expansion | 132.57K i/s | 46.27K i/s | 78.43K i/s | 58.86K i/s |
120
+ | Complex flattening | 32.18K i/s | 11.32K i/s | 17.41K i/s | 14.11K i/s |
112
121
 
113
122
  ### Speedups
114
123
 
115
124
  | Comparison | Speedup |
116
125
  |------------|---------|
117
- | Native vs Pure (no YJIT) | 2.45x faster (avg) |
118
- | Native vs Pure (YJIT) | 1.6x faster (avg) |
119
- | YJIT impact on Pure Ruby | 1.52x faster (avg) |
126
+ | Native vs Pure (no JIT) | 3.2x faster (avg) |
127
+ | Native vs Pure (YJIT) | 1.92x faster (avg) |
128
+ | Native vs Pure (ZJIT) | 2.61x faster (avg) |
129
+ | YJIT impact on Pure Ruby | 1.66x faster (avg) |
130
+ | ZJIT impact on Pure Ruby | 1.23x faster (avg) |
131
+ | ZJIT vs YJIT | 1.33x slower (avg) |
120
132
 
121
133
  ---
122
134
 
@@ -140,4 +152,6 @@ rake benchmark:generate_docs
140
152
 
141
153
  - Benchmarks use benchmark-ips with 1-2s warmup and 2-5s measurement periods
142
154
  - Measurements show median iterations per second (i/s)
143
- - YJIT is enabled/disabled per subprocess for accurate comparison
155
+ - Each backend/JIT combination is measured in its own subprocess - Ruby allows only one JIT active per process, and each subprocess verifies the JIT it was launched with is the one actually running before measuring anything
156
+ - The C extension is measured without a JIT: its speed doesn't depend on a JIT compiling Cataract's own code
157
+ - ZJIT is an experimental, method-based JIT introduced in Ruby 4.0. It is not enabled by default and is not yet expected to outperform YJIT; it's included here to track its progress release over release
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [0.6.1] - 2026-07-31
2
+
3
+ - Fix: parsing after `Process.warmup` or `GC.compact` could raise `TypeError: uninitialized class`, or crash the process with a segfault, in the C extension. Cataract's struct classes (`Rule`, `Declaration`, `AtRule`, `Stylesheet`, `ImportStatement`, `MediaQuery`, `ConditionalGroup`) and its exception classes were cached in C globals that were never registered with the GC; compaction relocates objects, leaving those cached pointers dangling, and every subsequent parse then built its structs from a stale address. Nothing unusual was needed to hit this - `Process.warmup` compacts the heap and is commonly called once after boot. Present since the C extension shipped; the pure Ruby backend was unaffected.
4
+ - Fix: flattening a layered (comma-separated) `background` kept only the last layer in the pure Ruby backend - `background: url(a.png), url(b.png)` flattened to `background: url(b.png)`, discarding the rest with no error. Gradients were also misclassified, since only `url(...)` counted as a background image: `background: linear-gradient(#fff, #000), radial-gradient(circle, red, blue)` came back reordered and malformed, as `radial-gradient(circle, red, blue) linear-gradient(#fff, #000),`. The C extension was unaffected.
5
+
1
6
  ## [0.6.0] - 2026-07-30
2
7
 
3
8
  - Security: `ImportResolver.validate_url`'s `dangerous_path_prefixes` check now canonicalizes the `file://` path (via `File.expand_path`) before comparing against blocked prefixes, instead of comparing the raw path string. Previously a path containing `..` that didn't literally start with a blocked prefix (e.g. `file:///var/../etc/passwd`) bypassed the check entirely, even though the file was actually read from the resolved location. Reachable via `Stylesheet#load_uri`/`#load_file` (file scheme allowed by default) and via `@import` when a caller opts `file` into `allowed_schemes`.
data/README.md CHANGED
@@ -31,7 +31,7 @@ gem install cataract
31
31
 
32
32
  ### Requirements
33
33
 
34
- - Ruby >= 3.1.0
34
+ - Ruby >= 3.3.0
35
35
 
36
36
  ### Pure Ruby Implementation
37
37
 
data/cataract.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'A performant CSS parser with C extensions for accurate parsing of complex CSS structures including media queries, nested selectors, and CSS Color Level 4'
13
13
  spec.homepage = 'https://github.com/jamescook/cataract'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 3.1.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.3.0')
16
16
 
17
17
  spec.metadata['homepage_uri'] = spec.homepage
18
18
  spec.metadata['source_code_uri'] = spec.homepage
@@ -1325,6 +1325,26 @@ void Init_native_extension(void) {
1325
1325
  // with its own #to_s, not something this backend attaches methods to.
1326
1326
  cStylesheet = rb_define_class_under(mCataract, "Stylesheet", rb_cObject);
1327
1327
 
1328
+ // Register the cached class and exception VALUEs with the GC.
1329
+ //
1330
+ // Keeping them alive isn't the concern - the constants above do that. GC
1331
+ // compaction is: it moves objects, and an unregistered C global is left
1332
+ // pointing at the old address. rb_struct_new(cRule, ...) on a stale
1333
+ // pointer then fails with "uninitialized class", so any caller that
1334
+ // compacts the heap - Process.warmup and GC.compact both do - breaks
1335
+ // every subsequent parse.
1336
+ rb_global_variable(&cRule);
1337
+ rb_global_variable(&cDeclaration);
1338
+ rb_global_variable(&cAtRule);
1339
+ rb_global_variable(&cStylesheet);
1340
+ rb_global_variable(&cImportStatement);
1341
+ rb_global_variable(&cMediaQuery);
1342
+ rb_global_variable(&cConditionalGroup);
1343
+ rb_global_variable(&eCataractError);
1344
+ rb_global_variable(&eDepthError);
1345
+ rb_global_variable(&eSizeError);
1346
+ rb_global_variable(&eParseError);
1347
+
1328
1348
  // Every native-specific entry point lives under Cataract::Backends::Native,
1329
1349
  // never directly on Cataract itself, so the pure Ruby backend can be loaded
1330
1350
  // in the same process without either backend clobbering the other's methods.
@@ -166,6 +166,13 @@ module Cataract
166
166
  BACKGROUND_ATTACHMENT_KEYWORDS = %w[scroll fixed local].freeze
167
167
  BACKGROUND_POSITION_KEYWORDS = %w[left right center top bottom].freeze
168
168
 
169
+ # Gradient functions that set background-image. Mirrors the set the C
170
+ # extension recognizes, so both backends classify the same tokens.
171
+ BACKGROUND_IMAGE_FUNCTIONS = %w[
172
+ linear-gradient( radial-gradient( conic-gradient(
173
+ repeating-linear-gradient( repeating-radial-gradient(
174
+ ].freeze
175
+
169
176
  # Merge stylesheet according to CSS cascade rules
170
177
  #
171
178
  # @param stylesheet [Stylesheet] Stylesheet to merge
@@ -830,8 +837,17 @@ module Cataract
830
837
  position = nil
831
838
 
832
839
  parts.each do |part|
833
- if starts_with_url?(part) || part == 'none'
834
- image = part
840
+ if background_image?(part)
841
+ # Layered backgrounds are comma-separated and arrive as one part
842
+ # per layer. Assigning would keep only the last and silently drop
843
+ # the rest, so append instead. split_on_whitespace builds each
844
+ # part fresh, so appending mutates a string we own - no
845
+ # allocation, and nothing else references it.
846
+ if image
847
+ image << ' ' << part
848
+ else
849
+ image = part
850
+ end
835
851
  elsif BACKGROUND_REPEAT_KEYWORDS.include?(part)
836
852
  repeat = part
837
853
  elsif BACKGROUND_ATTACHMENT_KEYWORDS.include?(part)
@@ -858,6 +874,17 @@ module Cataract
858
874
  result
859
875
  end
860
876
 
877
+ # Value tokens that set background-image: a url, any of the gradient
878
+ # functions, or the `none` keyword. Gradients count as images; left out
879
+ # they fall through to the position and color branches and end up
880
+ # filed as neither.
881
+ def background_image?(value)
882
+ return true if value == 'none'
883
+ return true if starts_with_url?(value)
884
+
885
+ BACKGROUND_IMAGE_FUNCTIONS.any? { |function| value.start_with?(function) }
886
+ end
887
+
861
888
  # Check if value starts with 'url('
862
889
  def starts_with_url?(value)
863
890
  return false if value.bytesize < 4
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cataract
4
- VERSION = '0.6.0'
4
+ VERSION = '0.6.1'
5
5
  end
data/lib/cataract.rb CHANGED
@@ -86,8 +86,8 @@ module Cataract
86
86
  #
87
87
  # @see Stylesheet#parse
88
88
  # @see Stylesheet.parse
89
- def parse_css(css, **options)
90
- Stylesheet.parse(css, **options)
89
+ def parse_css(css, **)
90
+ Stylesheet.parse(css, **)
91
91
  end
92
92
 
93
93
  # Flatten CSS rules according to CSS cascade rules.
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.6.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Cook
@@ -121,14 +121,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: 3.1.0
124
+ version: 3.3.0
125
125
  required_rubygems_version: !ruby/object:Gem::Requirement
126
126
  requirements:
127
127
  - - ">="
128
128
  - !ruby/object:Gem::Version
129
129
  version: '0'
130
130
  requirements: []
131
- rubygems_version: 4.0.10
131
+ rubygems_version: 4.0.16
132
132
  specification_version: 4
133
133
  summary: High-performance CSS parser with C extensions
134
134
  test_files: []