jekyll-sanity-checker 1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c87617b0a35196dbf0935ed56672d5125d96ae6c
4
+ data.tar.gz: cfc830023fefb829707cd486de73e6bf64fdb5df
5
+ SHA512:
6
+ metadata.gz: 0ceee07753ade7b5da1ebec8c12c82510a3fade2b670867ce7f2ec13b47072d2ae221f46990e48e0ef3149757e02d7de2377364ec7e82fe842585e9980b93d6d
7
+ data.tar.gz: 147e95264502c2073444b6aa18d904905675311518c0aa8b44c8eb90a62cbed573b0927d5f64dd85be0e324fe6806d78af24f143592533524352d161358d3fb8
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ Gemfile.lock
2
+ # Generated by `rake`.
3
+ spec/output/*
data/.rubocop.yml ADDED
@@ -0,0 +1,56 @@
1
+ Style/RedundantReturn:
2
+ AllowMultipleReturnValues: true
3
+
4
+ LineLength:
5
+ Enabled: false
6
+
7
+ HashSyntax:
8
+ Enabled: false
9
+
10
+ BracesAroundHashParameters:
11
+ Enabled: false
12
+
13
+ BlockNesting:
14
+ Enabled: false
15
+
16
+ AssignmentInCondition:
17
+ Enabled: false
18
+
19
+ CollectionMethods:
20
+ Enabled: false
21
+
22
+ CyclomaticComplexity:
23
+ Enabled: false
24
+
25
+ MethodLength:
26
+ Enabled: false
27
+
28
+ PerlBackrefs:
29
+ Enabled: false
30
+
31
+ RescueModifier:
32
+ Enabled: false
33
+
34
+ HandleExceptions:
35
+ Enabled: false
36
+
37
+ ClassLength:
38
+ Enabled: false
39
+
40
+ Documentation:
41
+ Enabled: false
42
+
43
+ RegexpLiteral:
44
+ Enabled: false
45
+
46
+ SignalException:
47
+ EnforcedStyle: only_raise
48
+
49
+ Style/ClassAndModuleChildren:
50
+ Enabled: false
51
+
52
+ Style/TrailingComma:
53
+ Enabled: false
54
+
55
+ Metrics/PerceivedComplexity:
56
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ Jekyll Sanity Checker
2
+ -
3
+
4
+ This is a little, *opinionated*, gem that will find mistakes in your HTML/CSS
5
+ at `jekyll build` -time.
6
+
7
+ Here are the things that this plugin enforces:
8
+
9
+ * No internal broken links or images in HTML or CSS `url()`.
10
+ * One sane style for URIs.
11
+
12
+ Just drop `lib/jekyll_sanity_checker.rb` in your `_plugins` directory, or
13
+ include `gems: [jekyll_sanity_checker]` in your `_config.yml`. See more
14
+ instructions about Jekyll plugins at <http://jekyllrb.com/docs/plugins/>.
15
+
16
+ How does it work?
17
+ -
18
+
19
+ This plugin does *not* use one of the standard mechanisms listed at
20
+ <http://jekyllrb.com/docs/plugins/>. Instead, it prepends itself into the
21
+ `Jekyll::Site` class and elaborates the `process` method.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = 'spec/**/*.rb'
5
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'jekyll_sanity_checker/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'jekyll-sanity-checker'
9
+ spec.version = JekyllSanityChecker::VERSION
10
+ spec.authors = ['Edmund Huber']
11
+ spec.email = 'edmund@fastly.com'
12
+ spec.license = 'MIT'
13
+ spec.summary = spec.description = 'A Jekyll plugin that checks the generated HTML and CSS for common mistakes.'
14
+ spec.files = `git ls-files -z`.split("\x0")
15
+ spec.test_files = spec.files.grep(/spec/)
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.required_ruby_version = '> 1.9.3'
19
+
20
+ spec.add_dependency 'css_parser', '~> 1.3', '>= 1.3.5'
21
+ spec.add_dependency 'jekyll', '~> 2.4'
22
+ spec.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.1'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'minitest', '~> 5.0'
26
+ spec.add_development_dependency 'rake'
27
+ spec.add_development_dependency 'rubocop', '~> 0.25', '>= 0.25.0'
28
+ end
@@ -0,0 +1,162 @@
1
+ require 'addressable/uri'
2
+ require 'css_parser'
3
+ require 'nokogiri'
4
+
5
+ module JekyllSanityChecker
6
+ class CheckFailed < StandardError
7
+ end
8
+
9
+ def process
10
+ super
11
+
12
+ # Check that the Jekyll config has the stuff we'll need.
13
+ begin
14
+ config['sanity_check']['host']
15
+ rescue
16
+ raise Jekyll::Errors::FatalException, 'need to fix up your Jekyll configuration for this plugin to work! Please see README.md'
17
+ end
18
+
19
+ JekyllSanityChecker.load_redirects(@config)
20
+
21
+ Jekyll.logger.info('checking for dumb mistakes!')
22
+
23
+ run_success = true
24
+
25
+ Dir.glob("#{dest}/**/*.html") do |html|
26
+ run_success = run_success && JekyllSanityChecker.check_html(@config, html)
27
+ end
28
+
29
+ Dir.glob("#{dest}/**/*.css") do |css|
30
+ run_success = run_success && JekyllSanityChecker.check_css(@config, css)
31
+ end
32
+
33
+ raise Jekyll::Errors::FatalException, 'checks failed' unless run_success
34
+ end
35
+
36
+ def check_html(config, page)
37
+ success = true
38
+ doc = Nokogiri::HTML::Document.parse File.read(page)
39
+ doc.css('a').each do |link|
40
+ begin
41
+ # Does the link have an href atttribute?
42
+ raise CheckFailed, 'no href on <a>' unless link.keys.include? 'href'
43
+
44
+ # If it's a hash-only URL then that's fine.
45
+ next if link['href'].start_with? '#'
46
+
47
+ check_resource(config, link['href'], %w(mailto), true)
48
+ rescue CheckFailed => e
49
+ Jekyll.logger.error("on #{page}, line #{link.line}, problem with '#{link}': #{e.message}")
50
+ success = false
51
+ end
52
+ end
53
+ doc.css('img').each do |img|
54
+ begin
55
+ # Does the img have a src attribute?
56
+ raise CheckFailed, 'no src on <img>' unless img.keys.include? 'src'
57
+ check_resource(config, img['src'], %w(data))
58
+ rescue CheckFailed => e
59
+ Jekyll.logger.error("on #{page}, line #{img.line}, problem with '#{img}': #{e.message}")
60
+ success = false
61
+ end
62
+ end
63
+ success
64
+ end
65
+ module_function :check_html
66
+
67
+ def check_css(config, stylesheet)
68
+ parser = CssParser::Parser.new
69
+ parser.load_file! stylesheet
70
+ success = true
71
+ parser.each_rule_set do |rule_set, _|
72
+ rule_set.each_declaration do |_, value, _|
73
+ match = /url\((.*?)\)/.match value
74
+ next if match.nil?
75
+ begin
76
+ check_resource(config, match[1].tr('\'"', ''), %w(data))
77
+ rescue CheckFailed => e
78
+ Jekyll.logger.error("in #{stylesheet}, problem with selector '#{rule_set.selectors.join ' '}': #{e.message}")
79
+ success = false
80
+ end
81
+ end
82
+ end
83
+ success
84
+ end
85
+ module_function :check_css
86
+
87
+ def load_redirects(config)
88
+ apache_redirects = nil
89
+ begin
90
+ apache_redirects = config['sanity_check']['apache_redirects']
91
+ rescue
92
+ end
93
+ @redirects = {}
94
+ unless apache_redirects.nil?
95
+ File.open(apache_redirects, 'r:UTF-8').each_line do |line|
96
+ m = /Redirect (\S+) (\S+) (\S+)/.match line
97
+ @redirects[m[2]] = m[3] unless m.nil?
98
+ end
99
+ end
100
+ @redirects
101
+ end
102
+ module_function :load_redirects
103
+
104
+ def self.check_resource(config, src, shortcircuit_valid_schemes = [], loose_html = false)
105
+ # Parse the URL,
106
+ begin
107
+ uri = Addressable::URI.parse(src)
108
+ rescue Addressable::URI::InvalidURIError
109
+ raise CheckFailed, 'not a URI'
110
+ end
111
+
112
+ # Maybe the scheme tells us that the URI is okay.
113
+ return if shortcircuit_valid_schemes.include? uri.scheme
114
+
115
+ # Otherwise the scheme should be http or https.
116
+ raise CheckFailed, 'not a valid scheme' unless [nil, 'http', 'https'].include? uri.scheme
117
+
118
+ # Only absolute URLs please.
119
+ this_host = config['sanity_check']['host']
120
+ raise CheckFailed, "fully-qualified URL back to #{this_host}" if uri.host == this_host
121
+
122
+ # Resolve redirects..
123
+ if @redirects.include? uri.path
124
+ redirect_uri = Addressable::URI.parse(@redirects[uri.path])
125
+ # .. if redirect is just a path, use that,
126
+ if redirect_uri.host.nil?
127
+ uri.path = redirect_uri.path
128
+ else
129
+ # otherwise use redirect host/path .
130
+ uri = redirect_uri
131
+ end
132
+ end
133
+
134
+ # And if it's a local resource,
135
+ if uri.host.nil?
136
+ # Check that it's not a relative url.
137
+ raise CheckFailed, 'relative URL' unless uri.path.start_with? '/'
138
+
139
+ parts = URI.unescape(uri.path).split('/').reject { |s| s.empty? }
140
+ path = ([File.expand_path(config['destination'])] + parts).join(File::SEPARATOR)
141
+
142
+ if loose_html
143
+ if File.directory?(path)
144
+ # We treat links to directories special: if there is a directory/index.html, it is ok.
145
+ raise CheckFailed, 'directory reference without an index.html' unless File.file? "#{path}/index.html"
146
+ else
147
+ # A bla.html can be referenced as bla.
148
+ raise CheckFailed, "file '#{path}' does not exist" unless File.file?(path) || File.file?("#{path}.html")
149
+ end
150
+ else
151
+ # Check that the file exists.
152
+ raise CheckFailed, "file '#{path}' does not exist" unless File.file? path
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ module Jekyll
159
+ class Site
160
+ prepend JekyllSanityChecker
161
+ end
162
+ end
@@ -0,0 +1,3 @@
1
+ module JekyllSanityChecker
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1 @@
1
+ <a href="http:"></a>
@@ -0,0 +1,3 @@
1
+ .a {
2
+ thing: url(/missing);
3
+ }
@@ -0,0 +1 @@
1
+ <a href="http://www.computers.com/derp"></a>
@@ -0,0 +1 @@
1
+ <img src="http://www.computers.com/derp">
@@ -0,0 +1 @@
1
+ <img src="/missing">
@@ -0,0 +1 @@
1
+ <img>
@@ -0,0 +1 @@
1
+ <img src="thing">
File without changes
@@ -0,0 +1 @@
1
+ <a href="/missing.html"></a>
File without changes
@@ -0,0 +1 @@
1
+ <a href="/no-index"></a>
@@ -0,0 +1 @@
1
+ <a href="relative"></a>
@@ -0,0 +1 @@
1
+ <a href="nonsense://www.fastly.com"></a>
@@ -0,0 +1,85 @@
1
+ require 'jekyll'
2
+ require 'jekyll_sanity_checker'
3
+ require 'minitest/autorun'
4
+
5
+ # A stub logger class for capturing errors from Jekyll.
6
+ class CaptureError
7
+ attr_reader :captured
8
+
9
+ def initialize
10
+ @captured = nil
11
+ end
12
+
13
+ def error(message) # rubocop:disable Style/TrivialAccessors
14
+ @captured = message
15
+ end
16
+
17
+ def method_missing(_m, *_args, &_block)
18
+ end
19
+ end
20
+
21
+ # Install the stub logger.
22
+ $capture_error = CaptureError.new # rubocop:disable Style/GlobalVars
23
+ Jekyll.logger = $capture_error # rubocop:disable Style/GlobalVars
24
+ Jekyll.logger.log_level = :error
25
+
26
+ def refute_sane(page, error)
27
+ config_hash = {
28
+ 'source' => "spec/fixtures/#{page}",
29
+ 'destination' => 'spec/output',
30
+ 'sanity_check' => {
31
+ 'host' => 'www.computers.com'
32
+ }
33
+ }
34
+ site = Jekyll::Site.new(Jekyll.configuration(config_hash))
35
+ assert_raises Jekyll::Errors::FatalException do
36
+ site.process
37
+ end
38
+ assert_match error, $capture_error.captured # rubocop:disable Style/GlobalVars
39
+ end
40
+
41
+ describe 'Jekyll sanity checker' do
42
+ it 'warns about fully-qualified URLs' do
43
+ refute_sane 'fully-qualified-url', /fully-qualified URL back to/
44
+ end
45
+
46
+ it 'warns about unrecognizable schemes' do
47
+ refute_sane 'unrecognizable-scheme', /not a valid scheme/
48
+ end
49
+
50
+ it 'warns about relative URLs' do
51
+ refute_sane 'relative-url', /relative URL/
52
+ end
53
+
54
+ it 'warns about missing index.html' do
55
+ refute_sane 'no-index-html', /without an index\.html/
56
+ end
57
+
58
+ it 'warns about missing files' do
59
+ refute_sane 'missing-file', /does not exist/
60
+ end
61
+
62
+ it 'warns about <img> without src' do
63
+ refute_sane 'img-missing-src', /no src on <img>/
64
+ end
65
+
66
+ it 'warns about <img> with fully-qualified URL src' do
67
+ refute_sane 'img-fully-qualified-url', /fully-qualified URL back to/
68
+ end
69
+
70
+ it 'warns about <img> with relative URL src' do
71
+ refute_sane 'img-relative-url', /relative URL/
72
+ end
73
+
74
+ it 'warns about <img> with missing src' do
75
+ refute_sane 'img-missing-file', /does not exist/
76
+ end
77
+
78
+ it 'warns about CSS with missing url()' do
79
+ refute_sane 'css-url-missing-file', /does not exist/
80
+ end
81
+
82
+ it 'warns about malformed URLs in <a>' do
83
+ refute_sane 'a-invalid-uri', /not a URI/
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-sanity-checker
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Edmund Huber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: css_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ - - '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 1.3.5
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - - '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.5
33
+ - !ruby/object:Gem::Dependency
34
+ name: jekyll
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: '2.4'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: '2.4'
47
+ - !ruby/object:Gem::Dependency
48
+ name: nokogiri
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.6'
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 1.6.1
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '1.6'
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: 1.6.1
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ version: '1.6'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ version: '1.6'
81
+ - !ruby/object:Gem::Dependency
82
+ name: minitest
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: '5.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ~>
93
+ - !ruby/object:Gem::Version
94
+ version: '5.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rake
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rubocop
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ~>
114
+ - !ruby/object:Gem::Version
115
+ version: '0.25'
116
+ - - '>='
117
+ - !ruby/object:Gem::Version
118
+ version: 0.25.0
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '0.25'
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: 0.25.0
129
+ description: A Jekyll plugin that checks the generated HTML and CSS for common mistakes.
130
+ email: edmund@fastly.com
131
+ executables: []
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - .gitignore
136
+ - .rubocop.yml
137
+ - Gemfile
138
+ - README.md
139
+ - Rakefile
140
+ - jekyll-sanity-checker.gemspec
141
+ - lib/jekyll_sanity_checker.rb
142
+ - lib/jekyll_sanity_checker/version.rb
143
+ - spec/fixtures/a-invalid-uri/test.html
144
+ - spec/fixtures/css-url-missing-file/test.css
145
+ - spec/fixtures/fully-qualified-url/test.html
146
+ - spec/fixtures/img-fully-qualified-url/test.html
147
+ - spec/fixtures/img-missing-file/test.html
148
+ - spec/fixtures/img-missing-src/test.html
149
+ - spec/fixtures/img-relative-url/test.html
150
+ - spec/fixtures/img-relative-url/thing
151
+ - spec/fixtures/missing-file/test.html
152
+ - spec/fixtures/no-index-html/no-index/crumb
153
+ - spec/fixtures/no-index-html/test.html
154
+ - spec/fixtures/relative-url/test.html
155
+ - spec/fixtures/unrecognizable-scheme/test.html
156
+ - spec/lib/sanity_checker.rb
157
+ homepage:
158
+ licenses:
159
+ - MIT
160
+ metadata: {}
161
+ post_install_message:
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - '>'
168
+ - !ruby/object:Gem::Version
169
+ version: 1.9.3
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ requirements: []
176
+ rubyforge_project:
177
+ rubygems_version: 2.2.1
178
+ signing_key:
179
+ specification_version: 4
180
+ summary: A Jekyll plugin that checks the generated HTML and CSS for common mistakes.
181
+ test_files:
182
+ - jekyll-sanity-checker.gemspec
183
+ - spec/fixtures/a-invalid-uri/test.html
184
+ - spec/fixtures/css-url-missing-file/test.css
185
+ - spec/fixtures/fully-qualified-url/test.html
186
+ - spec/fixtures/img-fully-qualified-url/test.html
187
+ - spec/fixtures/img-missing-file/test.html
188
+ - spec/fixtures/img-missing-src/test.html
189
+ - spec/fixtures/img-relative-url/test.html
190
+ - spec/fixtures/img-relative-url/thing
191
+ - spec/fixtures/missing-file/test.html
192
+ - spec/fixtures/no-index-html/no-index/crumb
193
+ - spec/fixtures/no-index-html/test.html
194
+ - spec/fixtures/relative-url/test.html
195
+ - spec/fixtures/unrecognizable-scheme/test.html
196
+ - spec/lib/sanity_checker.rb
197
+ has_rdoc: