syntax_tree-css 0.1.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 +7 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/main.yml +34 -0
- data/.gitignore +2 -0
- data/.gitmodules +3 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +36 -0
- data/LICENSE +21 -0
- data/README.md +73 -0
- data/Rakefile +16 -0
- data/lib/syntax_tree/css/basic_visitor.rb +23 -0
- data/lib/syntax_tree/css/format.rb +14 -0
- data/lib/syntax_tree/css/nodes.rb +969 -0
- data/lib/syntax_tree/css/parser.rb +1188 -0
- data/lib/syntax_tree/css/pretty_print.rb +441 -0
- data/lib/syntax_tree/css/selectors.rb +519 -0
- data/lib/syntax_tree/css/version.rb +7 -0
- data/lib/syntax_tree/css/visitor.rb +154 -0
- data/lib/syntax_tree/css.rb +31 -0
- data/syntax_tree-css.gemspec +33 -0
- metadata +147 -0
@@ -0,0 +1,519 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module CSS
|
5
|
+
# Parses selectors according to https://www.w3.org/TR/selectors-4 from the
|
6
|
+
# version dated 7 May 2022.
|
7
|
+
class Selectors
|
8
|
+
class ParseError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class MissingTokenError < ParseError
|
12
|
+
end
|
13
|
+
|
14
|
+
# A custom enumerator around the list of tokens. This allows us to save a
|
15
|
+
# reference to where we are when we're looking at the stream and rollback
|
16
|
+
# to that point if we need to.
|
17
|
+
class TokenEnumerator
|
18
|
+
class Rollback < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :tokens, :index
|
22
|
+
|
23
|
+
def initialize(tokens)
|
24
|
+
@tokens = tokens
|
25
|
+
@index = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def next
|
29
|
+
@tokens[@index].tap { @index += 1}
|
30
|
+
end
|
31
|
+
|
32
|
+
def peek
|
33
|
+
@tokens[@index]
|
34
|
+
end
|
35
|
+
|
36
|
+
def transaction
|
37
|
+
saved = @index
|
38
|
+
yield
|
39
|
+
rescue Rollback
|
40
|
+
@index = saved
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
AttributeSelector = Struct.new(:wq_name, :matcher, keyword_init: true)
|
46
|
+
AttributeSelectorMatcher = Struct.new(:attr_matcher, :token, :modifier, keyword_init: true)
|
47
|
+
AttrMatcher = Struct.new(:prefix, keyword_init: true)
|
48
|
+
AttrModifier = Struct.new(:value, keyword_init: true)
|
49
|
+
|
50
|
+
# The class of an element, e.g., .foo
|
51
|
+
# https://www.w3.org/TR/selectors-4/#typedef-class-selector
|
52
|
+
class ClassSelector < Node
|
53
|
+
attr_reader :value
|
54
|
+
|
55
|
+
def initialize(value:)
|
56
|
+
@value = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def accept(visitor)
|
60
|
+
visitor.visit_class_selector(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def child_nodes
|
64
|
+
[value]
|
65
|
+
end
|
66
|
+
|
67
|
+
alias deconstruct child_nodes
|
68
|
+
|
69
|
+
def deconstruct_keys(keys)
|
70
|
+
{ value: value }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Combinator = Struct.new(:value, keyword_init: true)
|
75
|
+
ComplexSelector = Struct.new(:left, :combinator, :right, keyword_init: true)
|
76
|
+
CompoundSelector = Struct.new(:type, :subclasses, :pseudo_elements, keyword_init: true)
|
77
|
+
|
78
|
+
# The ID of an element, e.g., #foo
|
79
|
+
# https://www.w3.org/TR/selectors-4/#typedef-id-selector
|
80
|
+
class IdSelector < Node
|
81
|
+
attr_reader :value
|
82
|
+
|
83
|
+
def initialize(value:)
|
84
|
+
@value = value
|
85
|
+
end
|
86
|
+
|
87
|
+
def accept(visitor)
|
88
|
+
visitor.visit_id_selector(self)
|
89
|
+
end
|
90
|
+
|
91
|
+
def child_nodes
|
92
|
+
[value]
|
93
|
+
end
|
94
|
+
|
95
|
+
alias deconstruct child_nodes
|
96
|
+
|
97
|
+
def deconstruct_keys(keys)
|
98
|
+
{ value: value }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
NsPrefix = Struct.new(:value, keyword_init: true)
|
103
|
+
|
104
|
+
# A pseudo class function call, like :nth-child.
|
105
|
+
class PseudoClassFunction < Node
|
106
|
+
attr_reader :name, :arguments
|
107
|
+
|
108
|
+
def initialize(name:, arguments:)
|
109
|
+
@name = name
|
110
|
+
@arguments = arguments
|
111
|
+
end
|
112
|
+
|
113
|
+
def accept(visitor)
|
114
|
+
visitor.visit_pseudo_class_function(self)
|
115
|
+
end
|
116
|
+
|
117
|
+
def child_nodes
|
118
|
+
arguments
|
119
|
+
end
|
120
|
+
|
121
|
+
alias deconstruct child_nodes
|
122
|
+
|
123
|
+
def deconstruct_keys(keys)
|
124
|
+
{ name: name, arguments: arguments }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# A pseudo class selector, like :hover.
|
129
|
+
# https://www.w3.org/TR/selectors-4/#typedef-pseudo-class-selector
|
130
|
+
class PseudoClassSelector < Node
|
131
|
+
attr_reader :value
|
132
|
+
|
133
|
+
def initialize(value:)
|
134
|
+
@value = value
|
135
|
+
end
|
136
|
+
|
137
|
+
def accept(visitor)
|
138
|
+
visitor.visit_pseudo_class_selector(self)
|
139
|
+
end
|
140
|
+
|
141
|
+
def child_nodes
|
142
|
+
[value]
|
143
|
+
end
|
144
|
+
|
145
|
+
alias deconstruct child_nodes
|
146
|
+
|
147
|
+
def deconstruct_keys(keys)
|
148
|
+
{ value: value }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# A pseudo element selector, like ::before.
|
153
|
+
# https://www.w3.org/TR/selectors-4/#typedef-pseudo-element-selector
|
154
|
+
class PseudoElementSelector < Node
|
155
|
+
attr_reader :value
|
156
|
+
|
157
|
+
def initialize(value:)
|
158
|
+
@value = value
|
159
|
+
end
|
160
|
+
|
161
|
+
def accept(visitor)
|
162
|
+
visitor.visit_pseudo_element_selector(self)
|
163
|
+
end
|
164
|
+
|
165
|
+
def child_nodes
|
166
|
+
[value]
|
167
|
+
end
|
168
|
+
|
169
|
+
alias deconstruct child_nodes
|
170
|
+
|
171
|
+
def deconstruct_keys(keys)
|
172
|
+
{ value: value }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
RelativeSelector = Struct.new(:combinator, :complex_selector, keyword_init: true)
|
177
|
+
|
178
|
+
# A selector for a specific tag name.
|
179
|
+
# https://www.w3.org/TR/selectors-4/#typedef-type-selector
|
180
|
+
class TypeSelector < Node
|
181
|
+
attr_reader :prefix, :value
|
182
|
+
|
183
|
+
def initialize(prefix:, value:)
|
184
|
+
@prefix = prefix
|
185
|
+
@value = value
|
186
|
+
end
|
187
|
+
|
188
|
+
def accept(visitor)
|
189
|
+
visitor.visit_type_selector(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def child_nodes
|
193
|
+
[prefix, value]
|
194
|
+
end
|
195
|
+
|
196
|
+
alias deconstruct child_nodes
|
197
|
+
|
198
|
+
def deconstruct_keys(keys)
|
199
|
+
{ prefix: prefix, value: value }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# The name of an element, e.g., foo
|
204
|
+
class WqName < Node
|
205
|
+
attr_reader :prefix, :name
|
206
|
+
|
207
|
+
def initialize(prefix:, name:)
|
208
|
+
@prefix = prefix
|
209
|
+
@name = name
|
210
|
+
end
|
211
|
+
|
212
|
+
def accept(visitor)
|
213
|
+
visitor.visit_wqname(self)
|
214
|
+
end
|
215
|
+
|
216
|
+
def child_nodes
|
217
|
+
[prefix, name]
|
218
|
+
end
|
219
|
+
|
220
|
+
alias deconstruct child_nodes
|
221
|
+
|
222
|
+
def deconstruct_keys(keys)
|
223
|
+
{ prefix: prefix, name: name }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
attr_reader :tokens
|
228
|
+
|
229
|
+
def initialize(tokens)
|
230
|
+
@tokens = TokenEnumerator.new(tokens)
|
231
|
+
end
|
232
|
+
|
233
|
+
def parse
|
234
|
+
selector_list
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
#-------------------------------------------------------------------------
|
240
|
+
# Parsing methods
|
241
|
+
#-------------------------------------------------------------------------
|
242
|
+
|
243
|
+
# <selector-list> = <complex-selector-list>
|
244
|
+
def selector_list
|
245
|
+
complex_selector_list
|
246
|
+
end
|
247
|
+
|
248
|
+
# <complex-selector-list> = <complex-selector>#
|
249
|
+
def complex_selector_list
|
250
|
+
one_or_more { complex_selector }
|
251
|
+
end
|
252
|
+
|
253
|
+
# <compound-selector-list> = <compound-selector>#
|
254
|
+
def compound_selector_list
|
255
|
+
one_or_more { compound_selector }
|
256
|
+
end
|
257
|
+
|
258
|
+
# <simple-selector-list> = <simple-selector>#
|
259
|
+
def simple_selector_list
|
260
|
+
one_or_more { simple_selector }
|
261
|
+
end
|
262
|
+
|
263
|
+
# <relative-selector-list> = <relative-selector>#
|
264
|
+
def relative_selector_list
|
265
|
+
one_or_more { relative_selector }
|
266
|
+
end
|
267
|
+
|
268
|
+
# <complex-selector> = <compound-selector> [ <combinator>? <compound-selector> ]*
|
269
|
+
def complex_selector
|
270
|
+
left = compound_selector
|
271
|
+
|
272
|
+
loop do
|
273
|
+
if (combinator = maybe { combinator })
|
274
|
+
ComplexSelector.new(left: left, combinator: combinator, right: compound_selector)
|
275
|
+
elsif (right = maybe { compound_selector })
|
276
|
+
ComplexSelector.new(left: left, combinator: nil, right: right)
|
277
|
+
else
|
278
|
+
break
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
left
|
283
|
+
end
|
284
|
+
|
285
|
+
# <relative-selector> = <combinator>? <complex-selector>
|
286
|
+
def relative_selector
|
287
|
+
combinator = maybe { combinator }
|
288
|
+
|
289
|
+
if combinator
|
290
|
+
RelativeSelector.new(combinator: combinator, complex_selector: complex_selector)
|
291
|
+
else
|
292
|
+
complex_selector
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# <compound-selector> = [ <type-selector>? <subclass-selector>*
|
297
|
+
# [ <pseudo-element-selector> <pseudo-class-selector>* ]* ]!
|
298
|
+
def compound_selector
|
299
|
+
type = maybe { type_selector }
|
300
|
+
subclasses = []
|
301
|
+
|
302
|
+
while (subclass = maybe { subclass_selector })
|
303
|
+
subclasses << subclass
|
304
|
+
end
|
305
|
+
|
306
|
+
pseudo_elements = []
|
307
|
+
while (pseudo_element = maybe { pseudo_element_selector })
|
308
|
+
pseudo_classes = []
|
309
|
+
|
310
|
+
while (pseudo_class = maybe { pseudo_class_selector })
|
311
|
+
pseudo_classes << pseudo_class
|
312
|
+
end
|
313
|
+
|
314
|
+
pseudo_elements << [pseudo_element, pseudo_classes]
|
315
|
+
end
|
316
|
+
|
317
|
+
if type.nil? && subclasses.empty? && pseudo_elements.empty?
|
318
|
+
raise MissingTokenError, "Expected compound selector to produce something"
|
319
|
+
elsif type && subclasses.empty? && pseudo_elements.empty?
|
320
|
+
type
|
321
|
+
elsif type.nil? && subclasses.one? && pseudo_elements.empty?
|
322
|
+
subclasses.first
|
323
|
+
else
|
324
|
+
CompoundSelector.new(type: type, subclasses: subclasses, pseudo_elements: pseudo_elements)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# <simple-selector> = <type-selector> | <subclass-selector>
|
329
|
+
def simple_selector
|
330
|
+
options { maybe { type_selector } || maybe { subclass_selector } }
|
331
|
+
end
|
332
|
+
|
333
|
+
# <combinator> = '>' | '+' | '~' | [ '|' '|' ]
|
334
|
+
def combinator
|
335
|
+
value =
|
336
|
+
options do
|
337
|
+
maybe { consume(">") } ||
|
338
|
+
maybe { consume("+") } ||
|
339
|
+
maybe { consume("~") } ||
|
340
|
+
maybe { consume("|", "|") }
|
341
|
+
end
|
342
|
+
|
343
|
+
Combinator.new(value: value)
|
344
|
+
end
|
345
|
+
|
346
|
+
# <type-selector> = <wq-name> | <ns-prefix>? '*'
|
347
|
+
def type_selector
|
348
|
+
selector = maybe { wq_name }
|
349
|
+
return TypeSelector.new(prefix: nil, value: selector) if selector
|
350
|
+
|
351
|
+
prefix = maybe { ns_prefix }
|
352
|
+
TypeSelector.new(prefix: prefix, value: consume("*"))
|
353
|
+
end
|
354
|
+
|
355
|
+
# <ns-prefix> = [ <ident-token> | '*' ]? '|'
|
356
|
+
def ns_prefix
|
357
|
+
value = maybe { consume(IdentToken) } || maybe { consume("*") }
|
358
|
+
consume("|")
|
359
|
+
|
360
|
+
NsPrefix.new(value: value)
|
361
|
+
end
|
362
|
+
|
363
|
+
# <wq-name> = <ns-prefix>? <ident-token>
|
364
|
+
def wq_name
|
365
|
+
prefix = maybe { ns_prefix }
|
366
|
+
name = consume(IdentToken)
|
367
|
+
|
368
|
+
WqName.new(prefix: prefix, name: name)
|
369
|
+
end
|
370
|
+
|
371
|
+
# <subclass-selector> = <id-selector> | <class-selector> |
|
372
|
+
# <attribute-selector> | <pseudo-class-selector>
|
373
|
+
def subclass_selector
|
374
|
+
options do
|
375
|
+
maybe { id_selector } ||
|
376
|
+
maybe { class_selector } ||
|
377
|
+
maybe { attribute_selector } ||
|
378
|
+
maybe { pseudo_class_selector }
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# <id-selector> = <hash-token>
|
383
|
+
def id_selector
|
384
|
+
IdSelector.new(value: consume(HashToken))
|
385
|
+
end
|
386
|
+
|
387
|
+
# <class-selector> = '.' <ident-token>
|
388
|
+
def class_selector
|
389
|
+
consume(".")
|
390
|
+
ClassSelector.new(value: consume(IdentToken))
|
391
|
+
end
|
392
|
+
|
393
|
+
# <attribute-selector> = '[' <wq-name> ']' |
|
394
|
+
# '[' <wq-name> <attr-matcher> [ <string-token> | <ident-token> ] <attr-modifier>? ']'
|
395
|
+
def attribute_selector
|
396
|
+
consume(OpenSquareToken)
|
397
|
+
|
398
|
+
name = wq_name
|
399
|
+
matcher =
|
400
|
+
maybe do
|
401
|
+
AttributeSelectorMatcher.new(
|
402
|
+
attr_matcher: attr_matcher,
|
403
|
+
token: options { maybe { consume(StringToken) } || maybe { consume(IdentToken) } },
|
404
|
+
modifier: maybe { attr_modifier }
|
405
|
+
)
|
406
|
+
end
|
407
|
+
|
408
|
+
consume(CloseSquareToken)
|
409
|
+
AttributeSelector.new(wq_name: name, matcher: matcher)
|
410
|
+
end
|
411
|
+
|
412
|
+
# <attr-matcher> = [ '~' | '|' | '^' | '$' | '*' ]? '='
|
413
|
+
def attr_matcher
|
414
|
+
prefix =
|
415
|
+
maybe { consume("~") } ||
|
416
|
+
maybe { consume("|") } ||
|
417
|
+
maybe { consume("^") } ||
|
418
|
+
maybe { consume("$") } ||
|
419
|
+
maybe { consume("*") }
|
420
|
+
|
421
|
+
consume("=")
|
422
|
+
AttrMatcher.new(prefix: prefix)
|
423
|
+
end
|
424
|
+
|
425
|
+
# <attr-modifier> = i | s
|
426
|
+
def attr_modifier
|
427
|
+
value = options { maybe { consume("i") } || maybe { consume("s") } }
|
428
|
+
AttrModifier.new(value: value)
|
429
|
+
end
|
430
|
+
|
431
|
+
# <pseudo-class-selector> = ':' <ident-token> |
|
432
|
+
# ':' <function-token> <any-value> ')'
|
433
|
+
def pseudo_class_selector
|
434
|
+
consume(ColonToken)
|
435
|
+
|
436
|
+
case tokens.peek
|
437
|
+
in IdentToken
|
438
|
+
PseudoClassSelector.new(value: consume(IdentToken))
|
439
|
+
in Function
|
440
|
+
node = consume(Function)
|
441
|
+
function = PseudoClassFunction.new(name: node.name, arguments: node.value)
|
442
|
+
PseudoClassSelector.new(value: function)
|
443
|
+
else
|
444
|
+
raise MissingTokenError, "Expected pseudo class selector to produce something"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# <pseudo-element-selector> = ':' <pseudo-class-selector>
|
449
|
+
def pseudo_element_selector
|
450
|
+
consume(ColonToken)
|
451
|
+
PseudoElementSelector.new(value: pseudo_class_selector)
|
452
|
+
end
|
453
|
+
|
454
|
+
#-------------------------------------------------------------------------
|
455
|
+
# Helper methods
|
456
|
+
#-------------------------------------------------------------------------
|
457
|
+
|
458
|
+
def consume_whitespace
|
459
|
+
loop do
|
460
|
+
case tokens.peek
|
461
|
+
in CommentToken | WhitespaceToken
|
462
|
+
tokens.next
|
463
|
+
else
|
464
|
+
return
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def one_or_more
|
470
|
+
items = []
|
471
|
+
|
472
|
+
consume_whitespace
|
473
|
+
items << yield
|
474
|
+
|
475
|
+
loop do
|
476
|
+
consume_whitespace
|
477
|
+
if maybe { consume(CommaToken) }
|
478
|
+
consume_whitespace
|
479
|
+
items << yield
|
480
|
+
else
|
481
|
+
return items
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def consume(*values)
|
487
|
+
result =
|
488
|
+
values.map do |value|
|
489
|
+
case [value, tokens.peek]
|
490
|
+
in [String, DelimToken[value: token_value]] if value == token_value
|
491
|
+
tokens.next
|
492
|
+
in [Class, token] if token.is_a?(value)
|
493
|
+
tokens.next
|
494
|
+
in [_, token]
|
495
|
+
raise MissingTokenError, "Expected #{value} but got #{token.inspect}"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
result.size == 1 ? result.first : result
|
500
|
+
end
|
501
|
+
|
502
|
+
def maybe
|
503
|
+
tokens.transaction do
|
504
|
+
begin
|
505
|
+
yield
|
506
|
+
rescue MissingTokenError
|
507
|
+
raise TokenEnumerator::Rollback
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def options
|
513
|
+
value = yield
|
514
|
+
raise MissingTokenError, "Expected one of many to match" if value.nil?
|
515
|
+
value
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SyntaxTree
|
4
|
+
module CSS
|
5
|
+
# A visitor that walks through the tree.
|
6
|
+
class Visitor
|
7
|
+
def visit(node)
|
8
|
+
node&.accept(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_all(nodes)
|
12
|
+
nodes.map { |node| visit(node) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def visit_child_nodes(node)
|
16
|
+
visit_all(node.child_nodes)
|
17
|
+
end
|
18
|
+
|
19
|
+
#-------------------------------------------------------------------------
|
20
|
+
# CSS3 nodes
|
21
|
+
#-------------------------------------------------------------------------
|
22
|
+
|
23
|
+
# Visit an AtKeywordToken node.
|
24
|
+
alias visit_at_keyword visit_child_nodes
|
25
|
+
|
26
|
+
# Visit an AtRule node.
|
27
|
+
alias visit_at_rule visit_child_nodes
|
28
|
+
|
29
|
+
# Visit a BadStringToken node.
|
30
|
+
alias visit_bad_string_token visit_child_nodes
|
31
|
+
|
32
|
+
# Visit a BadURLToken node.
|
33
|
+
alias visit_bad_url_token visit_child_nodes
|
34
|
+
|
35
|
+
# Visit a CDCToken node.
|
36
|
+
alias visit_cdc_token visit_child_nodes
|
37
|
+
|
38
|
+
# Visit a CDOToken node.
|
39
|
+
alias visit_cdo_token visit_child_nodes
|
40
|
+
|
41
|
+
# Visit a CloseCurlyToken node.
|
42
|
+
alias visit_close_curly_token visit_child_nodes
|
43
|
+
|
44
|
+
# Visit a CloseParenToken node.
|
45
|
+
alias visit_close_paren_token visit_child_nodes
|
46
|
+
|
47
|
+
# Visit a CloseSquareToken node.
|
48
|
+
alias visit_close_square_token visit_child_nodes
|
49
|
+
|
50
|
+
# Visit a ColonToken node.
|
51
|
+
alias visit_colon_token visit_child_nodes
|
52
|
+
|
53
|
+
# Visit a CommentToken node.
|
54
|
+
alias visit_comment_token visit_child_nodes
|
55
|
+
|
56
|
+
# Visit a CommaToken node.
|
57
|
+
alias visit_comma_token visit_child_nodes
|
58
|
+
|
59
|
+
# Visit a CSSStyleSheet node.
|
60
|
+
alias visit_css_stylesheet visit_child_nodes
|
61
|
+
|
62
|
+
# Visit a Declaration node.
|
63
|
+
alias visit_declaration visit_child_nodes
|
64
|
+
|
65
|
+
# Visit a DelimToken node.
|
66
|
+
alias visit_delim_token visit_child_nodes
|
67
|
+
|
68
|
+
# Visit a DimensionToken node.
|
69
|
+
alias visit_dimension_token visit_child_nodes
|
70
|
+
|
71
|
+
# Visit an EOFToken node.
|
72
|
+
alias visit_eof_token visit_child_nodes
|
73
|
+
|
74
|
+
# Visit a Function node.
|
75
|
+
alias visit_function visit_child_nodes
|
76
|
+
|
77
|
+
# Visit a FunctionToken node.
|
78
|
+
alias visit_function_token visit_child_nodes
|
79
|
+
|
80
|
+
# Visit a HashToken node.
|
81
|
+
alias visit_hash_token visit_child_nodes
|
82
|
+
|
83
|
+
# Visit an IdentToken node.
|
84
|
+
alias visit_ident_token visit_child_nodes
|
85
|
+
|
86
|
+
# Visit a NumberToken node.
|
87
|
+
alias visit_number_token visit_child_nodes
|
88
|
+
|
89
|
+
# Visit an OpenCurlyToken node.
|
90
|
+
alias visit_open_curly_token visit_child_nodes
|
91
|
+
|
92
|
+
# Visit an OpenParenToken node.
|
93
|
+
alias visit_open_paren_token visit_child_nodes
|
94
|
+
|
95
|
+
# Visit an OpenSquareToken node.
|
96
|
+
alias visit_open_square_token visit_child_nodes
|
97
|
+
|
98
|
+
# Visit a PercentageToken node.
|
99
|
+
alias visit_percentage_token visit_child_nodes
|
100
|
+
|
101
|
+
# Visit a QualifiedRule node.
|
102
|
+
alias visit_qualified_rule visit_child_nodes
|
103
|
+
|
104
|
+
# Visit a SemicolonToken node.
|
105
|
+
alias visit_semicolon_token visit_child_nodes
|
106
|
+
|
107
|
+
# Visit a SimpleBlock node.
|
108
|
+
alias visit_simple_block visit_child_nodes
|
109
|
+
|
110
|
+
# Visit a StringToken node.
|
111
|
+
alias visit_string_token visit_child_nodes
|
112
|
+
|
113
|
+
# Visit a StyleRule node.
|
114
|
+
alias visit_style_rule visit_child_nodes
|
115
|
+
|
116
|
+
# Visit a StyleSheet node.
|
117
|
+
alias visit_stylesheet visit_child_nodes
|
118
|
+
|
119
|
+
# Visit a URange node.
|
120
|
+
alias visit_urange visit_child_nodes
|
121
|
+
|
122
|
+
# Visit a URLToken node.
|
123
|
+
alias visit_url_token visit_child_nodes
|
124
|
+
|
125
|
+
# Visit a WhitespaceToken node.
|
126
|
+
alias visit_whitespace_token visit_child_nodes
|
127
|
+
|
128
|
+
#-------------------------------------------------------------------------
|
129
|
+
# Selector nodes
|
130
|
+
#-------------------------------------------------------------------------
|
131
|
+
|
132
|
+
# Visit a Selectors::ClassSelector node.
|
133
|
+
alias visit_class_selector visit_child_nodes
|
134
|
+
|
135
|
+
# Visit a Selectors::IdSelector node.
|
136
|
+
alias visit_id_selector visit_child_nodes
|
137
|
+
|
138
|
+
# Visit a Selectors::PseudoClassFunction node.
|
139
|
+
alias visit_pseudo_class_function visit_child_nodes
|
140
|
+
|
141
|
+
# Visit a Selectors::PseudoClassSelector node.
|
142
|
+
alias visit_pseudo_class_selector visit_child_nodes
|
143
|
+
|
144
|
+
# Visit a Selectors::PseudoElementSelector node.
|
145
|
+
alias visit_pseudo_element_selector visit_child_nodes
|
146
|
+
|
147
|
+
# Visit a Selectors::TypeSelector node.
|
148
|
+
alias visit_type_selector visit_child_nodes
|
149
|
+
|
150
|
+
# Visit a Selectors::WqName node.
|
151
|
+
alias visit_wqname visit_child_nodes
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prettier_print"
|
4
|
+
require "syntax_tree"
|
5
|
+
|
6
|
+
require_relative "css/nodes"
|
7
|
+
require_relative "css/parser"
|
8
|
+
require_relative "css/selectors"
|
9
|
+
|
10
|
+
require_relative "css/basic_visitor"
|
11
|
+
require_relative "css/format"
|
12
|
+
require_relative "css/visitor"
|
13
|
+
require_relative "css/pretty_print"
|
14
|
+
|
15
|
+
module SyntaxTree
|
16
|
+
module CSS
|
17
|
+
def self.format(source, maxwidth = 80)
|
18
|
+
PrettierPrint.format(+"", maxwidth) { |q| parse(source).format(q) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse(source)
|
22
|
+
Parser.new(source).parse
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.read(filepath)
|
26
|
+
File.read(filepath)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
register_handler(".css", CSS)
|
31
|
+
end
|