habaki 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/ext/katana/extconf.rb +20 -0
  4. data/ext/katana/rb_katana.c +280 -0
  5. data/ext/katana/rb_katana.h +102 -0
  6. data/ext/katana/rb_katana_array.c +144 -0
  7. data/ext/katana/rb_katana_declaration.c +389 -0
  8. data/ext/katana/rb_katana_rule.c +461 -0
  9. data/ext/katana/rb_katana_selector.c +559 -0
  10. data/ext/katana/src/foundation.c +237 -0
  11. data/ext/katana/src/foundation.h +120 -0
  12. data/ext/katana/src/katana.h +590 -0
  13. data/ext/katana/src/katana.lex.c +4104 -0
  14. data/ext/katana/src/katana.lex.h +592 -0
  15. data/ext/katana/src/katana.tab.c +4422 -0
  16. data/ext/katana/src/katana.tab.h +262 -0
  17. data/ext/katana/src/parser.c +1563 -0
  18. data/ext/katana/src/parser.h +237 -0
  19. data/ext/katana/src/selector.c +659 -0
  20. data/ext/katana/src/selector.h +54 -0
  21. data/ext/katana/src/tokenizer.c +300 -0
  22. data/ext/katana/src/tokenizer.h +41 -0
  23. data/lib/habaki/charset_rule.rb +25 -0
  24. data/lib/habaki/declaration.rb +53 -0
  25. data/lib/habaki/declarations.rb +346 -0
  26. data/lib/habaki/error.rb +43 -0
  27. data/lib/habaki/font_face_rule.rb +24 -0
  28. data/lib/habaki/formal_syntax.rb +464 -0
  29. data/lib/habaki/formatter.rb +99 -0
  30. data/lib/habaki/import_rule.rb +34 -0
  31. data/lib/habaki/media_rule.rb +173 -0
  32. data/lib/habaki/namespace_rule.rb +31 -0
  33. data/lib/habaki/node.rb +52 -0
  34. data/lib/habaki/page_rule.rb +24 -0
  35. data/lib/habaki/qualified_name.rb +29 -0
  36. data/lib/habaki/rule.rb +48 -0
  37. data/lib/habaki/rules.rb +225 -0
  38. data/lib/habaki/selector.rb +98 -0
  39. data/lib/habaki/selectors.rb +49 -0
  40. data/lib/habaki/style_rule.rb +35 -0
  41. data/lib/habaki/stylesheet.rb +158 -0
  42. data/lib/habaki/sub_selector.rb +234 -0
  43. data/lib/habaki/sub_selectors.rb +42 -0
  44. data/lib/habaki/supports_rule.rb +65 -0
  45. data/lib/habaki/value.rb +321 -0
  46. data/lib/habaki/values.rb +86 -0
  47. data/lib/habaki/visitor/element.rb +50 -0
  48. data/lib/habaki/visitor/media.rb +22 -0
  49. data/lib/habaki/visitor/nokogiri_element.rb +56 -0
  50. data/lib/habaki.rb +39 -0
  51. metadata +190 -0
@@ -0,0 +1,464 @@
1
+ require 'strscan'
2
+ require 'yaml'
3
+
4
+ module Habaki
5
+ # property pattern matching
6
+ module FormalSyntax
7
+ class Node
8
+ attr_accessor :parent
9
+ attr_accessor :children
10
+ attr_accessor :type
11
+ attr_accessor :value
12
+ attr_accessor :occurence
13
+
14
+ attr_accessor :orig
15
+
16
+ def initialize(type = nil, value = nil, children = [], occurence = 1..1)
17
+ @type = type
18
+ @value = value
19
+ @children = children
20
+ @occurence = occurence
21
+ end
22
+
23
+ def push_children(child)
24
+ child.parent = self
25
+ @children << child
26
+ end
27
+
28
+ def traverse(&block)
29
+ block.call self
30
+ @children.each do |child|
31
+ child.traverse &block
32
+ end
33
+ end
34
+
35
+ def occurence_from_children!
36
+ case @type
37
+ when :and
38
+ occ_min = 0
39
+ occ_max = 0
40
+ @children.each do |child|
41
+ occ_min += child.occurence.begin
42
+ occ_max += child.occurence.end
43
+ end
44
+ @occurence = Range.new(occ_min, occ_max)
45
+ when :or_and
46
+ @occurence = Range.new(1, @children.length)
47
+ end
48
+ end
49
+
50
+ def n
51
+ Float::INFINITY
52
+ end
53
+
54
+ def to_s
55
+ str = ""
56
+ case @type
57
+ when :and
58
+ str += "[#{@children.map(&:to_s).join(" ")}]"
59
+ when :or
60
+ str += "[#{@children.map(&:to_s).join(" | ")}]"
61
+ when :or_and
62
+ str += "[#{@children.map(&:to_s).join(" || ")}]"
63
+ when :function
64
+ str += "#{@value}(#{@children.map(&:to_s).join(" ")})"
65
+ when :number
66
+ str += @value.to_s
67
+ when :token
68
+ str += @value
69
+ when :ref
70
+ str += "'#{@value}'"
71
+ when :type
72
+ str += "<#{@value}>"
73
+ else
74
+ str += @value
75
+ end
76
+
77
+ case @occurence.begin
78
+ when 0
79
+ case @occurence.end
80
+ when 1
81
+ str += "?"
82
+ when n
83
+ str += "*"
84
+ end
85
+ when 1
86
+ case @occurence.end
87
+ when 1
88
+ when n
89
+ str += "+"
90
+ else
91
+ str += "{#{@occurence.begin},#{@occurence.end}}"
92
+ end
93
+ end
94
+
95
+ str
96
+ end
97
+ end
98
+
99
+ class FormalSyntaxError < StandardError
100
+ end
101
+
102
+ # format syntax tree parser
103
+ class Tree
104
+ attr_accessor :properties
105
+
106
+ def initialize
107
+ end
108
+
109
+ def parse_all(data)
110
+ @properties = {}
111
+ data.each do |k, v|
112
+ begin
113
+ @properties[k] = Tree.parse(v)
114
+ rescue FormalSyntaxError => e
115
+ #STDERR.puts("#{k}: #{e}")
116
+ end
117
+ end
118
+ self
119
+ end
120
+
121
+ def self.tree
122
+ @@tree ||= self.new.parse_all(Tree.tree_data)
123
+ end
124
+
125
+ def self.tree_data
126
+ @@tree_data ||= YAML.load_file(File.join(File.dirname(__FILE__), '../../data/formal_syntax.yml')).freeze
127
+ end
128
+
129
+ def property(prop)
130
+ @properties[prop]
131
+ end
132
+
133
+ def self.n
134
+ Float::INFINITY
135
+ end
136
+
137
+ # formal syntax parser
138
+ def self.parse(str)
139
+ current_node = Node.new(:and)
140
+ current_node.orig = str
141
+ scanner = StringScanner.new(str.gsub("∞", "N"))
142
+
143
+ until scanner.eos?
144
+ case
145
+ when scanner.scan(/\[/)
146
+ prev_node = current_node
147
+ current_node = Node.new(:and)
148
+ current_node.parent = prev_node
149
+ when scanner.scan(/<([a-zA-Z0-9-]+)\(/)
150
+ prev_node = current_node
151
+ current_node = Node.new(:function_ref, scanner[1])
152
+ current_node.parent = prev_node
153
+ when scanner.scan(/([a-zA-Z0-9-]+)\(/)
154
+ prev_node = current_node
155
+ current_node = Node.new(:function, scanner[1])
156
+ current_node.parent = prev_node
157
+ when scanner.scan(/\?/)
158
+ prev_node = current_node.children.last
159
+ prev_node.occurence = 0..1
160
+ when scanner.scan(/\*/)
161
+ prev_node = current_node.children.last
162
+ prev_node.occurence = 0..n
163
+ when scanner.scan(/\+/)
164
+ prev_node = current_node.children.last
165
+ prev_node.occurence = 1..n
166
+ when scanner.scan(/\!/)
167
+ # at least one required
168
+ prev_node = current_node.children.last
169
+ prev_node.occurence = 1..n
170
+ when scanner.scan(/\#/)
171
+ # one or more comma separated
172
+ prev_node = current_node.children.last
173
+ # embed :type in :or
174
+ if prev_node.type == :type
175
+ emb_node = Node.new(:or)
176
+ emb_node.children << prev_node
177
+ emb_node.push_children(Node.new(:token, ","))
178
+ emb_node.occurence = 1..n
179
+ current_node.children[-1] = emb_node
180
+ else
181
+ prev_node.push_children(Node.new(:token, ","))
182
+ prev_node.occurence = 1..n
183
+ end
184
+ when scanner.scan(/\{(\d)\}/)
185
+ prev_node = current_node.children.last
186
+ prev_node.occurence = (scanner[1].to_i)..(scanner[1].to_i)
187
+ when scanner.scan(/\{(\d),(\d)\}/)
188
+ prev_node = current_node.children.last
189
+ prev_node.occurence = (scanner[1].to_i)..(scanner[2].to_i)
190
+ when scanner.scan(/\]/)
191
+ current_node.occurence_from_children!
192
+ if current_node.parent
193
+ prev_node = current_node
194
+ current_node = current_node.parent
195
+ current_node.push_children prev_node
196
+ else
197
+ raise FormalSyntaxError, "Formal syntax problem: #{current_node}"
198
+ end
199
+ when scanner.scan(/\)>?/)
200
+ if current_node.parent
201
+ prev_node = current_node
202
+ current_node = current_node.parent
203
+ current_node.push_children prev_node
204
+ else
205
+ raise FormalSyntaxError, "Formal syntax problem: #{current_node}"
206
+ end
207
+ when scanner.scan(/\s?(\/|,|:|;|%)\s?/)
208
+ current_node.push_children Node.new(:token, scanner[1])
209
+ when scanner.scan(/<([a-zA-Z0-9-]+)(\s\[\d,N?\d*\])?>/)
210
+ current_node.push_children Node.new(:type, scanner[1])
211
+ when scanner.scan(/([a-zA-Z-]+)/)
212
+ current_node.push_children Node.new(:ident, scanner[1]) #if scanner[1] != "inherit"
213
+ when scanner.scan(/([0-9]+)/)
214
+ current_node.push_children Node.new(:number, scanner[1].to_i)
215
+ when scanner.scan(/<?'([a-zA-Z-]+)'>?/)
216
+ current_node.push_children Node.new(:ref, scanner[1])
217
+ when scanner.scan(/'(..?)'/)
218
+ current_node.push_children Node.new(:token, scanner[1])
219
+ when scanner.scan(/\|\|/)
220
+ current_node.type = :or_and
221
+ when scanner.scan(/\|/)
222
+ current_node.type = :or
223
+ when scanner.scan(/\&\&/)
224
+ current_node.type = :and
225
+ else
226
+ result = scanner.scan(/.+/)
227
+ raise FormalSyntaxError, "Cannot parse formal syntax: #{result}"
228
+ end
229
+ scanner.scan(/\s+/)
230
+ end
231
+ current_node.occurence_from_children!
232
+ current_node
233
+ end
234
+ end
235
+
236
+ # formal syntax matcher result
237
+ class Match
238
+ # @return [String]
239
+ attr_accessor :reference
240
+ # @return [FormalSyntax::Node]
241
+ attr_accessor :node
242
+ # @return [Value]
243
+ attr_accessor :value
244
+
245
+ def initialize(ref, node)
246
+ @reference = ref
247
+ @node = node
248
+ end
249
+
250
+ def to_s
251
+ "#{@reference}: #{@value} => #{@node}"
252
+ end
253
+ end
254
+
255
+ # formal syntax matcher
256
+ class Matcher
257
+ # @return [Array<Match>]
258
+ attr_accessor :matches
259
+ attr_accessor :debug
260
+
261
+ # @param [Declaration] declaration
262
+ def initialize(declaration, tree = Tree.tree)
263
+ @declaration = declaration
264
+ @tree = tree
265
+ @reference = nil
266
+ @matches = []
267
+ @debug = false
268
+ @match = nil
269
+ end
270
+
271
+ # @return [Boolean]
272
+ def match
273
+ @idx = 0
274
+ node = @tree.properties[@declaration.property]
275
+ return false unless node
276
+
277
+ puts "MATCH #{node} (#{node.type}) #{node.occurence} WITH #{@declaration.to_s}" if @debug
278
+ @reference = @declaration.property
279
+ # always add inherit keyword
280
+ return true if @declaration.value == Habaki::Ident.new("inherit")
281
+
282
+ res = rec_match(node)
283
+ @matches.compact!
284
+ @matches.each_with_index do |match, idx|
285
+ match.value = @declaration.values[idx]
286
+ end
287
+
288
+ puts "MATCH? #{res} #{node.occurence}, #{@idx} / #{count_values}" if @debug
289
+ res && calc_occurence(node).include?(@idx) && @idx >= count_values
290
+ end
291
+
292
+ # @return [Boolean]
293
+ def match?
294
+ @match ||= match
295
+ end
296
+
297
+ private
298
+
299
+ def calc_occurence(node)
300
+ resolved_node = resolve_node(node)
301
+ if resolved_node.type == :or
302
+ occ_min = resolved_node.occurence.begin
303
+ occ_max = resolved_node.occurence.end
304
+ resolved_node.children.each do |child|
305
+ r_occ = calc_occurence(child)
306
+ #occ_min += r_occ.begin
307
+ occ_max += r_occ.end
308
+ end
309
+ Range.new(occ_min, occ_max)
310
+ else
311
+ resolved_node.occurence
312
+ end
313
+ end
314
+
315
+ def resolve_function(value)
316
+ if value.is_a?(Habaki::Function)
317
+ case value.data
318
+ when "calc", "min", "max", "clamp"
319
+ Length.new("0", :px)
320
+ when "attr"
321
+ String.new("")
322
+ else
323
+ value
324
+ end
325
+ else
326
+ value
327
+ end
328
+ end
329
+
330
+ def next_value
331
+ @idx += 1
332
+ end
333
+
334
+ def match_value_class(value, klass)
335
+ return false unless value.is_a?(klass)
336
+
337
+ next_value
338
+ true
339
+ end
340
+
341
+ def match_value_class_and_data(value, klass, node)
342
+ return false unless value.is_a?(klass)
343
+
344
+ if value.data == node.value
345
+ next_value
346
+ true
347
+ else
348
+ false
349
+ end
350
+ end
351
+
352
+ def resolve_node(node)
353
+ return node if %w[percentage length angle number integer string custom-ident uri hexcolor hex-color].include?(node.value)
354
+ resolved_node = node
355
+
356
+ if node.type == :ref
357
+ @reference = node.value
358
+ ref_node = @tree.properties[node.value]
359
+ resolved_node = ref_node if ref_node
360
+ end
361
+
362
+ if node.type == :type
363
+ alias_node = @tree.properties["<#{node.value}>"]
364
+ resolved_node = alias_node if alias_node
365
+ end
366
+
367
+ if node.type == :function_ref
368
+ alias_node = @tree.properties["<#{node.value}()>"]
369
+ resolved_node = alias_node if alias_node
370
+ end
371
+
372
+ resolved_node
373
+ end
374
+
375
+ def save_state(node, res)
376
+ @matches[@idx] = Match.new(@reference, node) if res && !@matches[@idx]
377
+ end
378
+
379
+ def count_values
380
+ @declaration.values.length
381
+ end
382
+
383
+ def rec_match(node)
384
+ value = resolve_function(@declaration.values[@idx])
385
+ return false unless value
386
+
387
+ resolved_node = resolve_node(node)
388
+
389
+ match = false
390
+ loop = resolved_node.occurence.end > count_values ? count_values : (resolved_node.occurence.end - resolved_node.occurence.begin + 1)
391
+
392
+ puts "[#{@idx}/#{count_values}] #{resolved_node.occurence}/#{loop} #{value} => #{resolved_node} (#{resolved_node.type})" if @debug
393
+
394
+ loop.times do |i|
395
+ occ_match =
396
+ case resolved_node.type
397
+ when :or_and
398
+ res = false
399
+ resolved_node.children.each do |child|
400
+ tres = rec_match(child)
401
+ save_state(child, tres)
402
+ res ||= tres
403
+ end
404
+ res
405
+ when :and
406
+ founds = 0
407
+ resolved_node.children.each do |child|
408
+ res = rec_match(child)
409
+ save_state(child, res)
410
+ # puts "AND CHILD #{child.occurence} #{child}" if @debug
411
+ founds += 1 if res
412
+ end
413
+ # puts "AND #{founds} #{resolved_node.occurence} #{resolved_node.occurence.include?(founds)} #{resolved_node}" if @debug
414
+ resolved_node.occurence.include?(founds)
415
+ when :or
416
+ res = false
417
+ resolved_node.children.each do |child|
418
+ tres = rec_match(child)
419
+ save_state(child, tres)
420
+ res ||= tres
421
+ break if tres
422
+ end
423
+ res
424
+ when :number
425
+ match_value_class_and_data(value, Habaki::Number, resolved_node)
426
+ when :type
427
+ case resolved_node.value
428
+ when "percentage"
429
+ match_value_class(value, Habaki::Percentage)
430
+ when "length"
431
+ # 0 is acceptable too
432
+ (match_value_class(value, Habaki::Length) && value.unit) || (match_value_class(value, Habaki::Number) && value.to_f == 0.0)
433
+ when "angle"
434
+ match_value_class(value, Habaki::Angle)
435
+ when "number", "integer"
436
+ match_value_class(value, Habaki::Number)
437
+ when "string", "custom-ident"
438
+ match_value_class(value, Habaki::String) || match_value_class(value, Habaki::Ident)
439
+ when "uri", "url"
440
+ match_value_class(value, Habaki::Url)
441
+ when "hexcolor", "hex-color"
442
+ match_value_class(value, Habaki::HexColor)
443
+ else
444
+ false
445
+ end
446
+ when :ident
447
+ match_value_class_and_data(value, Habaki::Ident, resolved_node)
448
+ when :function
449
+ match_value_class_and_data(value, Habaki::Function, resolved_node)
450
+ when :token
451
+ match_value_class_and_data(value, Habaki::Operator, resolved_node)
452
+ else
453
+ false
454
+ end
455
+
456
+ match ||= occ_match
457
+ puts " MATCH OCC? #{i}/#{loop} #{value} : #{resolved_node} #{occ_match} #{match}" if @debug
458
+ end
459
+ match
460
+ end
461
+
462
+ end
463
+ end
464
+ end
@@ -0,0 +1,99 @@
1
+ module Habaki
2
+ # output formatting
3
+ module Formatter
4
+ class Base
5
+ # @return [Integer]
6
+ attr_accessor :level
7
+
8
+ def initialize(level = 0)
9
+ @level = level
10
+ end
11
+
12
+ # @return [String]
13
+ def declaration_prefix
14
+ ""
15
+ end
16
+
17
+ # @return [String]
18
+ def declarations_prefix
19
+ ""
20
+ end
21
+
22
+ # @return [String]
23
+ def declarations_join
24
+ ""
25
+ end
26
+
27
+ # @return [String]
28
+ def declarations_suffix
29
+ ""
30
+ end
31
+
32
+ # @return [String]
33
+ def rules_prefix
34
+ ""
35
+ end
36
+
37
+ # @return [String]
38
+ def rules_join
39
+ ""
40
+ end
41
+
42
+ # @return [String]
43
+ def quote
44
+ "\""
45
+ end
46
+
47
+ # @return [self]
48
+ def +(num)
49
+ self.class.new(@level + num)
50
+ end
51
+ end
52
+
53
+ # default flat formatting
54
+ class Flat < Base
55
+ # @return [String]
56
+ def declarations_join
57
+ " "
58
+ end
59
+
60
+ # @return [String]
61
+ def rules_join
62
+ "\n"
63
+ end
64
+ end
65
+
66
+ # indented formatting
67
+ class Indented < Base
68
+ # @return [String]
69
+ def declaration_prefix
70
+ " " * @level
71
+ end
72
+
73
+ # @return [String]
74
+ def declarations_join
75
+ "\n"
76
+ end
77
+
78
+ # @return [String]
79
+ def declarations_prefix
80
+ "\n"
81
+ end
82
+
83
+ # @return [String]
84
+ def declarations_suffix
85
+ "\n"
86
+ end
87
+
88
+ # @return [String]
89
+ def rules_prefix
90
+ " " * @level
91
+ end
92
+
93
+ # @return [String]
94
+ def rules_join
95
+ "\n\n"
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,34 @@
1
+ module Habaki
2
+ # Rule for @import
3
+ class ImportRule < Rule
4
+ # @return [String]
5
+ attr_accessor :href
6
+ # @return [MediaQueries]
7
+ attr_accessor :medias
8
+
9
+ # @param [String] href
10
+ def initialize(href = nil)
11
+ @href = href
12
+ @medias = MediaQueries.new
13
+ end
14
+
15
+ # @return [Habaki::Stylesheet]
16
+ def stylesheet(base_dir: "")
17
+ Stylesheet.parse_file(base_dir+@href)
18
+ end
19
+
20
+ # @param [Formatter::Base] format
21
+ # @return [String]
22
+ def string(format = Formatter::Base.new)
23
+ "@import #{format.quote}#{@href}#{format.quote} #{@medias.string(format)};"
24
+ end
25
+
26
+ # @api private
27
+ # @param [Katana::ImportRule] rule
28
+ # @return [void]
29
+ def read_from_katana(rule)
30
+ @href = rule.href
31
+ @medias = MediaQueries.read_from_katana(rule.medias)
32
+ end
33
+ end
34
+ end