headhunter 0.0.1 → 0.0.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.
- checksums.yaml +7 -0
- data/lib/headhunter.rb +55 -0
- data/lib/headhunter/css_hunter.rb +100 -0
- data/lib/headhunter/css_validator.rb +131 -0
- data/lib/headhunter/html_validator.rb +58 -0
- data/lib/headhunter/rack/capturing_middleware.rb +28 -0
- data/lib/headhunter/rails.rb +21 -0
- data/lib/headhunter/templates/result.html +8 -0
- data/lib/headhunter/templates/results.html +39 -0
- metadata +82 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 79ba313e9b12c21aa35c4fc74bfe40f86801c9fd
|
4
|
+
data.tar.gz: afc84375e5fb68c8fe8af5d4e5af6f30826c0d51
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d6c891576972b9fba784d4985f874308a639c8f27973b4813c9542eb08eaca6ef8e5a5a68ac7218e9fb72d5760d36da761ecc67bb5a4f8b24322f86a22314ea2
|
7
|
+
data.tar.gz: 1c8dc6fed4c64758bb0dc3b07756fcd19b8b76ceb53a912cf7c64e09e4abe03b88397eaedc0c732e8795d81c93906c935166deb6d9d7b7f048488db0566ef800
|
data/lib/headhunter.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'headhunter/css_hunter'
|
2
|
+
require 'headhunter/css_validator'
|
3
|
+
require 'headhunter/html_validator'
|
4
|
+
require 'headhunter/rails'
|
5
|
+
# require 'rack/utils'
|
6
|
+
|
7
|
+
class Headhunter
|
8
|
+
attr_accessor :results
|
9
|
+
|
10
|
+
def initialize(root)
|
11
|
+
@root = root
|
12
|
+
|
13
|
+
precompile_assets!
|
14
|
+
|
15
|
+
@html_validator = HtmlValidator.new
|
16
|
+
@css_validator = CssValidator.new(stylesheets)
|
17
|
+
@css_hunter = CssHunter.new(stylesheets)
|
18
|
+
|
19
|
+
@css_validator.process!
|
20
|
+
end
|
21
|
+
|
22
|
+
def process!(url, html)
|
23
|
+
@html_validator.process!(url, html)
|
24
|
+
@css_hunter.process!(url, html)
|
25
|
+
end
|
26
|
+
|
27
|
+
def clean_up!
|
28
|
+
remove_assets!
|
29
|
+
end
|
30
|
+
|
31
|
+
def report
|
32
|
+
@html_validator.prepare_results_html
|
33
|
+
|
34
|
+
@html_validator.report
|
35
|
+
@css_validator.report
|
36
|
+
@css_hunter.report
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def precompile_assets!
|
42
|
+
# Remove existing assets! This seems to be necessary to make sure that they don't exist twice, see http://stackoverflow.com/questions/20938891
|
43
|
+
system 'rake assets:clobber HEADHUNTER=false &> /dev/null'
|
44
|
+
|
45
|
+
system 'rake assets:precompile HEADHUNTER=false &> /dev/null'
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_assets!
|
49
|
+
system 'rake assets:clobber HEADHUNTER=false &> /dev/null'
|
50
|
+
end
|
51
|
+
|
52
|
+
def stylesheets
|
53
|
+
Dir.chdir(@root) { Dir.glob('public/assets/*.css') }
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'css_parser'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
class Headhunter
|
6
|
+
class CssHunter
|
7
|
+
def initialize(stylesheets)
|
8
|
+
@stylesheets = stylesheets
|
9
|
+
@parsed_rules = {}
|
10
|
+
@unused_selectors = []
|
11
|
+
@used_selectors = []
|
12
|
+
|
13
|
+
load_css!
|
14
|
+
end
|
15
|
+
|
16
|
+
def process!(url, html)
|
17
|
+
analyze(html).each do |selector|
|
18
|
+
@unused_selectors.delete(selector)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def report
|
23
|
+
log.puts "Found #{@used_selectors.size + @unused_selectors.size} CSS selectors.".yellow
|
24
|
+
log.puts "#{@used_selectors.size} selectors are in use.".green if @used_selectors.size > 0
|
25
|
+
log.puts "#{@unused_selectors.size} selectors are not in use: #{@unused_selectors.sort.join(', ').red}".red if @unused_selectors.size > 0
|
26
|
+
log.puts
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def analyze(html)
|
32
|
+
doc = Nokogiri::HTML(html)
|
33
|
+
|
34
|
+
@unused_selectors.collect do |selector, declarations|
|
35
|
+
# We test against the selector stripped of any pseudo classes,
|
36
|
+
# but we report on the selector with its pseudo classes.
|
37
|
+
stripped_selector = strip(selector)
|
38
|
+
|
39
|
+
next if stripped_selector.empty?
|
40
|
+
|
41
|
+
if doc.search(stripped_selector).any?
|
42
|
+
@used_selectors << selector
|
43
|
+
selector
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load_css!
|
49
|
+
@stylesheets.each do |stylesheet|
|
50
|
+
new_selector_count = add_css!(fetch(stylesheet))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch(path)
|
55
|
+
log.puts(path)
|
56
|
+
|
57
|
+
loc = path
|
58
|
+
|
59
|
+
begin
|
60
|
+
open(loc).read
|
61
|
+
rescue Errno::ENOENT
|
62
|
+
raise FetchError.new("#{loc} was not found")
|
63
|
+
rescue OpenURI::HTTPError => e
|
64
|
+
raise FetchError.new("retrieving #{loc} raised an HTTP error: #{e.message}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def add_css!(css)
|
69
|
+
parser = CssParser::Parser.new
|
70
|
+
parser.add_block!(css)
|
71
|
+
|
72
|
+
selector_count = 0
|
73
|
+
|
74
|
+
parser.each_selector do |selector, declarations, specificity|
|
75
|
+
next if @unused_selectors.include?(selector)
|
76
|
+
next if selector =~ @ignore_selectors
|
77
|
+
next if has_pseudo_classes(selector) and @unused_selectors.include?(strip(selector))
|
78
|
+
|
79
|
+
@unused_selectors << selector
|
80
|
+
@parsed_rules[selector] = declarations
|
81
|
+
|
82
|
+
selector_count += 1
|
83
|
+
end
|
84
|
+
|
85
|
+
selector_count
|
86
|
+
end
|
87
|
+
|
88
|
+
def has_pseudo_classes(selector)
|
89
|
+
selector =~ /::?[\w\-]+/
|
90
|
+
end
|
91
|
+
|
92
|
+
def strip(selector)
|
93
|
+
selector = selector.gsub(/^@.*/, '') # @-webkit-keyframes ...
|
94
|
+
selector = selector.gsub(/:.*/, '') # input#x:nth-child(2):not(#z.o[type='file'])
|
95
|
+
selector
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class FetchError < StandardError; end
|
100
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
class Headhunter
|
5
|
+
class CssValidator
|
6
|
+
def initialize(stylesheets)
|
7
|
+
@profile = 'css3' # TODO: Option for profile css1 and css21
|
8
|
+
@stylesheets = stylesheets
|
9
|
+
@messages_per_stylesheet = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def process!
|
13
|
+
@stylesheets.each do |stylesheet|
|
14
|
+
css = fetch(stylesheet)
|
15
|
+
css = ' ' if css.empty? # The validator returns a 500 error if it receives an empty string
|
16
|
+
|
17
|
+
response = get_validation_response({text: css, profile: @profile, vextwarning: 'true'})
|
18
|
+
unless response_indicates_valid?(response)
|
19
|
+
process_errors(stylesheet, css, response)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def report
|
25
|
+
log.puts "Validated #{@stylesheets.size} stylesheets.".yellow
|
26
|
+
log.puts "#{x_stylesheets_be(@stylesheets.size - @messages_per_stylesheet.size)} valid.".green if @messages_per_stylesheet.size < @stylesheets.size
|
27
|
+
log.puts "#{x_stylesheets_be(@messages_per_stylesheet.size)} invalid.".red if @messages_per_stylesheet.size > 0
|
28
|
+
|
29
|
+
@messages_per_stylesheet.each_pair do |stylesheet, messages|
|
30
|
+
log.puts " #{extract_filename(stylesheet)}:".red
|
31
|
+
|
32
|
+
messages.each { |message| log.puts " - #{message}".red }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Converts a path like public/assets/application-d205d6f344d8623ca0323cb6f6bd7ca1.css to application.css
|
39
|
+
def extract_filename(path)
|
40
|
+
matches = path.match /^public\/assets\/(.*)-([a-z0-9]*)(\.css)/
|
41
|
+
matches[1] + matches[3]
|
42
|
+
end
|
43
|
+
|
44
|
+
def x_stylesheets_be(size)
|
45
|
+
if size <= 1
|
46
|
+
"#{size} stylesheet is"
|
47
|
+
else
|
48
|
+
"#{size} stylesheets are"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_errors(file, css, response)
|
53
|
+
@messages_per_stylesheet[file] = []
|
54
|
+
|
55
|
+
REXML::Document.new(response.body).root.each_element('//m:error') do |e|
|
56
|
+
@messages_per_stylesheet[file] << "#{error_line_prefix}: line #{e.elements['m:line'].text}: #{e.elements['m:message'].get_text.value.strip}\n"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch(path) # TODO: Move to Headhunter!
|
61
|
+
log.puts(path)
|
62
|
+
|
63
|
+
loc = path
|
64
|
+
|
65
|
+
begin
|
66
|
+
open(loc).read
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
raise FetchError.new("#{loc} was not found")
|
69
|
+
rescue OpenURI::HTTPError => e
|
70
|
+
raise FetchError.new("retrieving #{loc} raised an HTTP error: #{e.message}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_validation_response(query_params)
|
75
|
+
query_params.merge!({:output => 'soap12'})
|
76
|
+
get_validator_response(query_params)
|
77
|
+
end
|
78
|
+
|
79
|
+
def response_indicates_valid?(response)
|
80
|
+
response['x-w3c-validator-status'] == 'Valid'
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_validator_response(query_params = {})
|
84
|
+
response = call_validator(query_params)
|
85
|
+
|
86
|
+
raise "HTTP error: #{response.code}" unless response.is_a? Net::HTTPSuccess
|
87
|
+
return response
|
88
|
+
end
|
89
|
+
|
90
|
+
def call_validator(query_params)
|
91
|
+
boundary = Digest::MD5.hexdigest(Time.now.to_s)
|
92
|
+
data = encode_multipart_params(boundary, query_params)
|
93
|
+
return http_start(validator_host).post2(validator_path, data, "Content-type" => "multipart/form-data; boundary=#{boundary}" )
|
94
|
+
end
|
95
|
+
|
96
|
+
def encode_multipart_params(boundary, params = {})
|
97
|
+
ret = ''
|
98
|
+
params.each do |k,v|
|
99
|
+
unless v.empty?
|
100
|
+
ret << "\r\n--#{boundary}\r\n"
|
101
|
+
ret << "Content-Disposition: form-data; name=\"#{k.to_s}\"\r\n\r\n"
|
102
|
+
ret << v
|
103
|
+
end
|
104
|
+
end
|
105
|
+
ret << "\r\n--#{boundary}--\r\n"
|
106
|
+
ret
|
107
|
+
end
|
108
|
+
|
109
|
+
def http_start(host)
|
110
|
+
if ENV['http_proxy']
|
111
|
+
uri = URI.parse(ENV['http_proxy'])
|
112
|
+
proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo
|
113
|
+
Net::HTTP.start(host, nil, uri.host, uri.port, proxy_user, proxy_pass)
|
114
|
+
else
|
115
|
+
Net::HTTP.start(host)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def validator_host
|
120
|
+
'jigsaw.w3.org'
|
121
|
+
end
|
122
|
+
|
123
|
+
def validator_path
|
124
|
+
'/css-validator/validator'
|
125
|
+
end
|
126
|
+
|
127
|
+
def error_line_prefix
|
128
|
+
'Invalid css'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'html_validation'
|
2
|
+
|
3
|
+
class Headhunter
|
4
|
+
class HtmlValidator
|
5
|
+
def initialize
|
6
|
+
@valid_results = []
|
7
|
+
@invalid_results = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def process!(url, html)
|
11
|
+
html_validation = PageValidations::HTMLValidation.new.validation(html, random_name)
|
12
|
+
(html_validation.valid? ? @valid_results : @invalid_results) << html_validation
|
13
|
+
end
|
14
|
+
|
15
|
+
def prepare_results_html
|
16
|
+
html = File.read File.dirname(File.expand_path(__FILE__)) + '/templates/results.html'
|
17
|
+
html.gsub! '{{VALID_RESULTS}}', prepare_results_for(@valid_results)
|
18
|
+
html.gsub! '{{INVALID_RESULTS}}', prepare_results_for(@invalid_results)
|
19
|
+
File.open('.validation/results.html', 'w') { |file| file.write(html) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_results_for(results)
|
23
|
+
results.map do |result|
|
24
|
+
exceptions_html = ::Rack::Utils.escape_html(File.read(".validation/#{result.resource}.exceptions.txt"))
|
25
|
+
|
26
|
+
full_result_html = File.read File.dirname(File.expand_path(__FILE__)) + '/templates/result.html'
|
27
|
+
full_result_html.gsub! '{{RESOURCE}}', result.resource
|
28
|
+
full_result_html.gsub! '{{EXCEPTIONS}}', exceptions_html
|
29
|
+
full_result_html.gsub! '{{HTML_CONTEXT}}', 'context'
|
30
|
+
full_result_html.gsub! '{{LINK}}', "#{result.resource}.html.txt"
|
31
|
+
|
32
|
+
full_result_html
|
33
|
+
end.join
|
34
|
+
end
|
35
|
+
|
36
|
+
def report
|
37
|
+
log.puts "Validated #{@valid_results.size + @invalid_results.size} HTML pages.".yellow
|
38
|
+
log.puts "#{x_pages_be(@valid_results.size)} valid.".green if @valid_results.size > 0
|
39
|
+
log.puts "#{x_pages_be(@invalid_results.size)} invalid.".red if @invalid_results.size > 0
|
40
|
+
log.puts 'Open .validation/results.html to view full results.'
|
41
|
+
log.puts
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def x_pages_be(size)
|
47
|
+
if size <= 1
|
48
|
+
"#{size} page is"
|
49
|
+
else
|
50
|
+
"#{size} pages are"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def random_name
|
55
|
+
(0...8).map { (65 + rand(26)).chr }.join
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Headhunter
|
2
|
+
module Rack
|
3
|
+
class CapturingMiddleware
|
4
|
+
def initialize(app, headhunter)
|
5
|
+
@app = app
|
6
|
+
@hh = headhunter
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
response = @app.call(env)
|
11
|
+
process(response)
|
12
|
+
response
|
13
|
+
end
|
14
|
+
|
15
|
+
def process(rack_response)
|
16
|
+
status, headers, response = rack_response
|
17
|
+
|
18
|
+
if html = extract_html_from(response)
|
19
|
+
@hh.process!('unknown', html)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def extract_html_from(response)
|
24
|
+
response[0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'headhunter'
|
2
|
+
require 'headhunter/rack/capturing_middleware'
|
3
|
+
|
4
|
+
if ENV['HEADHUNTER'] == 'true'
|
5
|
+
class Headhunter
|
6
|
+
module Rails
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
initializer "headhunter.hijack" do |app|
|
9
|
+
head_hunter = Headhunter.new(::Rails.root)
|
10
|
+
|
11
|
+
at_exit do
|
12
|
+
head_hunter.report
|
13
|
+
head_hunter.clean_up!
|
14
|
+
end
|
15
|
+
|
16
|
+
app.middleware.insert(0, Headhunter::Rack::CapturingMiddleware, head_hunter)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Headhunter Results</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>Headhunter Results</h1>
|
8
|
+
|
9
|
+
<h2>Invalid results</h2>
|
10
|
+
<table>
|
11
|
+
<thead>
|
12
|
+
<tr>
|
13
|
+
<td>Resource</td>
|
14
|
+
<td>Exceptions</td>
|
15
|
+
<td>HTML context</td>
|
16
|
+
<td>Full source</td>
|
17
|
+
</tr>
|
18
|
+
</thead>
|
19
|
+
<tbody>
|
20
|
+
{{INVALID_RESULTS}}
|
21
|
+
</tbody>
|
22
|
+
</table>
|
23
|
+
|
24
|
+
<h2>Valid results</h2>
|
25
|
+
<table>
|
26
|
+
<thead>
|
27
|
+
<tr>
|
28
|
+
<td>Resource</td>
|
29
|
+
<td>Exceptions</td>
|
30
|
+
<td>HTML context</td>
|
31
|
+
<td>Full source</td>
|
32
|
+
</tr>
|
33
|
+
</thead>
|
34
|
+
<tbody>
|
35
|
+
{{VALID_RESULTS}}
|
36
|
+
</tbody>
|
37
|
+
</table>
|
38
|
+
</body>
|
39
|
+
</html>
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: headhunter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Joshua Muheim
|
@@ -14,23 +13,20 @@ dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: nokogiri
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: css_parser
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,65 +41,126 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: html_validation
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :runtime
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: colorize
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- -
|
59
|
+
- - '>='
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '0'
|
70
62
|
type: :runtime
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- -
|
66
|
+
- - '>='
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '0'
|
78
|
-
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fuubar
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2.0'
|
125
|
+
description: Headhunter is an HTML and CSS validation tool that injects itself into
|
126
|
+
your Rails feature tests and automagically checks all your generated HTML and CSS
|
127
|
+
for validity. In addition, it also looks out for unused (and therefore superfluous)
|
128
|
+
CSS selectors.
|
79
129
|
email: josh@muheimwebdesign.ch
|
80
130
|
executables: []
|
81
131
|
extensions: []
|
82
132
|
extra_rdoc_files: []
|
83
|
-
files:
|
133
|
+
files:
|
134
|
+
- lib/headhunter/css_hunter.rb
|
135
|
+
- lib/headhunter/css_validator.rb
|
136
|
+
- lib/headhunter/html_validator.rb
|
137
|
+
- lib/headhunter/rack/capturing_middleware.rb
|
138
|
+
- lib/headhunter/rails.rb
|
139
|
+
- lib/headhunter/templates/result.html
|
140
|
+
- lib/headhunter/templates/results.html
|
141
|
+
- lib/headhunter.rb
|
84
142
|
homepage: http://github.com/jmuheim/headhunter
|
85
143
|
licenses:
|
86
144
|
- MIT
|
145
|
+
metadata: {}
|
87
146
|
post_install_message:
|
88
147
|
rdoc_options: []
|
89
148
|
require_paths:
|
90
149
|
- lib
|
91
150
|
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
-
none: false
|
93
151
|
requirements:
|
94
|
-
- -
|
152
|
+
- - '>='
|
95
153
|
- !ruby/object:Gem::Version
|
96
154
|
version: '0'
|
97
155
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
156
|
requirements:
|
100
|
-
- -
|
157
|
+
- - '>='
|
101
158
|
- !ruby/object:Gem::Version
|
102
159
|
version: '0'
|
103
160
|
requirements: []
|
104
161
|
rubyforge_project:
|
105
|
-
rubygems_version: 1.
|
162
|
+
rubygems_version: 2.1.11
|
106
163
|
signing_key:
|
107
|
-
specification_version:
|
108
|
-
summary:
|
109
|
-
suite and validates your HTML after every request
|
164
|
+
specification_version: 4
|
165
|
+
summary: Zero config HTML & CSS validation tool for Rails apps
|
110
166
|
test_files: []
|