deadweight 0.1.1 → 0.1.2

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 (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; }