html-proofer 3.13.0 → 3.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/htmlproofer +3 -6
- data/lib/html-proofer.rb +9 -9
- data/lib/html-proofer/cache.rb +6 -14
- data/lib/html-proofer/check.rb +2 -0
- data/lib/html-proofer/check/favicon.rb +7 -5
- data/lib/html-proofer/check/html.rb +4 -4
- data/lib/html-proofer/check/images.rb +3 -7
- data/lib/html-proofer/check/links.rb +6 -4
- data/lib/html-proofer/check/opengraph.rb +0 -1
- data/lib/html-proofer/configuration.rb +5 -5
- data/lib/html-proofer/element.rb +31 -17
- data/lib/html-proofer/issue.rb +1 -3
- data/lib/html-proofer/middleware.rb +12 -12
- data/lib/html-proofer/runner.rb +3 -3
- data/lib/html-proofer/url_validator.rb +15 -14
- data/lib/html-proofer/utils.rb +0 -3
- data/lib/html-proofer/version.rb +1 -1
- metadata +33 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf06ea39b2c106240c1d6c10261b50bab110fa5620451690970c8226bd0022cb
|
4
|
+
data.tar.gz: 70446e5bf8162760c6583f53917c3774ad165d29af0bd01ae600c6069d09360b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab6ef8adc5d80cd409f1cd6248ce7b969f69c219b6878e8d4614852dfc8211430db2fd6863c5e5b03a42326dc315e5538d939f506f95a774b1f48df6a9c95b06
|
7
|
+
data.tar.gz: 603ff3aad5001b0484a753dda3c9b72fa6bac15e94d767f8cb8aaf009810c21b2e7cf5ee039d9261b05c0bb888cad85aa9da769ff0fc13489234bb841c3ee858
|
data/bin/htmlproofer
CHANGED
@@ -47,6 +47,7 @@ Mercenary.program(:htmlproofer) do |p|
|
|
47
47
|
p.option 'typhoeus_config', '--typhoeus-config CONFIG', String, 'JSON-formatted string of Typhoeus config. Will override the html-proofer defaults.'
|
48
48
|
p.option 'url_ignore', '--url-ignore link1,[link2,...]', Array, 'A comma-separated list of Strings or RegExps containing URLs that are safe to ignore. It affects all HTML attributes. Note that non-HTTP(S) URIs are always ignored'
|
49
49
|
p.option 'url_swap', '--url-swap re:string,[re:string,...]', Array, 'A comma-separated list containing key-value pairs of `RegExp => String`. It transforms URLs that match `RegExp` into `String` via `gsub`. The escape sequences `\\:` should be used to produce literal `:`s.'
|
50
|
+
p.option 'root_dir', '--root-folder PATH', String, 'The absolute path to the directory serving your html-files. Used when running html-proofer on a file, rather than a directory.'
|
50
51
|
|
51
52
|
p.action do |args, opts|
|
52
53
|
args = ['.'] if args.empty?
|
@@ -56,9 +57,7 @@ Mercenary.program(:htmlproofer) do |p|
|
|
56
57
|
|
57
58
|
# prepare everything to go to proofer
|
58
59
|
p.options.reject { |o| opts[o.config_key].nil? }.each do |option|
|
59
|
-
if opts[option.config_key].is_a?(Array)
|
60
|
-
opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) }
|
61
|
-
end
|
60
|
+
opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) } if opts[option.config_key].is_a?(Array)
|
62
61
|
options[option.config_key.to_sym] = opts[option.config_key]
|
63
62
|
end
|
64
63
|
|
@@ -82,9 +81,7 @@ Mercenary.program(:htmlproofer) do |p|
|
|
82
81
|
options[:validation][:report_missing_names] = opts['report_missing_names'] unless opts['report_missing_names'].nil?
|
83
82
|
options[:validation][:report_invalid_tags] = opts['report_invalid_tags'] unless opts['report_invalid_tags'].nil?
|
84
83
|
|
85
|
-
unless opts['typhoeus_config'].nil?
|
86
|
-
options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config'])
|
87
|
-
end
|
84
|
+
options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config']) unless opts['typhoeus_config'].nil?
|
88
85
|
|
89
86
|
unless opts['timeframe'].nil?
|
90
87
|
options[:cache] ||= {}
|
data/lib/html-proofer.rb
CHANGED
@@ -17,38 +17,38 @@ require 'fileutils'
|
|
17
17
|
begin
|
18
18
|
require 'awesome_print'
|
19
19
|
require 'pry-byebug'
|
20
|
-
rescue LoadError; end
|
20
|
+
rescue LoadError; end # rubocop:disable Lint/HandleExceptions
|
21
21
|
module HTMLProofer
|
22
|
-
def check_file(file, options = {})
|
22
|
+
def self.check_file(file, options = {})
|
23
23
|
raise ArgumentError unless file.is_a?(String)
|
24
24
|
raise ArgumentError, "#{file} does not exist" unless File.exist?(file)
|
25
|
+
|
25
26
|
options[:type] = :file
|
26
27
|
HTMLProofer::Runner.new(file, options)
|
27
28
|
end
|
28
|
-
module_function :check_file
|
29
29
|
|
30
|
-
def check_directory(directory, options = {})
|
30
|
+
def self.check_directory(directory, options = {})
|
31
31
|
raise ArgumentError unless directory.is_a?(String)
|
32
32
|
raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory)
|
33
|
+
|
33
34
|
options[:type] = :directory
|
34
35
|
HTMLProofer::Runner.new([directory], options)
|
35
36
|
end
|
36
|
-
module_function :check_directory
|
37
37
|
|
38
|
-
def check_directories(directories, options = {})
|
38
|
+
def self.check_directories(directories, options = {})
|
39
39
|
raise ArgumentError unless directories.is_a?(Array)
|
40
|
+
|
40
41
|
options[:type] = :directory
|
41
42
|
directories.each do |directory|
|
42
43
|
raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory)
|
43
44
|
end
|
44
45
|
HTMLProofer::Runner.new(directories, options)
|
45
46
|
end
|
46
|
-
module_function :check_directories
|
47
47
|
|
48
|
-
def check_links(links, options = {})
|
48
|
+
def self.check_links(links, options = {})
|
49
49
|
raise ArgumentError unless links.is_a?(Array)
|
50
|
+
|
50
51
|
options[:type] = :links
|
51
52
|
HTMLProofer::Runner.new(links, options)
|
52
53
|
end
|
53
|
-
module_function :check_links
|
54
54
|
end
|
data/lib/html-proofer/cache.rb
CHANGED
@@ -9,7 +9,7 @@ module HTMLProofer
|
|
9
9
|
include HTMLProofer::Utils
|
10
10
|
|
11
11
|
DEFAULT_STORAGE_DIR = File.join('tmp', '.htmlproofer')
|
12
|
-
DEFAULT_CACHE_FILE_NAME = 'cache.log'
|
12
|
+
DEFAULT_CACHE_FILE_NAME = 'cache.log'
|
13
13
|
|
14
14
|
attr_reader :exists, :cache_log, :storage_dir, :cache_file
|
15
15
|
|
@@ -120,9 +120,8 @@ module HTMLProofer
|
|
120
120
|
@cache_log.each_pair do |url, cache|
|
121
121
|
if within_timeframe?(cache['time'])
|
122
122
|
next if cache['message'].empty? # these were successes to skip
|
123
|
-
urls_to_check[url] = cache['filenames'] # these are failures to retry
|
124
123
|
else
|
125
|
-
urls_to_check[url] = cache['filenames'] #
|
124
|
+
urls_to_check[url] = cache['filenames'] # recheck expired links
|
126
125
|
end
|
127
126
|
end
|
128
127
|
urls_to_check
|
@@ -142,23 +141,16 @@ module HTMLProofer
|
|
142
141
|
end
|
143
142
|
|
144
143
|
def setup_cache!(options)
|
145
|
-
@storage_dir =
|
146
|
-
options[:storage_dir]
|
147
|
-
else
|
148
|
-
DEFAULT_STORAGE_DIR
|
149
|
-
end
|
144
|
+
@storage_dir = options[:storage_dir] || DEFAULT_STORAGE_DIR
|
150
145
|
|
151
146
|
FileUtils.mkdir_p(storage_dir) unless Dir.exist?(storage_dir)
|
152
147
|
|
153
|
-
cache_file_name =
|
154
|
-
options[:cache_file]
|
155
|
-
else
|
156
|
-
DEFAULT_CACHE_FILE_NAME
|
157
|
-
end
|
148
|
+
cache_file_name = options[:cache_file] || DEFAULT_CACHE_FILE_NAME
|
158
149
|
|
159
150
|
@cache_file = File.join(storage_dir, cache_file_name)
|
160
151
|
|
161
152
|
return unless File.exist?(cache_file)
|
153
|
+
|
162
154
|
contents = File.read(cache_file)
|
163
155
|
@cache_log = contents.empty? ? {} : JSON.parse(contents)
|
164
156
|
end
|
@@ -174,7 +166,7 @@ module HTMLProofer
|
|
174
166
|
when :days
|
175
167
|
@cache_datetime - measurement
|
176
168
|
when :hours
|
177
|
-
@cache_datetime - Rational(measurement/24.0)
|
169
|
+
@cache_datetime - Rational(measurement / 24.0)
|
178
170
|
end.to_time
|
179
171
|
end
|
180
172
|
end
|
data/lib/html-proofer/check.rb
CHANGED
@@ -29,6 +29,7 @@ module HTMLProofer
|
|
29
29
|
|
30
30
|
def add_to_external_urls(url)
|
31
31
|
return if @external_urls[url]
|
32
|
+
|
32
33
|
add_path_for_url(url)
|
33
34
|
end
|
34
35
|
|
@@ -45,6 +46,7 @@ module HTMLProofer
|
|
45
46
|
|
46
47
|
ObjectSpace.each_object(Class) do |c|
|
47
48
|
next unless c.superclass == self
|
49
|
+
|
48
50
|
classes << c
|
49
51
|
end
|
50
52
|
|
@@ -6,22 +6,24 @@ class FaviconCheck < ::HTMLProofer::Check
|
|
6
6
|
@html.xpath('//link[not(ancestor::pre or ancestor::code)]').each do |node|
|
7
7
|
favicon = create_element(node)
|
8
8
|
next if favicon.ignore?
|
9
|
+
|
9
10
|
found = true if favicon.rel.split(' ').last.eql? 'icon'
|
10
11
|
break if found
|
11
12
|
end
|
12
13
|
|
13
14
|
return if found
|
14
15
|
|
15
|
-
return if
|
16
|
+
return if immediate_redirect?
|
16
17
|
|
17
18
|
add_issue('no favicon specified')
|
18
19
|
end
|
19
20
|
|
20
21
|
private
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
@html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.start_with? '0;'
|
23
|
+
# allow any instant-redirect meta tag
|
24
|
+
def immediate_redirect?
|
25
|
+
@html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.start_with? '0;'
|
26
|
+
rescue StandardError
|
27
|
+
false
|
25
28
|
end
|
26
|
-
|
27
29
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class HtmlCheck < ::HTMLProofer::Check
|
4
|
-
SCRIPT_EMBEDS_MSG = /Element script embeds close tag
|
5
|
-
INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid
|
6
|
-
INVALID_PREFIX = /Namespace prefix
|
7
|
-
PARSE_ENTITY_REF = /htmlParseEntityRef: no name
|
4
|
+
SCRIPT_EMBEDS_MSG = /Element script embeds close tag/.freeze
|
5
|
+
INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid/.freeze
|
6
|
+
INVALID_PREFIX = /Namespace prefix/.freeze
|
7
|
+
PARSE_ENTITY_REF = /htmlParseEntityRef: no name/.freeze
|
8
8
|
|
9
9
|
def run
|
10
10
|
@html.errors.each do |error|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ImageCheck < ::HTMLProofer::Check
|
4
|
-
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d
|
4
|
+
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/.freeze
|
5
5
|
|
6
6
|
def empty_alt_tag?
|
7
7
|
@img.alt.nil? || @img.alt.strip.empty?
|
@@ -38,13 +38,9 @@ class ImageCheck < ::HTMLProofer::Check
|
|
38
38
|
add_issue("internal image #{@img.url} does not exist", line: line, content: content)
|
39
39
|
end
|
40
40
|
|
41
|
-
if empty_alt_tag? && !@img.ignore_empty_alt? && !@img.ignore_alt?
|
42
|
-
add_issue("image #{@img.url} does not have an alt attribute", line: line, content: content)
|
43
|
-
end
|
41
|
+
add_issue("image #{@img.url} does not have an alt attribute", line: line, content: content) if empty_alt_tag? && !@img.ignore_empty_alt? && !@img.ignore_alt?
|
44
42
|
|
45
|
-
if @img.check_img_http? && @img.scheme == 'http'
|
46
|
-
add_issue("image #{@img.url} uses the http scheme", line: line, content: content)
|
47
|
-
end
|
43
|
+
add_issue("image #{@img.url} uses the http scheme", line: line, content: content) if @img.check_img_http? && @img.scheme == 'http'
|
48
44
|
end
|
49
45
|
|
50
46
|
external_urls
|
@@ -35,6 +35,7 @@ class LinkCheck < ::HTMLProofer::Check
|
|
35
35
|
next if @link.allow_missing_href?
|
36
36
|
# HTML5 allows dropping the href: http://git.io/vBX0z
|
37
37
|
next if @html.internal_subset.name == 'html' && @html.internal_subset.external_id.nil?
|
38
|
+
|
38
39
|
add_issue('anchor has no href attribute', line: line, content: content)
|
39
40
|
next
|
40
41
|
end
|
@@ -47,9 +48,10 @@ class LinkCheck < ::HTMLProofer::Check
|
|
47
48
|
# we need to skip these for now; although the domain main be valid,
|
48
49
|
# curl/Typheous inaccurately return 404s for some links. cc https://git.io/vyCFx
|
49
50
|
next if @link.respond_to?(:rel) && @link.rel == 'dns-prefetch'
|
51
|
+
|
50
52
|
add_to_external_urls(@link.href)
|
51
53
|
next
|
52
|
-
elsif
|
54
|
+
elsif @link.internal? && !@link.exists?
|
53
55
|
add_issue("internally linking to #{@link.href}, which does not exist", line: line, content: content)
|
54
56
|
end
|
55
57
|
|
@@ -74,6 +76,7 @@ class LinkCheck < ::HTMLProofer::Check
|
|
74
76
|
handle_tel(link, line, content)
|
75
77
|
when 'http'
|
76
78
|
return unless @options[:enforce_https]
|
79
|
+
|
77
80
|
add_issue("#{link.href} is not an HTTPS link", line: line, content: content)
|
78
81
|
end
|
79
82
|
end
|
@@ -103,9 +106,7 @@ class LinkCheck < ::HTMLProofer::Check
|
|
103
106
|
add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist", line: line, content: content)
|
104
107
|
else
|
105
108
|
target_html = create_nokogiri link.absolute_path
|
106
|
-
unless hash_check target_html, link.hash
|
107
|
-
add_issue("linking to #{link.href}, but #{link.hash} does not exist", line: line, content: content)
|
108
|
-
end
|
109
|
+
add_issue("linking to #{link.href}, but #{link.hash} does not exist", line: line, content: content) unless hash_check target_html, link.hash
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
@@ -135,6 +136,7 @@ class LinkCheck < ::HTMLProofer::Check
|
|
135
136
|
|
136
137
|
def check_sri(line, content)
|
137
138
|
return unless SRI_REL_TYPES.include?(@link.rel)
|
139
|
+
|
138
140
|
if !defined?(@link.integrity) && !defined?(@link.crossorigin)
|
139
141
|
add_issue("SRI and CORS not provided in: #{@link.src}", line: line, content: content)
|
140
142
|
elsif !defined?(@link.integrity)
|
@@ -65,19 +65,19 @@ module HTMLProofer
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def self.parse_json_option(option_name, config)
|
68
|
-
raise ArgumentError
|
69
|
-
raise ArgumentError
|
68
|
+
raise ArgumentError, 'Must provide an option name in string format.' unless option_name.is_a?(String)
|
69
|
+
raise ArgumentError, 'Must provide an option name in string format.' if option_name.strip.empty?
|
70
70
|
|
71
71
|
return {} if config.nil?
|
72
72
|
|
73
|
-
raise ArgumentError
|
73
|
+
raise ArgumentError, 'Must provide a JSON configuration in string format.' unless config.is_a?(String)
|
74
74
|
|
75
75
|
return {} if config.strip.empty?
|
76
76
|
|
77
77
|
begin
|
78
78
|
JSON.parse(config)
|
79
|
-
rescue
|
80
|
-
raise ArgumentError
|
79
|
+
rescue StandardError
|
80
|
+
raise ArgumentError, "Option '" + option_name + "' did not contain valid JSON."
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
data/lib/html-proofer/element.rb
CHANGED
@@ -18,7 +18,7 @@ module HTMLProofer
|
|
18
18
|
instance_variable_set("@#{name}", value.value)
|
19
19
|
end
|
20
20
|
|
21
|
-
@aria_hidden =
|
21
|
+
@aria_hidden = defined?(@aria_hidden) && @aria_hidden == 'true' ? true : false
|
22
22
|
|
23
23
|
@data_proofer_ignore = defined?(@data_proofer_ignore)
|
24
24
|
|
@@ -56,9 +56,11 @@ module HTMLProofer
|
|
56
56
|
|
57
57
|
def url
|
58
58
|
return @url if defined?(@url)
|
59
|
+
|
59
60
|
@url = (@src || @srcset || @href || '').delete("\u200b").strip
|
60
61
|
@url = Addressable::URI.join(base.attr('href') || '', url).to_s if base
|
61
62
|
return @url if @check.options[:url_swap].empty?
|
63
|
+
|
62
64
|
@url = swap(@url, @check.options[:url_swap])
|
63
65
|
end
|
64
66
|
|
@@ -77,11 +79,11 @@ module HTMLProofer
|
|
77
79
|
end
|
78
80
|
|
79
81
|
def hash
|
80
|
-
parts
|
82
|
+
parts&.fragment
|
81
83
|
end
|
82
84
|
|
83
85
|
def scheme
|
84
|
-
parts
|
86
|
+
parts&.scheme
|
85
87
|
end
|
86
88
|
|
87
89
|
# path is to an external server
|
@@ -137,9 +139,22 @@ module HTMLProofer
|
|
137
139
|
!internal?
|
138
140
|
end
|
139
141
|
|
140
|
-
# path is an anchor or a query
|
141
142
|
def internal?
|
142
|
-
|
143
|
+
relative_link? || internal_absolute_link?
|
144
|
+
end
|
145
|
+
|
146
|
+
def internal_absolute_link?
|
147
|
+
url.start_with?('/')
|
148
|
+
end
|
149
|
+
|
150
|
+
def relative_link?
|
151
|
+
return false if remote?
|
152
|
+
|
153
|
+
hash_link || param_link || url.start_with?('.') || url =~ /^\w/
|
154
|
+
end
|
155
|
+
|
156
|
+
def link_points_to_same_page?
|
157
|
+
hash_link || param_link
|
143
158
|
end
|
144
159
|
|
145
160
|
def hash_link
|
@@ -150,21 +165,20 @@ module HTMLProofer
|
|
150
165
|
url.start_with?('?')
|
151
166
|
end
|
152
167
|
|
153
|
-
def slash_link
|
154
|
-
url.start_with?('|')
|
155
|
-
end
|
156
|
-
|
157
168
|
def file_path
|
158
|
-
return if path.nil?
|
169
|
+
return if path.nil? || path.empty?
|
159
170
|
|
160
171
|
path_dot_ext = ''
|
161
172
|
|
162
|
-
if @check.options[:assume_extension]
|
163
|
-
path_dot_ext = path + @check.options[:extension]
|
164
|
-
end
|
173
|
+
path_dot_ext = path + @check.options[:extension] if @check.options[:assume_extension]
|
165
174
|
|
166
175
|
if path =~ %r{^/} # path relative to root
|
167
|
-
|
176
|
+
if File.directory?(@check.src)
|
177
|
+
base = @check.src
|
178
|
+
else
|
179
|
+
root_dir = @check.options[:root_dir]
|
180
|
+
base = root_dir || File.dirname(@check.src)
|
181
|
+
end
|
168
182
|
elsif File.exist?(File.expand_path(path, @check.src)) || File.exist?(File.expand_path(path_dot_ext, @check.src)) # relative links, path is a file
|
169
183
|
base = File.dirname @check.path
|
170
184
|
elsif File.exist?(File.join(File.dirname(@check.path), path)) || File.exist?(File.join(File.dirname(@check.path), path_dot_ext)) # relative links in nested dir, path is a file
|
@@ -174,7 +188,6 @@ module HTMLProofer
|
|
174
188
|
end
|
175
189
|
|
176
190
|
file = File.join base, path
|
177
|
-
|
178
191
|
if @check.options[:assume_extension] && File.file?("#{file}#{@check.options[:extension]}")
|
179
192
|
file = "#{file}#{@check.options[:extension]}"
|
180
193
|
elsif File.directory?(file) && !unslashed_directory?(file) # implicit index support
|
@@ -187,6 +200,7 @@ module HTMLProofer
|
|
187
200
|
# checks if a file exists relative to the current pwd
|
188
201
|
def exists?
|
189
202
|
return @checked_paths[absolute_path] if @checked_paths.key? absolute_path
|
203
|
+
|
190
204
|
@checked_paths[absolute_path] = File.exist? absolute_path
|
191
205
|
end
|
192
206
|
|
@@ -221,9 +235,9 @@ module HTMLProofer
|
|
221
235
|
|
222
236
|
def html
|
223
237
|
# If link is on the same page, then URL is on the current page so can use the same HTML as for current page
|
224
|
-
if
|
238
|
+
if link_points_to_same_page?
|
225
239
|
@html
|
226
|
-
elsif
|
240
|
+
elsif relative_link?
|
227
241
|
# link on another page, e.g. /about#Team - need to get HTML from the other page
|
228
242
|
create_nokogiri(absolute_path)
|
229
243
|
end
|
data/lib/html-proofer/issue.rb
CHANGED
@@ -56,9 +56,7 @@ module HTMLProofer
|
|
56
56
|
@logger.log :error, " * #{issue}"
|
57
57
|
else
|
58
58
|
msg = " * #{issue.send(second_report)}#{issue.line}"
|
59
|
-
if !issue.content.nil? && !issue.content.empty?
|
60
|
-
msg = "#{msg}\n #{issue.content}"
|
61
|
-
end
|
59
|
+
msg = "#{msg}\n #{issue.content}" if !issue.content.nil? && !issue.content.empty?
|
62
60
|
@logger.log(:error, msg)
|
63
61
|
end
|
64
62
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
module HTMLProofer
|
4
4
|
class Middleware
|
5
|
+
include HTMLProofer::Utils
|
5
6
|
|
6
7
|
class InvalidHtmlError < StandardError
|
7
8
|
def initialize(failures)
|
@@ -9,18 +10,18 @@ module HTMLProofer
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def message
|
12
|
-
|
13
|
+
"HTML Validation errors (skip by adding `?proofer-ignore` to URL): \n#{@failures.join("\n")}"
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
def self.options
|
17
18
|
@options ||= {
|
18
|
-
type:
|
19
|
-
allow_missing_href:
|
20
|
-
allow_hash_href:
|
19
|
+
type: :file,
|
20
|
+
allow_missing_href: true, # Permitted in html5
|
21
|
+
allow_hash_href: true,
|
21
22
|
check_external_hash: true,
|
22
|
-
check_html:
|
23
|
-
url_ignore:
|
23
|
+
check_html: true,
|
24
|
+
url_ignore: [/.*/] # Don't try to check local files exist
|
24
25
|
}
|
25
26
|
end
|
26
27
|
|
@@ -46,20 +47,21 @@ module HTMLProofer
|
|
46
47
|
'<BR',
|
47
48
|
'<P',
|
48
49
|
'<!--'
|
49
|
-
]
|
50
|
+
].freeze
|
50
51
|
|
51
52
|
def call(env)
|
52
53
|
result = @app.call(env)
|
53
54
|
return result if env['REQUEST_METHOD'] != 'GET'
|
54
55
|
return result if env['QUERY_STRING'] =~ /proofer-ignore/
|
55
56
|
return result if result.first != 200
|
57
|
+
|
56
58
|
body = []
|
57
59
|
result.last.each { |e| body << e }
|
58
60
|
|
59
61
|
body = body.join('')
|
60
62
|
begin
|
61
63
|
html = body.lstrip
|
62
|
-
rescue
|
64
|
+
rescue StandardError
|
63
65
|
return result # Invalid encoding; it's not gonna be html.
|
64
66
|
end
|
65
67
|
if HTML_SIGNATURE.any? { |sig| html.upcase.start_with? sig }
|
@@ -67,12 +69,10 @@ module HTMLProofer
|
|
67
69
|
'response',
|
68
70
|
Middleware.options
|
69
71
|
).check_parsed(
|
70
|
-
Nokogiri::HTML(
|
72
|
+
Nokogiri::HTML(clean_content(html)), 'response'
|
71
73
|
)
|
72
74
|
|
73
|
-
|
74
|
-
raise InvalidHtmlError.new(parsed[:failures])
|
75
|
-
end
|
75
|
+
raise InvalidHtmlError, parsed[:failures] unless parsed[:failures].empty?
|
76
76
|
end
|
77
77
|
result
|
78
78
|
end
|
data/lib/html-proofer/runner.rb
CHANGED
@@ -103,9 +103,7 @@ module HTMLProofer
|
|
103
103
|
check = Object.const_get(klass).new(src, path, html, @options)
|
104
104
|
check.run
|
105
105
|
external_urls = check.external_urls
|
106
|
-
if @options[:url_swap]
|
107
|
-
external_urls = Hash[check.external_urls.map { |url, file| [swap(url, @options[:url_swap]), file] }]
|
108
|
-
end
|
106
|
+
external_urls = Hash[check.external_urls.map { |url, file| [swap(url, @options[:url_swap]), file] }] if @options[:url_swap]
|
109
107
|
result[:external_urls].merge!(external_urls)
|
110
108
|
result[:failures].concat(check.issues)
|
111
109
|
end
|
@@ -148,6 +146,7 @@ module HTMLProofer
|
|
148
146
|
|
149
147
|
def checks
|
150
148
|
return @checks if defined?(@checks) && !@checks.nil?
|
149
|
+
|
151
150
|
@checks = HTMLProofer::Check.subchecks.map(&:name)
|
152
151
|
@checks.delete('FaviconCheck') unless @options[:check_favicon]
|
153
152
|
@checks.delete('HtmlCheck') unless @options[:check_html]
|
@@ -159,6 +158,7 @@ module HTMLProofer
|
|
159
158
|
def failed_tests
|
160
159
|
result = []
|
161
160
|
return result if @failures.empty?
|
161
|
+
|
162
162
|
@failures.each { |f| result << f.to_s }
|
163
163
|
result
|
164
164
|
end
|
@@ -36,6 +36,7 @@ module HTMLProofer
|
|
36
36
|
|
37
37
|
def remove_query_values
|
38
38
|
return nil if @external_urls.nil?
|
39
|
+
|
39
40
|
paths_with_queries = {}
|
40
41
|
iterable_external_urls = @external_urls.dup
|
41
42
|
@external_urls.each_key do |url|
|
@@ -46,6 +47,7 @@ module HTMLProofer
|
|
46
47
|
nil
|
47
48
|
end
|
48
49
|
next if uri.nil? || uri.query.nil?
|
50
|
+
|
49
51
|
iterable_external_urls.delete(url) unless new_url_query_values?(uri, paths_with_queries)
|
50
52
|
end
|
51
53
|
iterable_external_urls
|
@@ -108,9 +110,9 @@ module HTMLProofer
|
|
108
110
|
external_urls.each_pair do |url, filenames|
|
109
111
|
url = begin
|
110
112
|
clean_url(url)
|
111
|
-
|
112
|
-
|
113
|
-
|
113
|
+
rescue URI::Error, Addressable::URI::InvalidURIError
|
114
|
+
add_external_issue(filenames, "#{url} is an invalid URL")
|
115
|
+
next
|
114
116
|
end
|
115
117
|
|
116
118
|
method = if hash?(url) && @options[:check_external_hash]
|
@@ -146,20 +148,18 @@ module HTMLProofer
|
|
146
148
|
response_code = response.code
|
147
149
|
response.body.gsub!("\x00", '')
|
148
150
|
|
149
|
-
if filenames.nil?
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
debug_msg = if filenames.nil?
|
152
|
+
"Received a #{response_code} for #{href}"
|
153
|
+
else
|
154
|
+
"Received a #{response_code} for #{href} in #{filenames.join(' ')}"
|
155
|
+
end
|
154
156
|
|
155
157
|
@logger.log :debug, debug_msg
|
156
158
|
|
157
159
|
return if @options[:http_status_ignore].include?(response_code)
|
158
160
|
|
159
161
|
if response_code.between?(200, 299)
|
160
|
-
unless check_hash_in_2xx_response(href, effective_url, response, filenames)
|
161
|
-
@cache.add(href, filenames, response_code)
|
162
|
-
end
|
162
|
+
@cache.add(href, filenames, response_code) unless check_hash_in_2xx_response(href, effective_url, response, filenames)
|
163
163
|
elsif response.timed_out?
|
164
164
|
handle_timeout(href, filenames, response_code)
|
165
165
|
elsif response_code.zero?
|
@@ -168,6 +168,7 @@ module HTMLProofer
|
|
168
168
|
queue_request(:get, href, filenames)
|
169
169
|
else
|
170
170
|
return if @options[:only_4xx] && !response_code.between?(400, 499)
|
171
|
+
|
171
172
|
# Received a non-successful http response.
|
172
173
|
msg = "External link #{href} failed: #{response_code} #{response.return_message}"
|
173
174
|
add_external_issue(filenames, msg, response_code)
|
@@ -191,9 +192,7 @@ module HTMLProofer
|
|
191
192
|
xpath << [%(//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])]
|
192
193
|
# when linking to a file on GitHub, like #L12-L34, only the first "L" portion
|
193
194
|
# will be identified as a linkable portion
|
194
|
-
if hash =~ /\A(L\d)+/
|
195
|
-
xpath << [%(//td[@id="#{Regexp.last_match[1]}"])]
|
196
|
-
end
|
195
|
+
xpath << [%(//td[@id="#{Regexp.last_match[1]}"])] if hash =~ /\A(L\d)+/
|
197
196
|
end
|
198
197
|
|
199
198
|
return unless body_doc.xpath(xpath.join('|')).empty?
|
@@ -208,6 +207,7 @@ module HTMLProofer
|
|
208
207
|
msg = "External link #{href} failed: got a time out (response code #{response_code})"
|
209
208
|
@cache.add(href, filenames, 0, msg)
|
210
209
|
return if @options[:only_4xx]
|
210
|
+
|
211
211
|
add_external_issue(filenames, msg, response_code)
|
212
212
|
end
|
213
213
|
|
@@ -218,6 +218,7 @@ module HTMLProofer
|
|
218
218
|
Either way, the return message (if any) from the server is: #{return_message}"
|
219
219
|
@cache.add(href, filenames, 0, msg)
|
220
220
|
return if @options[:only_4xx]
|
221
|
+
|
221
222
|
add_external_issue(filenames, msg, response_code)
|
222
223
|
end
|
223
224
|
|
data/lib/html-proofer/utils.rb
CHANGED
@@ -17,7 +17,6 @@ module HTMLProofer
|
|
17
17
|
|
18
18
|
Nokogiri::HTML(clean_content(content))
|
19
19
|
end
|
20
|
-
module_function :create_nokogiri
|
21
20
|
|
22
21
|
def swap(href, replacement)
|
23
22
|
replacement.each do |link, replace|
|
@@ -25,7 +24,6 @@ module HTMLProofer
|
|
25
24
|
end
|
26
25
|
href
|
27
26
|
end
|
28
|
-
module_function :swap
|
29
27
|
|
30
28
|
# address a problem with Nokogiri's parsing URL entities
|
31
29
|
# problem from http://git.io/vBYU1
|
@@ -35,6 +33,5 @@ module HTMLProofer
|
|
35
33
|
url.gsub(/&(?!amp;)/, '&')
|
36
34
|
end
|
37
35
|
end
|
38
|
-
module_function :clean_content
|
39
36
|
end
|
40
37
|
end
|
data/lib/html-proofer/version.rb
CHANGED
metadata
CHANGED
@@ -1,59 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: html-proofer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen Torikian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: addressable
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: mercenary
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0.3'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: nokogiri
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '1.10'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '1.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: parallel
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
@@ -67,21 +67,21 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rainbow
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '3.0'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '3.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: typhoeus
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
@@ -95,21 +95,21 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '1.3'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: yell
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '2.
|
103
|
+
version: '2.0'
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '2.
|
110
|
+
version: '2.0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: awesome_print
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - ">="
|
@@ -123,7 +123,7 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: codecov
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
@@ -137,7 +137,7 @@ dependencies:
|
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
|
-
name:
|
140
|
+
name: pry-byebug
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - ">="
|
@@ -151,7 +151,7 @@ dependencies:
|
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
154
|
+
name: rake
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - ">="
|
@@ -165,7 +165,7 @@ dependencies:
|
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
|
-
name:
|
168
|
+
name: redcarpet
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
171
|
- - ">="
|
@@ -193,7 +193,7 @@ dependencies:
|
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '3.1'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
|
-
name:
|
196
|
+
name: rubocop
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
198
198
|
requirements:
|
199
199
|
- - ">="
|
@@ -207,7 +207,7 @@ dependencies:
|
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
209
|
- !ruby/object:Gem::Dependency
|
210
|
-
name:
|
210
|
+
name: rubocop-performance
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
212
212
|
requirements:
|
213
213
|
- - ">="
|
@@ -221,7 +221,7 @@ dependencies:
|
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
|
-
name:
|
224
|
+
name: rubocop-standard
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
226
226
|
requirements:
|
227
227
|
- - ">="
|
@@ -235,33 +235,33 @@ dependencies:
|
|
235
235
|
- !ruby/object:Gem::Version
|
236
236
|
version: '0'
|
237
237
|
- !ruby/object:Gem::Dependency
|
238
|
-
name:
|
238
|
+
name: timecop
|
239
239
|
requirement: !ruby/object:Gem::Requirement
|
240
240
|
requirements:
|
241
241
|
- - "~>"
|
242
242
|
- !ruby/object:Gem::Version
|
243
|
-
version: '
|
243
|
+
version: '0.8'
|
244
244
|
type: :development
|
245
245
|
prerelease: false
|
246
246
|
version_requirements: !ruby/object:Gem::Requirement
|
247
247
|
requirements:
|
248
248
|
- - "~>"
|
249
249
|
- !ruby/object:Gem::Version
|
250
|
-
version: '
|
250
|
+
version: '0.8'
|
251
251
|
- !ruby/object:Gem::Dependency
|
252
|
-
name:
|
252
|
+
name: vcr
|
253
253
|
requirement: !ruby/object:Gem::Requirement
|
254
254
|
requirements:
|
255
255
|
- - "~>"
|
256
256
|
- !ruby/object:Gem::Version
|
257
|
-
version: '
|
257
|
+
version: '2.9'
|
258
258
|
type: :development
|
259
259
|
prerelease: false
|
260
260
|
version_requirements: !ruby/object:Gem::Requirement
|
261
261
|
requirements:
|
262
262
|
- - "~>"
|
263
263
|
- !ruby/object:Gem::Version
|
264
|
-
version: '
|
264
|
+
version: '2.9'
|
265
265
|
description: Test your rendered HTML files to make sure they're accurate.
|
266
266
|
email:
|
267
267
|
- gjtorikian@gmail.com
|