p_css 0.1.1 → 0.1.3

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: 9e5c2f5073fa34e7847e6ce57005acbd33a912c544b6ea6971f8c50fc6f9f416
4
- data.tar.gz: 4b6de8fb7d7919fafdc4e4ba249024d022c628675c0ecc66d65cba85f6132b51
3
+ metadata.gz: df1cd693075fe04da6a0c9ce4c65c9c4ef5f85c5b84bb82bd0136cf62fe52552
4
+ data.tar.gz: 9a9fc875c3872c49396c5b6c753a099ba3cc344efa7aa97745958d277630c7cb
5
5
  SHA512:
6
- metadata.gz: ee478932c614e644236fe532663bf781c72397795db988ea34c92ff02a7659da0e076a2f547397f94b6500168b4fafa9071b728d0dd38b05075dfcf5589201fa
7
- data.tar.gz: 9b87600e33a0500fd61c0b919e4709795a0cea47755eded8f343adb5341ee4c47a49b5197429cd443102adc88b0ebbcfdb2ee26a7e2a3133387c3650daae44fe
6
+ metadata.gz: 28c76784dac592aa39cfaa5100b69e4765861ec2ab1ed536fb95dfb963481de2bd64d6b6b954f71b14b17ea4a328cc65af6f7a63df662f2083c2ef5ec623e892
7
+ data.tar.gz: 291d12b999205c032e7cebaa5da4b69aff717cb9f7b9743c3c54ce99539b59ec49612b87e9bcf105d374cc79871d8955fc57f18200b8ba76c396894db14f349f
data/lib/css/cascade.rb CHANGED
@@ -115,9 +115,8 @@ module CSS
115
115
  # Index
116
116
  # ----------------------------------------------------------------
117
117
 
118
- Index = Data.define(:by_id, :by_class, :by_tag, :universal)
119
-
120
- EMPTY_INDEXES = [].freeze
118
+ Index = Data.define(:by_id, :by_class, :by_tag, :universal)
119
+ AnchorKey = Data.define(:kind, :name)
121
120
 
122
121
  def build_index(entries)
123
122
  by_id = {}
@@ -126,19 +125,19 @@ module CSS
126
125
  universal = []
127
126
 
128
127
  entries.each_with_index do |entry, idx|
129
- keys = Set.new
128
+ seen = Set.new
130
129
 
131
130
  entry.selector_pairs.each do |sel, _spec|
132
131
  key = anchor_key(sel)
133
132
 
134
- next if keys.include?(key)
133
+ next if seen.include?(key)
135
134
 
136
- keys << key
135
+ seen << key
137
136
 
138
- case key.first
139
- when :id then (by_id[key.last] ||= []) << idx
140
- when :class then (by_class[key.last] ||= []) << idx
141
- when :tag then (by_tag[key.last] ||= []) << idx
137
+ case key.kind
138
+ when :id then (by_id[key.name] ||= []) << idx
139
+ when :class then (by_class[key.name] ||= []) << idx
140
+ when :tag then (by_tag[key.name] ||= []) << idx
142
141
  when :universal then universal << idx
143
142
  end
144
143
  end
@@ -158,44 +157,51 @@ module CSS
158
157
  # they will be tested against every element, but real-world
159
158
  # stylesheets rarely have many such rules.
160
159
  def anchor_key(complex_selector)
161
- compound = complex_selector.compounds.last
162
-
163
- compound.components.each do |c|
164
- return [:id, c.name] if c.is_a?(Selectors::IdSelector)
165
- end
166
- compound.components.each do |c|
167
- return [:class, c.name] if c.is_a?(Selectors::ClassSelector)
168
- end
169
- compound.components.each do |c|
170
- return [:tag, c.name.downcase] if c.is_a?(Selectors::TypeSelector)
160
+ class_name = nil
161
+ tag_name = nil
162
+
163
+ complex_selector.compounds.last.components.each do |c|
164
+ case c
165
+ when Selectors::IdSelector then return AnchorKey.new(kind: :id, name: c.name)
166
+ when Selectors::ClassSelector then class_name ||= c.name
167
+ when Selectors::TypeSelector then tag_name ||= c.name.downcase
168
+ end
171
169
  end
172
170
 
173
- [:universal]
171
+ return AnchorKey.new(kind: :class, name: class_name) if class_name
172
+ return AnchorKey.new(kind: :tag, name: tag_name) if tag_name
173
+
174
+ AnchorKey.new(kind: :universal, name: nil)
174
175
  end
175
176
 
176
177
  # Resolve helpers
177
178
  # ----------------------------------------------------------------
178
179
 
180
+ # Buckets are appended in source-order at compile time, so each is
181
+ # already sorted ascending and unique. Concatenating them and using
182
+ # `sort! + uniq!` is cheaper than going through a `Set`: integers
183
+ # sort in C, and `uniq!` on a sorted array only removes adjacent
184
+ # duplicates.
179
185
  def collect_candidate_indexes(element, cache)
180
- seen = Set.new
186
+ out = []
181
187
 
182
- el_id = Selectors::Matcher.id_of(element, cache)
188
+ el_id = Selectors::Matcher.id_of(element, cache)
189
+ bucket = @index.by_id[el_id] if el_id
183
190
 
184
- if el_id && (bucket = @index.by_id[el_id])
185
- seen.merge(bucket)
186
- end
191
+ out.concat(bucket) if bucket
187
192
 
188
193
  Selectors::Matcher.classes_of(element, cache).each do |cls|
189
194
  bucket = @index.by_class[cls]
190
- seen.merge(bucket) if bucket
195
+ out.concat(bucket) if bucket
191
196
  end
192
197
 
193
- tag_bucket = @index.by_tag[Selectors::Matcher.tag_of(element, cache)]
194
- seen.merge(tag_bucket) if tag_bucket
198
+ bucket = @index.by_tag[Selectors::Matcher.tag_of(element, cache)]
199
+ out.concat(bucket) if bucket
195
200
 
196
- seen.merge(@index.universal)
197
-
198
- seen.to_a.sort!
201
+ out.concat(@index.universal)
202
+ out.sort!
203
+ out.uniq!
204
+ out
199
205
  end
200
206
 
201
207
  def best_matching_specificity(element, selector_pairs, cache)
@@ -30,7 +30,7 @@ module CSS
30
30
  # for every selector in a hot loop (e.g. `Cascade#resolve` against
31
31
  # hundreds of rules). Keyed by `Object#object_id`; only valid for
32
32
  # the duration of a single matcher invocation.
33
- Context = Struct.new(:tag, :id, :classes, keyword_init: true)
33
+ Context = Data.define(:tag, :id, :classes)
34
34
 
35
35
  EMPTY_CLASS_SET = Set.new.freeze
36
36
 
@@ -425,10 +425,6 @@ module CSS
425
425
  element[lower]
426
426
  end
427
427
 
428
- def class_list(element)
429
- attr(element, 'class').to_s.split(/\s+/)
430
- end
431
-
432
428
  def parent_element(element)
433
429
  p = element.respond_to?(:parent) ? element.parent : nil
434
430
 
data/lib/css/tokenizer.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  module CSS
2
2
  # Tokenizer based on CSS Syntax Module Level 3/4 §4.
3
3
  # https://www.w3.org/TR/css-syntax-3/#tokenization
4
+ #
5
+ # Not thread-safe: an instance carries mutable cursors (`@pos`,
6
+ # `@newline_cursor`) that advance over the input. Allocate one
7
+ # tokenizer per thread.
4
8
  class Tokenizer
5
9
  include CodePoints
6
10
 
@@ -21,9 +25,10 @@ module CSS
21
25
  PREPROCESS_RE = /\r\n?|\f|\0/.freeze
22
26
 
23
27
  def initialize(input, preserve_comments: false)
24
- @input = preprocess(input)
28
+ @chars = preprocess(input)
25
29
  @pos = 0
26
- @newlines = collect_newline_offsets(@input)
30
+ @newlines = collect_newline_offsets(@chars)
31
+ @newline_cursor = 0
27
32
  @preserve_comments = preserve_comments
28
33
  end
29
34
 
@@ -43,7 +48,7 @@ module CSS
43
48
  def next_token
44
49
  consume_comments unless @preserve_comments
45
50
 
46
- return Token.new(:eof) if @pos >= @input.length
51
+ return Token.new(:eof) if @pos >= @chars.length
47
52
 
48
53
  start_offset = @pos
49
54
  tok = consume_one_token
@@ -127,18 +132,25 @@ module CSS
127
132
  end
128
133
  end
129
134
 
135
+ # Random access on a non-ascii-only UTF-8 String is O(distance from
136
+ # the cached character index), and the peek-ahead pattern (`peek`,
137
+ # `peek(1)`, `peek(2)`) defeats the cache — empirically ~200× slower
138
+ # than indexing a flat Array. Splitting into `chars` once amortizes
139
+ # the UTF-8 walk and gives us O(1) random access for the rest of
140
+ # tokenization.
130
141
  def preprocess(input)
131
- input.encode('UTF-8').gsub(PREPROCESS_RE) {
132
- $~[0] == "\0" ? CodePoints::REPLACEMENT : "\n"
133
- }
142
+ input
143
+ .encode('UTF-8')
144
+ .gsub(PREPROCESS_RE) { $~[0] == "\0" ? CodePoints::REPLACEMENT : "\n" }
145
+ .chars
134
146
  end
135
147
 
136
148
  def peek(offset = 0)
137
- @input[@pos + offset]
149
+ @chars[@pos + offset]
138
150
  end
139
151
 
140
152
  def consume
141
- c = @input[@pos]
153
+ c = @chars[@pos]
142
154
  return nil if c.nil?
143
155
 
144
156
  @pos += 1
@@ -149,21 +161,34 @@ module CSS
149
161
  @pos -= 1
150
162
  end
151
163
 
152
- def collect_newline_offsets(input)
164
+ def collect_newline_offsets(chars)
153
165
  offsets = []
154
- i = -1
166
+ i = 0
167
+ n = chars.length
168
+
169
+ while i < n
170
+ offsets << i if chars[i] == "\n"
171
+ i += 1
172
+ end
155
173
 
156
- offsets << i while (i = input.index("\n", i + 1))
157
174
  offsets
158
175
  end
159
176
 
160
- # Newline characters themselves are reported as belonging to the line
161
- # they terminate (col = offset + 1 on line 1, etc).
177
+ # Newline characters themselves are reported as belonging to the
178
+ # line they terminate (col = offset + 1 on line 1, etc).
179
+ #
180
+ # Tokens are emitted in order, so the offsets passed in are
181
+ # monotonically non-decreasing. We keep a running cursor into
182
+ # `@newlines` and advance linearly — amortized O(1) per call,
183
+ # vs. O(log n) per call with a fresh `bsearch`.
162
184
  def line_column_at(offset)
163
- idx = @newlines.bsearch_index { it >= offset } || @newlines.size
164
- prev_nl = idx.zero? ? -1 : @newlines[idx - 1]
185
+ while @newline_cursor < @newlines.size && @newlines[@newline_cursor] < offset
186
+ @newline_cursor += 1
187
+ end
188
+
189
+ prev_nl = @newline_cursor.zero? ? -1 : @newlines[@newline_cursor - 1]
165
190
 
166
- [idx + 1, offset - prev_nl]
191
+ [@newline_cursor + 1, offset - prev_nl]
167
192
  end
168
193
 
169
194
  def whitespace?(c)
@@ -242,7 +267,7 @@ module CSS
242
267
  end
243
268
 
244
269
  def eof?
245
- @pos >= @input.length
270
+ @pos >= @chars.length
246
271
  end
247
272
 
248
273
  def consume_whitespace
data/lib/css/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module CSS
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.3'
3
3
  end
@@ -0,0 +1,22 @@
1
+ module CSS
2
+ # See `CSS.cascade` for the typical entry point.
3
+ class Cascade
4
+ type inline_style = String | Nodes::Block | Array[Nodes::Declaration]
5
+
6
+ class Match < Data
7
+ attr_reader declaration: Nodes::Declaration
8
+ attr_reader specificity: Selectors::Specificity
9
+ attr_reader inline: bool
10
+ attr_reader order: Integer
11
+
12
+ def self.new: (declaration: Nodes::Declaration, specificity: Selectors::Specificity, inline: bool, order: Integer) -> Match
13
+ end
14
+
15
+ def initialize: (Nodes::Stylesheet stylesheet, ?context: MediaQueries::Context) -> void
16
+
17
+ # Returns Hash<String, Declaration> of winning declarations after
18
+ # !important > inline > stylesheet > specificity > source-order
19
+ # sorting.
20
+ def resolve: (untyped element, ?inline_style: inline_style?) -> Hash[String, Nodes::Declaration]
21
+ end
22
+ end
@@ -0,0 +1,107 @@
1
+ module CSS
2
+ module MediaQueries
3
+ module Node
4
+ end
5
+
6
+ type modifier = :not | :only
7
+
8
+ type comparison_op = :eq | :lt | :le | :gt | :ge
9
+
10
+ type media_condition = MediaNot | MediaAnd | MediaOr | MediaFeature | GeneralEnclosed
11
+
12
+ type feature_value = Token | Ratio
13
+
14
+ class MediaQueryList < Data
15
+ include Node
16
+
17
+ attr_reader queries: Array[MediaQuery]
18
+
19
+ def self.new: (queries: Array[MediaQuery]) -> MediaQueryList
20
+ end
21
+
22
+ class MediaQuery < Data
23
+ include Node
24
+
25
+ attr_reader modifier: modifier?
26
+ attr_reader type: String?
27
+ attr_reader condition: media_condition?
28
+
29
+ def self.new: (modifier: modifier?, type: String?, condition: media_condition?) -> MediaQuery
30
+ end
31
+
32
+ class MediaNot < Data
33
+ include Node
34
+
35
+ attr_reader operand: media_condition
36
+
37
+ def self.new: (operand: media_condition) -> MediaNot
38
+ end
39
+
40
+ class MediaAnd < Data
41
+ include Node
42
+
43
+ attr_reader operands: Array[media_condition]
44
+
45
+ def self.new: (operands: Array[media_condition]) -> MediaAnd
46
+ end
47
+
48
+ class MediaOr < Data
49
+ include Node
50
+
51
+ attr_reader operands: Array[media_condition]
52
+
53
+ def self.new: (operands: Array[media_condition]) -> MediaOr
54
+ end
55
+
56
+ # `op` is `nil` for boolean form (`(color)`), `:eq` for plain
57
+ # (`(min-width: 600px)`) and explicit `=`, otherwise a comparison.
58
+ # `value` mirrors that — `nil` for boolean form.
59
+ class MediaFeature < Data
60
+ include Node
61
+
62
+ attr_reader name: String
63
+ attr_reader op: comparison_op?
64
+ attr_reader value: feature_value?
65
+
66
+ def self.new: (name: String, op: comparison_op?, value: feature_value?) -> MediaFeature
67
+ end
68
+
69
+ class GeneralEnclosed < Data
70
+ include Node
71
+
72
+ attr_reader tokens: Array[component_value]
73
+
74
+ def self.new: (tokens: Array[component_value]) -> GeneralEnclosed
75
+ end
76
+
77
+ class Ratio < Data
78
+ include Node
79
+
80
+ attr_reader numerator: Numeric
81
+ attr_reader denominator: Numeric
82
+
83
+ def self.new: (numerator: Numeric, denominator: Numeric) -> Ratio
84
+
85
+ def to_f: () -> Float
86
+ end
87
+
88
+ # User-agent context against which a `MediaQueryList` is evaluated.
89
+ # `features` is keyed by feature name (no `min-`/`max-` prefix);
90
+ # values follow Media Queries 4 conventions (lengths in CSS px,
91
+ # resolution in `dppx`, identifiers as Strings, booleans as 1/0).
92
+ class Context < Data
93
+ DEFAULTS: Hash[String, untyped]
94
+
95
+ attr_reader features: Hash[String, untyped]
96
+
97
+ def self.new: (features: Hash[String, untyped]) -> Context
98
+
99
+ # `Context.default('width' => 1200, 'prefers-color-scheme' => 'dark')`
100
+ def self.default: (**untyped overrides) -> Context
101
+
102
+ def []: ((String | Symbol) name) -> untyped
103
+ def media_type: () -> String
104
+ def with: (**untyped overrides) -> Context
105
+ end
106
+ end
107
+ end
data/sig/css/nodes.rbs ADDED
@@ -0,0 +1,76 @@
1
+ module CSS
2
+ module Nodes
3
+ # `Token` here covers preserved comments when the stylesheet was
4
+ # parsed with `preserve_comments: true`.
5
+ type rule = AtRule | QualifiedRule | Token
6
+
7
+ type block_item = Declaration | QualifiedRule | AtRule | Token
8
+
9
+ class Stylesheet < Data
10
+ attr_reader rules: Array[rule]
11
+
12
+ def self.new: (rules: Array[rule]) -> Stylesheet
13
+ end
14
+
15
+ # `prelude` is the list of component values before `{` or `;`;
16
+ # `block` is `nil` for at-rules ending with `;` (e.g. `@charset "UTF-8";`).
17
+ class AtRule < Data
18
+ attr_reader name: String
19
+ attr_reader prelude: Array[component_value]
20
+ attr_reader block: Block?
21
+
22
+ def self.new: (name: String, prelude: Array[component_value], block: Block?) -> AtRule
23
+ end
24
+
25
+ class QualifiedRule < Data
26
+ attr_reader prelude: Array[component_value]
27
+ attr_reader block: Block
28
+
29
+ def self.new: (prelude: Array[component_value], block: Block) -> QualifiedRule
30
+ end
31
+
32
+ class Block < Data
33
+ attr_reader items: Array[block_item]
34
+
35
+ def self.new: (items: Array[block_item]) -> Block
36
+ end
37
+
38
+ class Declaration < Data
39
+ attr_reader name: String
40
+ attr_reader value: Array[component_value]
41
+ attr_reader important: bool
42
+
43
+ def self.new: (name: String, value: Array[component_value], important: bool) -> Declaration
44
+ end
45
+
46
+ class Function < Data
47
+ attr_reader name: String
48
+ attr_reader value: Array[component_value]
49
+
50
+ def self.new: (name: String, value: Array[component_value]) -> Function
51
+ end
52
+
53
+ # `open` is one of `(`, `[`, `{`.
54
+ class SimpleBlock < Data
55
+ attr_reader open: String
56
+ attr_reader value: Array[component_value]
57
+
58
+ def self.new: (open: String, value: Array[component_value]) -> SimpleBlock
59
+
60
+ def braced?: () -> bool
61
+ def bracketed?: () -> bool
62
+ def parenthesized?: () -> bool
63
+ end
64
+
65
+ # Inclusive code-point range produced by `CSS.parse_urange`.
66
+ class UnicodeRange < Data
67
+ attr_reader first: Integer
68
+ attr_reader last: Integer
69
+
70
+ def self.new: (first: Integer, last: Integer) -> UnicodeRange
71
+
72
+ def cover?: (Integer cp) -> bool
73
+ def to_s: () -> String
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,161 @@
1
+ module CSS
2
+ module Selectors
3
+ # Marker module included by every Selector AST node so callers can
4
+ # discriminate `Selectors::Node` from raw component values.
5
+ module Node
6
+ end
7
+
8
+ type combinator = :descendant | :child | :next_sibling | :subsequent_sibling
9
+
10
+ type simple_selector =
11
+ TypeSelector | UniversalSelector | NestingSelector
12
+ | IdSelector | ClassSelector | AttributeSelector
13
+ | PseudoClass | PseudoElement
14
+
15
+ type attribute_matcher = :exact | :includes | :dash | :prefix | :suffix | :substring
16
+
17
+ type case_flag = :i | :s
18
+
19
+ # `argument` is `nil` for plain `:hover` etc., a `SelectorList` for
20
+ # `:not / :is / :where / :matches`, an `AnB` for `:nth-*`, or an
21
+ # opaque component-value array for unrecognized functional pseudos
22
+ # (including `:has`, which is intentionally not parsed as a selector
23
+ # list yet).
24
+ type pseudo_argument = nil | SelectorList | AnB | Array[component_value]
25
+
26
+ class SelectorList < Data
27
+ include Node
28
+
29
+ attr_reader selectors: Array[ComplexSelector]
30
+
31
+ def self.new: (selectors: Array[ComplexSelector]) -> SelectorList
32
+
33
+ def to_s: () -> String
34
+ end
35
+
36
+ # `compounds.size == combinators.size + 1`. `combinators[i]` joins
37
+ # `compounds[i]` to `compounds[i + 1]`.
38
+ class ComplexSelector < Data
39
+ include Node
40
+
41
+ attr_reader compounds: Array[CompoundSelector]
42
+ attr_reader combinators: Array[combinator]
43
+
44
+ def self.new: (compounds: Array[CompoundSelector], combinators: Array[combinator]) -> ComplexSelector
45
+
46
+ def to_s: () -> String
47
+ end
48
+
49
+ class CompoundSelector < Data
50
+ include Node
51
+
52
+ attr_reader components: Array[simple_selector]
53
+
54
+ def self.new: (components: Array[simple_selector]) -> CompoundSelector
55
+
56
+ def to_s: () -> String
57
+ end
58
+
59
+ class TypeSelector < Data
60
+ include Node
61
+
62
+ attr_reader name: String
63
+
64
+ def self.new: (name: String) -> TypeSelector
65
+ end
66
+
67
+ class UniversalSelector < Data
68
+ include Node
69
+
70
+ def self.new: () -> UniversalSelector
71
+ end
72
+
73
+ class NestingSelector < Data
74
+ include Node
75
+
76
+ def self.new: () -> NestingSelector
77
+ end
78
+
79
+ class IdSelector < Data
80
+ include Node
81
+
82
+ attr_reader name: String
83
+
84
+ def self.new: (name: String) -> IdSelector
85
+ end
86
+
87
+ class ClassSelector < Data
88
+ include Node
89
+
90
+ attr_reader name: String
91
+
92
+ def self.new: (name: String) -> ClassSelector
93
+ end
94
+
95
+ class AttributeSelector < Data
96
+ include Node
97
+
98
+ attr_reader name: String
99
+ attr_reader matcher: attribute_matcher?
100
+ attr_reader value: String?
101
+ attr_reader case_flag: case_flag?
102
+
103
+ def self.new: (name: String, matcher: attribute_matcher?, value: String?, case_flag: case_flag?) -> AttributeSelector
104
+ end
105
+
106
+ class PseudoClass < Data
107
+ include Node
108
+
109
+ attr_reader name: String
110
+ attr_reader argument: pseudo_argument
111
+
112
+ def self.new: (name: String, argument: pseudo_argument) -> PseudoClass
113
+ end
114
+
115
+ class PseudoElement < Data
116
+ include Node
117
+
118
+ attr_reader name: String
119
+ attr_reader argument: pseudo_argument
120
+
121
+ def self.new: (name: String, argument: pseudo_argument) -> PseudoElement
122
+ end
123
+
124
+ class AnB < Data
125
+ include Node
126
+
127
+ attr_reader step: Integer
128
+ attr_reader offset: Integer
129
+
130
+ def self.new: (step: Integer, offset: Integer) -> AnB
131
+
132
+ def to_s: () -> String
133
+ end
134
+
135
+ # Selectors §16 specificity tuple `(a, b, c)` — id / class+attr+pseudo /
136
+ # type+pseudo-element counts.
137
+ class Specificity < Data
138
+ include Comparable
139
+
140
+ ZERO: Specificity
141
+
142
+ attr_reader a: Integer
143
+ attr_reader b: Integer
144
+ attr_reader c: Integer
145
+
146
+ def self.new: (a: Integer, b: Integer, c: Integer) -> Specificity
147
+
148
+ def <=>: (untyped other) -> Integer?
149
+ def +: (Specificity other) -> Specificity
150
+ def to_s: () -> String
151
+ end
152
+
153
+ module Matcher
154
+ def self.matches?: (untyped element, untyped selector, ?cache: Hash[Integer, untyped]) -> bool
155
+
156
+ def self.tag_of: (untyped element, ?Hash[Integer, untyped]?) -> String
157
+ def self.id_of: (untyped element, ?Hash[Integer, untyped]?) -> String?
158
+ def self.classes_of: (untyped element, ?Hash[Integer, untyped]?) -> Set[String]
159
+ end
160
+ end
161
+ end
data/sig/css/token.rbs ADDED
@@ -0,0 +1,33 @@
1
+ module CSS
2
+ class Token
3
+ TYPES: Array[Symbol]
4
+
5
+ type token_type =
6
+ :ident | :function | :at_keyword | :hash | :string | :bad_string
7
+ | :url | :bad_url | :delim | :number | :percentage | :dimension
8
+ | :whitespace | :cdo | :cdc | :comment
9
+ | :colon | :semicolon | :comma
10
+ | :lbracket | :rbracket | :lparen | :rparen | :lbrace | :rbrace
11
+ | :eof
12
+
13
+ attr_reader type: token_type
14
+ attr_reader value: untyped
15
+ attr_reader flag: Symbol?
16
+ attr_reader unit: String?
17
+ attr_reader position: Position?
18
+
19
+ def initialize: (token_type type, ?untyped value, ?flag: Symbol?, ?unit: String?, ?position: Position?) -> void
20
+
21
+ def whitespace?: () -> bool
22
+ def comment?: () -> bool
23
+ def trivia?: () -> bool
24
+
25
+ def assign_position!: (Position pos) -> self
26
+
27
+ def ==: (untyped other) -> bool
28
+ def eql?: (untyped other) -> bool
29
+ def hash: () -> Integer
30
+
31
+ def inspect: () -> String
32
+ end
33
+ end
data/sig/css.rbs ADDED
@@ -0,0 +1,94 @@
1
+ # CSS Syntax Level 4 toolkit — top-level entry points and shared types.
2
+ #
3
+ # Public API surface lives directly on `CSS`; AST nodes live under
4
+ # `CSS::Nodes`, `CSS::Selectors`, and `CSS::MediaQueries`. Per-token
5
+ # source positions are `CSS::Position`.
6
+ module CSS
7
+ # Inputs that carry CSS source. `String` is tokenized internally; an
8
+ # array of component values (as produced by `CSS.parse_component_values`
9
+ # or by reading a rule's `prelude`) is consumed structurally.
10
+ type input = String | Array[component_value]
11
+
12
+ # The output of `CSS.parse_component_value` and the element type of a
13
+ # rule's `prelude` / declaration's `value`.
14
+ type component_value = Token | Nodes::Function | Nodes::SimpleBlock
15
+
16
+ # Bracket-pair information used by both the parser (open token type →
17
+ # open char, close token type) and the serializer.
18
+ BRACKET_OPEN_CHAR: Hash[Symbol, String]
19
+ BRACKET_CLOSE_TYPE: Hash[Symbol, Symbol]
20
+ BRACKET_PAIRS: Hash[String, String]
21
+
22
+ class ParseError < StandardError
23
+ attr_reader position: Position?
24
+
25
+ def initialize: (String message, ?position: Position?) -> void
26
+ end
27
+
28
+ # 1-based line / column with 0-based character offsets into the
29
+ # preprocessed input.
30
+ class Position < Data
31
+ attr_reader line: Integer
32
+ attr_reader column: Integer
33
+ attr_reader offset: Integer
34
+ attr_reader end_offset: Integer
35
+
36
+ def self.new: (line: Integer, column: Integer, offset: Integer, end_offset: Integer) -> Position
37
+
38
+ def to_s: () -> String
39
+ end
40
+
41
+ # Tokenization (Syntax 4 §4)
42
+ # ---------------------------------------------------------------
43
+
44
+ def self.tokenize: (String input, ?preserve_comments: bool) -> Array[Token]
45
+
46
+ # Parsing (Syntax 4 §5.3)
47
+ # ---------------------------------------------------------------
48
+
49
+ def self.parse_stylesheet: (input input, ?preserve_comments: bool) -> Nodes::Stylesheet
50
+ def self.parse: (input input, ?preserve_comments: bool) -> Nodes::Stylesheet
51
+
52
+ def self.parse_rule: (input input, ?preserve_comments: bool) -> (Nodes::AtRule | Nodes::QualifiedRule)
53
+ def self.parse_declaration: (input input, ?preserve_comments: bool) -> Nodes::Declaration
54
+ def self.parse_block_contents: (input input, ?preserve_comments: bool) -> Nodes::Block
55
+
56
+ def self.parse_component_value: (input input, ?preserve_comments: bool) -> component_value
57
+ def self.parse_component_values: (input input, ?preserve_comments: bool) -> Array[component_value]
58
+ def self.parse_comma_separated_values: (input input, ?preserve_comments: bool) -> Array[Array[component_value]]
59
+
60
+ # Selectors (Selectors 4)
61
+ # ---------------------------------------------------------------
62
+
63
+ def self.parse_selector_list: (String | Array[component_value] input) -> Selectors::SelectorList
64
+ def self.parse_selector: (String | Array[component_value] input) -> Selectors::ComplexSelector
65
+ def self.parse_anb: (String | Array[Token] input) -> Selectors::AnB
66
+
67
+ def self.specificity: (Selectors::Node selector) -> Selectors::Specificity
68
+
69
+ type selector = Selectors::SelectorList | Selectors::ComplexSelector | Selectors::CompoundSelector
70
+
71
+ def self.matches?: (untyped element, String | selector selector, ?cache: Hash[Integer, untyped]) -> bool
72
+
73
+ # Media queries (Media Queries 4)
74
+ # ---------------------------------------------------------------
75
+
76
+ def self.parse_media_query_list: (String | Array[component_value] input) -> MediaQueries::MediaQueryList
77
+
78
+ type media_context_input = MediaQueries::Context | Hash[String, untyped]
79
+
80
+ def self.media_matches?: (String | MediaQueries::MediaQueryList query_list, media_context_input context) -> bool
81
+
82
+ # Other transformations
83
+ # ---------------------------------------------------------------
84
+
85
+ def self.parse_urange: (_ToS input) -> Nodes::UnicodeRange
86
+
87
+ def self.desugar: (Nodes::Stylesheet stylesheet) -> Nodes::Stylesheet
88
+
89
+ def self.cascade: (Nodes::Stylesheet stylesheet, ?context: MediaQueries::Context) -> Cascade
90
+
91
+ def self.serialize: (untyped node) -> String
92
+
93
+ VERSION: String
94
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: p_css
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keita Urashima
@@ -45,6 +45,12 @@ files:
45
45
  - lib/css/urange.rb
46
46
  - lib/css/version.rb
47
47
  - lib/p_css.rb
48
+ - sig/css.rbs
49
+ - sig/css/cascade.rbs
50
+ - sig/css/media_queries.rbs
51
+ - sig/css/nodes.rbs
52
+ - sig/css/selectors.rbs
53
+ - sig/css/token.rbs
48
54
  homepage: https://github.com/ursm/p_css
49
55
  licenses:
50
56
  - MIT