html-proofer 4.4.3 → 5.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/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
|