html-proofer 3.13.0 → 3.15.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/htmlproofer +15 -12
- data/lib/html-proofer.rb +10 -10
- data/lib/html-proofer/cache.rb +6 -14
- data/lib/html-proofer/check.rb +5 -2
- data/lib/html-proofer/check/favicon.rb +7 -5
- data/lib/html-proofer/check/html.rb +27 -18
- data/lib/html-proofer/check/images.rb +3 -7
- data/lib/html-proofer/check/links.rb +7 -5
- data/lib/html-proofer/check/opengraph.rb +3 -4
- data/lib/html-proofer/configuration.rb +9 -6
- data/lib/html-proofer/element.rb +45 -23
- data/lib/html-proofer/issue.rb +1 -3
- data/lib/html-proofer/log.rb +15 -11
- data/lib/html-proofer/middleware.rb +13 -12
- data/lib/html-proofer/runner.rb +8 -6
- data/lib/html-proofer/url_validator.rb +16 -15
- data/lib/html-proofer/utils.rb +2 -14
- data/lib/html-proofer/version.rb +1 -1
- metadata +34 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d96b191b03d99b1d4fac61814a324dc0e46d69073318591fce74077dd484b512
|
4
|
+
data.tar.gz: 35bb1cd3d4ab1666c699b144afca8c20860db957156610a78d83f1f25154fde2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1e3e8e502e52aff8e11aa917232cf288be5afcfea255b5b5bdfef61ef327e8d2899a4c394ba529fc13f6e246fa1523650d3689205f68c9f4cfd005077c4f2a4
|
7
|
+
data.tar.gz: d33bacd4efe1d0f6fee8eeb4d3491738b7d82a840e43fac77374a019319dc099e75d7e4d6440e3ed0dcba6b2c42d78178dd18351289fb2f8f4a9d32cad185353
|
data/bin/htmlproofer
CHANGED
@@ -20,13 +20,13 @@ Mercenary.program(:htmlproofer) do |p|
|
|
20
20
|
p.option 'as_links', '--as-links', 'Assumes that `PATH` is a comma-separated array of links to check.'
|
21
21
|
p.option 'alt_ignore', '--alt-ignore image1,[image2,...]', Array, 'A comma-separated list of Strings or RegExps containing `img`s whose missing `alt` tags are safe to ignore'
|
22
22
|
p.option 'assume_extension', '--assume-extension', 'Automatically add extension (e.g. `.html`) to file paths, to allow extensionless URLs (as supported by Jekyll 3 and GitHub Pages) (default: `false`).'
|
23
|
-
p.option 'checks_to_ignore', '--checks-to-ignore check1,[check2,...]', Array, '
|
23
|
+
p.option 'checks_to_ignore', '--checks-to-ignore check1,[check2,...]', Array, 'A comma-separated list of Strings indicating which checks you do not want to run (default: `[]`)'
|
24
24
|
p.option 'check_external_hash', '--check-external-hash', 'Checks whether external hashes exist (even if the webpage exists). This slows the checker down (default: `false`).'
|
25
25
|
p.option 'check_favicon', '--check-favicon', 'Enables the favicon checker (default: `false`).'
|
26
|
-
p.option 'check_html', '--check-html', 'Enables HTML validation errors from
|
26
|
+
p.option 'check_html', '--check-html', 'Enables HTML validation errors from Nokogumbo (default: `false`).'
|
27
27
|
p.option 'check_img_http', '--check-img-http', 'Fails an image if it\'s marked as `http` (default: `false`).'
|
28
28
|
p.option 'check_opengraph', '--check-opengraph', 'Enables the Open Graph checker (default: `false`).'
|
29
|
-
p.option 'check_sri', '--check-sri', 'Check that `<link>` and `<script>` external resources
|
29
|
+
p.option 'check_sri', '--check-sri', 'Check that `<link>` and `<script>` external resources use SRI (default: `false`).'
|
30
30
|
p.option 'directory_index_file', '--directory-index-file <filename>', String, 'Sets the file to look for when a link refers to a directory. (default: `index.html`)'
|
31
31
|
p.option 'disable_external', '--disable-external', 'If `true`, does not run the external link checker, which can take a lot of time (default: `false`)'
|
32
32
|
p.option 'empty_alt_ignore', '--empty-alt-ignore', 'If `true`, ignores images with empty alt tags'
|
@@ -37,9 +37,12 @@ Mercenary.program(:htmlproofer) do |p|
|
|
37
37
|
p.option 'file_ignore', '--file-ignore file1,[file2,...]', Array, 'A comma-separated list of Strings or RegExps containing file paths that are safe to ignore'
|
38
38
|
p.option 'http_status_ignore', '--http-status-ignore 123,[xxx, ...]', Array, 'A comma-separated list of numbers representing status codes to ignore.'
|
39
39
|
p.option 'internal_domains', '--internal-domains domain1,[domain2,...]', Array, 'A comma-separated list of Strings containing domains that will be treated as internal urls.'
|
40
|
-
p.option 'report_invalid_tags', '--report-invalid-tags', '
|
41
|
-
p.option 'report_missing_names', '--report-missing-names', '
|
42
|
-
p.option 'report_script_embeds', '--report-script-embeds', '
|
40
|
+
p.option 'report_invalid_tags', '--report-invalid-tags', 'When `check_html` is enabled, HTML markup that is unknown to Nokogumbo are reported as errors (default: `false`)'
|
41
|
+
p.option 'report_missing_names', '--report-missing-names', 'When `check_html` is enabled, HTML markup that are missing entity names are reported as errors (default: `false`)'
|
42
|
+
p.option 'report_script_embeds', '--report-script-embeds', 'When `check_html` is enabled, `script` tags containing markup are reported as errors (default: `false`)'
|
43
|
+
p.option 'report_missing_doctype', '--report-missing-doctype', 'When `check_html` is enabled, HTML markup with missing or out-of-order `DOCTYPE` are reported as errors (default: `false`)'
|
44
|
+
p.option 'report_eof_tags', '--report-eof-tags', 'When `check_html` is enabled, HTML markup with tags that are malformed are reported as errors (default: `false`)'
|
45
|
+
p.option 'report_mismatched_tags', '--report-mismatched-tags', 'When `check_html` is enabled, HTML markup with mismatched tags are reported as errors (default: `false`)'
|
43
46
|
p.option 'log_level', '--log-level <level>', String, 'Sets the logging level, as determined by Yell. One of `:debug`, `:info`, `:warn`, `:error`, or `:fatal`. (default: `:info`)'
|
44
47
|
p.option 'only_4xx', '--only-4xx', 'Only reports errors for links that fall within the 4xx status code range'
|
45
48
|
p.option 'storage_dir', '--storage-dir PATH', String, 'Directory where to store the cache log (default: "tmp/.htmlproofer")'
|
@@ -47,6 +50,7 @@ Mercenary.program(:htmlproofer) do |p|
|
|
47
50
|
p.option 'typhoeus_config', '--typhoeus-config CONFIG', String, 'JSON-formatted string of Typhoeus config. Will override the html-proofer defaults.'
|
48
51
|
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
52
|
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.'
|
53
|
+
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
54
|
|
51
55
|
p.action do |args, opts|
|
52
56
|
args = ['.'] if args.empty?
|
@@ -56,9 +60,7 @@ Mercenary.program(:htmlproofer) do |p|
|
|
56
60
|
|
57
61
|
# prepare everything to go to proofer
|
58
62
|
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
|
63
|
+
opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) } if opts[option.config_key].is_a?(Array)
|
62
64
|
options[option.config_key.to_sym] = opts[option.config_key]
|
63
65
|
end
|
64
66
|
|
@@ -81,10 +83,11 @@ Mercenary.program(:htmlproofer) do |p|
|
|
81
83
|
options[:validation][:report_script_embeds] = opts['report_script_embeds'] unless opts['report_script_embeds'].nil?
|
82
84
|
options[:validation][:report_missing_names] = opts['report_missing_names'] unless opts['report_missing_names'].nil?
|
83
85
|
options[:validation][:report_invalid_tags] = opts['report_invalid_tags'] unless opts['report_invalid_tags'].nil?
|
86
|
+
options[:validation][:report_missing_doctype] = opts['report_missing_doctype'] unless opts['report_missing_doctype'].nil?
|
87
|
+
options[:validation][:report_eof_tags] = opts['report_eof_tags'] unless opts['report_eof_tags'].nil?
|
88
|
+
options[:validation][:report_mismatched_tags] = opts['report_mismatched_tags'] unless opts['report_mismatched_tags'].nil?
|
84
89
|
|
85
|
-
unless opts['typhoeus_config'].nil?
|
86
|
-
options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config'])
|
87
|
-
end
|
90
|
+
options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config']) unless opts['typhoeus_config'].nil?
|
88
91
|
|
89
92
|
unless opts['timeframe'].nil?
|
90
93
|
options[:cache] ||= {}
|
data/lib/html-proofer.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
def require_all(path)
|
4
4
|
dir = File.join(File.dirname(__FILE__), path)
|
5
|
-
Dir[File.join(dir, '*.rb')].each do |f|
|
5
|
+
Dir[File.join(dir, '*.rb')].sort.each do |f|
|
6
6
|
require f
|
7
7
|
end
|
8
8
|
end
|
@@ -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/SuppressedException
|
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
@@ -5,10 +5,11 @@ module HTMLProofer
|
|
5
5
|
class Check
|
6
6
|
attr_reader :node, :html, :element, :src, :path, :options, :issues, :external_urls
|
7
7
|
|
8
|
-
def initialize(src, path, html, options)
|
8
|
+
def initialize(src, path, html, logger, options)
|
9
9
|
@src = src
|
10
10
|
@path = path
|
11
11
|
@html = remove_ignored(html)
|
12
|
+
@logger = logger
|
12
13
|
@options = options
|
13
14
|
@issues = []
|
14
15
|
@external_urls = {}
|
@@ -16,7 +17,7 @@ module HTMLProofer
|
|
16
17
|
|
17
18
|
def create_element(node)
|
18
19
|
@node = node
|
19
|
-
Element.new(node, self)
|
20
|
+
Element.new(node, self, @logger)
|
20
21
|
end
|
21
22
|
|
22
23
|
def run
|
@@ -29,6 +30,7 @@ module HTMLProofer
|
|
29
30
|
|
30
31
|
def add_to_external_urls(url)
|
31
32
|
return if @external_urls[url]
|
33
|
+
|
32
34
|
add_path_for_url(url)
|
33
35
|
end
|
34
36
|
|
@@ -45,6 +47,7 @@ module HTMLProofer
|
|
45
47
|
|
46
48
|
ObjectSpace.each_object(Class) do |c|
|
47
49
|
next unless c.superclass == self
|
50
|
+
|
48
51
|
classes << c
|
49
52
|
end
|
50
53
|
|
@@ -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,28 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class HtmlCheck < ::HTMLProofer::Check
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
# tags embedded in scripts are used in templating languages: http://git.io/vOovv
|
5
|
+
SCRIPT_EMBEDS_MSG = /Element script embeds close tag/.freeze
|
6
|
+
INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid/.freeze
|
7
|
+
INVALID_PREFIX = /Namespace prefix/.freeze
|
8
|
+
PARSE_ENTITY_REF = /htmlParseEntityRef: no name/.freeze
|
9
|
+
DOCTYPE_MSG = /Expected a doctype token/.freeze
|
10
|
+
EOF_IN_TAG = /End of input in tag/.freeze
|
11
|
+
MISMATCHED_TAGS = /That tag isn't allowed here/.freeze
|
8
12
|
|
9
13
|
def run
|
10
14
|
@html.errors.each do |error|
|
11
|
-
message
|
12
|
-
|
13
|
-
|
14
|
-
if message =~ INVALID_TAG_MSG || message =~ INVALID_PREFIX
|
15
|
-
next unless options[:validation][:report_invalid_tags]
|
16
|
-
end
|
17
|
-
|
18
|
-
if message =~ PARSE_ENTITY_REF
|
19
|
-
next unless options[:validation][:report_missing_names]
|
20
|
-
end
|
21
|
-
|
22
|
-
# tags embedded in scripts are used in templating languages: http://git.io/vOovv
|
23
|
-
next if !options[:validation][:report_script_embeds] && message =~ SCRIPT_EMBEDS_MSG
|
15
|
+
add_issue(error.message, line: error.line) if report?(error.message)
|
16
|
+
end
|
17
|
+
end
|
24
18
|
|
25
|
-
|
19
|
+
def report?(message)
|
20
|
+
case message
|
21
|
+
when SCRIPT_EMBEDS_MSG
|
22
|
+
options[:validation][:report_script_embeds]
|
23
|
+
when INVALID_TAG_MSG, INVALID_PREFIX
|
24
|
+
options[:validation][:report_invalid_tags]
|
25
|
+
when PARSE_ENTITY_REF
|
26
|
+
options[:validation][:report_missing_names]
|
27
|
+
when DOCTYPE_MSG
|
28
|
+
options[:validation][:report_missing_doctype]
|
29
|
+
when EOF_IN_TAG
|
30
|
+
options[:validation][:report_eof_tags]
|
31
|
+
when MISMATCHED_TAGS
|
32
|
+
options[:validation][:report_mismatched_tags]
|
33
|
+
else
|
34
|
+
true
|
26
35
|
end
|
27
36
|
end
|
28
37
|
end
|
@@ -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
|
@@ -34,7 +34,8 @@ class LinkCheck < ::HTMLProofer::Check
|
|
34
34
|
if missing_href?
|
35
35
|
next if @link.allow_missing_href?
|
36
36
|
# HTML5 allows dropping the href: http://git.io/vBX0z
|
37
|
-
next if @html.internal_subset.name == 'html' && @html.internal_subset.external_id.nil?
|
37
|
+
next if @html.internal_subset.nil? || (@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)
|
@@ -1,11 +1,10 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
class OpenGraphElement < ::HTMLProofer::Element
|
5
4
|
attr_reader :src
|
6
5
|
|
7
|
-
def initialize(obj, check)
|
8
|
-
super(obj, check)
|
6
|
+
def initialize(obj, check, logger)
|
7
|
+
super(obj, check, logger)
|
9
8
|
# Fake up src from the content attribute
|
10
9
|
instance_variable_set('@src', @content)
|
11
10
|
|
@@ -24,7 +23,7 @@ class OpenGraphCheck < ::HTMLProofer::Check
|
|
24
23
|
|
25
24
|
def run
|
26
25
|
@html.css('meta[property="og:url"], meta[property="og:image"]').each do |m|
|
27
|
-
@opengraph = OpenGraphElement.new(m, self)
|
26
|
+
@opengraph = OpenGraphElement.new(m, self, @logger)
|
28
27
|
|
29
28
|
next if @opengraph.ignore?
|
30
29
|
|
@@ -51,7 +51,10 @@ module HTMLProofer
|
|
51
51
|
VALIDATION_DEFAULTS = {
|
52
52
|
report_script_embeds: false,
|
53
53
|
report_missing_names: false,
|
54
|
-
report_invalid_tags: false
|
54
|
+
report_invalid_tags: false,
|
55
|
+
report_missing_doctype: false,
|
56
|
+
report_eof_tags: false,
|
57
|
+
report_mismatched_tags: false
|
55
58
|
}.freeze
|
56
59
|
|
57
60
|
CACHE_DEFAULTS = {}.freeze
|
@@ -65,19 +68,19 @@ module HTMLProofer
|
|
65
68
|
end
|
66
69
|
|
67
70
|
def self.parse_json_option(option_name, config)
|
68
|
-
raise ArgumentError
|
69
|
-
raise ArgumentError
|
71
|
+
raise ArgumentError, 'Must provide an option name in string format.' unless option_name.is_a?(String)
|
72
|
+
raise ArgumentError, 'Must provide an option name in string format.' if option_name.strip.empty?
|
70
73
|
|
71
74
|
return {} if config.nil?
|
72
75
|
|
73
|
-
raise ArgumentError
|
76
|
+
raise ArgumentError, 'Must provide a JSON configuration in string format.' unless config.is_a?(String)
|
74
77
|
|
75
78
|
return {} if config.strip.empty?
|
76
79
|
|
77
80
|
begin
|
78
81
|
JSON.parse(config)
|
79
|
-
rescue
|
80
|
-
raise ArgumentError
|
82
|
+
rescue StandardError
|
83
|
+
raise ArgumentError, "Option '" + option_name + "' did not contain valid JSON."
|
81
84
|
end
|
82
85
|
end
|
83
86
|
end
|
data/lib/html-proofer/element.rb
CHANGED
@@ -10,15 +10,21 @@ module HTMLProofer
|
|
10
10
|
|
11
11
|
attr_reader :id, :name, :alt, :href, :link, :src, :line, :data_proofer_ignore
|
12
12
|
|
13
|
-
def initialize(obj, check)
|
13
|
+
def initialize(obj, check, logger)
|
14
|
+
@logger = logger
|
14
15
|
# Construct readable ivars for every element
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
begin
|
17
|
+
obj.attributes.each_pair do |attribute, value|
|
18
|
+
name = attribute.tr('-:.;', '_').to_s.to_sym
|
19
|
+
(class << self; self; end).send(:attr_reader, name)
|
20
|
+
instance_variable_set("@#{name}", value.value)
|
21
|
+
end
|
22
|
+
rescue NameError => e
|
23
|
+
@logger.log :error, "Attribute set `#{obj}` contains an error!"
|
24
|
+
raise e
|
19
25
|
end
|
20
26
|
|
21
|
-
@aria_hidden =
|
27
|
+
@aria_hidden = defined?(@aria_hidden) && @aria_hidden == 'true' ? true : false
|
22
28
|
|
23
29
|
@data_proofer_ignore = defined?(@data_proofer_ignore)
|
24
30
|
|
@@ -56,9 +62,11 @@ module HTMLProofer
|
|
56
62
|
|
57
63
|
def url
|
58
64
|
return @url if defined?(@url)
|
65
|
+
|
59
66
|
@url = (@src || @srcset || @href || '').delete("\u200b").strip
|
60
67
|
@url = Addressable::URI.join(base.attr('href') || '', url).to_s if base
|
61
68
|
return @url if @check.options[:url_swap].empty?
|
69
|
+
|
62
70
|
@url = swap(@url, @check.options[:url_swap])
|
63
71
|
end
|
64
72
|
|
@@ -77,11 +85,11 @@ module HTMLProofer
|
|
77
85
|
end
|
78
86
|
|
79
87
|
def hash
|
80
|
-
parts
|
88
|
+
parts&.fragment
|
81
89
|
end
|
82
90
|
|
83
91
|
def scheme
|
84
|
-
parts
|
92
|
+
parts&.scheme
|
85
93
|
end
|
86
94
|
|
87
95
|
# path is to an external server
|
@@ -137,9 +145,22 @@ module HTMLProofer
|
|
137
145
|
!internal?
|
138
146
|
end
|
139
147
|
|
140
|
-
# path is an anchor or a query
|
141
148
|
def internal?
|
142
|
-
|
149
|
+
relative_link? || internal_absolute_link?
|
150
|
+
end
|
151
|
+
|
152
|
+
def internal_absolute_link?
|
153
|
+
url.start_with?('/')
|
154
|
+
end
|
155
|
+
|
156
|
+
def relative_link?
|
157
|
+
return false if remote?
|
158
|
+
|
159
|
+
hash_link || param_link || url.start_with?('.') || url =~ /^\S/
|
160
|
+
end
|
161
|
+
|
162
|
+
def link_points_to_same_page?
|
163
|
+
hash_link || param_link
|
143
164
|
end
|
144
165
|
|
145
166
|
def hash_link
|
@@ -150,21 +171,20 @@ module HTMLProofer
|
|
150
171
|
url.start_with?('?')
|
151
172
|
end
|
152
173
|
|
153
|
-
def slash_link
|
154
|
-
url.start_with?('|')
|
155
|
-
end
|
156
|
-
|
157
174
|
def file_path
|
158
|
-
return if path.nil?
|
175
|
+
return if path.nil? || path.empty?
|
159
176
|
|
160
177
|
path_dot_ext = ''
|
161
178
|
|
162
|
-
if @check.options[:assume_extension]
|
163
|
-
path_dot_ext = path + @check.options[:extension]
|
164
|
-
end
|
179
|
+
path_dot_ext = path + @check.options[:extension] if @check.options[:assume_extension]
|
165
180
|
|
166
181
|
if path =~ %r{^/} # path relative to root
|
167
|
-
|
182
|
+
if File.directory?(@check.src)
|
183
|
+
base = @check.src
|
184
|
+
else
|
185
|
+
root_dir = @check.options[:root_dir]
|
186
|
+
base = root_dir || File.dirname(@check.src)
|
187
|
+
end
|
168
188
|
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
189
|
base = File.dirname @check.path
|
170
190
|
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 +194,6 @@ module HTMLProofer
|
|
174
194
|
end
|
175
195
|
|
176
196
|
file = File.join base, path
|
177
|
-
|
178
197
|
if @check.options[:assume_extension] && File.file?("#{file}#{@check.options[:extension]}")
|
179
198
|
file = "#{file}#{@check.options[:extension]}"
|
180
199
|
elsif File.directory?(file) && !unslashed_directory?(file) # implicit index support
|
@@ -187,6 +206,7 @@ module HTMLProofer
|
|
187
206
|
# checks if a file exists relative to the current pwd
|
188
207
|
def exists?
|
189
208
|
return @checked_paths[absolute_path] if @checked_paths.key? absolute_path
|
209
|
+
|
190
210
|
@checked_paths[absolute_path] = File.exist? absolute_path
|
191
211
|
end
|
192
212
|
|
@@ -220,12 +240,14 @@ module HTMLProofer
|
|
220
240
|
end
|
221
241
|
|
222
242
|
def html
|
223
|
-
# If link is on the same page, then URL is on the current page
|
224
|
-
if
|
243
|
+
# If link is on the same page, then URL is on the current page. use the same HTML as for current page
|
244
|
+
if link_points_to_same_page?
|
225
245
|
@html
|
226
|
-
elsif
|
246
|
+
elsif internal?
|
227
247
|
# link on another page, e.g. /about#Team - need to get HTML from the other page
|
228
248
|
create_nokogiri(absolute_path)
|
249
|
+
else
|
250
|
+
raise NotImplementedError, 'HTMLProofer should not have gotten here. Please report this as a bug.'
|
229
251
|
end
|
230
252
|
end
|
231
253
|
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
|
data/lib/html-proofer/log.rb
CHANGED
@@ -7,16 +7,27 @@ module HTMLProofer
|
|
7
7
|
class Log
|
8
8
|
include Yell::Loggable
|
9
9
|
|
10
|
+
STDOUT_LEVELS = %i[debug info warn].freeze
|
11
|
+
STDERR_LEVELS = %i[error fatal].freeze
|
12
|
+
|
10
13
|
def initialize(log_level)
|
11
14
|
@logger = Yell.new(format: false, \
|
12
15
|
name: 'HTMLProofer', \
|
13
16
|
level: "gte.#{log_level}") do |l|
|
14
|
-
l.adapter :stdout, level:
|
15
|
-
l.adapter :stderr, level:
|
17
|
+
l.adapter :stdout, level: 'lte.warn'
|
18
|
+
l.adapter :stderr, level: 'gte.error'
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
19
22
|
def log(level, message)
|
23
|
+
log_with_color(level, message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def log_with_color(level, message)
|
27
|
+
@logger.send level, colorize(level, message)
|
28
|
+
end
|
29
|
+
|
30
|
+
def colorize(level, message)
|
20
31
|
color = case level
|
21
32
|
when :debug
|
22
33
|
:cyan
|
@@ -28,15 +39,8 @@ module HTMLProofer
|
|
28
39
|
:red
|
29
40
|
end
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def log_with_color(level, color, message)
|
35
|
-
@logger.send level, colorize(color, message)
|
36
|
-
end
|
37
|
-
|
38
|
-
def colorize(color, message)
|
39
|
-
if $stdout.isatty && $stderr.isatty
|
42
|
+
if (STDOUT_LEVELS.include?(level) && $stdout.isatty) || \
|
43
|
+
(STDERR_LEVELS.include?(level) && $stderr.isatty)
|
40
44
|
Rainbow(message).send(color)
|
41
45
|
else
|
42
46
|
message
|
@@ -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,19 @@ 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 if local files exist
|
25
|
+
validation: { report_eof_tags: true }
|
24
26
|
}
|
25
27
|
end
|
26
28
|
|
@@ -46,20 +48,21 @@ module HTMLProofer
|
|
46
48
|
'<BR',
|
47
49
|
'<P',
|
48
50
|
'<!--'
|
49
|
-
]
|
51
|
+
].freeze
|
50
52
|
|
51
53
|
def call(env)
|
52
54
|
result = @app.call(env)
|
53
55
|
return result if env['REQUEST_METHOD'] != 'GET'
|
54
56
|
return result if env['QUERY_STRING'] =~ /proofer-ignore/
|
55
57
|
return result if result.first != 200
|
58
|
+
|
56
59
|
body = []
|
57
60
|
result.last.each { |e| body << e }
|
58
61
|
|
59
62
|
body = body.join('')
|
60
63
|
begin
|
61
64
|
html = body.lstrip
|
62
|
-
rescue
|
65
|
+
rescue StandardError
|
63
66
|
return result # Invalid encoding; it's not gonna be html.
|
64
67
|
end
|
65
68
|
if HTML_SIGNATURE.any? { |sig| html.upcase.start_with? sig }
|
@@ -67,12 +70,10 @@ module HTMLProofer
|
|
67
70
|
'response',
|
68
71
|
Middleware.options
|
69
72
|
).check_parsed(
|
70
|
-
Nokogiri::
|
73
|
+
Nokogiri::HTML5(html, max_errors: -1), 'response'
|
71
74
|
)
|
72
75
|
|
73
|
-
|
74
|
-
raise InvalidHtmlError.new(parsed[:failures])
|
75
|
-
end
|
76
|
+
raise InvalidHtmlError, parsed[:failures] unless parsed[:failures].empty?
|
76
77
|
end
|
77
78
|
result
|
78
79
|
end
|
data/lib/html-proofer/runner.rb
CHANGED
@@ -45,7 +45,7 @@ module HTMLProofer
|
|
45
45
|
end
|
46
46
|
|
47
47
|
if @failures.empty?
|
48
|
-
@logger.
|
48
|
+
@logger.log :info, 'HTML-Proofer finished successfully.'
|
49
49
|
else
|
50
50
|
print_failed_tests
|
51
51
|
end
|
@@ -100,12 +100,10 @@ module HTMLProofer
|
|
100
100
|
@src.each do |src|
|
101
101
|
checks.each do |klass|
|
102
102
|
@logger.log :debug, "Checking #{klass.to_s.downcase} on #{path} ..."
|
103
|
-
check = Object.const_get(klass).new(src, path, html, @options)
|
103
|
+
check = Object.const_get(klass).new(src, path, html, @logger, @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,9 @@ module HTMLProofer
|
|
148
146
|
|
149
147
|
def checks
|
150
148
|
return @checks if defined?(@checks) && !@checks.nil?
|
149
|
+
|
150
|
+
return (@checks = ['LinkCheck']) if @type == :links
|
151
|
+
|
151
152
|
@checks = HTMLProofer::Check.subchecks.map(&:name)
|
152
153
|
@checks.delete('FaviconCheck') unless @options[:check_favicon]
|
153
154
|
@checks.delete('HtmlCheck') unless @options[:check_html]
|
@@ -159,6 +160,7 @@ module HTMLProofer
|
|
159
160
|
def failed_tests
|
160
161
|
result = []
|
161
162
|
return result if @failures.empty?
|
163
|
+
|
162
164
|
@failures.each { |f| result << f.to_s }
|
163
165
|
result
|
164
166
|
end
|
@@ -169,7 +171,7 @@ module HTMLProofer
|
|
169
171
|
sorted_failures.sort_and_report
|
170
172
|
count = @failures.length
|
171
173
|
failure_text = pluralize(count, 'failure', 'failures')
|
172
|
-
raise @logger.colorize :
|
174
|
+
raise @logger.colorize :fatal, "HTML-Proofer found #{failure_text}!"
|
173
175
|
end
|
174
176
|
end
|
175
177
|
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]
|
@@ -144,22 +146,20 @@ module HTMLProofer
|
|
144
146
|
href = response.request.base_url.to_s
|
145
147
|
method = response.request.options[:method]
|
146
148
|
response_code = response.code
|
147
|
-
response.body.
|
149
|
+
response.body.delete!("\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
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'nokogumbo'
|
4
4
|
|
5
5
|
module HTMLProofer
|
6
6
|
module Utils
|
@@ -15,9 +15,8 @@ module HTMLProofer
|
|
15
15
|
path
|
16
16
|
end
|
17
17
|
|
18
|
-
Nokogiri::
|
18
|
+
Nokogiri::HTML5(content, max_errors: -1)
|
19
19
|
end
|
20
|
-
module_function :create_nokogiri
|
21
20
|
|
22
21
|
def swap(href, replacement)
|
23
22
|
replacement.each do |link, replace|
|
@@ -25,16 +24,5 @@ module HTMLProofer
|
|
25
24
|
end
|
26
25
|
href
|
27
26
|
end
|
28
|
-
module_function :swap
|
29
|
-
|
30
|
-
# address a problem with Nokogiri's parsing URL entities
|
31
|
-
# problem from http://git.io/vBYU1
|
32
|
-
# solution from http://git.io/vBYUi
|
33
|
-
def clean_content(string)
|
34
|
-
string.gsub(%r{(?:https?:)?//([^>]+)}i) do |url|
|
35
|
-
url.gsub(/&(?!amp;)/, '&')
|
36
|
-
end
|
37
|
-
end
|
38
|
-
module_function :clean_content
|
39
27
|
end
|
40
28
|
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.15.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen Torikian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-03-26 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: nokogumbo
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2.0'
|
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: '2.0'
|
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
|
@@ -308,7 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
308
308
|
- !ruby/object:Gem::Version
|
309
309
|
version: '0'
|
310
310
|
requirements: []
|
311
|
-
rubygems_version: 3.
|
311
|
+
rubygems_version: 3.1.2
|
312
312
|
signing_key:
|
313
313
|
specification_version: 4
|
314
314
|
summary: A set of tests to validate your HTML output. These tests check if your image
|