markdown-helpers 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
+ 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
@@ -0,0 +1,2 @@
1
+ require_relative './markdown-helpers/link_checker.rb'
2
+ require_relative './markdown-helpers/doc_tree_builder.rb'
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: []