p_css 0.1.9 → 0.2.0.beta1
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/Cargo.lock +282 -0
- data/Cargo.toml +3 -0
- data/ext/css_native/Cargo.toml +12 -0
- data/ext/css_native/extconf.rb +4 -0
- data/ext/css_native/src/lib.rs +117 -0
- data/ext/css_native/src/matcher.rs +356 -0
- data/ext/css_native/src/selectors.rs +411 -0
- data/ext/css_native/src/snapshot.rs +370 -0
- data/ext/css_native/src/state.rs +174 -0
- data/ext/css_native/src/tokenizer.rs +596 -0
- data/lib/css/native.rb +179 -0
- data/lib/css/version.rb +1 -1
- metadata +34 -5
data/lib/css/native.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require_relative '../css'
|
|
2
|
+
|
|
3
|
+
# cibuildgem ships precompiled binaries at lib/css/<ruby_minor>/css_native.{so,bundle,dll}.
|
|
4
|
+
# When users compile the gem from source (no platform gem available),
|
|
5
|
+
# extconf.rb places the binary at lib/css/css_native.{so,bundle,dll}.
|
|
6
|
+
# Try the version-specific path first, fall back to the non-versioned one.
|
|
7
|
+
begin
|
|
8
|
+
ruby_minor = RUBY_VERSION[/\d+\.\d+/]
|
|
9
|
+
require "css/#{ruby_minor}/css_native"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
require_relative 'css_native'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Rust defines matches?, matches_any?, match_indices with a required
|
|
15
|
+
# `state` argument (3 args). Wrap them in Ruby so callers can pass 2
|
|
16
|
+
# args when stateful pseudos aren't relevant.
|
|
17
|
+
module CSS
|
|
18
|
+
module Native
|
|
19
|
+
class Snapshot
|
|
20
|
+
alias_method :_native_matches?, :matches?
|
|
21
|
+
alias_method :_native_matches_any?, :matches_any?
|
|
22
|
+
alias_method :_native_match_indices, :match_indices
|
|
23
|
+
|
|
24
|
+
def matches?(element, selector, state = nil)
|
|
25
|
+
_native_matches?(element, selector, state)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def matches_any?(element, selectors, state = nil)
|
|
29
|
+
_native_matches_any?(element, selectors, state)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def match_indices(element, selectors, state = nil)
|
|
33
|
+
_native_match_indices(element, selectors, state)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module CSS
|
|
40
|
+
module Native
|
|
41
|
+
# High-level wrapper: takes a Nokogiri element + a selector (string or
|
|
42
|
+
# parsed AST) and returns matches?(element). Falls back to the pure-Ruby
|
|
43
|
+
# Matcher when the selector contains features the native matcher doesn't
|
|
44
|
+
# support yet (pseudo-classes, :not, etc.).
|
|
45
|
+
#
|
|
46
|
+
# `document:` / `snapshot:` let callers control snapshot reuse across
|
|
47
|
+
# many matches against the same DOM. With neither, a per-document
|
|
48
|
+
# snapshot is cached by document identity.
|
|
49
|
+
class << self
|
|
50
|
+
def matches?(element, selector, snapshot: nil, document: nil, state: nil)
|
|
51
|
+
ast = selector.is_a?(String) ? CSS.parse_selector_list(selector) : selector
|
|
52
|
+
compiled = compile_or_nil(ast)
|
|
53
|
+
snap = snapshot || snapshot_for(document || element.document)
|
|
54
|
+
|
|
55
|
+
return CSS.matches?(element, ast, state: state) unless compiled
|
|
56
|
+
|
|
57
|
+
native_state = state.nil? ? nil : (state.is_a?(State) ? state : snap.compile_state(state))
|
|
58
|
+
snap.matches?(element, compiled, native_state)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def compile_or_nil(ast)
|
|
62
|
+
Selector.compile(ast)
|
|
63
|
+
rescue Unsupported
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def snapshot_for(document)
|
|
70
|
+
(@snapshots ||= {}.compare_by_identity)[document] ||= Snapshot.from_document(document)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Subclass of CSS::Cascade that uses the native matcher for the inner
|
|
75
|
+
# rule-matching loop. Selectors are pre-compiled at construction —
|
|
76
|
+
# those that can't be compiled (pseudo-classes etc.) fall through to
|
|
77
|
+
# the pure-Ruby matcher, so behavior is identical to CSS::Cascade.
|
|
78
|
+
#
|
|
79
|
+
# Requires a Nokogiri document at construction; the snapshot is built
|
|
80
|
+
# once and reused for every resolve(). Mutate the DOM and you must
|
|
81
|
+
# construct a fresh CSS::Native::Cascade.
|
|
82
|
+
class Cascade < CSS::Cascade
|
|
83
|
+
def initialize(stylesheet, document, context: CSS::MediaQueries::Context.default)
|
|
84
|
+
super(stylesheet, context: context)
|
|
85
|
+
|
|
86
|
+
@snapshot = Snapshot.from_document(document)
|
|
87
|
+
@compiled_by_ast = {}.compare_by_identity
|
|
88
|
+
|
|
89
|
+
@entries.each do |entry|
|
|
90
|
+
entry.selector_pairs.each {|ast, _spec|
|
|
91
|
+
@compiled_by_ast[ast] = Native.compile_or_nil(ast)
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Override: batch every candidate's compiled selectors into one FFI
|
|
97
|
+
# hop per resolve (GVL released), then merge in any Ruby-fallback
|
|
98
|
+
# matches. Cuts per-resolve FFI cost from O(candidates) to O(1).
|
|
99
|
+
def resolve(element, inline_style: nil, state: nil)
|
|
100
|
+
cache = {}
|
|
101
|
+
candidates = collect_candidate_indexes(element, cache)
|
|
102
|
+
order = 0
|
|
103
|
+
matches = []
|
|
104
|
+
native_state = state && @snapshot.compile_state(state)
|
|
105
|
+
|
|
106
|
+
best_by_entry = native_pass(element, candidates, native_state)
|
|
107
|
+
ruby_fallback_pass(element, candidates, best_by_entry, cache, state)
|
|
108
|
+
|
|
109
|
+
candidates.each do |entry_idx|
|
|
110
|
+
spec = best_by_entry[entry_idx] or next
|
|
111
|
+
|
|
112
|
+
@entries[entry_idx].declarations.each {|decl|
|
|
113
|
+
order += 1
|
|
114
|
+
matches << CSS::Cascade::Match.new(declaration: decl, specificity: spec, inline: false, order: order)
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if inline_style
|
|
119
|
+
inline_declarations(inline_style).each {|decl|
|
|
120
|
+
order += 1
|
|
121
|
+
matches << CSS::Cascade::Match.new(
|
|
122
|
+
declaration: decl,
|
|
123
|
+
specificity: CSS::Selectors::Specificity::ZERO,
|
|
124
|
+
inline: true,
|
|
125
|
+
order: order
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
pick_winners(matches)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Flatten the candidates' compiled selectors into one batched
|
|
136
|
+
# match_indices call. Returns Hash{entry_idx => best_specificity}.
|
|
137
|
+
def native_pass(element, candidates, native_state)
|
|
138
|
+
positions = []
|
|
139
|
+
sels = []
|
|
140
|
+
|
|
141
|
+
candidates.each do |entry_idx|
|
|
142
|
+
@entries[entry_idx].selector_pairs.each {|ast, spec|
|
|
143
|
+
compiled = @compiled_by_ast[ast] or next
|
|
144
|
+
|
|
145
|
+
positions << [entry_idx, spec]
|
|
146
|
+
sels << compiled
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
best_by_entry = {}
|
|
151
|
+
|
|
152
|
+
return best_by_entry if sels.empty?
|
|
153
|
+
|
|
154
|
+
@snapshot.match_indices(element, sels, native_state).each {|i|
|
|
155
|
+
entry_idx, spec = positions[i]
|
|
156
|
+
cur = best_by_entry[entry_idx]
|
|
157
|
+
|
|
158
|
+
best_by_entry[entry_idx] = spec if cur.nil? || spec > cur
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
best_by_entry
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Run pure-Ruby matching for any selectors that didn't compile
|
|
165
|
+
# (pseudo-classes, etc.), merging results into best_by_entry.
|
|
166
|
+
def ruby_fallback_pass(element, candidates, best_by_entry, cache, state)
|
|
167
|
+
candidates.each do |entry_idx|
|
|
168
|
+
@entries[entry_idx].selector_pairs.each {|ast, spec|
|
|
169
|
+
next if @compiled_by_ast[ast]
|
|
170
|
+
next unless Selectors::Matcher.matches?(element, ast, cache: cache, state: state)
|
|
171
|
+
|
|
172
|
+
cur = best_by_entry[entry_idx]
|
|
173
|
+
best_by_entry[entry_idx] = spec if cur.nil? || spec > cur
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
data/lib/css/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,24 +1,50 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: p_css
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Keita Urashima
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
-
dependencies:
|
|
11
|
+
date: 2026-05-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rb_sys
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.9'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.9'
|
|
12
27
|
description: p_css is a Ruby implementation of the CSS Syntax Level 4 tokenizer and
|
|
13
28
|
parser, including support for CSS nesting.
|
|
14
29
|
email:
|
|
15
30
|
- ursm@ursm.jp
|
|
16
31
|
executables: []
|
|
17
|
-
extensions:
|
|
32
|
+
extensions:
|
|
33
|
+
- ext/css_native/extconf.rb
|
|
18
34
|
extra_rdoc_files: []
|
|
19
35
|
files:
|
|
36
|
+
- Cargo.lock
|
|
37
|
+
- Cargo.toml
|
|
20
38
|
- LICENSE.txt
|
|
21
39
|
- README.md
|
|
40
|
+
- ext/css_native/Cargo.toml
|
|
41
|
+
- ext/css_native/extconf.rb
|
|
42
|
+
- ext/css_native/src/lib.rs
|
|
43
|
+
- ext/css_native/src/matcher.rs
|
|
44
|
+
- ext/css_native/src/selectors.rs
|
|
45
|
+
- ext/css_native/src/snapshot.rs
|
|
46
|
+
- ext/css_native/src/state.rs
|
|
47
|
+
- ext/css_native/src/tokenizer.rs
|
|
22
48
|
- lib/css.rb
|
|
23
49
|
- lib/css/cascade.rb
|
|
24
50
|
- lib/css/code_points.rb
|
|
@@ -28,6 +54,7 @@ files:
|
|
|
28
54
|
- lib/css/media_queries/evaluator.rb
|
|
29
55
|
- lib/css/media_queries/nodes.rb
|
|
30
56
|
- lib/css/media_queries/parser.rb
|
|
57
|
+
- lib/css/native.rb
|
|
31
58
|
- lib/css/nesting.rb
|
|
32
59
|
- lib/css/nodes.rb
|
|
33
60
|
- lib/css/parser.rb
|
|
@@ -59,6 +86,7 @@ metadata:
|
|
|
59
86
|
changelog_uri: https://github.com/ursm/p_css/releases
|
|
60
87
|
source_code_uri: https://github.com/ursm/p_css
|
|
61
88
|
rubygems_mfa_required: 'true'
|
|
89
|
+
post_install_message:
|
|
62
90
|
rdoc_options: []
|
|
63
91
|
require_paths:
|
|
64
92
|
- lib
|
|
@@ -73,7 +101,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
101
|
- !ruby/object:Gem::Version
|
|
74
102
|
version: '0'
|
|
75
103
|
requirements: []
|
|
76
|
-
rubygems_version: 3.
|
|
104
|
+
rubygems_version: 3.5.22
|
|
105
|
+
signing_key:
|
|
77
106
|
specification_version: 4
|
|
78
107
|
summary: A CSS Syntax Level 4 parser for Ruby, with nesting support.
|
|
79
108
|
test_files: []
|