html-proofer 4.4.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e54554dcf4b21e6724cff760438b0a116c2871922f7ae0373b9d234d8202b2af
4
- data.tar.gz: 4ab71e2407af3f65381bd48fec4ef9bba19a9849265884adfa7d6a7d7dd797ac
3
+ metadata.gz: f2c984bfe049fba10c746d289d8d0256f219d6ae8f5af152f6f8af68e639301b
4
+ data.tar.gz: c407f3995e873bbbc42d90de1f082827a40caa90cb12e5cdba7fd514500d1124
5
5
  SHA512:
6
- metadata.gz: 228448293b92c6e5e5938201278f963fc2573c28869721d90671f31e84747cde91c6cfc5059da3b8404c4de874e67c319391b270de24e1df5afcf20062bda698
7
- data.tar.gz: da4e98326f717141c3c433cd9afd651987a8a091f5a07cd2cf95e0b5034e0a33214bf63cc1f85b234999b2eb6047afb926437ca4ab56d0eb8f81e5eaaa4aab2f
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+/.freeze
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
- module Configuration
6
+ class Configuration
5
7
  DEFAULT_TESTS = ["Links", "Images", "Scripts"].freeze
6
8
 
7
9
  PROOFER_DEFAULTS = {
@@ -41,71 +43,279 @@ 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
51
49
  def generate_defaults(opts)
52
- validate_options(default_options, opts)
53
-
54
50
  options = PROOFER_DEFAULTS.merge(opts)
55
51
 
56
52
  options[:typhoeus] = HTMLProofer::Configuration::TYPHOEUS_DEFAULTS.merge(opts[:typhoeus] || {})
57
53
  options[:hydra] = HTMLProofer::Configuration::HYDRA_DEFAULTS.merge(opts[:hydra] || {})
58
54
 
59
- options[:parallel] = HTMLProofer::Configuration::PARALLEL_DEFAULTS.merge(opts[:parallel] || {})
60
55
  options[:cache] = HTMLProofer::Configuration::CACHE_DEFAULTS.merge(opts[:cache] || {})
61
56
 
62
57
  options.delete(:src)
63
58
 
64
59
  options
65
60
  end
61
+ end
62
+
63
+ def initialize
64
+ @options = {}
65
+ end
66
+
67
+ def parse_cli_options(args)
68
+ define_options.parse!(args)
66
69
 
67
- def to_regex?(item)
68
- if item.start_with?("/") && item.end_with?("/")
69
- Regexp.new(item[1...-1])
70
- else
71
- item
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
91
+
92
+ set_option(opts, "--extensions [EXT1,EXT2,...]") do |long_opt_symbol, list|
93
+ @options[long_opt_symbol] = list.nil? ? [] : list.split(",")
94
+ end
72
95
  end
73
- end
74
96
 
75
- def parse_json_option(option_name, config, symbolize_names: true)
76
- raise ArgumentError, "Must provide an option name in string format." unless option_name.is_a?(String)
77
- raise ArgumentError, "Must provide an option name in string format." if option_name.strip.empty?
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
113
+
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
78
169
 
79
- return {} if config.nil?
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
80
184
 
81
- raise ArgumentError, "Must provide a JSON configuration in string format." unless config.is_a?(String)
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
82
189
 
83
- return {} if config.strip.empty?
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
193
+
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
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
84
204
 
85
- begin
86
- JSON.parse(config, { symbolize_names: symbolize_names })
87
- rescue StandardError
88
- raise ArgumentError, "Option '#{option_name} did not contain valid JSON."
205
+ section(opts, "General Configuration") do
206
+ set_option(opts, "--version") do
207
+ puts HTMLProofer::VERSION
208
+ exit(0)
209
+ end
89
210
  end
90
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
91
221
 
92
- private
222
+ private def str_to_regexp_map(arg)
223
+ arg.split(",").each_with_object({}) do |s, hsh|
224
+ split = s.split(/(?<!\\):/, 2)
93
225
 
94
- def default_options
95
- PROOFER_DEFAULTS.merge(typhoeus: TYPHOEUS_DEFAULTS).merge(hydra: HYDRA_DEFAULTS).merge(parallel: PARALLEL_DEFAULTS)
226
+ re = split[0].gsub(/\\:/, ":")
227
+ string = split[1].gsub(/\\:/, ":")
228
+ hsh[Regexp.new(re)] = string
96
229
  end
230
+ end
97
231
 
98
- def validate_options(defaults, options)
99
- defaults.each do |key, default_value|
100
- next unless options.key?(key)
232
+ private def section(opts, heading, &_block)
233
+ opts.separator("\n#{heading}:\n")
234
+ yield
235
+ end
101
236
 
102
- value = options[key]
103
- raise TypeError, "Invalid value for '#{key}': '#{value}'. Expected #{default_value.class}." unless value.is_a?(default_value.class)
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])
104
241
 
105
- # Iterate over nested hashes
106
- validate_options(default_value, value) if default_value.is_a?(Hash)
107
- end
242
+ opts.on(long_arg, *args) do |arg|
243
+ yield long_opt_symbol, arg
244
+ end
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."
108
267
  end
109
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
110
320
  end
111
321
  end
@@ -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(old_attr)
127
- attrs = @runner.options[:swap_attributes][@node.name]
124
+ private def swap_attributes!
125
+ return unless attribute_swapped?
128
126
 
129
- new_attr = attrs.find do |(o, _)|
130
- o == old_attr
131
- end&.last
127
+ attr_swaps = @runner.options[:swap_attributes][@node.name]
132
128
 
133
- return nil if blank?(new_attr)
129
+ attr_swaps.flatten.each_slice(2) do |(old_attr, new_attr)|
130
+ next if blank?(node[old_attr])
134
131
 
135
- @node[new_attr]
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 Cli < HTMLProofer::Reporter
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"]
@@ -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::Cli.new(logger: @logger)
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} ... \n\n")
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
- if @options[:parallel][:enable]
101
- Parallel.map(files, @options[:parallel]) { |file| load_file(file[:path], file[:source]) }
102
- else
103
- files.map do |file|
104
- load_file(file[:path], file[:source])
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTMLProofer
4
- VERSION = "4.4.2"
4
+ VERSION = "5.0.0"
5
5
  end
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,10 +22,10 @@ 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
- options = prepare_options(options, :file)
28
+ options[:type] = :file
29
29
  HTMLProofer::Runner.new(file, options)
30
30
  end
31
31
 
@@ -33,14 +33,14 @@ module HTMLProofer
33
33
  raise ArgumentError unless directory.is_a?(String)
34
34
  raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory)
35
35
 
36
- options = prepare_options(options, :directory)
36
+ options[:type] = :directory
37
37
  HTMLProofer::Runner.new([directory], options)
38
38
  end
39
39
 
40
40
  def check_directories(directories, options = {})
41
41
  raise ArgumentError unless directories.is_a?(Array)
42
42
 
43
- options = prepare_options(options, :directory)
43
+ options[:type] = :directory
44
44
  directories.each do |directory|
45
45
  raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory)
46
46
  end
@@ -50,20 +50,9 @@ module HTMLProofer
50
50
  def check_links(links, options = {})
51
51
  raise ArgumentError unless links.is_a?(Array)
52
52
 
53
- options = prepare_options(options, :links)
53
+ options[:type] = :links
54
54
  HTMLProofer::Runner.new(links, options)
55
55
  end
56
-
57
- private
58
-
59
- def prepare_options(options, type)
60
- options = {} if options.nil?
61
-
62
- raise ArgumentError, "Options must be a Hash" unless options.is_a?(Hash)
63
-
64
- options[:type] = type
65
- options
66
- end
67
56
  end
68
57
  end
69
58
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html-proofer
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.2
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen Torikian
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-07 00:00:00.000000000 Z
11
+ date: 2022-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: mercenary
28
+ name: async
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.3'
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: '0.3'
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
- - bin/htmlproofer
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/cli.rb
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: 2.6.0
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