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.
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