html-proofer 4.4.3 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/htmlproofer +17 -0
- data/lib/html_proofer/check/images.rb +1 -1
- data/lib/html_proofer/cli.rb +28 -0
- data/lib/html_proofer/configuration.rb +252 -22
- data/lib/html_proofer/element.rb +10 -11
- data/lib/html_proofer/reporter/{cli.rb → terminal.rb} +1 -1
- data/lib/html_proofer/runner.rb +13 -9
- data/lib/html_proofer/version.rb +1 -1
- data/lib/html_proofer.rb +2 -2
- metadata +9 -22
- data/bin/htmlproofer +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2c984bfe049fba10c746d289d8d0256f219d6ae8f5af152f6f8af68e639301b
|
4
|
+
data.tar.gz: c407f3995e873bbbc42d90de1f082827a40caa90cb12e5cdba7fd514500d1124
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 732365ae4207754f70b27843a52d4f8186673fe1282ddaae03244ebff07517816371e970b75e465539bac51e931d51d4b1856866fcd6a3a575ae6e980d7c6465
|
7
|
+
data.tar.gz: ab01a8425d206863584e6c84f395ab85a22eff9d2e1df0daa4e0f72771fc9bde95a8992da17d2ef8195439ad1de2412135758989bb9ba29766b1978d95e0fad4
|
data/exe/htmlproofer
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$stdout.sync = true
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
7
|
+
|
8
|
+
require "html-proofer"
|
9
|
+
require "benchmark"
|
10
|
+
|
11
|
+
exit_status = -1
|
12
|
+
cli = HTMLProofer::CLI.new
|
13
|
+
|
14
|
+
time = Benchmark.realtime { exit_status = cli.run }
|
15
|
+
|
16
|
+
puts "Finished in #{time.round(2)} seconds"
|
17
|
+
exit exit_status
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module HTMLProofer
|
4
4
|
class Check
|
5
5
|
class Images < HTMLProofer::Check
|
6
|
-
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d
|
6
|
+
SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/
|
7
7
|
|
8
8
|
def run
|
9
9
|
@html.css("img, source").each do |node|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTMLProofer
|
4
|
+
# The CLI is a class responsible of handling all the command line interface
|
5
|
+
# logic.
|
6
|
+
class CLI
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(args = ARGV)
|
14
|
+
@options, path = HTMLProofer::Configuration.new.parse_cli_options(args)
|
15
|
+
|
16
|
+
paths = path.split(",")
|
17
|
+
|
18
|
+
if @options[:as_links]
|
19
|
+
links = path.split(",").map(&:strip)
|
20
|
+
HTMLProofer.check_links(links, @options).run
|
21
|
+
elsif File.directory?(paths.first)
|
22
|
+
HTMLProofer.check_directories(paths, @options).run
|
23
|
+
else
|
24
|
+
HTMLProofer.check_file(path, @options).run
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "optparse"
|
4
|
+
|
3
5
|
module HTMLProofer
|
4
|
-
|
6
|
+
class Configuration
|
5
7
|
DEFAULT_TESTS = ["Links", "Images", "Scripts"].freeze
|
6
8
|
|
7
9
|
PROOFER_DEFAULTS = {
|
@@ -41,10 +43,6 @@ module HTMLProofer
|
|
41
43
|
max_concurrency: 50,
|
42
44
|
}.freeze
|
43
45
|
|
44
|
-
PARALLEL_DEFAULTS = {
|
45
|
-
enable: true,
|
46
|
-
}.freeze
|
47
|
-
|
48
46
|
CACHE_DEFAULTS = {}.freeze
|
49
47
|
|
50
48
|
class << self
|
@@ -54,38 +52,270 @@ module HTMLProofer
|
|
54
52
|
options[:typhoeus] = HTMLProofer::Configuration::TYPHOEUS_DEFAULTS.merge(opts[:typhoeus] || {})
|
55
53
|
options[:hydra] = HTMLProofer::Configuration::HYDRA_DEFAULTS.merge(opts[:hydra] || {})
|
56
54
|
|
57
|
-
options[:parallel] = HTMLProofer::Configuration::PARALLEL_DEFAULTS.merge(opts[:parallel] || {})
|
58
55
|
options[:cache] = HTMLProofer::Configuration::CACHE_DEFAULTS.merge(opts[:cache] || {})
|
59
56
|
|
60
57
|
options.delete(:src)
|
61
58
|
|
62
59
|
options
|
63
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@options = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_cli_options(args)
|
68
|
+
define_options.parse!(args)
|
69
|
+
|
70
|
+
input = ARGV.empty? ? "." : ARGV.join(",")
|
71
|
+
|
72
|
+
[@options, input]
|
73
|
+
end
|
74
|
+
|
75
|
+
private def define_options
|
76
|
+
OptionParser.new do |opts|
|
77
|
+
opts.banner = "Usage: htmlproofer [options] PATH/LINK"
|
78
|
+
|
79
|
+
section(opts, "Input Options") do
|
80
|
+
set_option(opts, "--as-links") do |long_opt_symbol, arg|
|
81
|
+
@options[long_opt_symbol] = arg
|
82
|
+
end
|
83
|
+
|
84
|
+
set_option(opts, "--assume-extension EXT") do |long_opt_symbol, arg|
|
85
|
+
@options[long_opt_symbol] = arg
|
86
|
+
end
|
87
|
+
|
88
|
+
set_option(opts, "--directory-index-file FILENAME") do |long_opt_symbol, arg|
|
89
|
+
@options[long_opt_symbol] = arg
|
90
|
+
end
|
64
91
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
item
|
92
|
+
set_option(opts, "--extensions [EXT1,EXT2,...]") do |long_opt_symbol, list|
|
93
|
+
@options[long_opt_symbol] = list.nil? ? [] : list.split(",")
|
94
|
+
end
|
70
95
|
end
|
71
|
-
end
|
72
96
|
|
73
|
-
|
74
|
-
|
75
|
-
|
97
|
+
section(opts, "Check Configuration") do
|
98
|
+
set_option(opts, "--[no-]allow-hash-href") do |long_opt_symbol, arg|
|
99
|
+
@options[long_opt_symbol] = arg
|
100
|
+
end
|
101
|
+
|
102
|
+
set_option(opts, "--[no-]allow-missing-href") do |long_opt_symbol, arg|
|
103
|
+
@options[long_opt_symbol] = arg
|
104
|
+
end
|
105
|
+
|
106
|
+
set_option(opts, "--checks [CHECK1,CHECK2,...]") do |long_opt_symbol, list|
|
107
|
+
@options[long_opt_symbol] = list.nil? ? [] : list.split(",")
|
108
|
+
end
|
109
|
+
|
110
|
+
set_option(opts, "--[no-]check-external-hash") do |long_opt_symbol, arg|
|
111
|
+
@options[long_opt_symbol] = arg
|
112
|
+
end
|
76
113
|
|
77
|
-
|
114
|
+
set_option(opts, "--[no-]check-internal-hash") do |long_opt_symbol, arg|
|
115
|
+
@options[long_opt_symbol] = arg
|
116
|
+
end
|
117
|
+
|
118
|
+
set_option(opts, "--[no-]check-sri") do |long_opt_symbol, arg|
|
119
|
+
@options[long_opt_symbol] = arg
|
120
|
+
end
|
121
|
+
|
122
|
+
set_option(opts, "--[no-]disable-external") do |long_opt_symbol, arg|
|
123
|
+
@options[long_opt_symbol] = arg
|
124
|
+
end
|
125
|
+
|
126
|
+
set_option(opts, "--[no-]enforce-https") do |long_opt_symbol, arg|
|
127
|
+
@options[long_opt_symbol] = arg
|
128
|
+
end
|
129
|
+
|
130
|
+
set_option(opts, "--root-dir <DIR>") do |long_opt_symbol, arg|
|
131
|
+
@options[long_opt_symbol] = arg
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
section(opts, "Ignore Configuration") do
|
136
|
+
set_option(opts, "--ignore-files [FILE1,FILE2,...]") do |long_opt_symbol, list|
|
137
|
+
@options[long_opt_symbol] = list.nil? ? [] : list.split(",")
|
138
|
+
end
|
139
|
+
|
140
|
+
set_option(opts, "--[no-]ignore-empty-alt") do |long_opt_symbol, arg|
|
141
|
+
@options[long_opt_symbol] = arg
|
142
|
+
end
|
143
|
+
|
144
|
+
set_option(opts, "--[no-]ignore-empty-mailto") do |long_opt_symbol, arg|
|
145
|
+
@options[long_opt_symbol] = arg
|
146
|
+
end
|
147
|
+
|
148
|
+
set_option(opts, "--[no-]ignore-missing-alt") do |long_opt_symbol, arg|
|
149
|
+
@options[long_opt_symbol] = arg
|
150
|
+
end
|
151
|
+
|
152
|
+
set_option(opts, "--ignore-status-codes [500,401,420,...]") do |long_opt_symbol, list|
|
153
|
+
@options[long_opt_symbol] = list.nil? ? [] : list.split(",").map(&:to_i)
|
154
|
+
end
|
155
|
+
|
156
|
+
set_option(opts, "--ignore-urls [URL1, URL2,...]") do |long_opt_symbol, list|
|
157
|
+
@options[long_opt_symbol] = if list.nil?
|
158
|
+
[]
|
159
|
+
else
|
160
|
+
list.split(",").each_with_object([]) do |url, arr|
|
161
|
+
arr << to_regex?(url)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
set_option(opts, "--only-status-codes [404,451,...]") do |long_opt_symbol, list|
|
167
|
+
@options[long_opt_symbol] = list.nil? ? [] : list.split(",")
|
168
|
+
end
|
169
|
+
|
170
|
+
set_option(opts, "--only-4xx") do |long_opt_symbol, arg|
|
171
|
+
@options[long_opt_symbol] = arg
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
section(opts, "Transforms Configuration") do
|
176
|
+
set_option(opts, "--swap-attributes <CONFIG>") do |long_opt_symbol, arg|
|
177
|
+
@options[long_opt_symbol] = parse_json_option("swap_attributes", arg, symbolize_names: false)
|
178
|
+
end
|
179
|
+
|
180
|
+
set_option(opts, "--swap-urls [re:string,re:string,...]") do |long_opt_symbol, arg|
|
181
|
+
@options[long_opt_symbol] = str_to_regexp_map(arg)
|
182
|
+
end
|
183
|
+
end
|
78
184
|
|
79
|
-
|
185
|
+
section(opts, "Dependencies Configuration") do
|
186
|
+
set_option(opts, "--typhoeus <CONFIG>") do |long_opt_symbol, arg|
|
187
|
+
@options[long_opt_symbol] = parse_json_option("typhoeus", arg, symbolize_names: false)
|
188
|
+
end
|
80
189
|
|
81
|
-
|
190
|
+
set_option(opts, "--hydra <CONFIG>") do |long_opt_symbol, _list|
|
191
|
+
@options[long_opt_symbol] = parse_json_option("hydra", arg, symbolize_names: false)
|
192
|
+
end
|
82
193
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
raise ArgumentError, "Option '#{option_name} did not contain valid JSON."
|
194
|
+
set_option(opts, "--cache <CONFIG>") do |long_opt_symbol, _list|
|
195
|
+
@options[long_opt_symbol] = parse_json_option("cache", arg, symbolize_names: false)
|
196
|
+
end
|
87
197
|
end
|
198
|
+
|
199
|
+
section(opts, "Reporting Configuration") do
|
200
|
+
set_option(opts, "--log-level <LEVEL>") do |long_opt_symbol, arg|
|
201
|
+
@options[long_opt_symbol] = arg.to_sym
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
section(opts, "General Configuration") do
|
206
|
+
set_option(opts, "--version") do
|
207
|
+
puts HTMLProofer::VERSION
|
208
|
+
exit(0)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
private def to_regex?(item)
|
215
|
+
if item.start_with?("/") && item.end_with?("/")
|
216
|
+
Regexp.new(item[1...-1])
|
217
|
+
else
|
218
|
+
item
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
private def str_to_regexp_map(arg)
|
223
|
+
arg.split(",").each_with_object({}) do |s, hsh|
|
224
|
+
split = s.split(/(?<!\\):/, 2)
|
225
|
+
|
226
|
+
re = split[0].gsub(/\\:/, ":")
|
227
|
+
string = split[1].gsub(/\\:/, ":")
|
228
|
+
hsh[Regexp.new(re)] = string
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
private def section(opts, heading, &_block)
|
233
|
+
opts.separator("\n#{heading}:\n")
|
234
|
+
yield
|
235
|
+
end
|
236
|
+
|
237
|
+
private def set_option(opts, long_arg, &block)
|
238
|
+
long_opt_symbol = parse_long_opt(long_arg)
|
239
|
+
args = []
|
240
|
+
args += Array(ConfigurationHelp::TEXT[long_opt_symbol])
|
241
|
+
|
242
|
+
opts.on(long_arg, *args) do |arg|
|
243
|
+
yield long_opt_symbol, arg
|
88
244
|
end
|
89
245
|
end
|
246
|
+
|
247
|
+
# Converts the option into a symbol,
|
248
|
+
# e.g. '--allow-hash-href' => :allow_hash_href.
|
249
|
+
private def parse_long_opt(long_opt)
|
250
|
+
long_opt[2..].sub("[no-]", "").sub(/ .*/, "").tr("-", "_").gsub(/[\[\]]/, "").to_sym
|
251
|
+
end
|
252
|
+
|
253
|
+
def parse_json_option(option_name, config, symbolize_names: true)
|
254
|
+
raise ArgumentError, "Must provide an option name in string format." unless option_name.is_a?(String)
|
255
|
+
raise ArgumentError, "Must provide an option name in string format." if option_name.strip.empty?
|
256
|
+
|
257
|
+
return {} if config.nil?
|
258
|
+
|
259
|
+
raise ArgumentError, "Must provide a JSON configuration in string format." unless config.is_a?(String)
|
260
|
+
|
261
|
+
return {} if config.strip.empty?
|
262
|
+
|
263
|
+
begin
|
264
|
+
JSON.parse(config, { symbolize_names: symbolize_names })
|
265
|
+
rescue StandardError
|
266
|
+
raise ArgumentError, "Option '#{option_name} did not contain valid JSON."
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
module ConfigurationHelp
|
271
|
+
TEXT = {
|
272
|
+
as_links: ["Assumes that `PATH` is a comma-separated array of links to check."],
|
273
|
+
assume_extension: ["Automatically add specified extension to files for internal links, ",
|
274
|
+
"to allow extensionless URLs (as supported by most servers) (default: `.html`).",],
|
275
|
+
directory_index_file: ["Sets the file to look for when a link refers to a directory. (default: `index.html`)."],
|
276
|
+
extensions: ["A comma-separated list of Strings indicating the file extensions you",
|
277
|
+
"would like to check (default: `.html`)",],
|
278
|
+
|
279
|
+
allow_hash_href: ['"If `true`, assumes `href="#"` anchors are valid (default: `true`)"'],
|
280
|
+
allow_missing_href: ["If `true`, does not flag `a` tags missing `href`. In HTML5, this is technically ",
|
281
|
+
"allowed, but could also be human error. (default: `false`)",],
|
282
|
+
checks: ["A comma-separated list of Strings indicating which checks you",
|
283
|
+
"want to run (default: `[\"Links\", \"Images\", \"Scripts\"]",],
|
284
|
+
check_external_hash: ["Checks whether external hashes exist (even if the webpage exists) (default: `true`)."],
|
285
|
+
check_internal_hash: ["Checks whether internal hashes exist (even if the webpage exists) (default: `true`)."],
|
286
|
+
check_sri: ["Check that `<link>` and `<script>` external resources use SRI (default: `false`)."],
|
287
|
+
disable_external: ["If `true`, does not run the external link checker (default: `false`)."],
|
288
|
+
enforce_https: ["Fails a link if it\'s not marked as `https` (default: `true`)."],
|
289
|
+
root_dir: ["The absolute path to the directory serving your html-files."],
|
290
|
+
|
291
|
+
ignore_empty_alt: ["If `true`, ignores images with empty/missing ",
|
292
|
+
"alt tags (in other words, `<img alt>` and `<img alt=\"\">`",
|
293
|
+
"are valid; set this to `false` to flag those) (default: `true`).",],
|
294
|
+
ignore_empty_mailto: ["If `true`, allows `mailto:` `href`s which don't",
|
295
|
+
"contain an email address (default: `false`)'.",],
|
296
|
+
ignore_missing_alt: ["If `true`, ignores images with missing alt tags (default: `false`)."],
|
297
|
+
ignore_status_codes: ["A comma-separated list of numbers representing status codes to ignore."],
|
298
|
+
ignore_files: ["A comma-separated list of Strings or RegExps containing file paths that are safe to ignore"],
|
299
|
+
ignore_urls: ["A comma-separated list of Strings or RegExps containing URLs that are",
|
300
|
+
"safe to ignore. This affects all HTML attributes, such as `alt` tags on images.",],
|
301
|
+
only_status_codes: ["A comma-separated list of numbers representing the only status codes to report on."],
|
302
|
+
only_4xx: ["Only reports errors for links that fall within the 4xx status code range."],
|
303
|
+
|
304
|
+
swap_attributes: ["JSON-formatted config that maps element names to the",
|
305
|
+
"preferred attribute to check (default: `{}`).",],
|
306
|
+
swap_urls: ["A comma-separated list containing key-value pairs of `RegExp => String`.",
|
307
|
+
"It transforms URLs that match `RegExp` into `String` via `gsub`.",
|
308
|
+
"The escape sequences `\\:` should be used to produce literal `:`s.",],
|
309
|
+
|
310
|
+
typhoeus: ["JSON-formatted string of Typhoeus config; if set, overrides the html-proofer defaults."],
|
311
|
+
hydra: ["JSON-formatted string of Hydra config; if set, overrides the html-proofer defaults."],
|
312
|
+
cache: ["JSON-formatted string of cache config; if set, overrides the html-proofer defaults."],
|
313
|
+
|
314
|
+
log_level: ["Sets the logging level. One of `:debug`, `:info`, ",
|
315
|
+
"`:warn`, `:error`, or `:fatal`. (default: `:info`)",],
|
316
|
+
|
317
|
+
version: ["Prints the version of html-proofer."],
|
318
|
+
}.freeze
|
319
|
+
end
|
90
320
|
end
|
91
321
|
end
|
data/lib/html_proofer/element.rb
CHANGED
@@ -13,6 +13,8 @@ module HTMLProofer
|
|
13
13
|
@runner = runner
|
14
14
|
@node = node
|
15
15
|
|
16
|
+
swap_attributes!
|
17
|
+
|
16
18
|
@base_url = base_url
|
17
19
|
@url = Attribute::Url.new(runner, link_attribute, base_url: base_url)
|
18
20
|
|
@@ -26,7 +28,6 @@ module HTMLProofer
|
|
26
28
|
|
27
29
|
def meta_content
|
28
30
|
return nil unless meta_tag?
|
29
|
-
return swap_attributes("content") if attribute_swapped?
|
30
31
|
|
31
32
|
@node["content"]
|
32
33
|
end
|
@@ -37,7 +38,6 @@ module HTMLProofer
|
|
37
38
|
|
38
39
|
def src
|
39
40
|
return nil if !img_tag? && !script_tag? && !source_tag?
|
40
|
-
return swap_attributes("src") if attribute_swapped?
|
41
41
|
|
42
42
|
@node["src"]
|
43
43
|
end
|
@@ -52,7 +52,6 @@ module HTMLProofer
|
|
52
52
|
|
53
53
|
def srcset
|
54
54
|
return nil if !img_tag? && !source_tag?
|
55
|
-
return swap_attributes("srcset") if attribute_swapped?
|
56
55
|
|
57
56
|
@node["srcset"]
|
58
57
|
end
|
@@ -63,7 +62,6 @@ module HTMLProofer
|
|
63
62
|
|
64
63
|
def href
|
65
64
|
return nil if !a_tag? && !link_tag?
|
66
|
-
return swap_attributes("href") if attribute_swapped?
|
67
65
|
|
68
66
|
@node["href"]
|
69
67
|
end
|
@@ -123,16 +121,17 @@ module HTMLProofer
|
|
123
121
|
return true unless blank?(attrs)
|
124
122
|
end
|
125
123
|
|
126
|
-
private def swap_attributes
|
127
|
-
|
124
|
+
private def swap_attributes!
|
125
|
+
return unless attribute_swapped?
|
128
126
|
|
129
|
-
|
130
|
-
o == old_attr
|
131
|
-
end&.last
|
127
|
+
attr_swaps = @runner.options[:swap_attributes][@node.name]
|
132
128
|
|
133
|
-
|
129
|
+
attr_swaps.flatten.each_slice(2) do |(old_attr, new_attr)|
|
130
|
+
next if blank?(node[old_attr])
|
134
131
|
|
135
|
-
|
132
|
+
node[new_attr] = node[old_attr]
|
133
|
+
node.delete(old_attr)
|
134
|
+
end
|
136
135
|
end
|
137
136
|
|
138
137
|
private def ancestors_ignorable?
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module HTMLProofer
|
4
4
|
class Reporter
|
5
|
-
class
|
5
|
+
class Terminal < HTMLProofer::Reporter
|
6
6
|
def report
|
7
7
|
msg = failures.each_with_object([]) do |(check_name, failures), arr|
|
8
8
|
str = ["For the #{check_name} check, the following failures were found:\n"]
|
data/lib/html_proofer/runner.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "async"
|
4
|
+
|
3
5
|
module HTMLProofer
|
4
6
|
class Runner
|
5
7
|
include HTMLProofer::Utils
|
@@ -30,18 +32,18 @@ module HTMLProofer
|
|
30
32
|
@current_source = nil
|
31
33
|
@current_filename = nil
|
32
34
|
|
33
|
-
@reporter = Reporter::
|
35
|
+
@reporter = Reporter::Terminal.new(logger: @logger)
|
34
36
|
end
|
35
37
|
|
36
38
|
def run
|
37
39
|
check_text = pluralize(checks.length, "check", "checks")
|
38
40
|
|
39
41
|
if @type == :links
|
40
|
-
@logger.log(:info, "Running #{check_text} (#{format_checks_list(checks)}) on #{@source}
|
42
|
+
@logger.log(:info, "Running #{check_text} (#{format_checks_list(checks)}) on #{@source} ...\n\n")
|
41
43
|
check_list_of_links unless @options[:disable_external]
|
42
44
|
else
|
43
45
|
@logger.log(:info,
|
44
|
-
"Running #{check_text} (#{format_checks_list(checks)}) in #{@source} on *#{@options[:extensions].join(", ")} files...\n\n")
|
46
|
+
"Running #{check_text} (#{format_checks_list(checks)}) in #{@source} on *#{@options[:extensions].join(", ")} files ...\n\n")
|
45
47
|
|
46
48
|
check_files
|
47
49
|
@logger.log(:info, "Ran on #{pluralize(files.length, "file", "files")}!\n\n")
|
@@ -97,13 +99,15 @@ module HTMLProofer
|
|
97
99
|
|
98
100
|
# Walks over each implemented check and runs them on the files, in parallel.
|
99
101
|
def process_files
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
loaded_files = []
|
103
|
+
files.each do |file|
|
104
|
+
Async do |task|
|
105
|
+
task.async do
|
106
|
+
loaded_files << load_file(file[:path], file[:source])
|
107
|
+
end
|
105
108
|
end
|
106
109
|
end
|
110
|
+
loaded_files
|
107
111
|
end
|
108
112
|
|
109
113
|
def load_file(path, source)
|
@@ -238,7 +242,7 @@ module HTMLProofer
|
|
238
242
|
private def format_checks_list(checks)
|
239
243
|
checks.map do |check|
|
240
244
|
check.sub(/HTMLProofer::Check::/, "")
|
241
|
-
end.join(", ")
|
245
|
+
end.sort.join(", ")
|
242
246
|
end
|
243
247
|
end
|
244
248
|
end
|
data/lib/html_proofer/version.rb
CHANGED
data/lib/html_proofer.rb
CHANGED
@@ -5,13 +5,13 @@ lib_dir = File.join(File.dirname(__dir__), "lib")
|
|
5
5
|
gem_loader = Zeitwerk::Loader.for_gem
|
6
6
|
gem_loader.inflector.inflect(
|
7
7
|
"html_proofer" => "HTMLProofer",
|
8
|
+
"cli" => "CLI",
|
8
9
|
)
|
9
10
|
gem_loader.ignore(File.join(lib_dir, "html-proofer.rb"))
|
10
11
|
gem_loader.setup
|
11
12
|
|
12
13
|
require "html_proofer/version"
|
13
14
|
|
14
|
-
require "parallel"
|
15
15
|
require "fileutils"
|
16
16
|
|
17
17
|
if ENV.fetch("DEBUG", false)
|
@@ -22,7 +22,7 @@ end
|
|
22
22
|
module HTMLProofer
|
23
23
|
class << self
|
24
24
|
def check_file(file, options = {})
|
25
|
-
raise ArgumentError unless file.is_a?(String)
|
25
|
+
raise ArgumentError, "File isn't a string" unless file.is_a?(String)
|
26
26
|
raise ArgumentError, "#{file} does not exist" unless File.exist?(file)
|
27
27
|
|
28
28
|
options[:type] = :file
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: html-proofer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen Torikian
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2022-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
@@ -25,19 +25,19 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: async
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.1'
|
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: '2.1'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: nokogiri
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.13'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: parallel
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.10'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.10'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: rainbow
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -270,7 +256,7 @@ executables:
|
|
270
256
|
extensions: []
|
271
257
|
extra_rdoc_files: []
|
272
258
|
files:
|
273
|
-
-
|
259
|
+
- exe/htmlproofer
|
274
260
|
- lib/html-proofer.rb
|
275
261
|
- lib/html_proofer.rb
|
276
262
|
- lib/html_proofer/attribute.rb
|
@@ -282,12 +268,13 @@ files:
|
|
282
268
|
- lib/html_proofer/check/links.rb
|
283
269
|
- lib/html_proofer/check/open_graph.rb
|
284
270
|
- lib/html_proofer/check/scripts.rb
|
271
|
+
- lib/html_proofer/cli.rb
|
285
272
|
- lib/html_proofer/configuration.rb
|
286
273
|
- lib/html_proofer/element.rb
|
287
274
|
- lib/html_proofer/failure.rb
|
288
275
|
- lib/html_proofer/log.rb
|
289
276
|
- lib/html_proofer/reporter.rb
|
290
|
-
- lib/html_proofer/reporter/
|
277
|
+
- lib/html_proofer/reporter/terminal.rb
|
291
278
|
- lib/html_proofer/runner.rb
|
292
279
|
- lib/html_proofer/url_validator.rb
|
293
280
|
- lib/html_proofer/url_validator/external.rb
|
@@ -309,7 +296,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
309
296
|
requirements:
|
310
297
|
- - ">="
|
311
298
|
- !ruby/object:Gem::Version
|
312
|
-
version:
|
299
|
+
version: '3.1'
|
313
300
|
- - "<"
|
314
301
|
- !ruby/object:Gem::Version
|
315
302
|
version: '4.0'
|
data/bin/htmlproofer
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
$stdout.sync = true
|
5
|
-
|
6
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
-
|
8
|
-
require 'html-proofer'
|
9
|
-
require 'mercenary'
|
10
|
-
|
11
|
-
Mercenary.program(:htmlproofer) do |p|
|
12
|
-
p.version HTMLProofer::VERSION
|
13
|
-
p.description %(Test your rendered HTML files to make sure they're accurate.)
|
14
|
-
p.syntax 'htmlproofer PATH [options]'
|
15
|
-
|
16
|
-
p.description 'Runs the HTML-Proofer suite on the files in PATH. For more details, see the README.'
|
17
|
-
|
18
|
-
p.option 'allow_hash_href', '--allow-hash-href=<true|false>', 'String', 'If `true`, assumes `href="#"` anchors are valid (default: `true`)'
|
19
|
-
p.option 'allow_missing_href', '--allow-missing-href=<true|false>', 'String', 'If `true`, does not flag `a` tags missing `href`. In HTML5, this is technically allowed, but could also be human error. (default: `false`)'
|
20
|
-
p.option 'as_links', '--as-links', 'Assumes that `PATH` is a comma-separated array of links to check.'
|
21
|
-
p.option 'assume_extension', '--assume-extension <ext>', 'Automatically add specified extension to files for internal links, to allow extensionless URLs (as supported by most servers) (default: `.html`).'
|
22
|
-
p.option 'checks', '--checks check1,[check2,...]', Array, 'A comma-separated list of Strings indicating which checks you want to run (default: `["Links", "Images", "Scripts"]`)'
|
23
|
-
p.option 'check_external_hash', '--check-external-hash=<true|false>', 'String', 'Checks whether external hashes exist (even if the webpage exists) (default: `true`).'
|
24
|
-
p.option 'check_internal_hash', '--check-internal-hash=<true|false>', 'String', 'Checks whether internal hashes exist (even if the webpage exists) (default: `true`).'
|
25
|
-
p.option 'check_sri', '--check-sri=<true|false>', 'String', 'Check that `<link>` and `<script>` external resources use SRI (default: `false`).'
|
26
|
-
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`)'
|
27
|
-
p.option 'disable_external', '--disable-external=<true|false>', String, 'If `true`, does not run the external link checker (default: `false`)'
|
28
|
-
p.option 'enforce_https', '--enforce-https=<true|false>', String, 'Fails a link if it\'s not marked as `https` (default: `true`).'
|
29
|
-
p.option 'extensions', '--extensions ext1,[ext2,...[', Array, 'A comma-separated list of Strings indicating the file extensions you would like to check (including the dot) (default: `.html`)'
|
30
|
-
p.option 'ignore_empty_alt', '--ignore-empty-alt=<true|false>', 'String', 'If `true`, ignores images with empty/missing alt tags (in other words, `<img alt>` and `<img alt="">` are valid; set this to `false` to flag those) (default: `true`)'
|
31
|
-
p.option 'ignore_empty_mailto', '--ignore-empty-mailto=<true|false>', 'String', 'If `true`, allows `mailto:` `href`s which do not contain an email address (default: `false`)'
|
32
|
-
p.option 'ignore_files', '--ignore-files file1,[file2,...]', Array, 'A comma-separated list of Strings or RegExps containing file paths that are safe to ignore'
|
33
|
-
p.option 'ignore_missing_alt', '--ignore-missing-alt=<true|false>', 'String', 'If `true`, ignores images with missing alt tags (default: `false`)'
|
34
|
-
p.option 'ignore_status_codes', '--ignore-status-codes 123,[xxx, ...]', Array, 'A comma-separated list of numbers representing status codes to ignore.'
|
35
|
-
p.option 'ignore_urls', '--ignore-urls link1,[link2,...]', Array, 'A comma-separated list of Strings or RegExps containing URLs that are safe to ignore. This affects all HTML attributes, such as `alt` tags on images.'
|
36
|
-
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`)'
|
37
|
-
p.option 'only_4xx', '--only-4xx', 'Only reports errors for links that fall within the 4xx status code range'
|
38
|
-
p.option 'root_dir', '--root-dir PATH', String, 'The absolute path to the directory serving your html-files.'
|
39
|
-
p.option 'swap_attributes', '--swap-attributes CONFIG', String, 'JSON-formatted config that maps element names to the preferred attribute to check (default: `{}`).'
|
40
|
-
p.option 'swap_urls', '--swap-urls 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.'
|
41
|
-
|
42
|
-
p.option 'typhoeus', '--typhoeus CONFIG', String, 'JSON-formatted string of Typhoeus config. Will override the html-proofer defaults.'
|
43
|
-
p.option 'hydra', '--hydra CONFIG', String, 'JSON-formatted string of Hydra config. Will override the html-proofer defaults.'
|
44
|
-
p.option 'parallel', '--parallel CONFIG', String, 'JSON-formatted string of Parallel config. Will override the html-proofer defaults.'
|
45
|
-
p.option 'cache', '--cache CONFIG', String, 'JSON-formatted string of cache config. Will override the html-proofer defaults.'
|
46
|
-
|
47
|
-
p.action do |args, opts|
|
48
|
-
args = ['.'] if args.empty?
|
49
|
-
path = args.first
|
50
|
-
|
51
|
-
options = {}
|
52
|
-
|
53
|
-
# prepare everything to go to proofer
|
54
|
-
p.options.reject { |o| opts[o.config_key].nil? }.each do |option|
|
55
|
-
opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) } if opts[option.config_key].is_a?(Array)
|
56
|
-
options[option.config_key.to_sym] = opts[option.config_key]
|
57
|
-
end
|
58
|
-
|
59
|
-
# some minor manipulation of a special option
|
60
|
-
unless opts['swap_urls'].nil?
|
61
|
-
options[:swap_urls] = {}
|
62
|
-
opts['swap_urls'].each do |s|
|
63
|
-
splt = s.split(/(?<!\\):/, 2)
|
64
|
-
|
65
|
-
re = splt[0].gsub(/\\:/, ':')
|
66
|
-
string = splt[1].gsub(/\\:/, ':')
|
67
|
-
options[:swap_urls][Regexp.new(re)] = string
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# check booleans
|
72
|
-
[:allow_hash_href, :allow_missing_href, :check_external_hash, :check_internal_hash, :check_sri, :disable_external, :enforce_https, :ignore_empty_alt, :ignore_empty_mailto, :ignore_missing_alt].each do |option|
|
73
|
-
next if (val = opts[option.to_s]).nil?
|
74
|
-
if val == "false"
|
75
|
-
options[option] = false
|
76
|
-
else
|
77
|
-
options[option] = true
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
options[:log_level] = opts['log_level'].to_sym unless opts['log_level'].nil?
|
82
|
-
|
83
|
-
options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus', opts['typhoeus'], symbolize_names: false) unless opts['typhoeus'].nil?
|
84
|
-
options[:hydra] = HTMLProofer::Configuration.parse_json_option('hydra', opts['hydra']) unless opts['hydra'].nil?
|
85
|
-
options[:parallel] = HTMLProofer::Configuration.parse_json_option('parallel', opts['parallel']) unless opts['parallel'].nil?
|
86
|
-
options[:cache] = HTMLProofer::Configuration.parse_json_option('cache', opts['cache']) unless opts['cache'].nil?
|
87
|
-
|
88
|
-
options[:swap_attributes] = HTMLProofer::Configuration.parse_json_option('swap_attributes', opts['swap_attributes'], symbolize_names: false) unless opts['swap_attributes'].nil?
|
89
|
-
|
90
|
-
options[:ignore_status_codes] = Array(options[:ignore_status_codes]).map(&:to_i)
|
91
|
-
|
92
|
-
paths = path.split(',')
|
93
|
-
if opts['as_links']
|
94
|
-
links = path.split(',').map(&:strip)
|
95
|
-
HTMLProofer.check_links(links, options).run
|
96
|
-
elsif File.directory?(paths.first)
|
97
|
-
HTMLProofer.check_directories(paths, options).run
|
98
|
-
else
|
99
|
-
HTMLProofer.check_file(path, options).run
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|