markdown-helpers 0.1.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 +7 -0
- data/bin/markdownh +20 -0
- data/lib/markdown-helpers/doc_tree_builder.rb +104 -0
- data/lib/markdown-helpers/link_checker.rb +162 -0
- data/lib/markdown-helpers.rb +2 -0
- metadata +76 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 7d195ce1a339bdbd1d82427afd58d0634bd9af51
|
|
4
|
+
data.tar.gz: 35b15149f335408471568967cdbc2a178a814690
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6af36d716dc6b80b54ff3b0742a51a0034750f913c1e08f8ab9feef2fd17cb234f9fde2d5317a46f988ec738224a873c1c41eb9aa22412f54f652c9f81951368
|
|
7
|
+
data.tar.gz: 74b256a38c0a59e61ac6b9cba1905d386ef84451a0f30555689da285f7d519371a6d221b448040e7649af79ad7b8c29bbe441eee5c0b19e31a9dad36e2656d6a
|
data/bin/markdownh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require 'markdown-helpers'
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
# 'markdownh' CLI
|
|
6
|
+
class MarkdownH < Thor
|
|
7
|
+
desc 'check_links CONFIG', 'check for broken links'
|
|
8
|
+
def check_links(config)
|
|
9
|
+
LinkChecker.new(config).check
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc 'generate_index CONFIG', 'generate a markdown link tree'
|
|
13
|
+
def generate_index(config)
|
|
14
|
+
doc_builder = DocBuilder.new(config)
|
|
15
|
+
doc_builder.generate_index
|
|
16
|
+
doc_builder.write
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
MarkdownH.start(ARGV)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
|
|
4
|
+
# Class for building an index tree from
|
|
5
|
+
# erb and directory root
|
|
6
|
+
class DocBuilder
|
|
7
|
+
def initialize(config_file)
|
|
8
|
+
@config = YAML.load_file(config_file)
|
|
9
|
+
@config['base_header'] ||= ''
|
|
10
|
+
@config['base_level'] ||= 1
|
|
11
|
+
if @config['base_level'] < 1
|
|
12
|
+
puts ':base_level must be greater than 0'
|
|
13
|
+
exit
|
|
14
|
+
end
|
|
15
|
+
@config['ignore'] ||= []
|
|
16
|
+
@doc_index = ''
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# recursive call on directories starting at @doc_directory
|
|
20
|
+
# writes indented list block for each directory
|
|
21
|
+
def generate_index(
|
|
22
|
+
options = {
|
|
23
|
+
directory: @config['doc_directory'],
|
|
24
|
+
level: @config['base_level'],
|
|
25
|
+
header_level: @config['base_header']
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
sub_directories = Dir.entries(options[:directory])
|
|
29
|
+
directory_last = true
|
|
30
|
+
sub_directories.each do |filename|
|
|
31
|
+
next if ignore_file?(filename, options[:directory])
|
|
32
|
+
path = File.join(options[:directory], filename)
|
|
33
|
+
if File.directory?(path)
|
|
34
|
+
# case where two directories are back to back
|
|
35
|
+
options[:level] = options[:level] - 1 if directory_last && options[:level] > 0
|
|
36
|
+
concat_directory(path, options[:level] + 1, options[:header_level])
|
|
37
|
+
directory_last = true
|
|
38
|
+
else
|
|
39
|
+
concat_file(path, options[:level])
|
|
40
|
+
directory_last = false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def write
|
|
46
|
+
template = File.read(@config['erb_path'])
|
|
47
|
+
erb = ERB.new(template)
|
|
48
|
+
File.write(@config['output_file'], erb.result(binding))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# format and concat directory
|
|
54
|
+
def concat_directory(path, level, header_level)
|
|
55
|
+
indent = ' ' * (level)
|
|
56
|
+
description = File.basename(path)
|
|
57
|
+
.split('_') # split on underscores
|
|
58
|
+
.map(&:capitalize) # capitalize each word
|
|
59
|
+
.join(' ') # make it one string
|
|
60
|
+
repo_path = path.sub(File.dirname(@config['output_file']), '.')
|
|
61
|
+
# return if nothing below directory
|
|
62
|
+
return if empty_directory?(path)
|
|
63
|
+
@doc_index << "\n#{indent}#{header_level} [#{description}](#{repo_path})\n"
|
|
64
|
+
generate_index(directory: path, level: level, header_level: header_level + '#')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# format and concat file
|
|
68
|
+
def concat_file(path, level)
|
|
69
|
+
indent = ' ' * (level)
|
|
70
|
+
line = File.open(path, &:readline)
|
|
71
|
+
repo_path = path.sub(File.dirname(@config['output_file']), '.')
|
|
72
|
+
description = line.sub(/^#* */, '').chomp
|
|
73
|
+
@doc_index << "#{indent}- [#{description}](#{repo_path})\n"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# empty == no subdirectories with .md files
|
|
77
|
+
def empty_directory?(directory)
|
|
78
|
+
Dir.entries(directory).each do |filename|
|
|
79
|
+
next if ignore_file?(filename, directory)
|
|
80
|
+
return false if filename.include?('.md')
|
|
81
|
+
path = File.join(directory, filename)
|
|
82
|
+
return false if File.directory?(path) && !empty_directory?(path)
|
|
83
|
+
end
|
|
84
|
+
return true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def ignore_file?(filename, directory)
|
|
88
|
+
path = File.join(directory, filename)
|
|
89
|
+
# current or parent dir
|
|
90
|
+
if filename == '.' || filename == '..'
|
|
91
|
+
return true
|
|
92
|
+
# explicit ignore in config
|
|
93
|
+
elsif @config['ignore'].include?(filename)
|
|
94
|
+
return true
|
|
95
|
+
# symlinks, because of infinite loops on dir's
|
|
96
|
+
elsif File.symlink?(path)
|
|
97
|
+
return true
|
|
98
|
+
# is it a non-md file?
|
|
99
|
+
elsif File.file?(path) && !filename.include?('.md')
|
|
100
|
+
return true
|
|
101
|
+
end
|
|
102
|
+
return false
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'octokit'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
# Class to help verify local and external links
|
|
6
|
+
class LinkChecker
|
|
7
|
+
HTTP_ERRORS = [
|
|
8
|
+
EOFError,
|
|
9
|
+
Errno::ECONNRESET,
|
|
10
|
+
Errno::EINVAL,
|
|
11
|
+
Net::HTTPBadResponse,
|
|
12
|
+
Net::HTTPHeaderSyntaxError,
|
|
13
|
+
Net::ProtocolError,
|
|
14
|
+
Timeout::Error,
|
|
15
|
+
SocketError
|
|
16
|
+
]
|
|
17
|
+
def initialize(config_file)
|
|
18
|
+
@config = YAML.load_file(config_file)
|
|
19
|
+
unless @config['include'] && @config['include'].any?
|
|
20
|
+
puts "Must declare files in 'include' of config".red
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
@broken_links = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check
|
|
27
|
+
@config['include'].each do |included|
|
|
28
|
+
included_object = IncludeItem.new(included)
|
|
29
|
+
included_object.check_paths
|
|
30
|
+
@broken_links << included_object.broken_links.flatten
|
|
31
|
+
end
|
|
32
|
+
@broken_links.flatten!
|
|
33
|
+
if @broken_links.any?
|
|
34
|
+
@broken_links.each do |link_hash|
|
|
35
|
+
output = "Broken link: '#{link_hash['link']}'\n" \
|
|
36
|
+
" in file: '#{link_hash[:file]}'\n" \
|
|
37
|
+
" on line '#{link_hash[:line_number]}'"
|
|
38
|
+
puts output.red
|
|
39
|
+
end
|
|
40
|
+
exit(1)
|
|
41
|
+
else
|
|
42
|
+
puts 'No broken links :)'
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Helper class for dealing with each included item in config
|
|
47
|
+
class IncludeItem
|
|
48
|
+
attr_reader :broken_links
|
|
49
|
+
|
|
50
|
+
def initialize(config)
|
|
51
|
+
@config = config
|
|
52
|
+
@broken_links = []
|
|
53
|
+
@config['replacements'] ||= {}
|
|
54
|
+
@config['private_github'] ||= false
|
|
55
|
+
return unless @config['private_github']
|
|
56
|
+
unless ENV['GITHUB_OAUTH_TOKEN']
|
|
57
|
+
puts "Must specify 'GITHUB_OAUTH_TOKEN' env variable to use 'private_github' config option"
|
|
58
|
+
exit(1)
|
|
59
|
+
end
|
|
60
|
+
@github_client = Octokit::Client.new(access_token: ENV['GITHUB_OAUTH_TOKEN'])
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# iterate over list of paths
|
|
64
|
+
def check_paths
|
|
65
|
+
@config['paths'].each do |path|
|
|
66
|
+
check_path(path)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# glob each path
|
|
71
|
+
def check_path(path)
|
|
72
|
+
files = Dir.glob(path).select { |f| File.file?(f) } # only want files
|
|
73
|
+
files.each do |filename|
|
|
74
|
+
check_file(filename)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_file(filename)
|
|
79
|
+
file = File.open(filename, 'r')
|
|
80
|
+
file.each_with_index do |line, index|
|
|
81
|
+
return false if @config['exclude_comment'] && line.include?(@config['exclude_comment'])
|
|
82
|
+
links = line.scan(@config['pattern']).flatten
|
|
83
|
+
check_links(links, filename, index + 1) if links.any?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def check_links(links, file, line_number)
|
|
88
|
+
links.each do |link|
|
|
89
|
+
link = replace_values(link)
|
|
90
|
+
link = link.sub(/#.*$/, '') # scrub the anchor
|
|
91
|
+
next if check_link(link, file)
|
|
92
|
+
@broken_links << {
|
|
93
|
+
'link' => link,
|
|
94
|
+
:file => file,
|
|
95
|
+
:line_number => line_number
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def check_link(link, file)
|
|
101
|
+
if link.match(%r{https://github.com}) && @config['private_github']
|
|
102
|
+
check_github_link(link)
|
|
103
|
+
elsif link.match(/^http.*/)
|
|
104
|
+
check_external_link(link)
|
|
105
|
+
elsif link.match(/^#.*/)
|
|
106
|
+
check_section_link(link, file)
|
|
107
|
+
else
|
|
108
|
+
check_internal_link(link, file)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def check_github_link(link)
|
|
113
|
+
repo = link.match(%r{github\.com/([^/]*/[^/]*)/.*})[1]
|
|
114
|
+
path = link.match(%r{github\.com/.*/.*/blob/[^/]*/(.*)})[1]
|
|
115
|
+
begin
|
|
116
|
+
@github_client.rate_limit
|
|
117
|
+
@github_client.contents(repo, path: path)
|
|
118
|
+
rescue Octokit::NotFound
|
|
119
|
+
return false
|
|
120
|
+
end
|
|
121
|
+
true
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def check_external_link(link)
|
|
125
|
+
uri = URI(link)
|
|
126
|
+
begin
|
|
127
|
+
response = Net::HTTP.get_response(uri)
|
|
128
|
+
rescue *HTTP_ERRORS
|
|
129
|
+
puts "Error querying #{link}".red
|
|
130
|
+
return false
|
|
131
|
+
end
|
|
132
|
+
response.is_a?(Net::HTTPSuccess) ? true : false
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def check_section_link(link, file)
|
|
136
|
+
section = link.sub(%r{/#*/}, '').split('-').each(&:capitalize).join(' ')
|
|
137
|
+
File.readlines(file).grep(/#{section}/i).size > 0
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def check_internal_link(link, file)
|
|
141
|
+
File.exist?(File.join(File.dirname(file), link)) ? true : false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def replace_values(link)
|
|
145
|
+
@config['replacements'].each do |pair|
|
|
146
|
+
link = link.sub(pair['match'], pair['value'])
|
|
147
|
+
end
|
|
148
|
+
link
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Add colorize to the String class
|
|
154
|
+
class String
|
|
155
|
+
def colorize(color_code)
|
|
156
|
+
"\e[#{color_code}m#{self}\e[0m"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def red
|
|
160
|
+
colorize(31)
|
|
161
|
+
end
|
|
162
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: markdown-helpers
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mike
|
|
8
|
+
- Wood
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2015-10-30 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: octokit
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - ">="
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '3.0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '3.0'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: thor
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '0.19'
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '0.19'
|
|
42
|
+
description: some helpers for working with github markdown
|
|
43
|
+
email: michael.wood@optimizely.com
|
|
44
|
+
executables:
|
|
45
|
+
- markdownh
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- bin/markdownh
|
|
50
|
+
- lib/markdown-helpers.rb
|
|
51
|
+
- lib/markdown-helpers/doc_tree_builder.rb
|
|
52
|
+
- lib/markdown-helpers/link_checker.rb
|
|
53
|
+
homepage: https://rubygems.org/gems/markdown-helpers
|
|
54
|
+
licenses: []
|
|
55
|
+
metadata: {}
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: '0'
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubyforge_project:
|
|
72
|
+
rubygems_version: 2.2.2
|
|
73
|
+
signing_key:
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: some helpers for working with github markdown
|
|
76
|
+
test_files: []
|