aanand-deadweight 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,10 @@
2
2
 
3
3
  Deadweight is RCov for CSS, kind of. Given a set of stylesheets and a set of URLs, it determines which selectors are actually used and reports which can be "safely" deleted.
4
4
 
5
+ === Screencast!
6
+
7
+ Ryan Bates has worked his magic once again. Head over here for an excellent introduction to deadweight: http://railscasts.com/episodes/180-finding-unused-css
8
+
5
9
  === A Simple Example
6
10
 
7
11
  # lib/tasks/deadweight.rake
@@ -16,7 +20,19 @@ Deadweight is RCov for CSS, kind of. Given a set of stylesheets and a set of URL
16
20
  puts dw.run
17
21
  end
18
22
 
19
- This will output all unused selectors, one per line.
23
+ This will output all unused rules, one per line.
24
+
25
+ Alternately, you can run it from the command-line:
26
+
27
+ $ deadweight -s styles.css -s ie.css index.html about.html
28
+
29
+ You can pipe in CSS rules from STDIN:
30
+
31
+ $ cat styles.css | deadweight index.html
32
+
33
+ And you can use it as an HTTP proxy:
34
+
35
+ $ deadweight -l deadweight.log -s styles.css -w http://github.com/ -P
20
36
 
21
37
  === How You Install It
22
38
 
@@ -28,6 +44,7 @@ This will output all unused selectors, one per line.
28
44
  - By default, it looks at http://localhost:3000.
29
45
  - It's completely dumb about any classes, IDs or tags that are only added by your Javascript layer, but you can filter them out by setting +ignore_selectors+.
30
46
  - You can optionally tell it to use Mechanize, and set up more complicated targets for scraping by specifying them as Procs.
47
+ - There is experimental support for Lyndon (http://github.com/defunkt/lyndon) with -L
31
48
 
32
49
  === A More Complex Example, In Light of All That
33
50
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.1.0
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'deadweight/cli'
3
+
4
+ Deadweight::CLI.execute(STDOUT, STDIN, STDERR, ARGV.dup)
5
+
@@ -1,13 +1,18 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{deadweight}
5
- s.version = "0.0.3"
8
+ s.version = "0.1.0"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["Aanand Prasad"]
9
- s.date = %q{2009-08-18}
12
+ s.date = %q{2009-09-24}
13
+ s.default_executable = %q{deadweight}
10
14
  s.email = %q{aanand.prasad@gmail.com}
15
+ s.executables = ["deadweight"]
11
16
  s.extra_rdoc_files = [
12
17
  "LICENSE",
13
18
  "README.rdoc"
@@ -19,8 +24,11 @@ Gem::Specification.new do |s|
19
24
  "README.rdoc",
20
25
  "Rakefile",
21
26
  "VERSION",
27
+ "bin/deadweight",
22
28
  "deadweight.gemspec",
23
29
  "lib/deadweight.rb",
30
+ "lib/deadweight/cli.rb",
31
+ "test/cli_test.rb",
24
32
  "test/deadweight_test.rb",
25
33
  "test/fixtures/index.html",
26
34
  "test/fixtures/index2.html",
@@ -33,7 +41,8 @@ Gem::Specification.new do |s|
33
41
  s.rubygems_version = %q{1.3.4}
34
42
  s.summary = %q{RCov for CSS}
35
43
  s.test_files = [
36
- "test/deadweight_test.rb",
44
+ "test/cli_test.rb",
45
+ "test/deadweight_test.rb",
37
46
  "test/test_helper.rb"
38
47
  ]
39
48
 
@@ -4,17 +4,34 @@ require 'open-uri'
4
4
  require 'logger'
5
5
 
6
6
  class Deadweight
7
- attr_accessor :root, :stylesheets, :pages, :ignore_selectors, :mechanize, :log_file
7
+ attr_accessor :root, :stylesheets, :rules, :pages, :ignore_selectors, :mechanize, :log_file
8
+ attr_reader :unused_selectors
8
9
 
9
10
  def initialize
10
11
  @root = 'http://localhost:3000'
11
12
  @stylesheets = []
12
13
  @pages = []
14
+ @rules = ""
13
15
  @ignore_selectors = []
14
16
  @mechanize = false
15
17
  @log_file = STDERR
16
18
  end
17
19
 
20
+ def analyze(html)
21
+ doc = Hpricot(html)
22
+
23
+ found_selectors = []
24
+
25
+ @unused_selectors.collect do |selector, declarations|
26
+ # We test against the selector stripped of any pseudo classes,
27
+ # but we report on the selector with its pseudo classes.
28
+ unless doc.search(strip(selector)).empty?
29
+ log.info(" #{selector}")
30
+ selector
31
+ end
32
+ end
33
+ end
34
+
18
35
  # Find all unused CSS selectors and return them as an array.
19
36
  def run
20
37
  css = CssParser::Parser.new
@@ -23,28 +40,31 @@ class Deadweight
23
40
  css.add_block!(fetch(path))
24
41
  end
25
42
 
26
- unused_selectors = []
43
+ css.add_block!(rules)
44
+
45
+ @unused_selectors = {}
27
46
  total_selectors = 0
28
47
 
29
48
  css.each_selector do |selector, declarations, specificity|
30
- unless unused_selectors.include?(selector)
49
+ unless @unused_selectors[selector]
31
50
  total_selectors += 1
32
- unused_selectors << selector unless selector =~ ignore_selectors
51
+ @unused_selectors[selector] = declarations unless selector =~ ignore_selectors
33
52
  end
34
53
  end
35
54
 
36
55
  # Remove selectors with pseudo classes that already have an equivalent
37
56
  # without the pseudo class. Keep the ones that don't, we need to test
38
57
  # them.
39
- unused_selectors.reject! do |selector|
40
- has_pseudo_classes(selector) && unused_selectors.include?(strip(selector))
58
+ @unused_selectors.keys.each do |selector|
59
+ if has_pseudo_classes(selector) && @unused_selectors.include?(strip(selector))
60
+ @unused_selectors.delete(selector)
61
+ end
41
62
  end
42
63
 
43
64
  pages.each do |page|
44
- case page
45
- when String
46
- html = fetch(page)
47
- else
65
+ if page.respond_to?(:read)
66
+ html = page.read
67
+ elsif page.respond_to?(:call)
48
68
  result = instance_eval(&page)
49
69
 
50
70
  html = case result
@@ -53,27 +73,22 @@ class Deadweight
53
73
  else
54
74
  @agent.page.body
55
75
  end
76
+ else
77
+ html = fetch(page)
56
78
  end
57
79
 
58
- doc = Hpricot(html)
80
+ process!(html)
81
+ end
59
82
 
60
- found_selectors = []
83
+ log.info "found #{@unused_selectors.size} unused selectors out of #{total_selectors} total"
61
84
 
62
- unused_selectors.each do |selector|
63
- # We test against the selector stripped of any pseudo classes,
64
- # but we report on the selector with its pseudo classes.
65
- unless doc.search(strip(selector)).empty?
66
- log.info(" #{selector}")
67
- found_selectors << selector
68
- end
69
- end
85
+ @unused_selectors
86
+ end
70
87
 
71
- unused_selectors -= found_selectors
88
+ def process!(html)
89
+ analyze(html).each do |selector|
90
+ @unused_selectors.delete(selector)
72
91
  end
73
-
74
- log.info "found #{unused_selectors.size} unused selectors out of #{total_selectors} total"
75
-
76
- unused_selectors
77
92
  end
78
93
 
79
94
  # Returns the Mechanize instance, if +mechanize+ is set to +true+.
@@ -0,0 +1,187 @@
1
+ require 'optparse'
2
+ require 'deadweight'
3
+
4
+ class Deadweight
5
+ class CLI
6
+ attr_reader :stdout, :stdin, :stderr
7
+ attr_reader :arguments, :options
8
+ attr_reader :output
9
+
10
+ def self.execute(stdout, stdin, stderr, arguments = [])
11
+ @options = {
12
+ :log_file => stderr,
13
+ :output => stdout,
14
+ :proxy_port => 8002
15
+ }
16
+
17
+ self.parse_options(arguments)
18
+ self.new(stdout, stdin, stderr, arguments, @options).execute!
19
+ end
20
+
21
+ def self.option_parser
22
+ @option_parser ||= OptionParser.new do |opts|
23
+ opts.banner = "Usage: #{$0} [options] <url> [<url> ...]"
24
+
25
+ @options[:stylesheets] = []
26
+
27
+ opts.on("-L", "--lyndon", "Pre-process HTML with Lyndon") do
28
+ @options[:lyndon] = true
29
+ end
30
+
31
+ opts.on("-l", "--log FILE", "Where to write log messages") do |v|
32
+ @options[:log_file] = v
33
+ end
34
+
35
+ opts.on("-P", "--proxy", "Run in proxy mode") do
36
+ @options[:proxy] = true
37
+ end
38
+
39
+ opts.on("-p", "--proxy-port", "Port to run the proxy on") do |v|
40
+ @options[:proxy] = true
41
+ @options[:proxy_port] = v.to_i
42
+ end
43
+
44
+ opts.on("-O", "--output FILE",
45
+ "Where to output orphaned CSS rules") do |v|
46
+ @options[:output] = File.new(v, "w")
47
+ end
48
+
49
+ opts.on("-s", "--stylesheet FILE",
50
+ "Apply the specified stylesheet to the target") do |v|
51
+ @options[:stylesheets] << v
52
+ end
53
+
54
+ opts.on("-w", "--whitelist URL-PREFIX",
55
+ "Specifies a prefix for URLs to process") do |v|
56
+ @options[:whitelist] ||= []
57
+ @options[:whitelist] << v
58
+ end
59
+ end
60
+ end
61
+
62
+ def self.parse_options(arguments = [])
63
+ self.option_parser.parse!(arguments)
64
+ @options
65
+ end
66
+
67
+ def initialize(stdout, stdin, stderr, arguments = [], options = {})
68
+ @stdout = stdout
69
+ @stdin = stdin
70
+ @stderr = stderr
71
+ @arguments = arguments
72
+ @options = options
73
+ @output = options[:output]
74
+ end
75
+
76
+ def execute!
77
+ if options[:proxy]
78
+ proxy
79
+ elsif arguments.empty?
80
+ stdout.puts self.class.option_parser.help
81
+ else
82
+ process
83
+ end
84
+ end
85
+
86
+ def process
87
+ # TODO pass stylesheets + pages as args
88
+ dw = Deadweight.new
89
+
90
+ # TODO this should be the default
91
+ dw.root = ""
92
+
93
+ dw.log_file = options[:log_file]
94
+
95
+ dw.stylesheets = options[:stylesheets]
96
+
97
+ dw.rules = stdin.read if stdin.stat.size > 0
98
+
99
+ if options[:lyndon]
100
+ arguments.each do |file|
101
+ dw.pages << IO.popen("cat #{file} | lyndon 2> /dev/null")
102
+ end
103
+ else
104
+ dw.pages = arguments
105
+ end
106
+
107
+ unused_rules = dw.run
108
+ unused_rules.each do |k,v|
109
+ output.puts "#{k} { #{v} }"
110
+ end
111
+ end
112
+
113
+ def proxy
114
+ dw = Deadweight.new
115
+
116
+ # TODO note the boilerplate shared with #process
117
+ dw.root = ""
118
+ dw.log_file = options[:log_file]
119
+ dw.stylesheets = options[:stylesheets]
120
+ dw.rules = stdin.read if stdin.stat.size > 0
121
+
122
+ # initialize selectors
123
+ dw.run
124
+
125
+ stdout.puts "#{dw.unused_selectors.length} rules loaded."
126
+
127
+ require 'webrick/httpproxy'
128
+
129
+ @proxy = WEBrick::HTTPProxyServer.new \
130
+ :AccessLog => [
131
+ [options[:log_file], WEBrick::AccessLog::COMMON_LOG_FORMAT],
132
+ [options[:log_file], WEBrick::AccessLog::REFERER_LOG_FORMAT]
133
+ ],
134
+ :Logger => WEBrick::Log.new(options[:log_file]),
135
+ :Port => options[:proxy_port],
136
+ :ProxyContentHandler => lambda { |request, response|
137
+
138
+ parse_this = false
139
+
140
+ if options[:whitelist]
141
+ options[:whitelist].each do |x|
142
+ sliced_request_uri = response.request_uri.to_s[0..x.length - 1]
143
+ if sliced_request_uri.downcase == x.downcase
144
+ parse_this = true
145
+ break
146
+ end
147
+ end
148
+ else
149
+ parse_this = true
150
+ end
151
+
152
+ if parse_this && response.header["content-type"] =~ /text\/html/
153
+ # TODO this slows things down significantly; better would be to
154
+ # remove the Accept-Encoding header during the request phase
155
+ body = if response.header["content-encoding"] == "gzip"
156
+ Zlib::GzipReader.new(StringIO.new(response.body)).read
157
+ elsif response.header["content-encoding"] == "deflate"
158
+ Zlib::Inflate.inflate(response.body)
159
+ else
160
+ response.body
161
+ end
162
+ dw.process!(body)
163
+
164
+ stdout.puts "After reviewing <#{response.request_uri}>, there were #{dw.unused_selectors.length} rules left"
165
+ # stdout.puts "After reviewing <#{response.request_uri}>, these were left:"
166
+ # dw.unused_selectors.each do |k,v|
167
+ # stdout.puts "#{k} { #{v} }"
168
+ # end
169
+ end
170
+ }
171
+
172
+ trap('INT') do
173
+ @proxy.shutdown
174
+
175
+ # dump the remaining CSS rules if output is set
176
+ unless options[:output] == STDOUT
177
+ dw.unused_selectors.each do |k,v|
178
+ output.puts "#{k} { #{v} }"
179
+ end
180
+ end
181
+ end
182
+
183
+ @proxy.start
184
+ end
185
+ end
186
+ end
187
+
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class CliTest < Test::Unit::TestCase
4
+ COMMAND = "ruby -rubygems -Ilib bin/deadweight -s test/fixtures/style.css test/fixtures/index.html 2>/dev/null"
5
+
6
+ should "output unused selectors on STDOUT" do
7
+ @result = `#{COMMAND}`.split("\n")
8
+
9
+ assert_equal 1, @result.grep(/^#foo \.bar \.baz \{/).length
10
+ assert_equal 0, @result.grep(/^#foo \{/).length
11
+ assert_equal 0, @result.grep(/^#foo .bar \{/).length
12
+ end
13
+
14
+ should "accept CSS rules on STDIN" do
15
+ @result = `echo ".something { display: block; }" | #{COMMAND}`.split("\n")
16
+
17
+ assert_equal 1, @result.grep(/^\.something \{/).length
18
+ end
19
+ end
20
+
@@ -41,4 +41,20 @@ class DeadweightTest < Test::Unit::TestCase
41
41
 
42
42
  assert @dw.run.empty?
43
43
  end
44
+
45
+ should "accept IO objects as targets" do
46
+ @dw.pages << File.new(File.dirname(__FILE__) + '/fixtures/index2.html')
47
+
48
+ assert @dw.run.empty?
49
+ end
50
+
51
+ should "allow individual CSS rules to be appended" do
52
+ @dw.rules = ".something { display: block; }"
53
+
54
+ assert @dw.run.include?(".something")
55
+ end
56
+
57
+ should 'provide the results of its last run with #unused_selectors' do
58
+ assert_equal @result, @dw.unused_selectors
59
+ end
44
60
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aanand-deadweight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aanand Prasad
@@ -9,8 +9,8 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-18 00:00:00 -07:00
13
- default_executable:
12
+ date: 2009-09-24 00:00:00 -07:00
13
+ default_executable: deadweight
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: css_parser
@@ -34,8 +34,8 @@ dependencies:
34
34
  version:
35
35
  description:
36
36
  email: aanand.prasad@gmail.com
37
- executables: []
38
-
37
+ executables:
38
+ - deadweight
39
39
  extensions: []
40
40
 
41
41
  extra_rdoc_files:
@@ -48,8 +48,11 @@ files:
48
48
  - README.rdoc
49
49
  - Rakefile
50
50
  - VERSION
51
+ - bin/deadweight
51
52
  - deadweight.gemspec
52
53
  - lib/deadweight.rb
54
+ - lib/deadweight/cli.rb
55
+ - test/cli_test.rb
53
56
  - test/deadweight_test.rb
54
57
  - test/fixtures/index.html
55
58
  - test/fixtures/index2.html
@@ -83,5 +86,6 @@ signing_key:
83
86
  specification_version: 3
84
87
  summary: RCov for CSS
85
88
  test_files:
89
+ - test/cli_test.rb
86
90
  - test/deadweight_test.rb
87
91
  - test/test_helper.rb