habaki 0.5.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/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
|