habaki 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/ext/katana/extconf.rb +20 -0
- data/ext/katana/rb_katana.c +280 -0
- data/ext/katana/rb_katana.h +102 -0
- data/ext/katana/rb_katana_array.c +144 -0
- data/ext/katana/rb_katana_declaration.c +389 -0
- data/ext/katana/rb_katana_rule.c +461 -0
- data/ext/katana/rb_katana_selector.c +559 -0
- data/ext/katana/src/foundation.c +237 -0
- data/ext/katana/src/foundation.h +120 -0
- data/ext/katana/src/katana.h +590 -0
- data/ext/katana/src/katana.lex.c +4104 -0
- data/ext/katana/src/katana.lex.h +592 -0
- data/ext/katana/src/katana.tab.c +4422 -0
- data/ext/katana/src/katana.tab.h +262 -0
- data/ext/katana/src/parser.c +1563 -0
- data/ext/katana/src/parser.h +237 -0
- data/ext/katana/src/selector.c +659 -0
- data/ext/katana/src/selector.h +54 -0
- data/ext/katana/src/tokenizer.c +300 -0
- data/ext/katana/src/tokenizer.h +41 -0
- data/lib/habaki/charset_rule.rb +25 -0
- data/lib/habaki/declaration.rb +53 -0
- data/lib/habaki/declarations.rb +346 -0
- data/lib/habaki/error.rb +43 -0
- data/lib/habaki/font_face_rule.rb +24 -0
- data/lib/habaki/formal_syntax.rb +464 -0
- data/lib/habaki/formatter.rb +99 -0
- data/lib/habaki/import_rule.rb +34 -0
- data/lib/habaki/media_rule.rb +173 -0
- data/lib/habaki/namespace_rule.rb +31 -0
- data/lib/habaki/node.rb +52 -0
- data/lib/habaki/page_rule.rb +24 -0
- data/lib/habaki/qualified_name.rb +29 -0
- data/lib/habaki/rule.rb +48 -0
- data/lib/habaki/rules.rb +225 -0
- data/lib/habaki/selector.rb +98 -0
- data/lib/habaki/selectors.rb +49 -0
- data/lib/habaki/style_rule.rb +35 -0
- data/lib/habaki/stylesheet.rb +158 -0
- data/lib/habaki/sub_selector.rb +234 -0
- data/lib/habaki/sub_selectors.rb +42 -0
- data/lib/habaki/supports_rule.rb +65 -0
- data/lib/habaki/value.rb +321 -0
- data/lib/habaki/values.rb +86 -0
- data/lib/habaki/visitor/element.rb +50 -0
- data/lib/habaki/visitor/media.rb +22 -0
- data/lib/habaki/visitor/nokogiri_element.rb +56 -0
- data/lib/habaki.rb +39 -0
- metadata +190 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Habaki
|
2
|
+
# Array of {Selectors}
|
3
|
+
class Selectors < NodeArray
|
4
|
+
# parse selectors from string
|
5
|
+
# @param [String] data
|
6
|
+
# @return [Selectors]
|
7
|
+
def self.parse(data)
|
8
|
+
sels = self.new
|
9
|
+
sels.parse!(data)
|
10
|
+
sels
|
11
|
+
end
|
12
|
+
|
13
|
+
# parse selectors from string and append to current selectors
|
14
|
+
# @param [String] data
|
15
|
+
# @return [void]
|
16
|
+
def parse!(data)
|
17
|
+
return unless data
|
18
|
+
|
19
|
+
out = Katana.parse_selectors(data)
|
20
|
+
if out.selectors
|
21
|
+
read_from_katana(out.selectors)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# does one of theses selectors match {Visitor::Element} ?
|
26
|
+
# @param [Visitor::Element] element
|
27
|
+
# @return [Boolean]
|
28
|
+
def element_match?(element)
|
29
|
+
each do |selector|
|
30
|
+
return true if selector.element_match?(element)
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Formatter::Base] format
|
36
|
+
# @return [String]
|
37
|
+
def string(format = Formatter::Base.new)
|
38
|
+
string_join(format, ",")
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
# @param [Katana::Array<Katana::Selector>] sels
|
43
|
+
def read_from_katana(sels)
|
44
|
+
sels.each do |sel|
|
45
|
+
push Selector.read_from_katana(sel)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Habaki
|
2
|
+
# CSS style rule selectors + declarations
|
3
|
+
class StyleRule < Rule
|
4
|
+
# @return [Selectors]
|
5
|
+
attr_accessor :selectors
|
6
|
+
# @return [Declarations]
|
7
|
+
attr_accessor :declarations
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@selectors = Selectors.new
|
11
|
+
@declarations = Declarations.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# does rule match {Visitor::Element} ?
|
15
|
+
# @param [Visitor::Element] element
|
16
|
+
# @return [Boolean]
|
17
|
+
def element_match?(element)
|
18
|
+
selectors.element_match?(element)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Formatter::Base] format
|
22
|
+
# @return [String]
|
23
|
+
def string(format = Formatter::Base.new)
|
24
|
+
"#{@selectors.string(format)} {#{format.declarations_prefix}#{@declarations.string(format+1)}#{format.declarations_suffix}#{format.rules_prefix}}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
# @param [Katana::StyleRule] rule
|
29
|
+
# @return [void]
|
30
|
+
def read_from_katana(rule)
|
31
|
+
@selectors = Selectors.read_from_katana(rule.selectors)
|
32
|
+
@declarations = Declarations.read_from_katana(rule.declarations)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Habaki
|
2
|
+
# main structure
|
3
|
+
class Stylesheet < Node
|
4
|
+
include SelectorMatcher
|
5
|
+
|
6
|
+
# @return [Rules]
|
7
|
+
attr_accessor :rules
|
8
|
+
# @return [Array<Error>]
|
9
|
+
attr_accessor :errors
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@rules = Rules.new
|
13
|
+
@errors = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# traverse rules
|
17
|
+
# @param [Visitor::Media, String, NilClass] media
|
18
|
+
# @yieldparam [Rules] rules
|
19
|
+
# @return [void]
|
20
|
+
def each_rules(media = nil, &block)
|
21
|
+
block.call @rules
|
22
|
+
@rules.each do |rule|
|
23
|
+
next unless rule.rules
|
24
|
+
next if rule.is_a?(MediaRule) && !rule.media_match?(media)
|
25
|
+
block.call rule.rules
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# traverse all rules (including all media & supports)
|
30
|
+
# @param [Visitor::Media, String, NilClass] media
|
31
|
+
# @yieldparam [Rule] rule
|
32
|
+
# @return [void]
|
33
|
+
def each_rule(media = nil, &block)
|
34
|
+
each_rules(media) do |rules|
|
35
|
+
rules.each do |rule|
|
36
|
+
block.call rule
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# does selector exists ?
|
42
|
+
# @param [String] selector_str
|
43
|
+
# @param [Visitor::Media, String, NilClass] media
|
44
|
+
# @return [Boolean]
|
45
|
+
def has_selector?(selector_str, media = nil)
|
46
|
+
each_rule do |rule|
|
47
|
+
rule.each_selector do |selector|
|
48
|
+
return true if selector_str == selector.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# find rule from selector str
|
55
|
+
# @param [String] selector_str
|
56
|
+
# @param [Visitor::Media, String, NilClass] media
|
57
|
+
# @return [Array<Rule>]
|
58
|
+
def find_by_selector(selector_str, media = nil)
|
59
|
+
results = []
|
60
|
+
each_rule(media) do |rule|
|
61
|
+
rule.each_selector do |selector|
|
62
|
+
results << rule if selector_str == selector.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
results
|
66
|
+
end
|
67
|
+
|
68
|
+
# find declarations from selector str
|
69
|
+
# @param [String] selector_str
|
70
|
+
# @param [Visitor::Media, String, NilClass] media
|
71
|
+
# @return [Array<Declarations>]
|
72
|
+
def find_declarations_by_selector(selector_str, media = nil)
|
73
|
+
results = []
|
74
|
+
each_rule(media) do |rule|
|
75
|
+
next unless rule.selectors
|
76
|
+
results << rule.declarations if rule.selectors.map(&:to_s).include?(selector_str)
|
77
|
+
end
|
78
|
+
results
|
79
|
+
end
|
80
|
+
|
81
|
+
# remove rules with no declaration
|
82
|
+
def compact!
|
83
|
+
@rules.reject! { |rule| rule.declarations&.empty? || false }
|
84
|
+
@rules.each do |rule|
|
85
|
+
if rule.rules
|
86
|
+
rule.rules.reject! { |emb_rule| emb_rule.declarations&.empty? || false }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
@rules.reject! { |rule| rule.rules&.empty? || false }
|
90
|
+
end
|
91
|
+
|
92
|
+
# instanciate and parse from data
|
93
|
+
# @param [String] data
|
94
|
+
# @return [Stylesheet]
|
95
|
+
def self.parse(data)
|
96
|
+
stylesheet = self.new
|
97
|
+
stylesheet.parse!(data)
|
98
|
+
stylesheet
|
99
|
+
end
|
100
|
+
|
101
|
+
# instanciate and parse from file
|
102
|
+
# @param [String] filename
|
103
|
+
# @return [Stylesheet]
|
104
|
+
def self.parse_file(filename)
|
105
|
+
parse(File.read(filename))
|
106
|
+
end
|
107
|
+
|
108
|
+
# parse from data and append to current stylesheet
|
109
|
+
# @param [String] data
|
110
|
+
# @return [void]
|
111
|
+
def parse!(data)
|
112
|
+
return unless data
|
113
|
+
|
114
|
+
read_from_katana(Katana.parse(data))
|
115
|
+
end
|
116
|
+
|
117
|
+
# parse from file and append to current stylesheet
|
118
|
+
# @param [String] filename
|
119
|
+
# @return [void]
|
120
|
+
def parse_file!(filename)
|
121
|
+
parse(File.read(filename))
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param [Formatter::Base] format
|
125
|
+
# @return [String]
|
126
|
+
def string(format = Formatter::Base.new)
|
127
|
+
@rules.string(format)
|
128
|
+
end
|
129
|
+
|
130
|
+
# @api private
|
131
|
+
# @param [Katana::Output] out
|
132
|
+
# @return [void]
|
133
|
+
def read_from_katana(out)
|
134
|
+
@rules.read_from_katana(out.stylesheet.imports)
|
135
|
+
@rules.read_from_katana(out.stylesheet.rules)
|
136
|
+
|
137
|
+
# keep reference to this stylesheet in each rule
|
138
|
+
each_rule do |rule|
|
139
|
+
rule.stylesheet = self
|
140
|
+
end
|
141
|
+
|
142
|
+
out.errors.each do |err|
|
143
|
+
@errors << Error.read_from_katana(err)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# group of stylesheets to match through multiple {Stylesheet}
|
149
|
+
class Stylesheets < Array
|
150
|
+
include SelectorMatcher
|
151
|
+
|
152
|
+
def each_rule(media = nil, &block)
|
153
|
+
each do |stylesheet|
|
154
|
+
stylesheet.each_rule(media, &block)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
module Habaki
|
2
|
+
# CSS specificity score
|
3
|
+
class Specificity
|
4
|
+
attr_accessor :score
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@score = 0
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# part of selector (eg in p.t, p is a tag subselector and .t a class subselector)
|
12
|
+
class SubSelector < Node
|
13
|
+
# @return [Symbol]
|
14
|
+
attr_accessor :match
|
15
|
+
# @return [String]
|
16
|
+
attr_accessor :tag
|
17
|
+
# @return [Symbol]
|
18
|
+
attr_accessor :pseudo
|
19
|
+
|
20
|
+
# @return [String]
|
21
|
+
attr_accessor :attribute
|
22
|
+
# @return [String]
|
23
|
+
attr_accessor :value
|
24
|
+
# @return [String]
|
25
|
+
attr_accessor :argument
|
26
|
+
# @return [Selectors]
|
27
|
+
attr_accessor :selectors
|
28
|
+
|
29
|
+
# @return [SourcePosition]
|
30
|
+
attr_accessor :position
|
31
|
+
|
32
|
+
# is this selector on attribute ?
|
33
|
+
# @return [Boolean]
|
34
|
+
def attribute_selector?
|
35
|
+
@match.to_s.start_with?("attribute_")
|
36
|
+
end
|
37
|
+
|
38
|
+
# does sub selector match {Visitor::Element} ?
|
39
|
+
# @param [Visitor::Element] element
|
40
|
+
# @param [Specificity, nil] specificity
|
41
|
+
# @return [Boolean]
|
42
|
+
def element_match?(element, specificity = nil)
|
43
|
+
case @match
|
44
|
+
when :tag
|
45
|
+
tag_match?(element.tag_name, specificity)
|
46
|
+
when :class
|
47
|
+
class_match?(element.class_name, specificity)
|
48
|
+
when :id
|
49
|
+
id_match?(element.id_name, specificity)
|
50
|
+
when :pseudo_class
|
51
|
+
pseudo_class_match?(element, specificity)
|
52
|
+
when :pseudo_element
|
53
|
+
# TODO
|
54
|
+
false
|
55
|
+
else
|
56
|
+
if attribute_selector?
|
57
|
+
element_attr = element.attr(@attribute.local)
|
58
|
+
(element_attr ? attribute_value_match?(element_attr, specificity) : false)
|
59
|
+
else
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# does selector match tag ?
|
66
|
+
# @param [String] name
|
67
|
+
# @param [Specificity, nil] specificity
|
68
|
+
# @return [Boolean]
|
69
|
+
def tag_match?(name, specificity = nil)
|
70
|
+
match_with_specificity(@tag.local == name, specificity, 1) || @tag.local == "*"
|
71
|
+
end
|
72
|
+
|
73
|
+
# does selector match class ?
|
74
|
+
# @param [String] name
|
75
|
+
# @param [Specificity, nil] specificity
|
76
|
+
# @return [Boolean]
|
77
|
+
def class_match?(name, specificity = nil)
|
78
|
+
match_with_specificity(@value == name, specificity, 10)
|
79
|
+
end
|
80
|
+
|
81
|
+
# does selector match id ?
|
82
|
+
# @param [String] name
|
83
|
+
# @param [Specificity, nil] specificity
|
84
|
+
# @return [Boolean]
|
85
|
+
def id_match?(name, specificity = nil)
|
86
|
+
match_with_specificity(@value == name, specificity, 100)
|
87
|
+
end
|
88
|
+
|
89
|
+
# does selector match attribute value ?
|
90
|
+
# @param [String] val
|
91
|
+
# @param [Specificity, nil] specificity
|
92
|
+
# @return [Boolean]
|
93
|
+
def attribute_value_match?(val, specificity = nil)
|
94
|
+
match_with_specificity(
|
95
|
+
case @match
|
96
|
+
when :attribute_exact
|
97
|
+
val == @value
|
98
|
+
when :attribute_begin
|
99
|
+
val.start_with?(@value)
|
100
|
+
when :attribute_end
|
101
|
+
val.end_with?(@value)
|
102
|
+
when :attribute_contain
|
103
|
+
val.include?(@value)
|
104
|
+
when :attribute_hyphen
|
105
|
+
val.start_with?("#{@value}-")
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end, specificity, 10)
|
109
|
+
end
|
110
|
+
|
111
|
+
# does selector pseudo class match {Visitor::Element} ?
|
112
|
+
# @param [Visitor::Element] element
|
113
|
+
# @param [Specificity, nil] specificity
|
114
|
+
# @return [Boolean]
|
115
|
+
def pseudo_class_match?(element, specificity = nil)
|
116
|
+
match_with_specificity(
|
117
|
+
case @pseudo
|
118
|
+
when :root
|
119
|
+
element.tag_name == "html"
|
120
|
+
when :empty
|
121
|
+
element.children.empty?
|
122
|
+
when :first_child
|
123
|
+
parent_element = element.parent
|
124
|
+
parent_element&.children.first == element
|
125
|
+
when :last_child
|
126
|
+
parent_element = element.parent
|
127
|
+
parent_element&.children.last == element
|
128
|
+
when :only_child
|
129
|
+
parent_element = element.parent
|
130
|
+
parent_element&.children.length == 1 && parent_element&.children.first == element
|
131
|
+
when :nth_child
|
132
|
+
parent_element = element.parent
|
133
|
+
arg = @argument.split("+")
|
134
|
+
case arg[0]
|
135
|
+
when "odd"
|
136
|
+
((parent_element&.children.index(element) + 1) % 2 == 1)
|
137
|
+
when "even"
|
138
|
+
((parent_element&.children.index(element) + 1) % 2 == 0)
|
139
|
+
when /^\d+$/
|
140
|
+
parent_element&.children[@argument.to_i - 1] == element
|
141
|
+
when "n"
|
142
|
+
((parent_element&.children.index(element) + 1) % 1) == (arg[1]&.to_i || 0)
|
143
|
+
when /n$/
|
144
|
+
((parent_element&.children.index(element) + 1) % arg[0].sub("n", "").to_i) == (arg[1]&.to_i || 0)
|
145
|
+
else
|
146
|
+
# TODO "of type"
|
147
|
+
false
|
148
|
+
end
|
149
|
+
when :not
|
150
|
+
!@selectors.element_match?(element)
|
151
|
+
when :not_parsed, :unknown
|
152
|
+
true
|
153
|
+
else
|
154
|
+
# STDERR.puts "unsupported pseudo #{sub_sel.pseudo}"
|
155
|
+
false
|
156
|
+
end, specificity, 10)
|
157
|
+
end
|
158
|
+
|
159
|
+
# @param [Formatter::Base] format
|
160
|
+
# @return [String]
|
161
|
+
def string(format = Formatter::Base.new)
|
162
|
+
str = ""
|
163
|
+
if attribute_selector?
|
164
|
+
str += "[#{@attribute.string(format)}"
|
165
|
+
case @match
|
166
|
+
when :attribute_exact
|
167
|
+
str += "="
|
168
|
+
when :attribute_set
|
169
|
+
str += "]"
|
170
|
+
when :attribute_list
|
171
|
+
str += "~="
|
172
|
+
when :attribute_hyphen
|
173
|
+
str += "|="
|
174
|
+
when :attribute_begin
|
175
|
+
str += "^="
|
176
|
+
when :attribute_end
|
177
|
+
str += "$="
|
178
|
+
when :attribute_contain
|
179
|
+
str += "*="
|
180
|
+
end
|
181
|
+
str += "\"#{@value}\"]" if @match != :attribute_set
|
182
|
+
else
|
183
|
+
case @match
|
184
|
+
when :tag
|
185
|
+
str += @tag.string(format)
|
186
|
+
when :class
|
187
|
+
str += ".#{@value}"
|
188
|
+
when :id
|
189
|
+
str += "##{@value}"
|
190
|
+
when :pseudo_class, :pseudo_page_class
|
191
|
+
str += ":#{@value}"
|
192
|
+
case @pseudo
|
193
|
+
when :any, :not, :host, :host_context
|
194
|
+
str += @selectors.string(format)
|
195
|
+
str += ")"
|
196
|
+
when :lang, :nth_child, :nth_last_child, :nth_of_type, :nth_last_of_type
|
197
|
+
str += "#{@argument})"
|
198
|
+
end
|
199
|
+
when :pseudo_element
|
200
|
+
str += "::#{@value}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
str
|
204
|
+
end
|
205
|
+
|
206
|
+
# @api private
|
207
|
+
# @param [Katana::Selector] sel
|
208
|
+
def read_from_katana(sel)
|
209
|
+
@match = sel.match
|
210
|
+
@tag = QualifiedName.read_from_katana(sel.tag) if sel.tag
|
211
|
+
@pseudo = sel.pseudo
|
212
|
+
|
213
|
+
@attribute = QualifiedName.read_from_katana(sel.data.attribute) if sel.data.attribute
|
214
|
+
@value = sel.data.value
|
215
|
+
@argument = sel.data.argument
|
216
|
+
|
217
|
+
@selectors = Selectors.read_from_katana(sel.data.selectors) if sel.data.selectors
|
218
|
+
|
219
|
+
@position = SourcePosition.new(sel.position.line, sel.position.column)
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# @param [Boolean] comp
|
225
|
+
# @param [Specificity, nil] specificity
|
226
|
+
# @param [Integer] score
|
227
|
+
# @return [Boolean]
|
228
|
+
def match_with_specificity(comp, specificity, score)
|
229
|
+
specificity.score += score if specificity && comp
|
230
|
+
comp
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Habaki
|
2
|
+
# Array of {SubSelector}
|
3
|
+
class SubSelectors < NodeArray
|
4
|
+
# @return [Symbol]
|
5
|
+
attr_accessor :relation
|
6
|
+
|
7
|
+
# does every sub selectors match {Visitor::Element} ?
|
8
|
+
# @param [Visitor::Element] element
|
9
|
+
# @param [Specificity, nil] specificity
|
10
|
+
# @return [Boolean]
|
11
|
+
def element_match?(element, specificity = nil)
|
12
|
+
each do |sub_sel|
|
13
|
+
return false unless sub_sel.element_match?(element, specificity)
|
14
|
+
end
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Formatter::Base] format
|
19
|
+
# @return [String]
|
20
|
+
def string(format = Formatter::Base.new)
|
21
|
+
"#{string_relation}#{string_join(format, "")}"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def string_relation
|
27
|
+
case @relation
|
28
|
+
when :descendant
|
29
|
+
" "
|
30
|
+
when :child
|
31
|
+
" > "
|
32
|
+
when :direct_adjacent
|
33
|
+
" + "
|
34
|
+
when :indirect_adjacent
|
35
|
+
" ~ "
|
36
|
+
else
|
37
|
+
""
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Habaki
|
2
|
+
class SupportsExpression < Node
|
3
|
+
# @return [Symbol]
|
4
|
+
attr_accessor :operation
|
5
|
+
# @return [Array<SupportsExpression>]
|
6
|
+
attr_accessor :expressions
|
7
|
+
# @return [Declaration]
|
8
|
+
attr_accessor :declaration
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@expressions = []
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Formatter::Base] format
|
15
|
+
# @return [String]
|
16
|
+
def string(format = Formatter::Base.new)
|
17
|
+
str = ""
|
18
|
+
case @expressions.length
|
19
|
+
when 0
|
20
|
+
if @declaration
|
21
|
+
str += "(#{@declaration.string(format)})"
|
22
|
+
end
|
23
|
+
when 1
|
24
|
+
str += "(#{@operation == :not ? "not " : ""}" + @expressions[0].string(format) + ")"
|
25
|
+
when 2
|
26
|
+
str += "#{@expressions[0].string(format)} #{@operation} #{@expressions[1].string(format)}"
|
27
|
+
end
|
28
|
+
str
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
def read_from_katana(exp)
|
33
|
+
@operation = exp.operation
|
34
|
+
exp.expressions.each do |sub_exp|
|
35
|
+
@expressions << SupportsExpression.read_from_katana(sub_exp)
|
36
|
+
end
|
37
|
+
@declaration = Declaration.read_from_katana(exp.declaration) if exp.declaration
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# supports rule @supports
|
42
|
+
class SupportsRule < Rule
|
43
|
+
# @return [SupportsExpression]
|
44
|
+
attr_accessor :expression
|
45
|
+
# @return [Rules]
|
46
|
+
attr_accessor :rules
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@rules = Rules.new
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def string(format = Formatter::Base.new)
|
54
|
+
"@supports #{@expression.string(format)} {\n#{@rules.string(format + 1)}\n}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
# @param [Katana::SupportsRule] rule
|
59
|
+
# @return [void]
|
60
|
+
def read_from_katana(rule)
|
61
|
+
@expression = SupportsExpression.read_from_katana(rule.expression)
|
62
|
+
@rules = Rules.read_from_katana(rule.rules)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|