validate_html 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6a9d52bd97f5669d92f8d0536e3cef55fb5d5247c0e88b6555f375fc1d64ef11
4
+ data.tar.gz: e44041a0066907734a64ff8062f1bcf30bec36fe61f157b7296925a610e7b1aa
5
+ SHA512:
6
+ metadata.gz: 594282963b8803f2487f624960f3e67f9fd589ac5c30ff2d3d74b64af038f153b535f08912e20d402452d228ad9f2d65fb08c19a784b18da08fc76918b1547cc
7
+ data.tar.gz: d366535dff5ca823fbca68303f3ae2055957e5e9c1b4ba8c9d7419cfa3b6689fcb88e17cc4f57cb7d55fa2c9bd1d83cf1d08a75e57d806d4d7b192c5c295b94f
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ Unreleased
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in validate_html.gemspec
8
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Ackama
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # ValidateHTML
2
+
3
+ Validate HTML as it leaves your rails application.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'validate_html'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install validate_html
20
+
21
+ ## Usage
22
+
23
+ When included in your Gemfile, when in `development` & `test` environments ValidateHTML will automatically check html leaving your app via rack or html emails or turbo streams for invalid HTML using nokogiri.
24
+
25
+ To validate a block of HTML outside of these contexts (including outside of rails entirely), you can use
26
+ ```ruby
27
+ ValidateHTML.validate_html(my_html_here, **options)
28
+ ```
29
+
30
+ ## Development
31
+
32
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
33
+
34
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
35
+
36
+ ## Contributing
37
+
38
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ackama/validate_html.
39
+
40
+ ## License
41
+
42
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidateHTML
4
+ # Validate HTML from Turbo::StreamsChannel
5
+ # as called by ActiveSupport::Notifications.instrument
6
+ module ActiveSupportNotificationHandler
7
+ # Validate HTML from Turbo::StreamsChannel
8
+ # as called by ActiveSupport::Notifications.instrument
9
+ #
10
+ # @option payload :channel_class [String] if this is Turbo::StreamsChannel the validation will happen
11
+ # @option payload :data [String] the html to validate
12
+ # @return [Boolean] true if there are no validation errors
13
+ # @raise {InvalidHTMLError} if the data of the payload is invalid
14
+ # and {Configuration#raise_on_invalid_html} is true
15
+ # @see ValidateHTML.validate_html
16
+ def self.call(_name, _start, _finish, _id, payload)
17
+ return unless payload && payload[:channel_class] == 'Turbo::StreamsChannel'
18
+
19
+ ValidateHTML.validate_html(payload[:data])
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tmpdir'
4
+ require 'pathname'
5
+
6
+ module ValidateHTML
7
+ # Configuration attributes for ValidateHTML
8
+ #
9
+ # @see ValidateHTML.configure
10
+ class Configuration
11
+ # Set to false to not raise {InvalidHTMLError} by default
12
+ #
13
+ # Defaults to true
14
+ #
15
+ # @return [Boolean]
16
+ # @see ValidateHTML.validate_html
17
+ # @see ValidateHTML.raise_remembered_messages
18
+ # @see #remember_messages
19
+ attr_accessor :raise_on_invalid_html
20
+ alias_method :raise_on_invalid_html?, :raise_on_invalid_html
21
+
22
+ # Set to true to allow using {ValidateHTML.raise_remembered_messages}
23
+ #
24
+ # Defaults to false
25
+ #
26
+ # @return [Boolean]
27
+ # @see ValidateHTML.raise_remembered_messages
28
+ attr_accessor :remember_messages
29
+ alias_method :remember_messages?, :remember_messages
30
+
31
+ # Error messages to ignore
32
+ # @return [Array<String, Regexp>]
33
+ attr_reader :ignored_errors
34
+
35
+ # App-relative paths to skip automatic validation
36
+ # @return [Array<String, Regexp>]
37
+ attr_reader :ignored_paths
38
+
39
+ # The rails environments to initialize automatic validation
40
+ #
41
+ # Defaults to ["development", "test"]
42
+ #
43
+ # This won't take any effect if changed after the app is initialized
44
+ #
45
+ # @return [Array<String>]
46
+ attr_accessor :environments
47
+
48
+ def initialize
49
+ @raise_on_invalid_html = true
50
+ @ignored_errors = []
51
+ @ignored_paths = []
52
+ @environments = %w[development test]
53
+ @remember_messages = false
54
+ @snapshot_path = nil
55
+ end
56
+
57
+ # The directory to use for snapshots with invalid HTML
58
+ #
59
+ # @default
60
+ # if Rails is present, this will default to "tmp/invalid_html"
61
+ # otherwise it will default to a new directory created with Dir.mktmpdir
62
+ #
63
+ # @!attribute snapshot_path [rw]
64
+ # @return [Pathname]
65
+ # @param [Pathname, String] path
66
+ def snapshot_path
67
+ @snapshot_path ||= defined?(::Rails) ? ::Rails.root.join('tmp/invalid_html') : ::Pathname.new(::Dir.mktmpdir)
68
+ end
69
+
70
+ def snapshot_path=(path)
71
+ @snapshot_path = path.is_a?(Pathname) ? path : ::Pathname.new(path)
72
+ end
73
+
74
+ def ignored_errors=(errors)
75
+ @ignored_errors = errors
76
+ @ignored_errors_re = nil
77
+ end
78
+
79
+ def ignored_paths=(paths)
80
+ @ignored_paths = paths
81
+ @ignored_paths_re = nil
82
+ end
83
+
84
+ # @!visibility private
85
+ def ignored_errors_re
86
+ @ignored_errors_re ||= list_to_re(ignored_errors)
87
+ end
88
+
89
+ # @!visibility private
90
+ def ignored_paths_re
91
+ @ignored_paths_re ||= list_to_re(ignored_paths)
92
+ end
93
+
94
+ private
95
+
96
+ def list_to_re(list)
97
+ Regexp.union(list.map { |i| i.is_a?(Regexp) ? i : /\A#{Regexp.escape(i)}\z/ })
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidateHTML
4
+ # Validate the HTML of outgoing mail
5
+ # Use standalone or as an ActionMailer interceptor, observer, or preview_interceptor
6
+ module MailerObserver
7
+ class << self
8
+ # Validate the HTML of outgoing mail
9
+ # Use standalone or as an ActionMailer interceptor, observer, or preview_interceptor
10
+ #
11
+ # @param email [Mail]
12
+ # @return [Mail] unmodified from the passed in param
13
+ # @raise {InvalidHTMLError} if the html_part of the email is invalid
14
+ # and {Configuration#raise_on_invalid_html} is true
15
+ # @see ValidateHTML.validate_html
16
+ def perform(email)
17
+ html_part = email.html_part
18
+ return email unless html_part
19
+
20
+ ValidateHTML.validate_html(
21
+ html_part.body.raw_source,
22
+ content_type: html_part.content_type,
23
+ name: "email #{email.subject}"
24
+ )
25
+
26
+ email
27
+ end
28
+
29
+ alias_method :delivering_email, :perform
30
+ alias_method :delivered_email, :perform
31
+ alias_method :previewing_email, :perform
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidateHTML
4
+ # Rack Middleware to validate the HTML of outgoing responses
5
+ #
6
+ # This can be used with any rack app
7
+ class RackMiddleware
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ # @param env
13
+ # @return [Array<(status, headers, response)>]
14
+ # @raise {InvalidHTMLError} if the response has an HTML content type
15
+ # and the html is invalid
16
+ # and {Configuration#raise_on_invalid_html} is true
17
+ # and the request path isn't ignored by {Configuration#ignored_paths}
18
+ # @see ValidateHTML.validate_html
19
+ def call(env)
20
+ status, headers, response = @app.call(env)
21
+ path = ::Rack::Request.new(env).path
22
+ return [status, headers, response] unless checkable_path?(path)
23
+
24
+ body = find_body(response)
25
+
26
+ return [status, headers, response] unless html_content_type?(headers)
27
+
28
+ ValidateHTML.validate_html(body, content_type: headers['Content-Type'], name: path)
29
+
30
+ [status, headers, response]
31
+ end
32
+
33
+ private
34
+
35
+ def checkable_path?(path)
36
+ !ValidateHTML.configuration.ignored_paths_re.match?(path)
37
+ end
38
+
39
+ def html_content_type?(headers)
40
+ headers['Content-Type']&.match?(%r{\Atext/(?:vnd\.turbo-stream\.html|html)\b})
41
+ end
42
+
43
+ def find_body(response)
44
+ if response.respond_to?(:body)
45
+ find_body(response.body)
46
+ elsif response.respond_to?(:each) # request specs
47
+ response.each.to_a.join
48
+ elsif response.respond_to?(:to_str)
49
+ response.to_str
50
+ else
51
+ ''
52
+ end
53
+ rescue NoMethodError # sometimes things say they respond to body, then don't.
54
+ ''
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidateHTML
4
+ # Automatic integration of ValidateHTML with Rails
5
+ # require 'validate_html/railtie'
6
+ # or just ensure rails is required before validate_html
7
+ class Railtie < ::Rails::Railtie
8
+ initializer 'html_validator.configure_rails_initialization' do |app|
9
+ next unless ::ValidateHTML.configuration.environments.any? { |e| e.to_s == Rails.env }
10
+
11
+ app.configure do
12
+ config.middleware.use(::ValidateHTML::RackMiddleware)
13
+
14
+ next unless config.respond_to?(:action_mailer)
15
+
16
+ # run first to run before e.g. premailer, which does a parse-then-output step
17
+ # and will make assumptions with the output that we may want to correct
18
+ config.action_mailer.interceptors = [
19
+ ::ValidateHTML::MailerObserver,
20
+ *config.action_mailer.interceptors
21
+ ]
22
+
23
+ # and run again after
24
+ config.action_mailer.observers = [
25
+ *config.action_mailer.observers,
26
+ ::ValidateHTML::MailerObserver
27
+ ]
28
+
29
+ # for completeness, this might be overkill
30
+ config.action_mailer.preview_interceptors = [
31
+ *config.action_mailer.preview_interceptors,
32
+ ::ValidateHTML::MailerObserver
33
+ ]
34
+ end
35
+
36
+ ::ActiveSupport::Notifications.subscribe(
37
+ 'transmit.action_cable',
38
+ ::ValidateHTML::ActiveSupportNotificationHandler
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ValidateHTML
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ # frozen_string_literals: true
4
+
5
+ require_relative 'validate_html/version'
6
+ require_relative 'validate_html/configuration'
7
+ require_relative 'validate_html/rack_middleware'
8
+ require_relative 'validate_html/mailer_observer'
9
+ require_relative 'validate_html/active_support_notification_handler'
10
+ require_relative 'validate_html/railtie' if defined?(::Rails::Railtie)
11
+ require 'nokogiri'
12
+ require 'digest'
13
+
14
+ # Validate HTML as it leaves your rails application
15
+ module ValidateHTML
16
+ class Error < StandardError; end
17
+ # This error message will include the full html validation details,
18
+ # with a path to the snapshot to assist resolving the invalid html
19
+ #
20
+ # @example
21
+ # ValidateHTML.validate_html('<strong><em>Very Emphasized</strong></em>', name: 'My Emphasized Fragment')
22
+ # # raises: ValidateHTML::InvalidHTMLError with this message:
23
+ # #
24
+ # # Invalid html from My Emphasized Fragment (ValidateHTML::InvalidHTMLError)
25
+ # # Parsed using Nokogiri::HTML5::DocumentFragment
26
+ # # document saved at: [Configuration#snapshot_path]/1a8ce99806ddeccc3a5f2904ba07c7fa5ae4659d.html
27
+ # #
28
+ # # 1:28: ERROR: That tag isn't allowed here Currently open tags: html, strong, em.
29
+ # # <strong><em>Very Emphasized</strong></em>
30
+ # # ^
31
+ # # 1:37: ERROR: That tag isn't allowed here Currently open tags: html.
32
+ # # <strong><em>Very Emphasized</strong></em>
33
+ # # ^
34
+ # @see ValidateHTML.validate_html
35
+ # @see ValidateHTML.raise_remembered_messages
36
+ class InvalidHTMLError < Error; end
37
+
38
+ # This error will be raised when calling {ValidateHTML.raise_remembered_messages}
39
+ # when {Configuration#remember_messages} is false
40
+ #
41
+ # @see ValidateHTML.raise_remembered_messages
42
+ # @see Configuration#remember_messages
43
+ class NotRememberingMessagesError < Error; end
44
+
45
+ class << self
46
+ # Validate the HTML using by parsing it with nokogiri
47
+ #
48
+ # skip any errors matching patterns in {Configuration#ignored_errors}
49
+ #
50
+ # if there are any errors remaining:
51
+ # remember the errors if {Configuration#remember_messages} is true,
52
+ # save the invalid html into the {Configuration#snapshot_path} directory,
53
+ # and raise {InvalidHTMLError} with the full messages if raise_on_invalid_html is true
54
+ # or return false
55
+ #
56
+ # if there are no errors, return true
57
+ #
58
+ # @example
59
+ # ValidateHTML.validate_html('<strong><em>Very Emphasized</strong></em>', name: 'My Emphasized Fragment')
60
+ # # raises: ValidateHTML::InvalidHTMLError with this message:
61
+ # #
62
+ # # Invalid html from My Emphasized Fragment (ValidateHTML::InvalidHTMLError)
63
+ # # Parsed using Nokogiri::HTML5::DocumentFragment
64
+ # # Document saved at: [Configuration#snapshot_path]/1a8ce99806ddeccc3a5f2904ba07c7fa5ae4659d.html
65
+ # #
66
+ # # 1:28: ERROR: That tag isn't allowed here Currently open tags: html, strong, em.
67
+ # # <strong><em>Very Emphasized</strong></em>
68
+ # # ^
69
+ # # 1:37: ERROR: That tag isn't allowed here Currently open tags: html.
70
+ # # <strong><em>Very Emphasized</strong></em>
71
+ # # ^
72
+ #
73
+ # @param html [String]
74
+ # @param name [String] filename or http path or email subject or etc to print in the error message
75
+ # @param content_type [String] mime type of the document to assist determining encoding
76
+ # @param raise_on_invalid_html [Boolean] override {Configuration#raise_on_invalid_html}
77
+ # @return [Boolean] true if there are no validation errors
78
+ # @raise [InvalidHTMLError] if the html is not valid and raise_on_invalid_html is true
79
+ def validate_html(html, name: nil, content_type: nil, raise_on_invalid_html: configuration.raise_on_invalid_html?)
80
+ return true if html.empty?
81
+
82
+ doc = parse_html(html, find_encoding(content_type))
83
+
84
+ errors = filter_errors(doc.errors)
85
+
86
+ return true if errors.empty?
87
+
88
+ handle_errors(name, doc, html, errors, raise_on_invalid_html)
89
+
90
+ false
91
+ end
92
+
93
+ # Raise any remembered messages
94
+ #
95
+ # @return [void]
96
+ # @raise [InvalidHTMLError] if there are remembered messages
97
+ # @raise [NotRememberingMessagesError] if {Configuration#remember_messages} is false
98
+ def raise_remembered_messages
99
+ raise NotRememberingMessagesError unless configuration.remember_messages?
100
+ return if remembered_messages.empty?
101
+
102
+ messages = remembered_messages
103
+ forget_messages
104
+ raise InvalidHTMLError, messages.uniq.join("---\n")
105
+ end
106
+
107
+ # @!attribute [r] remembered_messages
108
+ # @return [Array<String>]
109
+ def remembered_messages
110
+ @remembered_messages ||= []
111
+ end
112
+
113
+ # Clear any remembered messages
114
+ # @return [void]
115
+ def forget_messages
116
+ @remembered_messages = []
117
+ end
118
+
119
+ # @!attribute [r] configuration
120
+ # @return [Configuration]
121
+ def configuration
122
+ @configuration ||= Configuration.new
123
+ end
124
+
125
+ # Configure ValidateHTML
126
+ #
127
+ # @example
128
+ # ValidateHTML.configure do |c|
129
+ # c.remember_messages = true
130
+ # c.environments = ['test']
131
+ # end
132
+ # @yieldparam config [Configuration]
133
+ # @return [void]
134
+ def configure
135
+ yield configuration
136
+ end
137
+
138
+ private
139
+
140
+ def filter_errors(errors)
141
+ errors.map(&:to_s).grep_v(configuration.ignored_errors_re)
142
+ end
143
+
144
+ def handle_errors(name, doc, body, errors, raise_on_invalid_html)
145
+ configuration.snapshot_path.mkpath
146
+ path = configuration.snapshot_path.join("#{::Digest::SHA1.hexdigest(body)}.html")
147
+ path.write(body)
148
+
149
+ message = <<~ERROR
150
+ Invalid html#{" from #{name}" if name}
151
+ Parsed using #{doc.class}
152
+ document saved at: #{path}
153
+
154
+ #{errors.join("\n")}
155
+ ERROR
156
+
157
+ remembered_messages << message if configuration.remember_messages?
158
+
159
+ raise InvalidHTMLError, message if raise_on_invalid_html
160
+
161
+ warn message
162
+ end
163
+
164
+ def parse_html(body, encoding)
165
+ case body
166
+ when /\A\s*<!doctype html>/i
167
+ ::Nokogiri::HTML5.parse(body, nil, encoding, max_errors: -1)
168
+ when /\A\s*<(!doctype|html)/i
169
+ ::Nokogiri::HTML4.parse(
170
+ body,
171
+ nil,
172
+ encoding,
173
+ ::Nokogiri::XML::ParseOptions::DEFAULT_HTML | ::Nokogiri::XML::ParseOptions::STRICT
174
+ )
175
+ else
176
+ ::Nokogiri::HTML5.fragment(body, encoding, max_errors: -1)
177
+ end
178
+ end
179
+
180
+ def find_encoding(content_type)
181
+ return unless content_type
182
+
183
+ parts = content_type.split(/;\s*/)
184
+ parts.find { |part| part.start_with?('charset=') }&.delete_prefix('charset=')
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'validate_html/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'validate_html'
9
+ spec.version = ValidateHTML::VERSION
10
+ spec.authors = ['Ackama']
11
+ spec.email = ['opensource@ackama.com']
12
+
13
+ spec.summary = 'Validate HTML files as they leave your app by rack or by mail or by turbo-stream'
14
+ spec.homepage = 'https://github.com/ackama/validate_html'
15
+ spec.license = 'MIT'
16
+
17
+ spec.required_ruby_version = '>= 2.7.0'
18
+
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = spec.homepage
22
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
23
+ spec.metadata['rubygems_mfa_required'] = 'true'
24
+ end
25
+
26
+ spec.files = Dir.glob('lib/**/{*,.*}') + %w[
27
+ CHANGELOG.md
28
+ Gemfile
29
+ LICENSE.txt
30
+ README.md
31
+ validate_html.gemspec
32
+ ]
33
+ spec.require_paths = ['lib']
34
+
35
+ spec.add_development_dependency 'bundler', '>= 2.0'
36
+ spec.add_development_dependency 'rake', '>= 12.0'
37
+ spec.add_development_dependency 'rspec', '>= 3.0'
38
+ spec.add_development_dependency 'yard'
39
+ spec.add_dependency 'nokogiri'
40
+ spec.add_development_dependency 'actionmailer'
41
+ spec.add_development_dependency 'activesupport'
42
+ spec.add_development_dependency 'leftovers'
43
+ spec.add_development_dependency 'mail'
44
+ spec.add_development_dependency 'pry'
45
+ spec.add_development_dependency 'railties'
46
+ spec.add_development_dependency 'rubocop'
47
+ spec.add_development_dependency 'rubocop-performance'
48
+ spec.add_development_dependency 'rubocop-rake'
49
+ spec.add_development_dependency 'rubocop-rspec'
50
+ spec.add_development_dependency 'simplecov'
51
+ spec.add_development_dependency 'simplecov-console'
52
+ spec.add_development_dependency 'spellr'
53
+ end
metadata ADDED
@@ -0,0 +1,311 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: validate_html
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ackama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '12.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '12.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: actionmailer
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: leftovers
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: mail
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: railties
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rubocop-performance
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: rubocop-rake
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ - !ruby/object:Gem::Dependency
210
+ name: rubocop-rspec
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: simplecov
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: simplecov-console
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
251
+ - !ruby/object:Gem::Dependency
252
+ name: spellr
253
+ requirement: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ type: :development
259
+ prerelease: false
260
+ version_requirements: !ruby/object:Gem::Requirement
261
+ requirements:
262
+ - - ">="
263
+ - !ruby/object:Gem::Version
264
+ version: '0'
265
+ description:
266
+ email:
267
+ - opensource@ackama.com
268
+ executables: []
269
+ extensions: []
270
+ extra_rdoc_files: []
271
+ files:
272
+ - CHANGELOG.md
273
+ - Gemfile
274
+ - LICENSE.txt
275
+ - README.md
276
+ - lib/validate_html.rb
277
+ - lib/validate_html/active_support_notification_handler.rb
278
+ - lib/validate_html/configuration.rb
279
+ - lib/validate_html/mailer_observer.rb
280
+ - lib/validate_html/rack_middleware.rb
281
+ - lib/validate_html/railtie.rb
282
+ - lib/validate_html/version.rb
283
+ - validate_html.gemspec
284
+ homepage: https://github.com/ackama/validate_html
285
+ licenses:
286
+ - MIT
287
+ metadata:
288
+ homepage_uri: https://github.com/ackama/validate_html
289
+ source_code_uri: https://github.com/ackama/validate_html
290
+ changelog_uri: https://github.com/ackama/validate_html/blob/main/CHANGELOG.md
291
+ rubygems_mfa_required: 'true'
292
+ post_install_message:
293
+ rdoc_options: []
294
+ require_paths:
295
+ - lib
296
+ required_ruby_version: !ruby/object:Gem::Requirement
297
+ requirements:
298
+ - - ">="
299
+ - !ruby/object:Gem::Version
300
+ version: 2.7.0
301
+ required_rubygems_version: !ruby/object:Gem::Requirement
302
+ requirements:
303
+ - - ">="
304
+ - !ruby/object:Gem::Version
305
+ version: '0'
306
+ requirements: []
307
+ rubygems_version: 3.3.7
308
+ signing_key:
309
+ specification_version: 4
310
+ summary: Validate HTML files as they leave your app by rack or by mail or by turbo-stream
311
+ test_files: []