blinkr 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/blinkr/report.rb CHANGED
@@ -6,9 +6,41 @@ module Blinkr
6
6
 
7
7
  TMPL = File.expand_path('report.html.slim', File.dirname(__FILE__))
8
8
 
9
- def self.render(errors, out_file)
10
- out_file ||= 'blinkr.html'
11
- File.open(out_file, 'w') { |file| file.write(Slim::Template.new(TMPL).render(OpenStruct.new({:errors => errors}))) }
9
+ def self.render(context, engine, config)
10
+
12
11
  end
12
+
13
+ def initialize context, engine, config
14
+ @context = context
15
+ @engine = engine
16
+ @config = config
17
+ end
18
+
19
+ def render
20
+ @context.severities = {}
21
+ @context.categories = {}
22
+ @context.types = {}
23
+ @context.pages.each do |url, page|
24
+ page.max_severity = 'success'
25
+ page.errors.each do |error|
26
+ raise "#{error.severity} not a valid severity. Must be one of #{SEVERITY.join(',')}" unless SEVERITY.include? error.severity
27
+ raise "#{error.category} must be specified." if error.category.nil?
28
+ @context.severities[error.severity] ||= OpenStruct.new({ :id => error.severity, :count => 0 })
29
+ @context.severities[error.severity].count += 1
30
+ page.max_severity = error.severity if SEVERITY.index(error.severity) > SEVERITY.index(page.max_severity)
31
+ @context.categories[error.category] ||= OpenStruct.new({ :id => @context.categories.length, :count => 0, :severities => Hash.new(0), :types => {} })
32
+ @context.categories[error.category].severities[error.severity] += 1
33
+ @context.categories[error.category].types[error.type] ||= OpenStruct.new({ :id => @context.categories[error.category].types.length, :count => 0, :severities => Hash.new(0) })
34
+ @context.categories[error.category].types[error.type].severities[error.severity] += 1
35
+ end
36
+ end
37
+ @context.error_count = @context.severities.reduce(0){ |sum, (severity, metadata)| sum += metadata.count }
38
+ File.open(@config.report, 'w') { |file| file.write(Slim::Template.new(TMPL).render(OpenStruct.new({ :blinkr => @context, :engine => @engine }))) }
39
+ end
40
+
41
+ private
42
+
43
+ SEVERITY = ['success', 'info', 'warning', 'danger']
44
+
13
45
  end
14
46
  end
@@ -0,0 +1,20 @@
1
+ module Blinkr
2
+ module Sitemap
3
+
4
+ def sitemap_locations
5
+ open.css('loc').collect { |loc| loc.content }
6
+ end
7
+
8
+ private
9
+
10
+ def open
11
+ puts "Loading sitemap from #{@config.sitemap}"
12
+ if @config.sitemap =~ URI::regexp
13
+ Nokogiri::XML(Typhoeus.get(@config.sitemap, followlocation: true).body)
14
+ else
15
+ Nokogiri::XML(File.open(@config.sitemap))
16
+ end
17
+ end
18
+
19
+ end
20
+ end
data/lib/blinkr/snap.js CHANGED
@@ -73,6 +73,8 @@ page.open(info.url, function(status) {
73
73
 
74
74
  var exit = function exit(err_code) {
75
75
  info.content = page.content;
76
+ // Re-read the URL in case we've been redirected
77
+ info.url = page.url;
76
78
  fs.write(out, JSON.stringify(info), 'w');
77
79
  if (err_code === undefined) {
78
80
  err_code = 0;
@@ -0,0 +1,98 @@
1
+ require 'typhoeus/request'
2
+ require 'typhoeus/response'
3
+ require 'typhoeus/hydra'
4
+ require 'typhoeus'
5
+ require 'blinkr/cache'
6
+ require 'blinkr/http_utils'
7
+
8
+ module Blinkr
9
+ class TyphoeusWrapper
10
+ include HttpUtils
11
+
12
+ attr_reader :count, :hydra
13
+
14
+ def initialize config, context
15
+ @config = config.validate
16
+ @hydra = Typhoeus::Hydra.new(max_concurrency: 200)
17
+ @count = 0
18
+ @context = context
19
+ end
20
+
21
+ def process_all urls, limit, &block
22
+ urls.each do |url|
23
+ process url, limit, &block
24
+ end
25
+ end
26
+
27
+ def process url, limit, &block
28
+ _process url, limit, limit, &block
29
+ end
30
+
31
+ def debug url
32
+ process(url, @config.max_retrys) do |resp|
33
+ puts "\n++++++++++"
34
+ puts "+ Blinkr +"
35
+ puts "++++++++++"
36
+ puts "\nRequest"
37
+ puts "======="
38
+ puts "Method: #{resp.request.options[:method]}"
39
+ puts "Max redirects: #{resp.request.options[:maxredirs]}"
40
+ puts "Follow location header: #{resp.request.options[:followlocation]}"
41
+ puts "Timeout (s): #{resp.request.options[:timeout] || 'none'}"
42
+ puts "Connection timeout (s): #{resp.request.options[:connecttimeout] || 'none'}"
43
+ puts "\nHeaders"
44
+ puts "-------"
45
+ unless resp.request.options[:headers].nil?
46
+ resp.request.options[:headers].each do |name, value|
47
+ puts "#{name}: #{value}"
48
+ end
49
+ end
50
+ puts "\nResponse"
51
+ puts "========"
52
+ puts "Status Code: #{resp.code}"
53
+ puts "Status Message: #{resp.status_message}"
54
+ puts "Message: #{resp.return_message}" unless resp.return_message.nil? || resp.return_message == 'No error'
55
+ puts "\nHeaders"
56
+ puts "-------"
57
+ puts resp.response_headers
58
+ end
59
+ @hydra.run
60
+ end
61
+
62
+ def name
63
+ 'typhoeus'
64
+ end
65
+
66
+ private
67
+
68
+ def _process url, limit, max, &block
69
+ unless @config.skipped? url
70
+ req = Typhoeus::Request.new(
71
+ url,
72
+ followlocation: true,
73
+ verbose: @config.vverbose
74
+ )
75
+ req.on_complete do |resp|
76
+ if retry? resp
77
+ if limit > 1
78
+ puts "Loading #{url} via typhoeus (attempt #{max - limit + 2} of #{max})" if @config.verbose
79
+ _process(url, limit - 1, max, &Proc.new)
80
+ else
81
+ puts "Loading #{url} via typhoeus failed" if @config.verbose
82
+ response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
83
+ response.request = Typhoeus::Request.new(url)
84
+ Typhoeus.stub(url).and_return(response)
85
+ block.call response, nil, nil
86
+ end
87
+ else
88
+ block.call resp, nil, nil
89
+ end
90
+ end
91
+ @hydra.queue req
92
+ @count += 1
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+
@@ -1,4 +1,4 @@
1
1
  module Blinkr
2
- VERSION='0.1.0'
2
+ VERSION='0.2.0'
3
3
  end
4
4
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blinkr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Muir
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-21 00:00:00.000000000 Z
11
+ date: 2014-07-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,10 +113,25 @@ files:
113
113
  - blinkr.gemspec
114
114
  - lib/blinkr.rb
115
115
  - lib/blinkr/cache.rb
116
- - lib/blinkr/check.rb
116
+ - lib/blinkr/config.rb
117
+ - lib/blinkr/engine.rb
118
+ - lib/blinkr/extensions/a_title.rb
119
+ - lib/blinkr/extensions/empty_a_href.rb
120
+ - lib/blinkr/extensions/img_alt.rb
121
+ - lib/blinkr/extensions/inline_css.rb
122
+ - lib/blinkr/extensions/javascript.rb
123
+ - lib/blinkr/extensions/links.rb
124
+ - lib/blinkr/extensions/meta.html.slim
125
+ - lib/blinkr/extensions/meta.rb
126
+ - lib/blinkr/extensions/pipeline.rb
127
+ - lib/blinkr/extensions/resources.rb
128
+ - lib/blinkr/http_utils.rb
129
+ - lib/blinkr/phantomjs_wrapper.rb
117
130
  - lib/blinkr/report.html.slim
118
131
  - lib/blinkr/report.rb
132
+ - lib/blinkr/sitemap.rb
119
133
  - lib/blinkr/snap.js
134
+ - lib/blinkr/typhoeus_wrapper.rb
120
135
  - lib/blinkr/version.rb
121
136
  homepage: ''
122
137
  licenses:
data/lib/blinkr/check.rb DELETED
@@ -1,231 +0,0 @@
1
- require 'nokogiri'
2
- require 'uri'
3
- require 'typhoeus'
4
- require 'blinkr/cache'
5
- require 'ostruct'
6
- require 'tempfile'
7
- require 'parallel'
8
-
9
- module Blinkr
10
- class Check
11
-
12
- SNAP_JS = File.expand_path('snap.js', File.dirname(__FILE__))
13
-
14
- def initialize base_url, sitemap: '', skips: [], max_retrys: 3, max_page_retrys: 3, verbose: false, vverbose: false, browser: 'typhoeus', viewport: 1200, ignore_fragments: false, ignores: [], phantomjs_threads: 8
15
- raise "Must specify base_url" if base_url.nil?
16
- unless sitemap.nil?
17
- @sitemap = sitemap
18
- else
19
- @sitemap = URI.join(base_url, 'sitemap.xml').to_s
20
- end
21
- @skips = skips || []
22
- @ignores = ignores || []
23
- @ignores.each {|ignore| raise "An ignore must be a hash" unless ignore.is_a? Hash}
24
- @base_url = base_url
25
- @max_retrys = max_retrys || 3
26
- @max_page_retrys = max_page_retrys || @max_retrys
27
- @browser = browser || 'typhoeus'
28
- @verbose = vverbose || verbose
29
- @vverbose = vverbose
30
- @viewport = viewport || 1200
31
- @ignore_fragments = ignore_fragments
32
- Typhoeus::Config.cache = @typhoeus_cache
33
- @hydra = Typhoeus::Hydra.new(max_concurrency: 200)
34
- @phantomjs_count = 0
35
- @typhoeus_count = 0
36
- @phantomjs_threads = phantomjs_threads || 8
37
- end
38
-
39
- def check
40
- @errors = OpenStruct.new({:links => {}})
41
- @errors.javascript = {} if @browser == 'phantomjs'
42
- @errors.resources = {} if @browser == 'phantomjs'
43
- puts "Loading sitemap from #{@sitemap}"
44
- if @sitemap =~ URI::regexp
45
- sitemap = Nokogiri::XML(Typhoeus.get(@sitemap, followlocation: true).body)
46
- else
47
- sitemap = Nokogiri::XML(File.open(@sitemap))
48
- end
49
- @links = {}
50
- pages(sitemap.css('loc').collect { |loc| loc.content }) do |resp|
51
- if resp.success?
52
- puts "Loaded page #{resp.request.base_url}" if @verbose
53
- body = Nokogiri::HTML(resp.body)
54
- body.css('a[href]').each do |a|
55
- attr = a.attribute('href')
56
- src = resp.request.base_url
57
- url = attr.value
58
- if !url.nil? && @skips.none? { |regex| regex.match(url) }
59
- url = sanitize url, src
60
- @links[url] ||= []
61
- @links[url] << {:src => src, :line => attr.line, :snippet => attr.parent.to_s}
62
- end
63
- end
64
- else
65
- puts "#{resp.code} #{resp.status_message} Unable to load page #{resp.request.base_url} #{'(' + resp.return_message + ')' unless resp.return_message.nil?}"
66
- end
67
- end
68
- @hydra.run
69
- puts "-----------------------------------------------" if @verbose
70
- puts " Page load complete, #{@links.size} links to check " if @verbose
71
- puts "-----------------------------------------------" if @verbose
72
- @links.each do |url, srcs|
73
- typhoeus(url) do |resp|
74
- puts "Loaded #{url} via typhoeus #{'(cached)' if resp.cached?}" if @verbose
75
- unless resp.success? || resp.code == 200
76
- srcs.each do |src|
77
- unless ignored? url, resp.code, resp.status_message || resp.return_message
78
- @errors.links[url] ||= OpenStruct.new({ :code => resp.code.nil? ? nil : resp.code.to_i, :status_message => resp.status_message, :return_message => resp.return_message, :refs => [], :uid => uid(url) })
79
- @errors.links[url].refs << OpenStruct.new({:src => src[:src], :src_location => "line #{src[:line]}", :snippet => src[:snippet]})
80
- end
81
- end
82
- end
83
- end
84
- end
85
- @hydra.run
86
- msg = ''
87
- msg << "Loaded #{@phantomjs_count} pages using phantomjs." if @phantomjs_count > 0
88
- msg << "Performed #{@typhoeus_count} requests using typhoeus."
89
- puts msg
90
- @errors
91
- end
92
-
93
- def single url
94
- typhoeus(url) do |resp|
95
- puts "\n++++++++++"
96
- puts "+ Blinkr +"
97
- puts "++++++++++"
98
- puts "\nRequest"
99
- puts "======="
100
- puts "Method: #{resp.request.options[:method]}"
101
- puts "Max redirects: #{resp.request.options[:maxredirs]}"
102
- puts "Follow location header: #{resp.request.options[:followlocation]}"
103
- puts "Timeout (s): #{resp.request.options[:timeout] || 'none'}"
104
- puts "Connection timeout (s): #{resp.request.options[:connecttimeout] || 'none'}"
105
- puts "\nHeaders"
106
- puts "-------"
107
- unless resp.request.options[:headers].nil?
108
- resp.request.options[:headers].each do |name, value|
109
- puts "#{name}: #{value}"
110
- end
111
- end
112
- puts "\nResponse"
113
- puts "========"
114
- puts "Status Code: #{resp.code}"
115
- puts "Status Message: #{resp.status_message}"
116
- puts "Message: #{resp.return_message}" unless resp.return_message.nil? || resp.return_message == 'No error'
117
- puts "\nHeaders"
118
- puts "-------"
119
- puts resp.response_headers
120
- end
121
- @hydra.run
122
- end
123
-
124
- private
125
-
126
- def ignored? url, code, message
127
- @ignores.any? { |ignore| ( ignore.has_key?('url') ? !ignore['url'].match(url).nil? : true ) && ( ignore.has_key?('code') ? ignore['code'] == code : true ) && ( ignore.has_key?('message') ? !ignore['message'].match(message).nil? : true ) }
128
- end
129
-
130
- def sanitize url, src
131
- begin
132
- uri = URI(url)
133
- uri = URI.join(src, url) if uri.path.nil? || uri.path.empty? || uri.relative?
134
- uri = URI.join(@base_url, uri) if uri.scheme.nil?
135
- uri.fragment = '' if @ignore_fragments
136
- url = uri.to_s
137
- rescue Exception => e
138
- end
139
- url.chomp('#').chomp('index.html')
140
- end
141
-
142
- def uid url
143
- url.gsub(/:|\/|\.|\?|#|%|=|&|,|~|;|\!|@|\)|\(|\s/, '_')
144
- end
145
-
146
- def pages urls
147
- if @browser == 'phantomjs'
148
- Parallel.each(urls, :in_threads => @phantomjs_threads) do |url|
149
- phantomjs url, @max_page_retrys, &Proc.new
150
- end
151
- else
152
- urls.each do |url|
153
- typhoeus url, @max_page_retrys, &Proc.new
154
- end
155
- end
156
- end
157
-
158
- def phantomjs url, limit = @max_retrys, max = -1
159
- max = limit if max == -1
160
- if @skips.none? { |regex| regex.match(url) }
161
- Tempfile.open('blinkr') do|f|
162
- if system "phantomjs #{SNAP_JS} #{url} #{@viewport} #{f.path}"
163
- json = JSON.load(File.read(f.path))
164
- json['resourceErrors'].each do |error|
165
- start = error['errorString'].rindex('server replied: ')
166
- errorString = error['errorString'].slice(start.nil? ? 0 : start + 16, error['errorString'].length) unless error['errorString'].nil?
167
- unless ignored? error['url'], error['errorCode'].nil? ? nil : error['errorCode'].to_i, errorString
168
- @errors.resources[url] ||= OpenStruct.new({:uid => uid(url), :messages => [] })
169
- @errors.resources[url].messages << OpenStruct.new(error.update({'errorString' => errorString}))
170
- end
171
- end
172
- json['javascriptErrors'].each do |error|
173
- @errors.javascript[url] ||= OpenStruct.new({:uid => uid(url), :messages => []})
174
- @errors.javascript[url].messages << OpenStruct.new(error)
175
- end
176
- response = Typhoeus::Response.new(code: 200, body: json['content'], mock: true)
177
- response.request = Typhoeus::Request.new(url)
178
- Typhoeus.stub(url).and_return(response)
179
- yield response
180
- else
181
- if limit > 1
182
- puts "Loading #{url} via phantomjs (attempt #{max - limit + 2} of #{max})" if @verbose
183
- phantomjs url, limit - 1, max, &Proc.new
184
- else
185
- puts "Loading #{url} via phantomjs failed" if @verbose
186
- response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
187
- response.request = Typhoeus::Request.new(url)
188
- Typhoeus.stub(url).and_return(response)
189
- yield response
190
- end
191
- end
192
- end
193
- @phantomjs_count += 1
194
- end
195
- end
196
-
197
- def retry? resp
198
- resp.timed_out? || (resp.code == 0 && [ "Server returned nothing (no headers, no data)", "SSL connect error", "Failure when receiving data from the peer" ].include?(resp.return_message) )
199
- end
200
-
201
- def typhoeus url, limit = @max_retrys, max = -1
202
- max = limit if max == -1
203
- if @skips.none? { |regex| regex.match(url) }
204
- req = Typhoeus::Request.new(
205
- url,
206
- followlocation: true,
207
- verbose: @vverbose
208
- )
209
- req.on_complete do |resp|
210
- if retry? resp
211
- if limit > 1
212
- puts "Loading #{url} via typhoeus (attempt #{max - limit + 2} of #{max})" if @verbose
213
- typhoeus(url, limit - 1, max, &Proc.new)
214
- else
215
- puts "Loading #{url} via typhoeus failed" if @verbose
216
- response = Typhoeus::Response.new(code: 0, status_message: "Server timed out after #{max} retries", mock: true)
217
- response.request = Typhoeus::Request.new(url)
218
- Typhoeus.stub(url).and_return(response)
219
- yield response
220
- end
221
- else
222
- yield resp
223
- end
224
- end
225
- @hydra.queue req
226
- @typhoeus_count += 1
227
- end
228
- end
229
- end
230
- end
231
-