deadweight 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/README.rdoc +17 -5
  2. data/Rakefile +0 -1
  3. data/VERSION +1 -1
  4. data/deadweight.gemspec +29 -9
  5. data/lib/deadweight.rb +51 -15
  6. data/lib/deadweight/cli.rb +8 -2
  7. data/test/cli_test.rb +9 -6
  8. data/test/test_helper.rb +2 -2
  9. data/vendor/gems/css_parser-0.9.1/CHANGELOG +13 -0
  10. data/vendor/gems/css_parser-0.9.1/LICENSE +21 -0
  11. data/vendor/gems/css_parser-0.9.1/README +58 -0
  12. data/vendor/gems/css_parser-0.9.1/lib/css_parser.rb +149 -0
  13. data/vendor/gems/css_parser-0.9.1/lib/css_parser/parser.rb +345 -0
  14. data/vendor/gems/css_parser-0.9.1/lib/css_parser/regexps.rb +46 -0
  15. data/vendor/gems/css_parser-0.9.1/lib/css_parser/rule_set.rb +380 -0
  16. data/vendor/gems/css_parser-0.9.1/test/fixtures/import-circular-reference.css +4 -0
  17. data/vendor/gems/css_parser-0.9.1/test/fixtures/import-with-media-types.css +3 -0
  18. data/vendor/gems/css_parser-0.9.1/test/fixtures/import1.css +3 -0
  19. data/vendor/gems/css_parser-0.9.1/test/fixtures/simple.css +6 -0
  20. data/vendor/gems/css_parser-0.9.1/test/fixtures/subdir/import2.css +3 -0
  21. data/vendor/gems/css_parser-0.9.1/test/test_css_parser_basic.rb +56 -0
  22. data/vendor/gems/css_parser-0.9.1/test/test_css_parser_downloading.rb +81 -0
  23. data/vendor/gems/css_parser-0.9.1/test/test_css_parser_media_types.rb +71 -0
  24. data/vendor/gems/css_parser-0.9.1/test/test_css_parser_misc.rb +143 -0
  25. data/vendor/gems/css_parser-0.9.1/test/test_css_parser_regexps.rb +68 -0
  26. data/vendor/gems/css_parser-0.9.1/test/test_helper.rb +8 -0
  27. data/vendor/gems/css_parser-0.9.1/test/test_merging.rb +88 -0
  28. data/vendor/gems/css_parser-0.9.1/test/test_rule_set.rb +74 -0
  29. data/vendor/gems/css_parser-0.9.1/test/test_rule_set_creating_shorthand.rb +90 -0
  30. data/vendor/gems/css_parser-0.9.1/test/test_rule_set_expanding_shorthand.rb +178 -0
  31. metadata +25 -13
@@ -0,0 +1,46 @@
1
+ module CssParser
2
+ # :stopdoc:
3
+ # Base types
4
+ RE_NL = Regexp.new('(\n|\r\n|\r|\f)')
5
+ RE_NON_ASCII = Regexp.new('([\x00-\xFF])', Regexp::IGNORECASE) #[^\0-\177]
6
+ RE_UNICODE = Regexp.new('(\\\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])*)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE)
7
+ RE_ESCAPE = Regexp.union(RE_UNICODE, '|(\\\\[^\n\r\f0-9a-f])')
8
+ RE_IDENT = Regexp.new("[\-]?([_a-z]|#{RE_NON_ASCII}|#{RE_ESCAPE})([_a-z0-9\-]|#{RE_NON_ASCII}|#{RE_ESCAPE})*", Regexp::IGNORECASE)
9
+
10
+ # General strings
11
+ RE_STRING1 = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
12
+ RE_STRING2 = Regexp.new('(\'(.[^\n\r\f\\\']*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\')')
13
+ RE_STRING = Regexp.union(RE_STRING1, RE_STRING2)
14
+
15
+ RE_URI = Regexp.new('(url\([\s]*([\s]*' + RE_STRING.to_s + '[\s]*)[\s]*\))|(url\([\s]*([!#$%&*\-~]|' + RE_NON_ASCII.to_s + '|' + RE_ESCAPE.to_s + ')*[\s]*)\)', Regexp::IGNORECASE | Regexp::EXTENDED | Regexp::MULTILINE)
16
+ URI_RX = /url\(("([^"]*)"|'([^']*)'|([^)]*))\)/im
17
+
18
+ # Initial parsing
19
+ RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["']+(.[^'"]*)["']\)?([\w\s\,]*);?/i
20
+
21
+ #--
22
+ #RE_AT_MEDIA_RULE = Regexp.new('(\"(.[^\n\r\f\\"]*|\\\\' + RE_NL.to_s + '|' + RE_ESCAPE.to_s + ')*\")')
23
+
24
+ #RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed
25
+ #++
26
+ IMPORTANT_IN_PROPERTY_RX = /[\s]*\!important[\s]*/i
27
+ STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
28
+ STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
29
+
30
+ # Special units
31
+ BOX_MODEL_UNITS_RX = /(auto|inherit|0|([\-]*([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)))([\s;]|\Z)/imx
32
+ RE_LENGTH_OR_PERCENTAGE = Regexp.new('([\-]*(([0-9]*\.[0-9]+)|[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%))', Regexp::IGNORECASE)
33
+ RE_BACKGROUND_POSITION = Regexp.new("((#{RE_LENGTH_OR_PERCENTAGE})|left|center|right|top|bottom)", Regexp::IGNORECASE | Regexp::EXTENDED)
34
+ FONT_UNITS_RX = /(([x]+\-)*small|medium|large[r]*|auto|inherit|([0-9]+|[0-9]*\.[0-9]+)(e[mx]+|px|[cm]+m|p[tc+]|in|\%)*)/i
35
+
36
+ # Patterns for specificity calculations
37
+ ELEMENTS_AND_PSEUDO_ELEMENTS_RX = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
38
+ NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
39
+
40
+ # Colours
41
+ RE_COLOUR_RGB = Regexp.new('(rgb[\s]*\([\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*,[\s-]*[\d]+(\.[\d]+)?[%\s]*\))', Regexp::IGNORECASE)
42
+ RE_COLOUR_HEX = /(#([0-9a-f]{6}|[0-9a-f]{3})([\s;]|$))/i
43
+ RE_COLOUR_NAMED = /([\s]*^)?(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|transparent)([\s]*$)?/i
44
+ RE_COLOUR = Regexp.union(RE_COLOUR_RGB, RE_COLOUR_HEX, RE_COLOUR_NAMED)
45
+ # :startdoc:
46
+ end
@@ -0,0 +1,380 @@
1
+ module CssParser
2
+ class RuleSet
3
+ # Patterns for specificity calculations
4
+ RE_ELEMENTS_AND_PSEUDO_ELEMENTS = /((^|[\s\+\>]+)[\w]+|\:(first\-line|first\-letter|before|after))/i
5
+ RE_NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES = /(\.[\w]+)|(\[[\w]+)|(\:(link|first\-child|lang))/i
6
+
7
+ # Array of selector strings.
8
+ attr_reader :selectors
9
+
10
+ # Integer with the specificity to use for this RuleSet.
11
+ attr_accessor :specificity
12
+
13
+ def initialize(selectors, block, specificity = nil)
14
+ @selectors = []
15
+ @specificity = specificity
16
+ @declarations = {}
17
+ parse_selectors!(selectors) if selectors
18
+ parse_declarations!(block)
19
+ end
20
+
21
+
22
+ # Get the value of a property
23
+ def get_value(property)
24
+ return '' unless property and not property.empty?
25
+
26
+ property = property.downcase.strip
27
+ properties = @declarations.inject('') do |val, (key, data)|
28
+ #puts "COMPARING #{key} #{key.inspect} against #{property} #{property.inspect}"
29
+ importance = data[:is_important] ? ' !important' : ''
30
+ val << "#{data[:value]}#{importance}; " if key.downcase.strip == property
31
+ val
32
+ end
33
+ return properties ? properties.strip : ''
34
+ end
35
+ alias_method :[], :get_value
36
+
37
+ # Add a CSS declaration to the current RuleSet.
38
+ #
39
+ # rule_set.add_declaration!('color', 'blue')
40
+ #
41
+ # puts rule_set['color']
42
+ # => 'blue;'
43
+ #
44
+ # rule_set.add_declaration!('margin', '0px auto !important')
45
+ #
46
+ # puts rule_set['margin']
47
+ # => '0px auto !important;'
48
+ #
49
+ # If the property already exists its value will be over-written.
50
+ def add_declaration!(property, value)
51
+ value.gsub!(/;\Z/, '')
52
+ is_important = !value.gsub!(CssParser::IMPORTANT_IN_PROPERTY_RX, '').nil?
53
+ property = property.downcase.strip
54
+ #puts "SAVING #{property} #{value} #{is_important.inspect}"
55
+ @declarations[property] = {:value => value, :is_important => is_important}
56
+ end
57
+ alias_method :[]=, :add_declaration!
58
+
59
+ # Iterate through selectors.
60
+ #
61
+ # Options
62
+ # - +force_important+ -- boolean
63
+ #
64
+ # ==== Example
65
+ # ruleset.each_selector do |sel, dec, spec|
66
+ # ...
67
+ # end
68
+ def each_selector(options = {}) # :yields: selector, declarations, specificity
69
+ declarations = declarations_to_s(options)
70
+ if @specificity
71
+ @selectors.each { |sel| yield sel.strip, declarations, @specificity }
72
+ else
73
+ @selectors.each { |sel| yield sel.strip, declarations, CssParser.calculate_specificity(sel) }
74
+ end
75
+ end
76
+
77
+ # Iterate through declarations.
78
+ def each_declaration # :yields: property, value, is_important
79
+ @declarations.each do |property, data|
80
+ value = data[:value]
81
+ #value += ' !important' if data[:is_important]
82
+ yield property.downcase.strip, value.strip, data[:is_important]
83
+ end
84
+ end
85
+
86
+ # Return all declarations as a string.
87
+ def declarations_to_s(options = {})
88
+ options = {:force_important => false}.merge(options)
89
+ str = ''
90
+ each_declaration do |prop, val, is_important|
91
+ importance = (options[:force_important] || is_important) ? ' !important' : ''
92
+ str += "#{prop}: #{val}#{importance}; "
93
+ end
94
+ str.gsub(/^[\s]+|[\n\r\f\t]*|[\s]+$/mx, '').strip
95
+ end
96
+
97
+ # Return the CSS rule set as a string.
98
+ def to_s
99
+ decs = declarations_to_s
100
+ "#{@selectors} { #{decs} }"
101
+ end
102
+
103
+ # Split shorthand declarations (e.g. +margin+ or +font+) into their constituent parts.
104
+ def expand_shorthand!
105
+ expand_dimensions_shorthand!
106
+ expand_font_shorthand!
107
+ expand_background_shorthand!
108
+ end
109
+
110
+ # Create shorthand declarations (e.g. +margin+ or +font+) whenever possible.
111
+ def create_shorthand!
112
+ create_background_shorthand!
113
+ create_dimensions_shorthand!
114
+ create_font_shorthand!
115
+ end
116
+
117
+ private
118
+ def parse_declarations!(block) # :nodoc:
119
+ @declarations = {}
120
+
121
+ return unless block
122
+
123
+ block.gsub!(/(^[\s]*)|([\s]*$)/, '')
124
+
125
+ block.split(/[\;$]+/m).each do |decs|
126
+ if matches = decs.match(/(.[^:]*)\:(.[^;]*)(;|\Z)/i)
127
+ property, value, end_of_declaration = matches.captures
128
+
129
+ add_declaration!(property, value)
130
+ end
131
+ end
132
+ end
133
+
134
+ #--
135
+ # TODO: way too simplistic
136
+ #++
137
+ def parse_selectors!(selectors) # :nodoc:
138
+ @selectors = selectors.split(',')
139
+ end
140
+
141
+ # Split shorthand dimensional declarations (e.g. <tt>margin: 0px auto;</tt>)
142
+ # into their constituent parts.
143
+ def expand_dimensions_shorthand! # :nodoc:
144
+ ['margin', 'padding'].each do |property|
145
+
146
+ next unless @declarations.has_key?(property)
147
+
148
+ value = @declarations[property][:value]
149
+ is_important = @declarations[property][:is_important]
150
+ t, r, b, l = nil
151
+
152
+ matches = value.scan(CssParser::BOX_MODEL_UNITS_RX)
153
+
154
+ case matches.length
155
+ when 1
156
+ t, r, b, l = matches[0][0], matches[0][0], matches[0][0], matches[0][0]
157
+ when 2
158
+ t, b = matches[0][0], matches[0][0]
159
+ r, l = matches[1][0], matches[1][0]
160
+ when 3
161
+ t = matches[0][0]
162
+ r, l = matches[1][0], matches[1][0]
163
+ b = matches[2][0]
164
+ when 4
165
+ t = matches[0][0]
166
+ r = matches[1][0]
167
+ b = matches[2][0]
168
+ l = matches[3][0]
169
+ end
170
+
171
+ @declarations["#{property}-top"] = {:value => t.to_s, :is_important => is_important}
172
+ @declarations["#{property}-right"] = {:value => r.to_s, :is_important => is_important}
173
+ @declarations["#{property}-bottom"] = {:value => b.to_s, :is_important => is_important}
174
+ @declarations["#{property}-left"] = {:value => l.to_s, :is_important => is_important}
175
+ @declarations.delete(property)
176
+ end
177
+ end
178
+
179
+ # Convert shorthand font declarations (e.g. <tt>font: 300 italic 11px/14px verdana, helvetica, sans-serif;</tt>)
180
+ # into their constituent parts.
181
+ def expand_font_shorthand! # :nodoc:
182
+ return unless @declarations.has_key?('font')
183
+
184
+ font_props = {}
185
+
186
+ # reset properties to 'normal' per http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
187
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
188
+ 'line-height'].each do |prop|
189
+ font_props[prop] = 'normal'
190
+ end
191
+
192
+ value = @declarations['font'][:value]
193
+ is_important = @declarations['font'][:is_important]
194
+
195
+ in_fonts = false
196
+
197
+ matches = value.scan(/("(.*[^"])"|'(.*[^'])'|(\w[^ ,]+))/)
198
+ matches.each do |match|
199
+ m = match[0].to_s.strip
200
+ m.gsub!(/[;]$/, '')
201
+
202
+ if in_fonts
203
+ if font_props.has_key?('font-family')
204
+ font_props['font-family'] += ', ' + m
205
+ else
206
+ font_props['font-family'] = m
207
+ end
208
+ elsif m =~ /normal|inherit/i
209
+ ['font-style', 'font-weight', 'font-variant'].each do |font_prop|
210
+ font_props[font_prop] = m unless font_props.has_key?(font_prop)
211
+ end
212
+ elsif m =~ /italic|oblique/i
213
+ font_props['font-style'] = m
214
+ elsif m =~ /small\-caps/i
215
+ font_props['font-variant'] = m
216
+ elsif m =~ /[1-9]00$|bold|bolder|lighter/i
217
+ font_props['font-weight'] = m
218
+ elsif m =~ CssParser::FONT_UNITS_RX
219
+ if m =~ /\//
220
+ font_props['font-size'], font_props['line-height'] = m.split('/')
221
+ else
222
+ font_props['font-size'] = m
223
+ end
224
+ in_fonts = true
225
+ end
226
+ end
227
+
228
+ font_props.each { |font_prop, font_val| @declarations[font_prop] = {:value => font_val, :is_important => is_important} }
229
+
230
+ @declarations.delete('font')
231
+ end
232
+
233
+
234
+ # Convert shorthand background declarations (e.g. <tt>background: url("chess.png") gray 50% repeat fixed;</tt>)
235
+ # into their constituent parts.
236
+ #
237
+ # See http://www.w3.org/TR/CSS21/colors.html#propdef-background
238
+ def expand_background_shorthand! # :nodoc:
239
+ return unless @declarations.has_key?('background')
240
+
241
+ value = @declarations['background'][:value]
242
+ is_important = @declarations['background'][:is_important]
243
+
244
+ bg_props = {}
245
+
246
+
247
+ if m = value.match(Regexp.union(CssParser::URI_RX, /none/i)).to_s
248
+ bg_props['background-image'] = m.strip unless m.empty?
249
+ value.gsub!(Regexp.union(CssParser::URI_RX, /none/i), '')
250
+ end
251
+
252
+ if m = value.match(/([\s]*^)?(scroll|fixed)([\s]*$)?/i).to_s
253
+ bg_props['background-attachment'] = m.strip unless m.empty?
254
+ end
255
+
256
+ if m = value.match(/([\s]*^)?(repeat(\-x|\-y)*|no\-repeat)([\s]*$)?/i).to_s
257
+ bg_props['background-repeat'] = m.strip unless m.empty?
258
+ end
259
+
260
+ if m = value.match(CssParser::RE_COLOUR).to_s
261
+ bg_props['background-color'] = m.strip unless m.empty?
262
+ end
263
+
264
+ value.scan(CssParser::RE_BACKGROUND_POSITION).each do |m|
265
+ if bg_props.has_key?('background-position')
266
+ bg_props['background-position'] += ' ' + m[0].to_s.strip unless m.empty?
267
+ else
268
+ bg_props['background-position'] = m[0].to_s.strip unless m.empty?
269
+ end
270
+ end
271
+
272
+
273
+ if value =~ /([\s]*^)?inherit([\s]*$)?/i
274
+ ['background-color', 'background-image', 'background-attachment', 'background-repeat', 'background-position'].each do |prop|
275
+ bg_props["#{prop}"] = 'inherit' unless bg_props.has_key?(prop) and not bg_props[prop].empty?
276
+ end
277
+ end
278
+
279
+ bg_props.each { |bg_prop, bg_val| @declarations[bg_prop] = {:value => bg_val, :is_important => is_important} }
280
+
281
+ @declarations.delete('background')
282
+ end
283
+
284
+
285
+ # Looks for long format CSS background properties (e.g. <tt>background-color</tt>) and
286
+ # converts them into a shorthand CSS <tt>background</tt> property.
287
+ def create_background_shorthand! # :nodoc:
288
+ new_value = ''
289
+ ['background-color', 'background-image', 'background-repeat',
290
+ 'background-position', 'background-attachment'].each do |property|
291
+ if @declarations.has_key?(property)
292
+ new_value += @declarations[property][:value] + ' '
293
+ @declarations.delete(property)
294
+ end
295
+ end
296
+
297
+ unless new_value.strip.empty?
298
+ @declarations['background'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
299
+ end
300
+ end
301
+
302
+ # Looks for long format CSS dimensional properties (i.e. <tt>margin</tt> and <tt>padding</tt>) and
303
+ # converts them into shorthand CSS properties.
304
+ def create_dimensions_shorthand! # :nodoc:
305
+ # geometric
306
+ directions = ['top', 'right', 'bottom', 'left']
307
+ ['margin', 'padding'].each do |property|
308
+ values = {}
309
+
310
+ foldable = @declarations.select { |dim, val| dim == "#{property}-top" or dim == "#{property}-right" or dim == "#{property}-bottom" or dim == "#{property}-left" }
311
+ # All four dimensions must be present
312
+ if foldable.length == 4
313
+ values = {}
314
+
315
+ directions.each { |d| values[d.to_sym] = @declarations["#{property}-#{d}"][:value].downcase.strip }
316
+
317
+ if values[:left] == values[:right]
318
+ if values[:top] == values[:bottom]
319
+ if values[:top] == values[:left] # All four sides are equal
320
+ new_value = values[:top]
321
+ else # Top and bottom are equal, left and right are equal
322
+ new_value = values[:top] + ' ' + values[:left]
323
+ end
324
+ else # Only left and right are equal
325
+ new_value = values[:top] + ' ' + values[:left] + ' ' + values[:bottom]
326
+ end
327
+ else # No sides are equal
328
+ new_value = values[:top] + ' ' + values[:right] + ' ' + values[:bottom] + ' ' + values[:left]
329
+ end # done creating 'new_value'
330
+
331
+ # Save the new value
332
+ unless new_value.strip.empty?
333
+ @declarations[property] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
334
+ end
335
+
336
+ # Delete the shorthand values
337
+ directions.each { |d| @declarations.delete("#{property}-#{d}") }
338
+ end
339
+ end # done iterating through margin and padding
340
+ end
341
+
342
+
343
+ # Looks for long format CSS font properties (e.g. <tt>font-weight</tt>) and
344
+ # tries to convert them into a shorthand CSS <tt>font</tt> property. All
345
+ # font properties must be present in order to create a shorthand declaration.
346
+ def create_font_shorthand! # :nodoc:
347
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
348
+ 'line-height', 'font-family'].each do |prop|
349
+ return unless @declarations.has_key?(prop)
350
+ end
351
+
352
+ new_value = ''
353
+ ['font-style', 'font-variant', 'font-weight'].each do |property|
354
+ unless @declarations[property][:value] == 'normal'
355
+ new_value += @declarations[property][:value] + ' '
356
+ end
357
+ end
358
+
359
+ new_value += @declarations['font-size'][:value]
360
+
361
+ unless @declarations['line-height'][:value] == 'normal'
362
+ new_value += '/' + @declarations['line-height'][:value]
363
+ end
364
+
365
+ new_value += ' ' + @declarations['font-family'][:value]
366
+
367
+ @declarations['font'] = {:value => new_value.gsub(/[\s]+/, ' ').strip}
368
+
369
+ ['font-style', 'font-variant', 'font-weight', 'font-size',
370
+ 'line-height', 'font-family'].each do |prop|
371
+ @declarations.delete(prop)
372
+ end
373
+
374
+ end
375
+
376
+
377
+
378
+
379
+ end
380
+ end
@@ -0,0 +1,4 @@
1
+ @import "import-circular-reference.css";
2
+
3
+ body { color: black; background: white; }
4
+ p { margin: 0px; }
@@ -0,0 +1,3 @@
1
+ @import "simple.css" print, tv, screen;
2
+
3
+ div { color: lime; }
@@ -0,0 +1,3 @@
1
+ @import 'subdir/import2.css';
2
+
3
+ div { color: lime; }
@@ -0,0 +1,6 @@
1
+ body {
2
+ color: black;
3
+ background: white;
4
+ }
5
+
6
+ p { margin: 0px; }