html-proofer 3.10.1 → 3.12.1

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: 3b785a108ad8a82bdc21dbc428240bcba1b77ac3a0a5cf08d0de68bac11583e5
4
- data.tar.gz: 52f4c91fb3a9ba242667a9a4b079fe6426fde92d29e7f121116c0544062a3a16
3
+ metadata.gz: 04d2b18950f5d8c6b2bcfadb24dec58d50d7c2fd8997977506a79a22d24aeeef
4
+ data.tar.gz: f33be2af98f5a199b563eb78aef31518c94b304c1fd120985dc3ee2febee233f
5
5
  SHA512:
6
- metadata.gz: 17c9ca468bb8ca4c20deb9387e3d85ff9c80281711f25cb87cdfbad22b733700a49ad5721e78b2169283d9178276434bef63974d250441de0297c3fbf4f698ea
7
- data.tar.gz: 89db5416f295bdfa12c8545d1b613aad7a4fb235f2e4c6470f2a2d4968e663d23689e6ca529e0cdf59e1fdcc3b95ca2467b62e9b83a6cab8bcb0790a12c12663
6
+ metadata.gz: 1e9fd7e64a8c76c74351128fd63a25c1c56ce11351fc8cd4ce0e91ee793586ab71609ae247d3fa66615b089546887a330cb2eeec9550888ebbf9f8abb59b628d
7
+ data.tar.gz: bed4a30375077f6bfd35900f75976e9f960deb316421e113b65b33ad7d61f9084be427f5241c3700aa1d8fbd717d4104389855db71a43f60f846af58c3a6694b
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
2
4
  STDOUT.sync = true
3
5
 
4
6
  $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
@@ -75,18 +77,24 @@ Mercenary.program(:htmlproofer) do |p|
75
77
  options[:error_sort] = opts['error-sort'].to_sym unless opts['error-sort'].nil?
76
78
  options[:log_level] = opts['log_level'].to_sym unless opts['log_level'].nil?
77
79
 
78
- # FIXME: this is gross
79
- options[:validation] = {}
80
- options[:validation][:report_script_embeds] = opts['report_script_embeds']
81
- options[:validation][:report_missing_names] = opts['report_missing_names']
82
- options[:validation][:report_invalid_tags] = opts['report_invalid_tags']
80
+ options[:validation] = HTMLProofer::Configuration::VALIDATION_DEFAULTS.dup
81
+ options[:validation][:report_script_embeds] = opts['report_script_embeds'] unless opts['report_script_embeds'].nil?
82
+ options[:validation][:report_missing_names] = opts['report_missing_names'] unless opts['report_missing_names'].nil?
83
+ options[:validation][:report_invalid_tags] = opts['report_invalid_tags'] unless opts['report_invalid_tags'].nil?
83
84
 
84
- options[:typhoeus] = {}
85
- options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config'])
85
+ unless opts['typhoeus_config'].nil?
86
+ options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config'])
87
+ end
86
88
 
87
- options[:cache] = {}
88
- options[:cache][:timeframe] = opts['timeframe'] unless opts['timeframe'].nil?
89
- options[:cache][:storage_dir] = opts['storage_dir'] unless opts['storage_dir'].nil?
89
+ unless opts['timeframe'].nil?
90
+ options[:cache] ||= {}
91
+ options[:cache][:timeframe] = opts['timeframe'] unless opts['timeframe'].nil?
92
+ end
93
+
94
+ unless opts['storage_dir'].nil?
95
+ options[:cache] ||= {}
96
+ options[:cache][:storage_dir] = opts['storage_dir'] unless opts['storage_dir'].nil?
97
+ end
90
98
 
91
99
  options[:http_status_ignore] = Array(options[:http_status_ignore]).map(&:to_i)
92
100
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  def require_all(path)
2
4
  dir = File.join(File.dirname(__FILE__), path)
3
5
  Dir[File.join(dir, '*.rb')].each do |f|
@@ -1,9 +1,8 @@
1
- require_relative 'utils'
1
+ # frozen_string_literal: true
2
2
 
3
+ require_relative 'utils'
4
+ require 'date'
3
5
  require 'json'
4
- require 'active_support/core_ext/string'
5
- require 'active_support/core_ext/date'
6
- require 'active_support/core_ext/numeric/time'
7
6
 
8
7
  module HTMLProofer
9
8
  class Cache
@@ -18,6 +17,9 @@ module HTMLProofer
18
17
  @logger = logger
19
18
  @cache_log = {}
20
19
 
20
+ @cache_datetime = DateTime.now
21
+ @cache_time = @cache_datetime.to_time
22
+
21
23
  if options.nil? || options.empty?
22
24
  define_singleton_method('use_cache?') { false }
23
25
  else
@@ -25,12 +27,10 @@ module HTMLProofer
25
27
  setup_cache!(options)
26
28
  @parsed_timeframe = parsed_timeframe(options[:timeframe])
27
29
  end
28
-
29
- @cache_time = Time.now
30
30
  end
31
31
 
32
32
  def within_timeframe?(time)
33
- (@parsed_timeframe..@cache_time).cover?(time)
33
+ (@parsed_timeframe..@cache_time).cover?(Time.parse(time))
34
34
  end
35
35
 
36
36
  def urls
@@ -43,16 +43,16 @@ module HTMLProofer
43
43
 
44
44
  def parsed_timeframe(timeframe)
45
45
  time, date = timeframe.match(/(\d+)(\D)/).captures
46
- time = time.to_f
46
+ time = time.to_i
47
47
  case date
48
48
  when 'M'
49
- time.months.ago
49
+ time_ago(time, :months)
50
50
  when 'w'
51
- time.weeks.ago
51
+ time_ago(time, :weeks)
52
52
  when 'd'
53
- time.days.ago
53
+ time_ago(time, :days)
54
54
  when 'h'
55
- time.hours.ago
55
+ time_ago(time, :hours)
56
56
  else
57
57
  raise ArgumentError, "#{date} is not a valid timeframe!"
58
58
  end
@@ -162,5 +162,20 @@ module HTMLProofer
162
162
  contents = File.read(cache_file)
163
163
  @cache_log = contents.empty? ? {} : JSON.parse(contents)
164
164
  end
165
+
166
+ private
167
+
168
+ def time_ago(measurement, unit)
169
+ case unit
170
+ when :months
171
+ @cache_datetime >> -measurement
172
+ when :weeks
173
+ @cache_datetime - measurement * 7
174
+ when :days
175
+ @cache_datetime - measurement
176
+ when :hours
177
+ @cache_datetime - Rational(measurement/24.0)
178
+ end.to_time
179
+ end
165
180
  end
166
181
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTMLProofer
2
4
  # Mostly handles issue management and collecting of external URLs.
3
5
  class Check
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class FaviconCheck < ::HTMLProofer::Check
2
4
  def run
3
5
  found = false
@@ -19,7 +21,7 @@ class FaviconCheck < ::HTMLProofer::Check
19
21
 
20
22
  def is_immediate_redirect?
21
23
  # allow any instant-redirect meta tag
22
- @html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.starts_with? '0;' rescue false
24
+ @html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.start_with? '0;' rescue false
23
25
  end
24
26
 
25
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class HtmlCheck < ::HTMLProofer::Check
2
4
  SCRIPT_EMBEDS_MSG = /Element script embeds close tag/
3
5
  INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid/
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ImageCheck < ::HTMLProofer::Check
2
4
  SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class LinkCheck < ::HTMLProofer::Check
2
4
  include HTMLProofer::Utils
3
5
 
@@ -44,7 +46,7 @@ class LinkCheck < ::HTMLProofer::Check
44
46
  check_sri(line, content) if @link.check_sri? && node.name == 'link'
45
47
  # we need to skip these for now; although the domain main be valid,
46
48
  # curl/Typheous inaccurately return 404s for some links. cc https://git.io/vyCFx
47
- next if @link.try(:rel) == 'dns-prefetch'
49
+ next if @link.respond_to?(:rel) && @link.rel == 'dns-prefetch'
48
50
  add_to_external_urls(@link.href)
49
51
  next
50
52
  elsif !@link.internal? && !@link.exists?
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  class OpenGraphElement < ::HTMLProofer::Element
4
5
  attr_reader :src
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ScriptCheck < ::HTMLProofer::Check
2
4
  attr_reader :src
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTMLProofer
2
4
  module Configuration
3
5
  require_relative 'version'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'addressable/uri'
2
4
  require_relative './utils'
3
5
 
@@ -28,7 +30,7 @@ module HTMLProofer
28
30
 
29
31
  @html = check.html
30
32
 
31
- parent_attributes = obj.ancestors.map { |a| a.try(:attributes) }
33
+ parent_attributes = obj.ancestors.map { |a| a.respond_to?(:attributes) && a.attributes }
32
34
  parent_attributes.pop # remove document at the end
33
35
  @parent_ignorable = parent_attributes.any? { |a| !a['data-proofer-ignore'].nil? }
34
36
 
@@ -54,7 +56,7 @@ module HTMLProofer
54
56
 
55
57
  def url
56
58
  return @url if defined?(@url)
57
- @url = (@src || @srcset || @href || '').delete("\u200b")
59
+ @url = (@src || @srcset || @href || '').delete("\u200b").strip
58
60
  @url = Addressable::URI.join(base.attr('href') || '', url).to_s if base
59
61
  return @url if @check.options[:url_swap].empty?
60
62
  @url = swap(@url, @check.options[:url_swap])
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTMLProofer
2
4
  class Issue
3
5
  attr_reader :path, :desc, :status, :line, :content
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yell'
2
- require 'colorized_string'
4
+ require 'rainbow'
3
5
 
4
6
  module HTMLProofer
5
7
  class Log
@@ -17,7 +19,7 @@ module HTMLProofer
17
19
  def log(level, message)
18
20
  color = case level
19
21
  when :debug
20
- :light_blue
22
+ :cyan
21
23
  when :info
22
24
  :blue
23
25
  when :warn
@@ -35,7 +37,7 @@ module HTMLProofer
35
37
 
36
38
  def colorize(color, message)
37
39
  if $stdout.isatty && $stderr.isatty
38
- ColorizedString.new(message).colorize(color)
40
+ Rainbow(message).send(color)
39
41
  else
40
42
  message
41
43
  end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTMLProofer
4
+ class Middleware
5
+
6
+ class InvalidHtmlError < StandardError
7
+ def initialize(failures)
8
+ @failures = failures
9
+ end
10
+
11
+ def message
12
+ "HTML Validation errors (skip by adding `?proofer-ignore` to URL): \n#{@failures.join("\n")}"
13
+ end
14
+ end
15
+
16
+ def self.options
17
+ @options ||= {
18
+ type: :file,
19
+ allow_missing_href: true, # Permitted in html5
20
+ allow_hash_href: true,
21
+ check_external_hash: true,
22
+ check_html: true,
23
+ url_ignore: [/.*/], # Don't try to check local files exist
24
+ }
25
+ end
26
+
27
+ def initialize(app)
28
+ @app = app
29
+ end
30
+
31
+ HTML_SIGNATURE = [
32
+ '<!DOCTYPE HTML',
33
+ '<HTML',
34
+ '<HEAD',
35
+ '<SCRIPT',
36
+ '<IFRAME',
37
+ '<H1',
38
+ '<DIV',
39
+ '<FONT',
40
+ '<TABLE',
41
+ '<A',
42
+ '<STYLE',
43
+ '<TITLE',
44
+ '<B',
45
+ '<BODY',
46
+ '<BR',
47
+ '<P',
48
+ '<!--'
49
+ ]
50
+
51
+ def call(env)
52
+ result = @app.call(env)
53
+ return result if env['REQUEST_METHOD'] != 'GET'
54
+ return result if env['QUERY_STRING'] =~ /proofer-ignore/
55
+ return result if result.first != 200
56
+ body = []
57
+ result.last.each { |e| body << e }
58
+
59
+ body = body.join('')
60
+ begin
61
+ html = body.lstrip
62
+ rescue
63
+ return result # Invalid encoding; it's not gonna be html.
64
+ end
65
+ if HTML_SIGNATURE.any? { |sig| html.upcase.start_with? sig }
66
+ parsed = HTMLProofer::Runner.new(
67
+ 'response',
68
+ Middleware.options
69
+ ).check_parsed(
70
+ Nokogiri::HTML(Utils.clean_content(html)), 'response'
71
+ )
72
+
73
+ if parsed[:failures].length > 0
74
+ raise InvalidHtmlError.new(parsed[:failures])
75
+ end
76
+ end
77
+ result
78
+ end
79
+ end
80
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTMLProofer
2
4
  class Runner
3
5
  include HTMLProofer::Utils
@@ -90,9 +92,8 @@ module HTMLProofer
90
92
  end
91
93
  end
92
94
 
93
- def check_path(path)
95
+ def check_parsed(html, path)
94
96
  result = { external_urls: {}, failures: [] }
95
- html = create_nokogiri(path)
96
97
 
97
98
  @src = [@src] if @type == :file
98
99
 
@@ -112,6 +113,10 @@ module HTMLProofer
112
113
  result
113
114
  end
114
115
 
116
+ def check_path(path)
117
+ check_parsed create_nokogiri(path), path
118
+ end
119
+
115
120
  def validate_urls
116
121
  url_validator = HTMLProofer::UrlValidator.new(@logger, @external_urls, @options)
117
122
  @failures.concat(url_validator.run)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'typhoeus'
2
4
  require 'uri'
3
5
  require_relative './utils'
@@ -144,8 +146,12 @@ module HTMLProofer
144
146
  response_code = response.code
145
147
  response.body.gsub!("\x00", '')
146
148
 
147
- debug_msg = "Received a #{response_code} for #{href}"
148
- debug_msg << " in #{filenames.join(' ')}" unless filenames.nil?
149
+ if filenames.nil?
150
+ debug_msg = "Received a #{response_code} for #{href}"
151
+ else
152
+ debug_msg = "Received a #{response_code} for #{href} in #{filenames.join(' ')}"
153
+ end
154
+
149
155
  @logger.log :debug, debug_msg
150
156
 
151
157
  return if @options[:http_status_ignore].include?(response_code)
@@ -179,13 +185,18 @@ module HTMLProofer
179
185
  body_doc = create_nokogiri(response.body)
180
186
 
181
187
  unencoded_hash = Addressable::URI.unescape(hash)
182
- xpath = %(//*[@name="#{hash}"]|/*[@name="#{unencoded_hash}"]|//*[@id="#{hash}"]|//*[@id="#{unencoded_hash}"])
188
+ xpath = [%(//*[@name="#{hash}"]|/*[@name="#{unencoded_hash}"]|//*[@id="#{hash}"]|//*[@id="#{unencoded_hash}"])]
183
189
  # user-content is a special addition by GitHub.
184
190
  if URI.parse(href).host =~ /github\.com/i
185
- xpath << %(|//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])
191
+ xpath << [%(//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])]
192
+ # when linking to a file on GitHub, like #L12-L34, only the first "L" portion
193
+ # will be identified as a linkable portion
194
+ if hash =~ /\A(L\d)+/
195
+ xpath << [%(//td[@id="#{Regexp.last_match[1]}"])]
196
+ end
186
197
  end
187
198
 
188
- return unless body_doc.xpath(xpath).empty?
199
+ return unless body_doc.xpath(xpath.join('|')).empty?
189
200
 
190
201
  msg = "External link #{href} failed: #{effective_url} exists, but the hash '#{hash}' does not"
191
202
  add_external_issue(filenames, msg, response.code)
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'nokogiri'
2
4
 
3
5
  module HTMLProofer
4
6
  module Utils
5
7
  def pluralize(count, single, plural)
6
- "#{count} " << (count == 1 ? single : plural)
8
+ "#{count} #{(count == 1 ? single : plural)}"
7
9
  end
8
10
 
9
11
  def create_nokogiri(path)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HTMLProofer
2
- VERSION = '3.10.1'.freeze
4
+ VERSION = '3.12.1'.freeze
3
5
  end
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: 3.10.1
4
+ version: 3.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen Torikian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-16 00:00:00.000000000 Z
11
+ date: 2019-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mercenary
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.3.2
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.3.2
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: nokogiri
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.9'
33
+ version: '1.10'
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: '1.9'
40
+ version: '1.10'
41
41
  - !ruby/object:Gem::Dependency
42
- name: colorize
42
+ name: rainbow
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.8'
47
+ version: '3.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.8'
54
+ version: '3.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: typhoeus
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -109,27 +109,21 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '2.3'
111
111
  - !ruby/object:Gem::Dependency
112
- name: activesupport
112
+ name: redcarpet
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: '4.2'
118
- - - "<"
119
- - !ruby/object:Gem::Version
120
- version: '6.0'
121
- type: :runtime
117
+ version: '0'
118
+ type: :development
122
119
  prerelease: false
123
120
  version_requirements: !ruby/object:Gem::Requirement
124
121
  requirements:
125
122
  - - ">="
126
123
  - !ruby/object:Gem::Version
127
- version: '4.2'
128
- - - "<"
129
- - !ruby/object:Gem::Version
130
- version: '6.0'
124
+ version: '0'
131
125
  - !ruby/object:Gem::Dependency
132
- name: redcarpet
126
+ name: rubocop
133
127
  requirement: !ruby/object:Gem::Requirement
134
128
  requirements:
135
129
  - - ">="
@@ -143,7 +137,7 @@ dependencies:
143
137
  - !ruby/object:Gem::Version
144
138
  version: '0'
145
139
  - !ruby/object:Gem::Dependency
146
- name: rubocop
140
+ name: rubocop-standard
147
141
  requirement: !ruby/object:Gem::Requirement
148
142
  requirements:
149
143
  - - ">="
@@ -157,7 +151,7 @@ dependencies:
157
151
  - !ruby/object:Gem::Version
158
152
  version: '0'
159
153
  - !ruby/object:Gem::Dependency
160
- name: rubocop-github
154
+ name: rubocop-performance
161
155
  requirement: !ruby/object:Gem::Requirement
162
156
  requirements:
163
157
  - - ">="
@@ -290,6 +284,7 @@ files:
290
284
  - lib/html-proofer/element.rb
291
285
  - lib/html-proofer/issue.rb
292
286
  - lib/html-proofer/log.rb
287
+ - lib/html-proofer/middleware.rb
293
288
  - lib/html-proofer/runner.rb
294
289
  - lib/html-proofer/url_validator.rb
295
290
  - lib/html-proofer/utils.rb
@@ -313,8 +308,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
313
308
  - !ruby/object:Gem::Version
314
309
  version: '0'
315
310
  requirements: []
316
- rubyforge_project:
317
- rubygems_version: 2.7.6
311
+ rubygems_version: 3.0.6
318
312
  signing_key:
319
313
  specification_version: 4
320
314
  summary: A set of tests to validate your HTML output. These tests check if your image