cataract 0.2.5 → 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/CHANGELOG.md +19 -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 +247 -454
- data/ext/cataract/cataract.h +126 -15
- data/ext/cataract/css_parser.c +782 -788
- 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/import_resolver.rb +36 -16
- data/lib/cataract/pure/byte_constants.rb +0 -1
- data/lib/cataract/pure/flatten.rb +143 -145
- data/lib/cataract/pure/parser.rb +495 -422
- data/lib/cataract/pure/serializer.rb +109 -104
- data/lib/cataract/pure/specificity.rb +112 -156
- data/lib/cataract/pure.rb +0 -7
- data/lib/cataract/stylesheet.rb +460 -412
- data/lib/cataract/version.rb +1 -1
- metadata +2 -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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a6424d26e77e0dd4e0167324dc6190f03a9ca041e51fa9e61a78163f6fe312b
|
|
4
|
+
data.tar.gz: 7c538f41ebd8e93a53caed4a5f066eea076277c26174bff7d22837730f03d90d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2297f4139e8025ac39b9467eaa4bd0541683af7e4870813159c557df813f411d389b59f5b28adb9c46f5390aba73405b937e946d9097b943e0ba70f6cb37963
|
|
7
|
+
data.tar.gz: 0b4d9b6ec564fd674ad8558b7b4f309494c0ca12628947a8501b17ccb3567e1a2667dd0ea89f88ad82f688ac580122ec7e9505b8149433504ef40d923228b75b
|
data/.github/workflows/test.yml
CHANGED
|
@@ -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']
|
|
21
|
+
ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
|
|
22
22
|
|
|
23
23
|
steps:
|
|
24
24
|
- uses: actions/checkout@v4
|
|
@@ -37,8 +37,8 @@ jobs:
|
|
|
37
37
|
env:
|
|
38
38
|
COVERAGE: 1
|
|
39
39
|
|
|
40
|
-
- name: Run debug test (Ruby
|
|
41
|
-
if: matrix.ruby == '
|
|
40
|
+
- name: Run debug test (Ruby 4.0 only)
|
|
41
|
+
if: matrix.ruby == '4.0'
|
|
42
42
|
run: |
|
|
43
43
|
bundle exec rake clean compile
|
|
44
44
|
bundle exec ruby -Ilib:test test/test_stylesheet.rb
|
|
@@ -46,7 +46,7 @@ jobs:
|
|
|
46
46
|
CONFIGURE_ARGS: --enable-debug
|
|
47
47
|
|
|
48
48
|
- name: Upload coverage to Codecov
|
|
49
|
-
if: runner.os == 'Linux' && matrix.ruby == '
|
|
49
|
+
if: runner.os == 'Linux' && matrix.ruby == '4.0'
|
|
50
50
|
uses: codecov/codecov-action@v5
|
|
51
51
|
with:
|
|
52
52
|
fail_ci_if_error: false
|
|
@@ -54,7 +54,7 @@ jobs:
|
|
|
54
54
|
verbose: true
|
|
55
55
|
|
|
56
56
|
- name: Upload coverage report artifact
|
|
57
|
-
if: runner.os == 'Linux' && matrix.ruby == '
|
|
57
|
+
if: runner.os == 'Linux' && matrix.ruby == '4.0'
|
|
58
58
|
uses: actions/upload-artifact@v4
|
|
59
59
|
with:
|
|
60
60
|
name: coverage-report
|
|
@@ -69,7 +69,7 @@ jobs:
|
|
|
69
69
|
- name: Set up Ruby
|
|
70
70
|
uses: ruby/setup-ruby@v1
|
|
71
71
|
with:
|
|
72
|
-
ruby-version: '
|
|
72
|
+
ruby-version: '4.0'
|
|
73
73
|
bundler-cache: true
|
|
74
74
|
|
|
75
75
|
- name: Run rubocop
|
data/.gitignore
CHANGED
|
@@ -10,6 +10,8 @@ ext/**/cataract.c
|
|
|
10
10
|
# Compiled extensions
|
|
11
11
|
lib/**/*.so
|
|
12
12
|
lib/**/*.bundle
|
|
13
|
+
ext/**/*.bundle
|
|
14
|
+
ext/**/*.dSYM/
|
|
13
15
|
|
|
14
16
|
# Makefile generated by extconf.rb
|
|
15
17
|
ext/**/Makefile
|
|
@@ -29,19 +31,31 @@ Thumbs.db
|
|
|
29
31
|
vendor/
|
|
30
32
|
.bundle/
|
|
31
33
|
|
|
34
|
+
# Node (this is a Ruby gem; no JS toolchain is tracked, but guard against stray npm installs)
|
|
35
|
+
node_modules/
|
|
36
|
+
|
|
32
37
|
# Test artifacts
|
|
33
38
|
coverage/
|
|
34
39
|
test/.benchmark_results/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
|
|
41
|
+
# Fuzzer output (crash inputs, logs, recent-input dumps)
|
|
42
|
+
scripts/fuzzer/output/
|
|
38
43
|
|
|
39
44
|
# Benchmark results
|
|
40
45
|
benchmarks/.results/
|
|
41
46
|
benchmarks/.benchmark_results/
|
|
42
47
|
|
|
48
|
+
# CSS analyzer example debug output
|
|
49
|
+
examples/css_analyzer/output/
|
|
50
|
+
|
|
43
51
|
# Documentation (generated by YARD)
|
|
44
52
|
docs/
|
|
45
53
|
!docs/files/
|
|
46
54
|
!docs/files/EXAMPLE.md
|
|
47
55
|
.yardoc/
|
|
56
|
+
|
|
57
|
+
# Claude Code local project instructions (personal, not shared)
|
|
58
|
+
.claude/CLAUDE.md
|
|
59
|
+
|
|
60
|
+
.cache/
|
|
61
|
+
.cocci/
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.3.0] - 2026-07-06
|
|
2
|
+
|
|
3
|
+
- Security/Breaking: `Stylesheet#load_uri` and `#load_file` now reject `file://` paths under `/etc/`, `/proc/`, `/sys/`, and `/dev/` by default (override with `dangerous_path_prefixes: []`). Previously these methods used a separate, unvalidated fetch implementation; they now reuse `ImportResolver` (the same validation and fetching `@import` resolution already uses), which also fixes `load_uri` not following HTTP redirects.
|
|
4
|
+
- Fix: rules nested 3+ levels deep, or nested inside a top-level `@media`/`@supports`/`@container`/`@scope` block, could serialize incorrectly (silently flattened, or printed as an unrelated top-level rule) in the pure Ruby backend.
|
|
5
|
+
- Fix: a bare type selector nested without `&` (invalid CSS nesting) no longer corrupts serialized output with unbalanced braces, in both the C and pure Ruby parsers.
|
|
6
|
+
- Fix: `@keyframes` parsing no longer pollutes the outer rule list with its own inner selectors.
|
|
7
|
+
- Fix: at-rules (`@keyframes`, `@font-face`, etc.) nested inside `@media` blocks now correctly keep their media context across parsing, merging (`add_block`, `concat`/`+`), `@import` resolution, and serialization - previously this could be lost, misattributed, or (combined with CSS nesting) raise `NoMethodError` in the pure Ruby serializer.
|
|
8
|
+
- Fix: `remove_rules!` and the `-` operator no longer desync media query ids from the rules that reference them.
|
|
9
|
+
- Fix: resolving `@import`s no longer leaves duplicate/colliding rule ids.
|
|
10
|
+
- Fix: a plain `@import "file.css";` (no media qualifier) no longer loses that file's own `@media` rules.
|
|
11
|
+
- Fix: `Stylesheet#concat`/`+` now merges the other stylesheet's media queries and selector-list groupings too - previously this silently dropped `h1, h2`-style grouping and corrupted media attribution.
|
|
12
|
+
- Fix: `@import` media queries with a type keyword (e.g. `"screen and (min-width: 500px)"`) no longer keep a stray `"and"` in the parsed conditions.
|
|
13
|
+
- Fix: `to_s`/`to_formatted_s` media filtering is now consistent between the two - both include base (non-media) rules when filtering to a specific media type; previously `to_s` excluded them while `to_formatted_s` included them.
|
|
14
|
+
- Fix: values containing `url(...;...)` no longer lose everything after the first semicolon when the rule also uses CSS nesting.
|
|
15
|
+
- Fix: `!important` now parses correctly with whitespace between `!` and `important`, per the CSS grammar.
|
|
16
|
+
- Fix: expanding `border`, `border-{top,right,bottom,left}`, `font`, `background`, and `list-style` shorthands (used when comparing rules for equality) no longer silently drops `!important`.
|
|
17
|
+
- Fix: `Declarations`/`Stylesheet` `#dup`/`#clone` now produce fully independent copies; previously mutating a copy could mutate the original.
|
|
18
|
+
- Performance: CSS selector specificity calculation is ~30% faster (no longer re-allocates an internal lookup array on every call).
|
|
19
|
+
- Performance: native (C extension) CSS flattening is ~1-5% faster (fewer string allocations when recreating shorthand properties).
|
|
2
20
|
|
|
3
21
|
## [0.2.5 - 2025-11-25]
|
|
4
22
|
|
data/Gemfile
CHANGED
|
@@ -16,10 +16,10 @@ gem 'css_parser', '~> 1.0' # for benchmarking against
|
|
|
16
16
|
gem 'minitest'
|
|
17
17
|
gem 'minitest-spec'
|
|
18
18
|
gem 'nokogiri' # for docs
|
|
19
|
-
gem 'ruby-prof', require: false # for profiling
|
|
19
|
+
gem 'ruby-prof', require: false, platforms: :ruby # for profiling (MRI only)
|
|
20
20
|
gem 'simplecov', require: false
|
|
21
21
|
gem 'simplecov-cobertura', require: false
|
|
22
|
-
gem 'stackprof', require: false # for profiling
|
|
22
|
+
gem 'stackprof', require: false, platforms: :ruby # for profiling (MRI only)
|
|
23
23
|
gem 'webmock', '~> 3.0' # for testing URL loading
|
|
24
24
|
|
|
25
25
|
gem 'overcommit', '~> 0.64', group: :development
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'erb'
|
|
4
|
+
require 'fileutils'
|
|
4
5
|
require 'uri'
|
|
5
6
|
require_relative '../../lib/cataract'
|
|
6
7
|
require_relative 'analyzers/properties'
|
|
@@ -71,13 +72,18 @@ module CSSAnalyzer
|
|
|
71
72
|
def save_parsed_css
|
|
72
73
|
# Generate a unique filename based on source
|
|
73
74
|
source_slug = @source.gsub(%r{[:/]}, '_').gsub(/[^a-zA-Z0-9_.-]/, '')
|
|
74
|
-
|
|
75
|
+
|
|
76
|
+
# Write next to this script (not the caller's cwd) so running the
|
|
77
|
+
# analyzer from repo root doesn't scatter debug output there.
|
|
78
|
+
output_dir = File.join(__dir__, 'output')
|
|
79
|
+
FileUtils.mkdir_p(output_dir)
|
|
80
|
+
path = File.join(output_dir, "parsed-css-#{source_slug}.css")
|
|
75
81
|
|
|
76
82
|
# Serialize stylesheet to CSS
|
|
77
83
|
css_output = @stylesheet.to_s
|
|
78
84
|
|
|
79
|
-
File.write(
|
|
80
|
-
warn "Parsed CSS saved to #{
|
|
85
|
+
File.write(path, css_output)
|
|
86
|
+
warn "Parsed CSS saved to #{path} (#{@stylesheet.size} rules, #{css_output.length} bytes)"
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
private
|
|
@@ -26,7 +26,7 @@ module CSSAnalyzer
|
|
|
26
26
|
# @param rule [Cataract::Rule] The rule to check
|
|
27
27
|
# @return [Array<Symbol>] Array of media query symbols this rule appears in
|
|
28
28
|
def media_queries_for_rule(rule)
|
|
29
|
-
stylesheet.
|
|
29
|
+
stylesheet.media_index.select { |_media, ids| ids.include?(rule.id) }.keys
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|