blob: 6ac147334036fa507d08d3ece7b1fc0caa356c1d [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'middleman'
require 'nokogiri'
require 'rainbow/ext/string'
require 'uri'
require 'net/http'
require 'redcarpet'
require File.join(File.dirname(__FILE__), 'lib', 'custom_renderer')
task :test do
HTML = <<EOT
<div class="tabs">
<div data-tab="PHP" data-lang="php">
```
Test 0 <>
Test 1 >
Test 3 <
Test 4 ><
Test 5 =>
Test 6 <=
Test 7 <>
<p><b>Test 8</b></p>
```
</div>
</div>
EOT
HTML2 = <<EOT
<div class="tabs">
<div data-tab="Ruby" data-lang="ruby">
```ruby
Test 0 <>
Test 1 >
Test 3 <
Test 4 ><
Test 5 =>
Test 6 <=
Test 7 <>
<p><b>Test</b></p>
# This is a ruby file.
class MyClass
def foo
'bar'
end
end
```
</div>
<div data-tab="Plain">
This is a test of **markdown** inside a tab!
```
// This tab does not have the data-lang attribute set!
$ cd path/to/your/file
```
</div>
<div data-tab="HTML" data-lang="html">
```html
<p>Yes you can still use HTML in code blocks!</p>
```
</div>
</div>
EOT
def block_html(raw_html)
done = raw_html.gsub(/(```.*?```)/m) do |match|
markdown = Redcarpet::Markdown.new(CustomRenderer, fenced_code_blocks: true)
markdown.render(match)
end
doc = Nokogiri::HTML::DocumentFragment.parse(done)
nodes = doc.css('div.tabs > div')
if nodes.empty?
raw_html
else
ul = Nokogiri::XML::Node.new('ul', doc)
ul['class'] = 'control'
nodes.each do |node|
title = node.attribute('data-tab').to_s
lang = node.attribute('data-lang').to_s
uuid = SecureRandom.uuid
id = "tab-#{uuid}"
li = Nokogiri::XML::Node.new('li', doc)
li['data-lang'] = lang
li.inner_html = %Q(<a href="##{id}">#{title}</a>)
ul.add_child(li)
node['id'] = id
end
nodes.first.before(ul)
doc.to_html
end
end
puts 'start block'
puts block_html(HTML2)
puts 'end block'
end
desc 'Check site for broken links'
task :check do
sets = []
cache = Sanity::Cache.new
Dir["build/**/*.html"].each do |filename|
p = Sanity::Page.new(filename, cache)
sets << p.check_links
end
html = Sanity::Report::HTML.new(sets.map{ |s| s.to_html })
File.open('sanity.html', 'w') { |file| file.write(html) }
end
module Sanity
module Report
class HTML
include Padrino::Helpers
HEADER = <<EOT
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sanity Report</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron">
<div class="container-fluid">
<h1>Sanity Report</h1>
</div>
</div>
<div class="container-fluid">
EOT
FOOTER = <<EOT
</div>
</body>
</html>
EOT
def initialize(content)
if content.respond_to?(:to_html)
html = content.to_html
else
html = content
end
@content = content_tag(:div, html, id: 'content')
end
def to_html
"#{HEADER}#{@content}#{FOOTER}"
end
def to_s
to_html
end
end
end
class ResultSet
include Padrino::Helpers
attr_accessor :set
def initialize(path, set = [])
@path = path
@set = set
end
def push(item)
@set.push(item)
end
def to_html
content_tag(:h2, @path) <<
content_tag(:table, class: 'table table-striped') do
content_tag(:thead) do
content_tag(:tr) do
content_tag(:th, 'Type') <<
content_tag(:th, 'Status') <<
content_tag(:th, 'URI') <<
content_tag(:th, 'Message') <<
content_tag(:th, 'Path')
end
end <<
content_tag(:tbody) do
@set.map do |item|
item.to_html
end
end
end
end
end
class Result
include Padrino::Helpers
attr_accessor :type, :status, :path, :uri, :message, :cache, :backtrace
def initialize
end
def exception=(e)
@status = :exception
@message = e.message
@backtrace = e.backtrace
end
def to_s
"#{@type} [#{@status}] #{@uri} #{@message} #{@path}".color(terminal_color)
end
def to_html
content_tag(:tr, class: bootstrap_css_class) do
content_tag(:td, @type) <<
content_tag(:td, @status) <<
content_tag(:td, @uri) <<
content_tag(:td, @message) <<
content_tag(:td, @path)
end
end
private
def bootstrap_css_class
case @status
when :success
'success'
when :info
'info'
when :warning
'warning'
when :error
'danger'
when :exception
'danger'
else
raise ArgumentError, "Status `#{@status}` is not a valid type"
end
end
def terminal_color
case @status
when :success
:green
when :info
:blue
when :warning
:yellow
when :error
:red
when :exception
:red
else
raise ArgumentError, "Status `#{@status}` is not a valid type"
end
end
end
class Cache
def initialize(store = {})
@store = store
end
def read(uri)
@store[uri]
end
def write(uri, value)
@store[uri] = value
end
def fetch(uri)
if block_given?
if exists?(uri)
read(uri)
else
write(uri, yield)
end
else
read(uri)
end
end
def exists?(uri)
@store.has_key?(uri)
end
end
class Page
INDEX_FILE = 'index.html'
def initialize(filename, cache = Sanity::Cache.new)
@filename = filename
@cache = cache
f = File.open(@filename)
@doc = Nokogiri::HTML(f)
@build_path = File.join(Middleman::Application.root, 'build')
f.close
end
def check_links
rs = Sanity::ResultSet.new(@filename)
@doc.css('a').each do |link|
uri = link['href']
r = check_href(uri)
r.path = @filename
puts r
rs.push(r)
end
rs
end
def check_href(href)
# TODO: add trailing slash, relative url, and in page anchor links.
# TODO: Test for missing titles!
# TODO: Test for unneeded .html extension!
# TODO: Switch from Nokogir to raw ID checker
case href
when /\A\s*\z/, nil
check_empty_href(href)
when /\A(https?):\/\/.+\z/
check_external_href(href)
when /\A#.+\z/
check_anchor_href(href)
when /\A#\z/
check_empty_anchor_href(href)
when /\A\/\z/
check_root_href(href)
when /\Amailto:.+\z/
check_mailto_href(href)
else
check_internal_href(href)
end
end
def check_external_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :external_uri
begin
response = @cache.fetch(href) do
r.cache = :miss
uri = URI(href)
Net::HTTP.get_response(uri)
end
case response
when Net::HTTPSuccess
r.status = :success
when Net::HTTPNotFound
r.status = :error
when Net::HTTPRedirection
location = response['location']
r.status = :info
r.message = "Redirect: #{location}"
else
r.status = :warning
r.message = "Response: #{response.class}"
end
rescue => e
r.exception = e
end
r
end
def check_anchor_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :anchor
begin
result = @doc.css(href)
if result.count > 0
r.status = :success
else
r.status = :error
end
rescue => e
r.exception = e
end
r
end
def check_empty_anchor_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :empty_anchor
r.status = :info
r
end
def check_root_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :root_path
filename = File.join(@build_path, INDEX_FILE)
if File.exist?(filename)
r.status = :success
else
r.status = :error
r.message "Not found: #{filename}"
end
r
end
def check_mailto_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :mailto
uri = URI.parse(href)
if uri.is_a?(URI::MailTo)
r.status = :success
else
r.status = :error
end
r
end
def check_empty_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :empty_uri
r.status = :error
r
end
def check_internal_href(href)
r = Sanity::Result.new
r.uri = href
r.type = :internal_uri
filename = File.join(@build_path, href.gsub('/', File::SEPARATOR))
if File.directory?(filename)
filename = File.join(filename, INDEX_FILE)
end
if File.exist?(filename)
r.status = :success
else
r.status = :error
end
r
end
end
end