html-proofer 2.5.2 → 3.0.0
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 +4 -4
- data/bin/htmlproofer +85 -0
- data/lib/html-proofer/cache.rb +153 -0
- data/lib/{html/proofer/checks → html-proofer/check}/favicon.rb +2 -8
- data/lib/html-proofer/check/html.rb +21 -0
- data/lib/html-proofer/check/images.rb +47 -0
- data/lib/html-proofer/check/links.rb +120 -0
- data/lib/html-proofer/check/scripts.rb +28 -0
- data/lib/html-proofer/check.rb +63 -0
- data/lib/html-proofer/configuration.rb +56 -0
- data/lib/html-proofer/element.rb +165 -0
- data/lib/{html/proofer/check_runner → html-proofer}/issue.rb +8 -10
- data/lib/html-proofer/log.rb +49 -0
- data/lib/html-proofer/runner.rb +160 -0
- data/lib/html-proofer/url_validator.rb +218 -0
- data/lib/html-proofer/utils.rb +40 -0
- data/lib/html-proofer/version.rb +3 -0
- data/lib/html-proofer.rb +47 -0
- metadata +49 -20
- data/bin/htmlproof +0 -85
- data/lib/html/proofer/cache.rb +0 -16
- data/lib/html/proofer/check_runner.rb +0 -69
- data/lib/html/proofer/checkable.rb +0 -164
- data/lib/html/proofer/checks/html.rb +0 -46
- data/lib/html/proofer/checks/images.rb +0 -54
- data/lib/html/proofer/checks/links.rb +0 -124
- data/lib/html/proofer/checks/scripts.rb +0 -40
- data/lib/html/proofer/log.rb +0 -42
- data/lib/html/proofer/url_validator.rb +0 -179
- data/lib/html/proofer/utils.rb +0 -26
- data/lib/html/proofer/version.rb +0 -5
- data/lib/html/proofer/xpathfunctions.rb +0 -9
- data/lib/html/proofer.rb +0 -202
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
class LinkCheckable < ::HTML::Proofer::Checkable
|
|
2
|
-
|
|
3
|
-
def href
|
|
4
|
-
real_attr @href
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
def id
|
|
8
|
-
real_attr @id
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def name
|
|
12
|
-
real_attr @name
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def missing_href?
|
|
16
|
-
href.nil? && name.nil? && id.nil?
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def placeholder?
|
|
20
|
-
(id || name) && href.nil?
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
class LinkCheck < ::HTML::Proofer::CheckRunner
|
|
25
|
-
include HTML::Proofer::Utils
|
|
26
|
-
|
|
27
|
-
def run
|
|
28
|
-
@html.css('a, link').each do |node|
|
|
29
|
-
link = LinkCheckable.new(node, self)
|
|
30
|
-
line = node.line
|
|
31
|
-
|
|
32
|
-
next if link.ignore?
|
|
33
|
-
next if link.href =~ /^javascript:/ # can't put this in ignore? because the URI does not parse
|
|
34
|
-
next if link.placeholder?
|
|
35
|
-
|
|
36
|
-
# is it even a valid URL?
|
|
37
|
-
unless link.valid?
|
|
38
|
-
add_issue("#{link.href} is an invalid URL", line)
|
|
39
|
-
next
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
check_schemes(link, line)
|
|
43
|
-
|
|
44
|
-
# is there even a href?
|
|
45
|
-
if link.missing_href?
|
|
46
|
-
add_issue('anchor has no href attribute', line)
|
|
47
|
-
next
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# intentionally here because we still want valid? & missing_href? to execute
|
|
51
|
-
next if link.non_http_remote?
|
|
52
|
-
|
|
53
|
-
# does the file even exist?
|
|
54
|
-
if link.remote?
|
|
55
|
-
add_to_external_urls(link.href, line)
|
|
56
|
-
next
|
|
57
|
-
elsif !link.internal?
|
|
58
|
-
add_issue("internally linking to #{link.href}, which does not exist", line) unless link.exists?
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# does the local directory have a trailing slash?
|
|
62
|
-
if link.unslashed_directory? link.absolute_path
|
|
63
|
-
add_issue("internally linking to a directory #{link.absolute_path} without trailing slash", line)
|
|
64
|
-
next
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# verify the target hash
|
|
68
|
-
handle_hash(link, line) if link.hash
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
external_urls
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def check_schemes(link, line)
|
|
75
|
-
case link.scheme
|
|
76
|
-
when 'mailto'
|
|
77
|
-
handle_mailto(link, line)
|
|
78
|
-
when 'tel'
|
|
79
|
-
handle_tel(link, line)
|
|
80
|
-
when 'http'
|
|
81
|
-
add_issue("#{link.href} is not an HTTPS link", line) if @options[:enforce_https]
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def handle_mailto(link, line)
|
|
86
|
-
if link.path.empty?
|
|
87
|
-
add_issue("#{link.href} contains no email address", line)
|
|
88
|
-
elsif !link.path.include?('@')
|
|
89
|
-
add_issue("#{link.href} contains an invalid email address", line)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def handle_tel(link, line)
|
|
94
|
-
add_issue("#{link.href} contains no phone number", line) if link.path.empty?
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def handle_hash(link, line)
|
|
98
|
-
if link.internal?
|
|
99
|
-
unless hash_check @html, link.hash
|
|
100
|
-
add_issue("linking to internal hash ##{link.hash} that does not exist", line)
|
|
101
|
-
end
|
|
102
|
-
elsif link.external?
|
|
103
|
-
external_link_check(link, line)
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def external_link_check(link, line)
|
|
108
|
-
if !link.exists?
|
|
109
|
-
add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist", line)
|
|
110
|
-
else
|
|
111
|
-
target_html = create_nokogiri link.absolute_path
|
|
112
|
-
unless hash_check target_html, link.hash
|
|
113
|
-
add_issue("linking to #{link.href}, but #{link.hash} does not exist", line)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def hash_check(html, href_hash)
|
|
119
|
-
html.xpath("//*[case_insensitive_equals(@id, '#{href_hash}')]", \
|
|
120
|
-
"//*[case_insensitive_equals(@name, '#{href_hash}')]", \
|
|
121
|
-
HTML::Proofer::XpathFunctions.new).length > 0
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
|
-
|
|
3
|
-
class ScriptCheckable < ::HTML::Proofer::Checkable
|
|
4
|
-
|
|
5
|
-
def src
|
|
6
|
-
real_attr @src
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def missing_src?
|
|
10
|
-
!src
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def blank?
|
|
14
|
-
@text.strip.empty?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
class ScriptCheck < ::HTML::Proofer::CheckRunner
|
|
20
|
-
def run
|
|
21
|
-
@html.css('script').each do |node|
|
|
22
|
-
script = ScriptCheckable.new(node, self)
|
|
23
|
-
line = node.line
|
|
24
|
-
|
|
25
|
-
next if script.ignore?
|
|
26
|
-
next unless script.blank?
|
|
27
|
-
|
|
28
|
-
# does the script exist?
|
|
29
|
-
if script.missing_src?
|
|
30
|
-
add_issue('script is empty and has no src attribute', line)
|
|
31
|
-
elsif script.remote?
|
|
32
|
-
add_to_external_urls(script.src, line)
|
|
33
|
-
else
|
|
34
|
-
add_issue("internal script #{script.src} does not exist", line) unless script.exists?
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
external_urls
|
|
39
|
-
end
|
|
40
|
-
end
|
data/lib/html/proofer/log.rb
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
require 'yell'
|
|
2
|
-
require 'colored'
|
|
3
|
-
|
|
4
|
-
module HTML
|
|
5
|
-
class Proofer
|
|
6
|
-
class Log
|
|
7
|
-
include Yell::Loggable
|
|
8
|
-
|
|
9
|
-
def initialize(verbose, verbosity = nil)
|
|
10
|
-
log_level = if verbosity.nil?
|
|
11
|
-
verbose ? :debug : :info
|
|
12
|
-
else
|
|
13
|
-
verbosity
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
@logger = Yell.new(:format => false, \
|
|
17
|
-
:name => 'HTML::Proofer', \
|
|
18
|
-
:level => "gte.#{log_level}") do |l|
|
|
19
|
-
l.adapter :stdout, :level => [:debug, :info, :warn]
|
|
20
|
-
l.adapter :stderr, :level => [:error, :fatal]
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def log(level, color, message)
|
|
25
|
-
@logger.send level, colorize(color, message)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def colorize(color, message)
|
|
29
|
-
if $stdout.isatty && $stderr.isatty
|
|
30
|
-
Colored.colorize(message, foreground: color)
|
|
31
|
-
else
|
|
32
|
-
message
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# dumb override to play nice with Typhoeus/Ethon
|
|
37
|
-
def debug(message = nil)
|
|
38
|
-
log(:debug, :yellow, message) unless message.nil?
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
require 'typhoeus'
|
|
2
|
-
require 'uri'
|
|
3
|
-
require_relative './utils'
|
|
4
|
-
|
|
5
|
-
module HTML
|
|
6
|
-
class Proofer
|
|
7
|
-
class UrlValidator
|
|
8
|
-
include HTML::Proofer::Utils
|
|
9
|
-
|
|
10
|
-
attr_accessor :logger, :external_urls, :iterable_external_urls, :hydra
|
|
11
|
-
|
|
12
|
-
def initialize(logger, external_urls, options, typhoeus_opts, hydra_opts)
|
|
13
|
-
@logger = logger
|
|
14
|
-
@external_urls = external_urls
|
|
15
|
-
@iterable_external_urls = {}
|
|
16
|
-
@failed_tests = []
|
|
17
|
-
@options = options
|
|
18
|
-
@hydra = Typhoeus::Hydra.new(hydra_opts)
|
|
19
|
-
@typhoeus_opts = typhoeus_opts
|
|
20
|
-
@external_domain_paths_with_queries = {}
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def run
|
|
24
|
-
@iterable_external_urls = remove_query_values
|
|
25
|
-
external_link_checker(@iterable_external_urls)
|
|
26
|
-
@failed_tests
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def remove_query_values
|
|
30
|
-
return if @external_urls.nil?
|
|
31
|
-
iterable_external_urls = @external_urls.dup
|
|
32
|
-
@external_urls.keys.each do |url|
|
|
33
|
-
uri = begin
|
|
34
|
-
Addressable::URI.parse(url)
|
|
35
|
-
rescue URI::Error, Addressable::URI::InvalidURIError
|
|
36
|
-
@logger.log :error, :red, "#{url} is an invalid URL"
|
|
37
|
-
nil
|
|
38
|
-
end
|
|
39
|
-
next if uri.nil? || uri.query.nil?
|
|
40
|
-
iterable_external_urls.delete(url) unless new_url_query_values?(uri)
|
|
41
|
-
end
|
|
42
|
-
iterable_external_urls
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# remember queries we've seen, ignore future ones
|
|
46
|
-
def new_url_query_values?(uri)
|
|
47
|
-
queries = uri.query_values.keys.join('-')
|
|
48
|
-
domain_path = extract_domain_path(uri)
|
|
49
|
-
if @external_domain_paths_with_queries[domain_path].nil?
|
|
50
|
-
@external_domain_paths_with_queries[domain_path] = [queries]
|
|
51
|
-
true
|
|
52
|
-
elsif !@external_domain_paths_with_queries[domain_path].include?(queries)
|
|
53
|
-
@external_domain_paths_with_queries[domain_path] << queries
|
|
54
|
-
true
|
|
55
|
-
else
|
|
56
|
-
false
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def extract_domain_path(uri)
|
|
61
|
-
uri.host + uri.path
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Proofer runs faster if we pull out all the external URLs and run the checks
|
|
65
|
-
# at the end. Otherwise, we're halting the consuming process for every file during
|
|
66
|
-
# the check_directory_of_files process.
|
|
67
|
-
#
|
|
68
|
-
# In addition, sorting the list lets libcurl keep connections to the same hosts alive.
|
|
69
|
-
#
|
|
70
|
-
# Finally, we'll first make a HEAD request, rather than GETing all the contents.
|
|
71
|
-
# If the HEAD fails, we'll fall back to GET, as some servers are not configured
|
|
72
|
-
# for HEAD. If we've decided to check for hashes, we must do a GET--HEAD is
|
|
73
|
-
# not an option.
|
|
74
|
-
def external_link_checker(external_urls)
|
|
75
|
-
external_urls = Hash[external_urls.sort]
|
|
76
|
-
|
|
77
|
-
count = external_urls.length
|
|
78
|
-
check_text = "#{count} " << (count == 1 ? 'external link' : 'external links')
|
|
79
|
-
logger.log :info, :blue, "Checking #{check_text}..."
|
|
80
|
-
|
|
81
|
-
Ethon.logger = logger # log from Typhoeus/Ethon
|
|
82
|
-
|
|
83
|
-
url_processor(external_urls)
|
|
84
|
-
|
|
85
|
-
logger.log :debug, :yellow, "Running requests for all #{hydra.queued_requests.size} external URLs..."
|
|
86
|
-
hydra.run
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def url_processor(external_urls)
|
|
90
|
-
external_urls.each_pair do |href, filenames|
|
|
91
|
-
href = begin
|
|
92
|
-
clean_url(href)
|
|
93
|
-
rescue URI::Error, Addressable::URI::InvalidURIError
|
|
94
|
-
add_external_issue(filenames, "#{href} is an invalid URL")
|
|
95
|
-
next
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
if hash?(href) && @options[:check_external_hash]
|
|
99
|
-
queue_request(:get, href, filenames)
|
|
100
|
-
else
|
|
101
|
-
queue_request(:head, href, filenames)
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def clean_url(href)
|
|
107
|
-
Addressable::URI.parse(href).normalize
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def queue_request(method, href, filenames)
|
|
111
|
-
request = Typhoeus::Request.new(href, @typhoeus_opts.merge({ :method => method }))
|
|
112
|
-
request.on_complete { |response| response_handler(response, filenames) }
|
|
113
|
-
hydra.queue request
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def response_handler(response, filenames)
|
|
117
|
-
effective_url = response.options[:effective_url]
|
|
118
|
-
href = response.request.base_url.to_s
|
|
119
|
-
method = response.request.options[:method]
|
|
120
|
-
response_code = response.code
|
|
121
|
-
|
|
122
|
-
debug_msg = "Received a #{response_code} for #{href}"
|
|
123
|
-
debug_msg << " in #{filenames.join(' ')}" unless filenames.nil?
|
|
124
|
-
logger.log :debug, :yellow, debug_msg
|
|
125
|
-
|
|
126
|
-
if response_code.between?(200, 299)
|
|
127
|
-
check_hash_in_2xx_response(href, effective_url, response, filenames)
|
|
128
|
-
elsif response.timed_out?
|
|
129
|
-
handle_timeout(href, filenames, response_code)
|
|
130
|
-
elsif method == :head
|
|
131
|
-
queue_request(:get, href, filenames)
|
|
132
|
-
else
|
|
133
|
-
return if @options[:only_4xx] && !response_code.between?(400, 499)
|
|
134
|
-
# Received a non-successful http response.
|
|
135
|
-
add_external_issue(filenames, "External link #{href} failed: #{response_code} #{response.return_message}", response_code)
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Even though the response was a success, we may have been asked to check
|
|
140
|
-
# if the hash on the URL exists on the page
|
|
141
|
-
def check_hash_in_2xx_response(href, effective_url, response, filenames)
|
|
142
|
-
return if @options[:only_4xx]
|
|
143
|
-
return unless @options[:check_external_hash]
|
|
144
|
-
return unless (hash = hash?(href))
|
|
145
|
-
|
|
146
|
-
body_doc = create_nokogiri(response.body)
|
|
147
|
-
|
|
148
|
-
# user-content is a special addition by GitHub.
|
|
149
|
-
xpath = %(//*[@name="#{hash}"]|//*[@id="#{hash}"])
|
|
150
|
-
if URI.parse(href).host.match(/github\.com/i)
|
|
151
|
-
xpath << %(|//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
return unless body_doc.xpath(xpath).empty?
|
|
155
|
-
|
|
156
|
-
add_external_issue filenames, "External link #{href} failed: #{effective_url} exists, but the hash '#{hash}' does not", response.code
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def handle_timeout(href, filenames, response_code)
|
|
160
|
-
return if @options[:only_4xx]
|
|
161
|
-
add_external_issue filenames, "External link #{href} failed: got a time out", response_code
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def add_external_issue(filenames, desc, status = nil)
|
|
165
|
-
if filenames.nil?
|
|
166
|
-
@failed_tests << CheckRunner::Issue.new('', desc, nil, status)
|
|
167
|
-
else
|
|
168
|
-
filenames.each { |f| @failed_tests << CheckRunner::Issue.new(f, desc, nil, status) }
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def hash?(url)
|
|
173
|
-
URI.parse(url).fragment
|
|
174
|
-
rescue URI::InvalidURIError
|
|
175
|
-
nil
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
data/lib/html/proofer/utils.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require 'nokogiri'
|
|
2
|
-
|
|
3
|
-
module HTML
|
|
4
|
-
class Proofer
|
|
5
|
-
module Utils
|
|
6
|
-
def create_nokogiri(path)
|
|
7
|
-
if File.exist? path
|
|
8
|
-
content = File.open(path).read
|
|
9
|
-
else
|
|
10
|
-
content = path
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
Nokogiri::HTML(content)
|
|
14
|
-
end
|
|
15
|
-
module_function :create_nokogiri
|
|
16
|
-
|
|
17
|
-
def swap(href, replacement)
|
|
18
|
-
replacement.each do |link, replace|
|
|
19
|
-
href = href.gsub(link, replace)
|
|
20
|
-
end
|
|
21
|
-
href
|
|
22
|
-
end
|
|
23
|
-
module_function :swap
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
data/lib/html/proofer/version.rb
DELETED
data/lib/html/proofer.rb
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
def require_all(path)
|
|
2
|
-
glob = File.join(File.dirname(__FILE__), path, '*.rb')
|
|
3
|
-
Dir[glob].each do |f|
|
|
4
|
-
require f
|
|
5
|
-
end
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
require_all 'proofer'
|
|
9
|
-
require_all 'proofer/check_runner'
|
|
10
|
-
require_all 'proofer/checks'
|
|
11
|
-
require_relative './proofer/utils'
|
|
12
|
-
require_relative './proofer/xpathfunctions'
|
|
13
|
-
|
|
14
|
-
require 'parallel'
|
|
15
|
-
|
|
16
|
-
begin
|
|
17
|
-
require 'awesome_print'
|
|
18
|
-
rescue LoadError; end
|
|
19
|
-
|
|
20
|
-
module HTML
|
|
21
|
-
|
|
22
|
-
class Proofer
|
|
23
|
-
include HTML::Proofer::Utils
|
|
24
|
-
|
|
25
|
-
attr_reader :options, :typhoeus_opts, :hydra_opts, :parallel_opts, :validation_opts, :external_urls, :iterable_external_urls
|
|
26
|
-
|
|
27
|
-
TYPHOEUS_DEFAULTS = {
|
|
28
|
-
:followlocation => true,
|
|
29
|
-
:headers => {
|
|
30
|
-
'User-Agent' => "Mozilla/5.0 (compatible; HTML Proofer/#{VERSION}; +https://github.com/gjtorikian/html-proofer)"
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
def initialize(src, opts = {})
|
|
35
|
-
@src = src
|
|
36
|
-
|
|
37
|
-
if opts[:verbose]
|
|
38
|
-
warn '`@options[:verbose]` will be removed in a future 3.x.x release: http://git.io/vGHHh'
|
|
39
|
-
end
|
|
40
|
-
if opts[:href_ignore]
|
|
41
|
-
warn '`@options[:href_ignore]` will be renamed in a future 3.x.x release: http://git.io/vGHHy'
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
@proofer_opts = {
|
|
45
|
-
:ext => '.html',
|
|
46
|
-
:check_favicon => false,
|
|
47
|
-
:href_swap => [],
|
|
48
|
-
:href_ignore => [],
|
|
49
|
-
:file_ignore => [],
|
|
50
|
-
:url_ignore => [],
|
|
51
|
-
:check_external_hash => false,
|
|
52
|
-
:alt_ignore => [],
|
|
53
|
-
:empty_alt_ignore => false,
|
|
54
|
-
:enforce_https => false,
|
|
55
|
-
:disable_external => false,
|
|
56
|
-
:verbose => false,
|
|
57
|
-
:only_4xx => false,
|
|
58
|
-
:directory_index_file => 'index.html',
|
|
59
|
-
:check_html => false,
|
|
60
|
-
:error_sort => :path,
|
|
61
|
-
:checks_to_ignore => []
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
@typhoeus_opts = TYPHOEUS_DEFAULTS.merge(opts[:typhoeus] || {})
|
|
65
|
-
opts.delete(:typhoeus)
|
|
66
|
-
|
|
67
|
-
@hydra_opts = opts[:hydra] || {}
|
|
68
|
-
opts.delete(:hydra)
|
|
69
|
-
|
|
70
|
-
# fall back to parallel defaults
|
|
71
|
-
@parallel_opts = opts[:parallel] || {}
|
|
72
|
-
opts.delete(:parallel)
|
|
73
|
-
|
|
74
|
-
@validation_opts = opts[:validation] || {}
|
|
75
|
-
opts.delete(:validation)
|
|
76
|
-
|
|
77
|
-
@options = @proofer_opts.merge(opts)
|
|
78
|
-
|
|
79
|
-
@failed_tests = []
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def logger
|
|
83
|
-
@logger ||= HTML::Proofer::Log.new(@options[:verbose], @options[:verbosity])
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def run
|
|
87
|
-
count = checks.length
|
|
88
|
-
check_text = "#{checks} " << (count == 1 ? 'check' : 'checks')
|
|
89
|
-
logger.log :info, :blue, "Running #{check_text} on #{@src} on *#{@options[:ext]}... \n\n"
|
|
90
|
-
|
|
91
|
-
if @src.is_a?(Array) && !@options[:disable_external]
|
|
92
|
-
check_list_of_links
|
|
93
|
-
else
|
|
94
|
-
check_directory_of_files
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
if @failed_tests.empty?
|
|
98
|
-
logger.log :info, :green, 'HTML-Proofer finished successfully.'
|
|
99
|
-
else
|
|
100
|
-
print_failed_tests
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def check_list_of_links
|
|
105
|
-
if @options[:href_swap]
|
|
106
|
-
@src = @src.map do |url|
|
|
107
|
-
swap(url, @options[:href_swap])
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
@external_urls = Hash[*@src.map { |s| [s, nil] }.flatten]
|
|
111
|
-
validate_urls
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Collects any external URLs found in a directory of files. Also collectes
|
|
115
|
-
# every failed test from check_files_for_internal_woes.
|
|
116
|
-
# Sends the external URLs to Typhoeus for batch processing.
|
|
117
|
-
def check_directory_of_files
|
|
118
|
-
@external_urls = {}
|
|
119
|
-
results = check_files_for_internal_woes
|
|
120
|
-
|
|
121
|
-
results.each do |item|
|
|
122
|
-
@external_urls.merge!(item[:external_urls])
|
|
123
|
-
@failed_tests.concat(item[:failed_tests])
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
validate_urls unless @options[:disable_external]
|
|
127
|
-
|
|
128
|
-
logger.log :info, :blue, "Ran on #{files.length} files!\n\n"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Walks over each implemented check and runs them on the files, in parallel.
|
|
132
|
-
def check_files_for_internal_woes
|
|
133
|
-
Parallel.map(files, @parallel_opts) do |path|
|
|
134
|
-
html = create_nokogiri(path)
|
|
135
|
-
result = { :external_urls => {}, :failed_tests => [] }
|
|
136
|
-
|
|
137
|
-
checks.each do |klass|
|
|
138
|
-
logger.log :debug, :yellow, "Checking #{klass.to_s.downcase} on #{path} ..."
|
|
139
|
-
check = Object.const_get(klass).new(@src, path, html, @options, @typhoeus_opts, @hydra_opts, @parallel_opts, @validation_opts)
|
|
140
|
-
check.run
|
|
141
|
-
result[:external_urls].merge!(check.external_urls)
|
|
142
|
-
result[:failed_tests].concat(check.issues) if check.issues.length > 0
|
|
143
|
-
end
|
|
144
|
-
result
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def validate_urls
|
|
149
|
-
url_validator = HTML::Proofer::UrlValidator.new(logger, @external_urls, @options, @typhoeus_opts, @hydra_opts)
|
|
150
|
-
@failed_tests.concat(url_validator.run)
|
|
151
|
-
@iterable_external_urls = url_validator.iterable_external_urls
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def files
|
|
155
|
-
if File.directory? @src
|
|
156
|
-
pattern = File.join(@src, '**', "*#{@options[:ext]}")
|
|
157
|
-
files = Dir.glob(pattern).select { |fn| File.file? fn }
|
|
158
|
-
files.reject { |f| ignore_file?(f) }
|
|
159
|
-
elsif File.extname(@src) == @options[:ext]
|
|
160
|
-
[@src].reject { |f| ignore_file?(f) }
|
|
161
|
-
else
|
|
162
|
-
[]
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def ignore_file?(file)
|
|
167
|
-
options[:file_ignore].each do |pattern|
|
|
168
|
-
return true if pattern.is_a?(String) && pattern == file
|
|
169
|
-
return true if pattern.is_a?(Regexp) && pattern =~ file
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
false
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def checks
|
|
176
|
-
return @checks unless @checks.nil?
|
|
177
|
-
@checks = HTML::Proofer::CheckRunner.checks.map(&:name)
|
|
178
|
-
@checks.delete('FaviconCheck') unless @options[:check_favicon]
|
|
179
|
-
@checks.delete('HtmlCheck') unless @options[:check_html]
|
|
180
|
-
@options[:checks_to_ignore].each do |ignored|
|
|
181
|
-
@checks.delete(ignored)
|
|
182
|
-
end
|
|
183
|
-
@checks
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def failed_tests
|
|
187
|
-
return [] if @failed_tests.empty?
|
|
188
|
-
result = []
|
|
189
|
-
@failed_tests.each { |f| result << f.to_s }
|
|
190
|
-
result
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def print_failed_tests
|
|
194
|
-
sorted_failures = HTML::Proofer::CheckRunner::SortedIssues.new(@failed_tests, @options[:error_sort], logger)
|
|
195
|
-
|
|
196
|
-
sorted_failures.sort_and_report
|
|
197
|
-
count = @failed_tests.length
|
|
198
|
-
failure_text = "#{count} " << (count == 1 ? 'failure' : 'failures')
|
|
199
|
-
fail logger.colorize :red, "HTML-Proofer found #{failure_text}!"
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|