html-proofer 2.3.0 → 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/check/favicon.rb +15 -0
- 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 +64 -289
- data/.gitignore +0 -26
- data/.travis.yml +0 -12
- data/Gemfile +0 -3
- data/LICENSE.txt +0 -22
- data/README.md +0 -265
- data/Rakefile +0 -21
- data/bin/htmlproof +0 -71
- data/html-proofer.gemspec +0 -31
- data/lib/html/proofer/check_runner.rb +0 -62
- data/lib/html/proofer/checkable.rb +0 -164
- data/lib/html/proofer/checks/favicon.rb +0 -21
- data/lib/html/proofer/checks/html.rb +0 -40
- data/lib/html/proofer/checks/images.rb +0 -57
- data/lib/html/proofer/checks/links.rb +0 -107
- data/lib/html/proofer/checks/scripts.rb +0 -39
- data/lib/html/proofer/log.rb +0 -38
- data/lib/html/proofer/url_validator.rb +0 -135
- data/lib/html/proofer/utils.rb +0 -24
- data/lib/html/proofer/version.rb +0 -6
- data/lib/html/proofer.rb +0 -189
- data/script/bootstrap +0 -5
- data/spec/html/proofer/checkable_spec.rb +0 -47
- data/spec/html/proofer/command_spec.rb +0 -88
- data/spec/html/proofer/favicon_spec.rb +0 -47
- data/spec/html/proofer/fixtures/favicon/favicon_absent.html +0 -4
- data/spec/html/proofer/fixtures/favicon/favicon_absent_apple.html +0 -7
- data/spec/html/proofer/fixtures/favicon/favicon_broken.html +0 -7
- data/spec/html/proofer/fixtures/favicon/favicon_broken_but_ignored.html +0 -7
- data/spec/html/proofer/fixtures/favicon/favicon_present.html +0 -7
- data/spec/html/proofer/fixtures/favicon/favicon_present_shortcut.html +0 -7
- data/spec/html/proofer/fixtures/html/div_inside_head.html +0 -6
- data/spec/html/proofer/fixtures/html/html5_tags.html +0 -9
- data/spec/html/proofer/fixtures/html/invalid_tag.html +0 -3
- data/spec/html/proofer/fixtures/html/missing_closing_quotes.html +0 -5
- data/spec/html/proofer/fixtures/html/opening_and_ending_tag_mismatch.html +0 -7
- data/spec/html/proofer/fixtures/html/svg_elements.html +0 -729
- data/spec/html/proofer/fixtures/html/unescaped_ampersand_in_attribute.html +0 -4
- data/spec/html/proofer/fixtures/html/unmatched_end_tag.html +0 -5
- data/spec/html/proofer/fixtures/images/emptyImageAltText.html +0 -10
- data/spec/html/proofer/fixtures/images/existingImageExternal.html +0 -9
- data/spec/html/proofer/fixtures/images/gpl.png +0 -0
- data/spec/html/proofer/fixtures/images/ignorableAltViaOptions.html +0 -13
- data/spec/html/proofer/fixtures/images/ignorableImages.html +0 -10
- data/spec/html/proofer/fixtures/images/ignoreAltButNotLink.html +0 -13
- data/spec/html/proofer/fixtures/images/image_missing_protocol_invalid.html +0 -9
- data/spec/html/proofer/fixtures/images/image_missing_protocol_valid.html +0 -9
- data/spec/html/proofer/fixtures/images/missingImageAlt.html +0 -9
- data/spec/html/proofer/fixtures/images/missingImageAltText.html +0 -9
- data/spec/html/proofer/fixtures/images/missingImageExternal.html +0 -9
- data/spec/html/proofer/fixtures/images/missingImageInternal.html +0 -9
- data/spec/html/proofer/fixtures/images/missingImageSrc.html +0 -9
- data/spec/html/proofer/fixtures/images/relativeToSelf.html +0 -2
- data/spec/html/proofer/fixtures/images/rootRelativeImages.html +0 -2
- data/spec/html/proofer/fixtures/images/srcSetCheck.html +0 -7
- data/spec/html/proofer/fixtures/images/srcSetIgnorable.html +0 -13
- data/spec/html/proofer/fixtures/images/srcSetMissingAlt.html +0 -7
- data/spec/html/proofer/fixtures/images/srcSetMissingImage.html +0 -7
- data/spec/html/proofer/fixtures/images/terribleImageName.html +0 -9
- data/spec/html/proofer/fixtures/images/workingDataURIImage.html +0 -3
- data/spec/html/proofer/fixtures/links/_site/folder.html/index.html +0 -1
- data/spec/html/proofer/fixtures/links/anchors_in_pre.html +0 -12
- data/spec/html/proofer/fixtures/links/attributeWithDash.html +0 -8
- data/spec/html/proofer/fixtures/links/blank_mailto_link.html +0 -9
- data/spec/html/proofer/fixtures/links/blank_tel_link.html +0 -9
- data/spec/html/proofer/fixtures/links/brokenHashExternal.html +0 -11
- data/spec/html/proofer/fixtures/links/brokenHashInternal.html +0 -14
- data/spec/html/proofer/fixtures/links/brokenHashOnTheWeb.html +0 -2
- data/spec/html/proofer/fixtures/links/brokenInternalLink.html +0 -0
- data/spec/html/proofer/fixtures/links/brokenLinkExternal.html +0 -11
- data/spec/html/proofer/fixtures/links/brokenLinkInternal.html +0 -10
- data/spec/html/proofer/fixtures/links/brokenLinkWithNumber.html +0 -8
- data/spec/html/proofer/fixtures/links/brokenUnixLinks.html +0 -5
- data/spec/html/proofer/fixtures/links/broken_hash_with_query.html +0 -9
- data/spec/html/proofer/fixtures/links/checkSSLLinks.html +0 -1
- data/spec/html/proofer/fixtures/links/ensure_typhoeus_options.html +0 -1
- data/spec/html/proofer/fixtures/links/erstiebegr%C3%BC%C3%9Fung.html +0 -0
- data/spec/html/proofer/fixtures/links/erstiebegr/303/274/303/237ung.html +0 -0
- data/spec/html/proofer/fixtures/links/escape_pipes.html +0 -9
- data/spec/html/proofer/fixtures/links/file.foo +0 -11
- data/spec/html/proofer/fixtures/links/folder/anchorLink.html +0 -0
- data/spec/html/proofer/fixtures/links/folder/assets/barrel.png +0 -0
- data/spec/html/proofer/fixtures/links/folder/index.html +0 -0
- data/spec/html/proofer/fixtures/links/folder/multiples/catalog/file.html +0 -8
- data/spec/html/proofer/fixtures/links/folder/multiples/javadoc/file.html +0 -8
- data/spec/html/proofer/fixtures/links/folder/relativeImage.html +0 -2
- data/spec/html/proofer/fixtures/links/folder-php/index.php +0 -2
- data/spec/html/proofer/fixtures/links/githubHash.html +0 -1
- data/spec/html/proofer/fixtures/links/gpl.png +0 -0
- data/spec/html/proofer/fixtures/links/hashReferringToSelf.html +0 -44
- data/spec/html/proofer/fixtures/links/head_link_href.html +0 -7
- data/spec/html/proofer/fixtures/links/head_link_href_absent.html +0 -7
- data/spec/html/proofer/fixtures/links/head_link_href_empty.html +0 -7
- data/spec/html/proofer/fixtures/links/ignorableLinks.html +0 -13
- data/spec/html/proofer/fixtures/links/ignorableLinksViaOptions.html +0 -13
- data/spec/html/proofer/fixtures/links/invalid_mailto_link.html +0 -9
- data/spec/html/proofer/fixtures/links/javascript_link.html +0 -9
- data/spec/html/proofer/fixtures/links/linkToFolder.html +0 -9
- data/spec/html/proofer/fixtures/links/linkTranslatedViaHrefSwap.html +0 -9
- data/spec/html/proofer/fixtures/links/linkWithHttps.html +0 -9
- data/spec/html/proofer/fixtures/links/linkWithRedirect.html +0 -12
- data/spec/html/proofer/fixtures/links/link_directory_without_slash.html +0 -1
- data/spec/html/proofer/fixtures/links/link_missing_protocol_invalid.html +0 -9
- data/spec/html/proofer/fixtures/links/link_missing_protocol_valid.html +0 -9
- data/spec/html/proofer/fixtures/links/link_pointing_to_directory.html +0 -1
- data/spec/html/proofer/fixtures/links/links_in_pre.html +0 -13
- data/spec/html/proofer/fixtures/links/mailto_link.html +0 -9
- data/spec/html/proofer/fixtures/links/missingLinkHref.html +0 -9
- data/spec/html/proofer/fixtures/links/multipleProblems.html +0 -11
- data/spec/html/proofer/fixtures/links/nodupe.html +0 -1
- data/spec/html/proofer/fixtures/links/non_standard_characters.html +0 -10
- data/spec/html/proofer/fixtures/links/notarealhash.html +0 -0
- data/spec/html/proofer/fixtures/links/other_protocols.html +0 -4
- data/spec/html/proofer/fixtures/links/placeholder_with_empty_id.html +0 -1
- data/spec/html/proofer/fixtures/links/placeholder_with_id.html +0 -1
- data/spec/html/proofer/fixtures/links/placeholder_with_name.html +0 -1
- data/spec/html/proofer/fixtures/links/redirected_error.html +0 -1
- data/spec/html/proofer/fixtures/links/relativeLinks.html +0 -4
- data/spec/html/proofer/fixtures/links/rootLink/folder/index.html +0 -1
- data/spec/html/proofer/fixtures/links/rootLink/index.html +0 -1
- data/spec/html/proofer/fixtures/links/rootLink/rootLink.html +0 -9
- data/spec/html/proofer/fixtures/links/tel_link.html +0 -9
- data/spec/html/proofer/fixtures/links/urlencoded-href.html +0 -2
- data/spec/html/proofer/fixtures/links/utf8Link.html +0 -2
- data/spec/html/proofer/fixtures/resources/Screen Shot 2012-08-09 at 7.51.18 AM.png +0 -0
- data/spec/html/proofer/fixtures/resources/books/assets/getting-good-with-git.jpg +0 -0
- data/spec/html/proofer/fixtures/resources/books/assets/pro-git.jpg +0 -0
- data/spec/html/proofer/fixtures/resources/books/assets/version-control-with-git.jpg +0 -0
- data/spec/html/proofer/fixtures/resources/books/nestedRelativeImages.html +0 -5
- data/spec/html/proofer/fixtures/resources/gpl.png +0 -0
- data/spec/html/proofer/fixtures/scripts/ignorableLinksViaOptions.html +0 -4
- data/spec/html/proofer/fixtures/scripts/script.js +0 -1
- data/spec/html/proofer/fixtures/scripts/script_broken_external.html +0 -9
- data/spec/html/proofer/fixtures/scripts/script_content.html +0 -9
- data/spec/html/proofer/fixtures/scripts/script_content_absent.html +0 -9
- data/spec/html/proofer/fixtures/scripts/script_in_pre.html +0 -5
- data/spec/html/proofer/fixtures/scripts/script_missing_internal.html +0 -9
- data/spec/html/proofer/fixtures/scripts/script_valid_internal.html +0 -9
- data/spec/html/proofer/fixtures/sorting/issue/broken_image_one.html +0 -1
- data/spec/html/proofer/fixtures/sorting/issue/broken_image_two.html +0 -4
- data/spec/html/proofer/fixtures/sorting/path/multiple_issues.html +0 -11
- data/spec/html/proofer/fixtures/sorting/path/single_issue.html +0 -1
- data/spec/html/proofer/fixtures/sorting/status/a_404.html +0 -1
- data/spec/html/proofer/fixtures/sorting/status/broken_link.html +0 -3
- data/spec/html/proofer/fixtures/sorting/status/missing_redirect.html +0 -1
- data/spec/html/proofer/fixtures/utils/lang-jp.html +0 -1
- data/spec/html/proofer/html_spec.rb +0 -57
- data/spec/html/proofer/images_spec.rb +0 -147
- data/spec/html/proofer/links_spec.rb +0 -364
- data/spec/html/proofer/scripts_spec.rb +0 -47
- data/spec/html/proofer/utils_spec.rb +0 -14
- data/spec/html/proofer_spec.rb +0 -122
- data/spec/spec_helper.rb +0 -52
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
|
-
|
|
3
|
-
class ImageCheckable < ::HTML::Proofer::Checkable
|
|
4
|
-
|
|
5
|
-
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/
|
|
6
|
-
|
|
7
|
-
def alt
|
|
8
|
-
@alt
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def empty_alt_tag?
|
|
12
|
-
alt.strip.empty?
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def terrible_filename?
|
|
16
|
-
src =~ SCREEN_SHOT_REGEX
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def src
|
|
20
|
-
real_attr(@src) || real_attr(@srcset)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def missing_src?
|
|
24
|
-
!src
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
class ImageCheck < ::HTML::Proofer::CheckRunner
|
|
30
|
-
def run
|
|
31
|
-
@html.css('img').each do |i|
|
|
32
|
-
img = ImageCheckable.new i, self
|
|
33
|
-
|
|
34
|
-
next if img.ignore?
|
|
35
|
-
|
|
36
|
-
# screenshot filenames should return because of terrible names
|
|
37
|
-
next add_issue("image has a terrible filename (#{img.src})", i.line) if img.terrible_filename?
|
|
38
|
-
|
|
39
|
-
# does the image exist?
|
|
40
|
-
if img.missing_src?
|
|
41
|
-
add_issue('image has no src or srcset attribute', i.line)
|
|
42
|
-
else
|
|
43
|
-
if img.remote?
|
|
44
|
-
add_to_external_urls img.src
|
|
45
|
-
else
|
|
46
|
-
add_issue("internal image #{img.src} does not exist", i.line) unless img.exists?
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
if img.alt.nil? || (img.empty_alt_tag? && !img.ignore_empty_alt?)
|
|
51
|
-
add_issue("image #{img.src} does not have an alt attribute", i.line)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
external_urls
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
|
-
class LinkCheckable < ::HTML::Proofer::Checkable
|
|
3
|
-
|
|
4
|
-
def href
|
|
5
|
-
real_attr @href
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def id
|
|
9
|
-
real_attr @id
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def name
|
|
13
|
-
real_attr @name
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def missing_href?
|
|
17
|
-
href.nil? && name.nil? && id.nil?
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def placeholder?
|
|
21
|
-
(id || name) && href.nil?
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
class LinkCheck < ::HTML::Proofer::CheckRunner
|
|
27
|
-
include HTML::Utils
|
|
28
|
-
|
|
29
|
-
def run
|
|
30
|
-
@html.css('a, link').each do |l|
|
|
31
|
-
link = LinkCheckable.new l, self
|
|
32
|
-
|
|
33
|
-
next if link.ignore?
|
|
34
|
-
next if link.href =~ /^javascript:/ # can't put this in ignore? because the URI does not parse
|
|
35
|
-
next if link.placeholder?
|
|
36
|
-
|
|
37
|
-
# is it even a valid URL?
|
|
38
|
-
unless link.valid?
|
|
39
|
-
add_issue("#{link.href} is an invalid URL", l.line)
|
|
40
|
-
next
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
case link.scheme
|
|
44
|
-
when 'mailto'
|
|
45
|
-
if link.path.empty?
|
|
46
|
-
add_issue("#{link.href} contains no email address", l.line)
|
|
47
|
-
elsif !link.path.include?('@')
|
|
48
|
-
add_issue("#{link.href} contains an invalid email address", l.line)
|
|
49
|
-
end
|
|
50
|
-
when 'tel'
|
|
51
|
-
add_issue("#{link.href} contains no phone number", l.line) if link.path.empty?
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# is there even a href?
|
|
55
|
-
if link.missing_href?
|
|
56
|
-
add_issue('anchor has no href attribute', l.line)
|
|
57
|
-
next
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# intentionally here because we still want valid? & missing_href? to execute
|
|
61
|
-
next if link.non_http_remote?
|
|
62
|
-
|
|
63
|
-
# does the file even exist?
|
|
64
|
-
if link.remote?
|
|
65
|
-
add_to_external_urls link.href
|
|
66
|
-
next
|
|
67
|
-
elsif !link.internal?
|
|
68
|
-
add_issue("internally linking to #{link.href}, which does not exist", l.line) unless link.exists?
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# does the local directory have a trailing slash?
|
|
72
|
-
if link.unslashed_directory? link.absolute_path
|
|
73
|
-
add_issue("internally linking to a directory #{link.absolute_path} without trailing slash", l.line)
|
|
74
|
-
next
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# verify the target hash
|
|
78
|
-
if link.hash
|
|
79
|
-
if link.internal?
|
|
80
|
-
unless hash_check @html, link.hash
|
|
81
|
-
add_issue("linking to internal hash ##{link.hash} that does not exist", l.line)
|
|
82
|
-
end
|
|
83
|
-
elsif link.external?
|
|
84
|
-
external_link_check(link)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
external_urls
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def external_link_check(link)
|
|
93
|
-
if !link.exists?
|
|
94
|
-
add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist", link.line)
|
|
95
|
-
else
|
|
96
|
-
target_html = create_nokogiri link.absolute_path
|
|
97
|
-
unless hash_check target_html, link.hash
|
|
98
|
-
add_issue("linking to #{link.href}, but #{link.hash} does not exist", link.line)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def hash_check(html, href_hash)
|
|
104
|
-
html.xpath("//*[@id='#{href_hash}']", "//*[@name='#{href_hash}']").length > 0
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
end
|
|
@@ -1,39 +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 |s|
|
|
22
|
-
script = ScriptCheckable.new s, self
|
|
23
|
-
|
|
24
|
-
next if script.ignore?
|
|
25
|
-
next unless script.blank?
|
|
26
|
-
|
|
27
|
-
# does the script exist?
|
|
28
|
-
if script.missing_src?
|
|
29
|
-
add_issue('script is empty and has no src attribute', s.line)
|
|
30
|
-
elsif script.remote?
|
|
31
|
-
add_to_external_urls script.src
|
|
32
|
-
else
|
|
33
|
-
add_issue("internal script #{script.src} does not exist", s.line) unless script.exists?
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
external_urls
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/html/proofer/log.rb
DELETED
|
@@ -1,38 +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)
|
|
10
|
-
log_level = verbose ? :debug : :info
|
|
11
|
-
|
|
12
|
-
@logger = Yell.new(:format => false, \
|
|
13
|
-
:name => 'HTML::Proofer', \
|
|
14
|
-
:level => "gte.#{log_level}") do |l|
|
|
15
|
-
l.adapter :stdout, :level => [:debug, :info, :warn]
|
|
16
|
-
l.adapter :stderr, :level => [:error, :fatal]
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def log(level, color, message)
|
|
21
|
-
@logger.send level, colorize(color, message)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def colorize(color, message)
|
|
25
|
-
if $stdout.isatty && $stderr.isatty
|
|
26
|
-
Colored.colorize(message, foreground: color)
|
|
27
|
-
else
|
|
28
|
-
message
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# dumb override to play nice with Typhoeus/Ethon
|
|
33
|
-
def debug(message = nil)
|
|
34
|
-
log(:debug, :yellow, message) unless message.nil?
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
require 'typhoeus'
|
|
2
|
-
require 'uri'
|
|
3
|
-
require_relative './utils'
|
|
4
|
-
|
|
5
|
-
module HTML
|
|
6
|
-
class Proofer
|
|
7
|
-
class UrlValidator
|
|
8
|
-
include Utils
|
|
9
|
-
|
|
10
|
-
attr_accessor :logger, :external_urls, :hydra
|
|
11
|
-
|
|
12
|
-
def initialize(logger, external_urls, options, typhoeus_opts, hydra_opts)
|
|
13
|
-
@logger = logger
|
|
14
|
-
@external_urls = external_urls
|
|
15
|
-
@failed_tests = []
|
|
16
|
-
@options = options
|
|
17
|
-
@hydra = Typhoeus::Hydra.new(hydra_opts)
|
|
18
|
-
@typhoeus_opts = typhoeus_opts
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def run
|
|
22
|
-
external_link_checker(external_urls)
|
|
23
|
-
@failed_tests
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Proofer runs faster if we pull out all the external URLs and run the checks
|
|
27
|
-
# at the end. Otherwise, we're halting the consuming process for every file during
|
|
28
|
-
# the check_directory_of_files process.
|
|
29
|
-
#
|
|
30
|
-
# In addition, sorting the list lets libcurl keep connections to the same hosts alive.
|
|
31
|
-
#
|
|
32
|
-
# Finally, we'll first make a HEAD request, rather than GETing all the contents.
|
|
33
|
-
# If the HEAD fails, we'll fall back to GET, as some servers are not configured
|
|
34
|
-
# for HEAD. If we've decided to check for hashes, we must do a GET--HEAD is
|
|
35
|
-
# not an option.
|
|
36
|
-
def external_link_checker(external_urls)
|
|
37
|
-
external_urls = Hash[external_urls.sort]
|
|
38
|
-
|
|
39
|
-
count = external_urls.length
|
|
40
|
-
check_text = "#{count} " << (count == 1 ? 'external link' : 'external links')
|
|
41
|
-
logger.log :info, :blue, "Checking #{check_text}..."
|
|
42
|
-
|
|
43
|
-
Ethon.logger = logger # log from Typhoeus/Ethon
|
|
44
|
-
|
|
45
|
-
url_processor(external_urls)
|
|
46
|
-
|
|
47
|
-
logger.log :debug, :yellow, "Running requests for all #{hydra.queued_requests.size} external URLs..."
|
|
48
|
-
hydra.run
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def url_processor(external_urls)
|
|
52
|
-
external_urls.each_pair do |href, filenames|
|
|
53
|
-
href = clean_url(href)
|
|
54
|
-
if hash?(href) && @options[:check_external_hash]
|
|
55
|
-
queue_request(:get, href, filenames)
|
|
56
|
-
else
|
|
57
|
-
queue_request(:head, href, filenames)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def clean_url(href)
|
|
63
|
-
Addressable::URI.parse(href).normalize
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def queue_request(method, href, filenames)
|
|
67
|
-
request = Typhoeus::Request.new(href, @typhoeus_opts.merge({ :method => method }))
|
|
68
|
-
request.on_complete { |response| response_handler(response, filenames) }
|
|
69
|
-
hydra.queue request
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def response_handler(response, filenames)
|
|
73
|
-
effective_url = response.options[:effective_url]
|
|
74
|
-
href = response.request.base_url.to_s
|
|
75
|
-
method = response.request.options[:method]
|
|
76
|
-
response_code = response.code
|
|
77
|
-
debug_msg = "Received a #{response_code} for #{href}"
|
|
78
|
-
debug_msg << " in #{filenames.join(' ')}" unless filenames.nil?
|
|
79
|
-
logger.log :debug, :yellow, debug_msg
|
|
80
|
-
|
|
81
|
-
if response_code.between?(200, 299)
|
|
82
|
-
check_hash_in_2xx_response(href, effective_url, response, filenames)
|
|
83
|
-
elsif response.timed_out?
|
|
84
|
-
handle_timeout(href, filenames, response_code)
|
|
85
|
-
elsif method == :head
|
|
86
|
-
queue_request(:get, href, filenames)
|
|
87
|
-
else
|
|
88
|
-
return if @options[:only_4xx] && !response_code.between?(400, 499)
|
|
89
|
-
# Received a non-successful http response.
|
|
90
|
-
add_failed_tests filenames, "External link #{href} failed: #{response_code} #{response.return_message}", response_code
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Even though the response was a success, we may have been asked to check
|
|
95
|
-
# if the hash on the URL exists on the page
|
|
96
|
-
def check_hash_in_2xx_response(href, effective_url, response, filenames)
|
|
97
|
-
return if @options[:only_4xx]
|
|
98
|
-
return unless @options[:check_external_hash]
|
|
99
|
-
return unless (hash = hash?(href))
|
|
100
|
-
|
|
101
|
-
body_doc = create_nokogiri(response.body)
|
|
102
|
-
|
|
103
|
-
# user-content is a special addition by GitHub.
|
|
104
|
-
xpath = %(//*[@name="#{hash}"]|//*[@id="#{hash}"])
|
|
105
|
-
if URI.parse(href).host.match(/github\.com/i)
|
|
106
|
-
xpath << %(|//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
return unless body_doc.xpath(xpath).empty?
|
|
110
|
-
|
|
111
|
-
add_failed_tests filenames, "External link #{href} failed: #{effective_url} exists, but the hash '#{hash}' does not", response.code
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def handle_timeout(href, filenames, response_code)
|
|
115
|
-
return if @options[:only_4xx]
|
|
116
|
-
add_failed_tests filenames, "External link #{href} failed: got a time out", response_code
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def add_failed_tests(filenames, desc, status = nil)
|
|
120
|
-
if filenames.nil?
|
|
121
|
-
@failed_tests << CheckRunner::Issue.new('', desc, nil, status)
|
|
122
|
-
else
|
|
123
|
-
filenames.each { |f| @failed_tests << CheckRunner::Issue.new(f, desc, nil, status) }
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def hash?(url)
|
|
128
|
-
URI.parse(url).fragment
|
|
129
|
-
rescue URI::InvalidURIError
|
|
130
|
-
nil
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
data/lib/html/proofer/utils.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
require 'nokogiri'
|
|
2
|
-
|
|
3
|
-
module HTML
|
|
4
|
-
module Utils
|
|
5
|
-
def create_nokogiri(path)
|
|
6
|
-
if File.exist? path
|
|
7
|
-
content = File.open(path).read
|
|
8
|
-
else
|
|
9
|
-
content = path
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
Nokogiri::HTML(content)
|
|
13
|
-
end
|
|
14
|
-
module_function :create_nokogiri
|
|
15
|
-
|
|
16
|
-
def swap(href, replacement)
|
|
17
|
-
replacement.each do |link, replace|
|
|
18
|
-
href = href.gsub(link, replace)
|
|
19
|
-
end
|
|
20
|
-
href
|
|
21
|
-
end
|
|
22
|
-
module_function :swap
|
|
23
|
-
end
|
|
24
|
-
end
|
data/lib/html/proofer/version.rb
DELETED
data/lib/html/proofer.rb
DELETED
|
@@ -1,189 +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
|
-
|
|
13
|
-
require 'parallel'
|
|
14
|
-
|
|
15
|
-
begin
|
|
16
|
-
require 'awesome_print'
|
|
17
|
-
rescue LoadError; end
|
|
18
|
-
|
|
19
|
-
module HTML
|
|
20
|
-
|
|
21
|
-
class Proofer
|
|
22
|
-
include Utils
|
|
23
|
-
|
|
24
|
-
attr_reader :options, :typhoeus_opts, :hydra_opts, :parallel_opts
|
|
25
|
-
|
|
26
|
-
TYPHOEUS_DEFAULTS = {
|
|
27
|
-
:followlocation => true,
|
|
28
|
-
:headers => {
|
|
29
|
-
"User-Agent" => "Mozilla/5.0 (compatible; HTML Proofer/#{VERSION}; +https://github.com/gjtorikian/html-proofer)"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
def initialize(src, opts = {})
|
|
34
|
-
@src = src
|
|
35
|
-
|
|
36
|
-
@proofer_opts = {
|
|
37
|
-
:ext => '.html',
|
|
38
|
-
:check_favicon => false,
|
|
39
|
-
:href_swap => [],
|
|
40
|
-
:href_ignore => [],
|
|
41
|
-
:file_ignore => [],
|
|
42
|
-
:url_ignore => [],
|
|
43
|
-
:check_external_hash => false,
|
|
44
|
-
:alt_ignore => [],
|
|
45
|
-
:empty_alt_ignore => false,
|
|
46
|
-
:disable_external => false,
|
|
47
|
-
:verbose => false,
|
|
48
|
-
:only_4xx => false,
|
|
49
|
-
:directory_index_file => 'index.html',
|
|
50
|
-
:check_html => false,
|
|
51
|
-
:error_sort => :path,
|
|
52
|
-
:checks_to_ignore => []
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@typhoeus_opts = TYPHOEUS_DEFAULTS.merge(opts[:typhoeus] || {})
|
|
56
|
-
opts.delete(:typhoeus)
|
|
57
|
-
|
|
58
|
-
@hydra_opts = opts[:hydra] || {}
|
|
59
|
-
opts.delete(:hydra)
|
|
60
|
-
|
|
61
|
-
# fall back to parallel defaults
|
|
62
|
-
@parallel_opts = opts[:parallel] || {}
|
|
63
|
-
opts.delete(:parallel)
|
|
64
|
-
|
|
65
|
-
@options = @proofer_opts.merge(opts)
|
|
66
|
-
|
|
67
|
-
@failed_tests = []
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def logger
|
|
71
|
-
@logger ||= HTML::Proofer::Log.new(@options[:verbose])
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def run
|
|
75
|
-
count = checks.length
|
|
76
|
-
check_text = "#{checks} " << (count == 1 ? 'check' : 'checks')
|
|
77
|
-
logger.log :info, :blue, "Running #{check_text} on #{@src} on *#{@options[:ext]}... \n\n"
|
|
78
|
-
|
|
79
|
-
if @src.is_a?(Array) && !@options[:disable_external]
|
|
80
|
-
check_list_of_links
|
|
81
|
-
else
|
|
82
|
-
check_directory_of_files
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
if @failed_tests.empty?
|
|
86
|
-
logger.log :info, :green, 'HTML-Proofer finished successfully.'
|
|
87
|
-
else
|
|
88
|
-
print_failed_tests
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def check_list_of_links
|
|
93
|
-
if @options[:href_swap]
|
|
94
|
-
@src = @src.map do |external_url|
|
|
95
|
-
swap(external_url, @options[:href_swap])
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
external_urls = Hash[*@src.map { |s| [s, nil] }.flatten]
|
|
99
|
-
validate_urls(external_urls)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Collects any external URLs found in a directory of files. Also collectes
|
|
103
|
-
# every failed test from check_files_for_internal_woes.
|
|
104
|
-
# Sends the external URLs to Typhoeus for batch processing.
|
|
105
|
-
def check_directory_of_files
|
|
106
|
-
external_urls = {}
|
|
107
|
-
results = check_files_for_internal_woes
|
|
108
|
-
|
|
109
|
-
results.each do |item|
|
|
110
|
-
external_urls.merge!(item[:external_urls])
|
|
111
|
-
@failed_tests.concat(item[:failed_tests])
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
validate_urls(external_urls) unless @options[:disable_external]
|
|
115
|
-
|
|
116
|
-
logger.log :info, :blue, "Ran on #{files.length} files!\n\n"
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Walks over each implemented check and runs them on the files, in parallel.
|
|
120
|
-
def check_files_for_internal_woes
|
|
121
|
-
Parallel.map(files, @parallel_opts) do |path|
|
|
122
|
-
html = create_nokogiri(path)
|
|
123
|
-
result = { :external_urls => {}, :failed_tests => [] }
|
|
124
|
-
|
|
125
|
-
checks.each do |klass|
|
|
126
|
-
logger.log :debug, :yellow, "Checking #{klass.to_s.downcase} on #{path} ..."
|
|
127
|
-
check = Object.const_get(klass).new(@src, path, html, @options, @typhoeus_opts, @hydra_opts, @parallel_opts)
|
|
128
|
-
check.run
|
|
129
|
-
result[:external_urls].merge!(check.external_urls)
|
|
130
|
-
result[:failed_tests].concat(check.issues) if check.issues.length > 0
|
|
131
|
-
end
|
|
132
|
-
result
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def validate_urls(external_urls)
|
|
137
|
-
url_validator = HTML::Proofer::UrlValidator.new(logger, external_urls, @options, @typhoeus_opts, @hydra_opts)
|
|
138
|
-
@failed_tests.concat(url_validator.run)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def files
|
|
142
|
-
if File.directory? @src
|
|
143
|
-
pattern = File.join(@src, '**', "*#{@options[:ext]}")
|
|
144
|
-
files = Dir.glob(pattern).select { |fn| File.file? fn }
|
|
145
|
-
files.reject { |f| ignore_file?(f) }
|
|
146
|
-
elsif File.extname(@src) == @options[:ext]
|
|
147
|
-
[@src].reject { |f| ignore_file?(f) }
|
|
148
|
-
else
|
|
149
|
-
[]
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def ignore_file?(file)
|
|
154
|
-
options[:file_ignore].each do |pattern|
|
|
155
|
-
return true if pattern.is_a?(String) && pattern == file
|
|
156
|
-
return true if pattern.is_a?(Regexp) && pattern =~ file
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
false
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def checks
|
|
163
|
-
return @checks unless @checks.nil?
|
|
164
|
-
@checks = HTML::Proofer::CheckRunner.checks.map(&:name)
|
|
165
|
-
@checks.delete('FaviconCheck') unless @options[:check_favicon]
|
|
166
|
-
@checks.delete('HtmlCheck') unless @options[:check_html]
|
|
167
|
-
@options[:checks_to_ignore].each do |ignored|
|
|
168
|
-
@checks.delete(ignored)
|
|
169
|
-
end
|
|
170
|
-
@checks
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def failed_tests
|
|
174
|
-
return [] if @failed_tests.empty?
|
|
175
|
-
result = []
|
|
176
|
-
@failed_tests.each { |f| result << f.to_s }
|
|
177
|
-
result
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def print_failed_tests
|
|
181
|
-
sorted_failures = HTML::Proofer::CheckRunner::SortedIssues.new(@failed_tests, @options[:error_sort], logger)
|
|
182
|
-
|
|
183
|
-
sorted_failures.sort_and_report
|
|
184
|
-
count = @failed_tests.length
|
|
185
|
-
failure_text = "#{count} " << (count == 1 ? 'failure' : 'failures')
|
|
186
|
-
fail logger.colorize :red, "HTML-Proofer found #{failure_text}!"
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
data/script/bootstrap
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
require 'spec_helper'
|
|
2
|
-
|
|
3
|
-
describe HTML::Proofer::Checkable do
|
|
4
|
-
describe '#initialize' do
|
|
5
|
-
it 'accepts the xmlns attribute' do
|
|
6
|
-
nokogiri = Nokogiri::HTML '<a xmlns:cc="http://creativecommons.org/ns#">Creative Commons</a>'
|
|
7
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('a').first, nil
|
|
8
|
-
expect(checkable.instance_variable_get(:@xmlns_cc)).to eq 'http://creativecommons.org/ns#'
|
|
9
|
-
end
|
|
10
|
-
it 'assignes the text node' do
|
|
11
|
-
nokogiri = Nokogiri::HTML '<p>One'
|
|
12
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('p').first, nil
|
|
13
|
-
expect(checkable.instance_variable_get(:@text)).to eq 'One'
|
|
14
|
-
end
|
|
15
|
-
it 'accepts the content attribute' do
|
|
16
|
-
nokogiri = Nokogiri::HTML '<meta name="twitter:card" content="summary">'
|
|
17
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('meta').first, nil
|
|
18
|
-
expect(checkable.instance_variable_get(:@content)).to eq 'summary'
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
describe '#ignores_pattern_check' do
|
|
22
|
-
it 'works for regex patterns' do
|
|
23
|
-
nokogiri = Nokogiri::HTML '<script src=/assets/main.js></script>'
|
|
24
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('script').first, nil
|
|
25
|
-
expect(checkable.ignores_pattern_check([/\/assets\/.*(js|css|png|svg)/])).to eq true
|
|
26
|
-
end
|
|
27
|
-
it 'works for string patterns' do
|
|
28
|
-
nokogiri = Nokogiri::HTML '<script src=/assets/main.js></script>'
|
|
29
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('script').first, nil
|
|
30
|
-
expect(checkable.ignores_pattern_check(['/assets/main.js'])).to eq true
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
describe '#url' do
|
|
34
|
-
it 'works for src attributes' do
|
|
35
|
-
nokogiri = Nokogiri::HTML '<img src=image.png />'
|
|
36
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('img').first, nil
|
|
37
|
-
expect(checkable.url).to eq 'image.png'
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
describe '#ignore' do
|
|
41
|
-
it 'works for twitter cards' do
|
|
42
|
-
nokogiri = Nokogiri::HTML '<meta name="twitter:url" data-proofer-ignore content="http://example.com/soon-to-be-published-url">'
|
|
43
|
-
checkable = HTML::Proofer::Checkable.new nokogiri.css('meta').first, nil
|
|
44
|
-
expect(checkable.ignore?).to eq true
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|