document_generator 0.0.2
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/.gitignore +24 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +83 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +6 -0
- data/_layouts/default.html +100 -0
- data/assets/_layouts/default.html +100 -0
- data/bin/doc_generate +5 -0
- data/coderay.css +129 -0
- data/document_generator.gemspec +31 -0
- data/index.md +7 -0
- data/lib/document_generator.rb +17 -0
- data/lib/document_generator/cli.rb +27 -0
- data/lib/document_generator/commit.rb +85 -0
- data/lib/document_generator/diff_file.rb +157 -0
- data/lib/document_generator/output.rb +27 -0
- data/lib/document_generator/repository.rb +89 -0
- data/lib/document_generator/version.rb +3 -0
- data/spec/lib/document_generator/cli_spec.rb +27 -0
- data/spec/lib/document_generator/commit_spec.rb +243 -0
- data/spec/lib/document_generator/diff_file_spec.rb +438 -0
- data/spec/lib/document_generator/repository_spec.rb +75 -0
- data/spec/spec_helper.rb +21 -0
- metadata +179 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'document_generator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'document_generator'
|
8
|
+
spec.version = DocumentGenerator::VERSION
|
9
|
+
spec.authors = ['wiscoDude', 'm5rk', 'stevenhallen']
|
10
|
+
spec.email = ['philip@stevenhallen.com', 'mark@stevenhallen.com']
|
11
|
+
spec.description = <<-DOCUMENT_GENERATOR
|
12
|
+
Generate documentation from a git repository.
|
13
|
+
DOCUMENT_GENERATOR
|
14
|
+
spec.summary = 'Generate documentation from a git repository.'
|
15
|
+
spec.homepage = 'http://github.com/stevenhallen/document_generator'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_runtime_dependency 'addressable', '~> 2.3'
|
24
|
+
spec.add_runtime_dependency 'git', '~> 1.2'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
29
|
+
spec.add_development_dependency 'rspec-fire', '~> 1.3'
|
30
|
+
spec.add_development_dependency 'simplecov', '~> 0.7'
|
31
|
+
end
|
data/index.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'git'
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
require 'document_generator/version'
|
9
|
+
|
10
|
+
require 'document_generator/cli'
|
11
|
+
require 'document_generator/commit'
|
12
|
+
require 'document_generator/diff_file'
|
13
|
+
require 'document_generator/output'
|
14
|
+
require 'document_generator/repository'
|
15
|
+
|
16
|
+
module DocumentGenerator
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DocumentGenerator
|
2
|
+
class CLI
|
3
|
+
def self.start(args)
|
4
|
+
options = parse(args)
|
5
|
+
|
6
|
+
Repository.new(options.url).generate
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(args)
|
10
|
+
options = OpenStruct.new
|
11
|
+
|
12
|
+
parser = OptionParser.new do |opts|
|
13
|
+
opts.on('-u', '--url URL',
|
14
|
+
'URL for the repository') do |url|
|
15
|
+
options.url = url
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
parser.parse!(args)
|
20
|
+
|
21
|
+
# TODO: Do something better than this.
|
22
|
+
raise OptionParser::MissingArgument unless options.url
|
23
|
+
|
24
|
+
options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module DocumentGenerator
|
2
|
+
class Commit
|
3
|
+
attr_accessor :base_url, :git_commit
|
4
|
+
|
5
|
+
def initialize(base_url, git_commit)
|
6
|
+
@base_url = base_url
|
7
|
+
@git_commit = git_commit
|
8
|
+
end
|
9
|
+
|
10
|
+
def diff_files
|
11
|
+
return [] unless git_commit.parent
|
12
|
+
|
13
|
+
git_commit.parent.diff(git_commit).map do |git_diff_file|
|
14
|
+
DiffFile.new(git_diff_file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def relative_filename
|
19
|
+
filename
|
20
|
+
end
|
21
|
+
|
22
|
+
def create
|
23
|
+
File.open(relative_filename, 'w') do |writer|
|
24
|
+
writer.write(header)
|
25
|
+
writer.write(details_of_commit_message) if details_of_commit_message
|
26
|
+
|
27
|
+
diff_files.each do |diff_file|
|
28
|
+
writer.write(diff_file.content)
|
29
|
+
end
|
30
|
+
|
31
|
+
writer.write(additional)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def header
|
36
|
+
<<-HEADER
|
37
|
+
---
|
38
|
+
layout: default
|
39
|
+
title: #{first_line_of_commit_message}
|
40
|
+
---
|
41
|
+
|
42
|
+
<h1 id="main">#{first_line_of_commit_message}</h1>
|
43
|
+
HEADER
|
44
|
+
end
|
45
|
+
|
46
|
+
def additional
|
47
|
+
<<-ADDITIONAL
|
48
|
+
|
49
|
+
### Additional Resources
|
50
|
+
|
51
|
+
* [Changes in this step in `diff` format](#{URI.join(base_url, 'commit/', git_commit.sha)})
|
52
|
+
|
53
|
+
ADDITIONAL
|
54
|
+
end
|
55
|
+
|
56
|
+
def commit_message_lines
|
57
|
+
git_commit.message.split("\n")
|
58
|
+
end
|
59
|
+
|
60
|
+
def first_line_of_commit_message
|
61
|
+
commit_message_lines.first
|
62
|
+
end
|
63
|
+
|
64
|
+
def details_of_commit_message
|
65
|
+
commit_message_lines[1..-1].join("\n") if commit_message_lines.length > 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def basename_prefix
|
69
|
+
message = first_line_of_commit_message
|
70
|
+
message = message.split.join('-')
|
71
|
+
message.gsub!(%r{[^\w-]}, '')
|
72
|
+
message.downcase!
|
73
|
+
message.tr!('_', '-')
|
74
|
+
message
|
75
|
+
end
|
76
|
+
|
77
|
+
def filename
|
78
|
+
"#{basename_prefix}.md"
|
79
|
+
end
|
80
|
+
|
81
|
+
def link
|
82
|
+
"<li><a href='#{basename_prefix}.html'>#{first_line_of_commit_message}</a></li>"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module DocumentGenerator
|
4
|
+
class DiffFile
|
5
|
+
attr_accessor :git_diff_file
|
6
|
+
|
7
|
+
def type
|
8
|
+
git_diff_file.type
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(git_diff_file)
|
12
|
+
@git_diff_file = git_diff_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def git_diff_file_lines
|
16
|
+
git_diff_file.patch.split("\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def patch_heading
|
20
|
+
"#{action_type} `#{git_diff_file.path}`"
|
21
|
+
end
|
22
|
+
|
23
|
+
def content
|
24
|
+
if type == 'deleted'
|
25
|
+
return patch_heading + "\n\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
temp = []
|
29
|
+
temp << patch_heading
|
30
|
+
|
31
|
+
if markdown_outputs.any?
|
32
|
+
markdown_outputs.each do |output|
|
33
|
+
temp << "\n\n"
|
34
|
+
temp << output.description
|
35
|
+
temp << "\n<pre><code>"
|
36
|
+
temp << output.escaped_content
|
37
|
+
temp << "</code></pre>\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if git_diff_file.type == "modified"
|
42
|
+
temp << "\n\n"
|
43
|
+
temp << "Becomes"
|
44
|
+
temp << "\n<pre><code>"
|
45
|
+
temp << ending_code
|
46
|
+
temp << "\n</code></pre>\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
temp << "\n\n"
|
50
|
+
|
51
|
+
temp.join
|
52
|
+
end
|
53
|
+
|
54
|
+
def ending_code
|
55
|
+
clean_lines = []
|
56
|
+
git_diff_file_lines[code_line_start..-1].each_with_index do |line, index|
|
57
|
+
|
58
|
+
if (line[0]) == "-" || ignore_line?(line)
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
if (line[0]) == "+"
|
63
|
+
line = remove_first_character(line)
|
64
|
+
end
|
65
|
+
clean_lines << line
|
66
|
+
end
|
67
|
+
Output.no_really_escape(CGI.escapeHTML(clean_lines.join("\n")))
|
68
|
+
end
|
69
|
+
|
70
|
+
def action_type
|
71
|
+
{ new: 'Create file',
|
72
|
+
modified: 'Update file',
|
73
|
+
deleted: 'Remove file' }.fetch(type.to_sym, type)
|
74
|
+
end
|
75
|
+
|
76
|
+
def markdown_outputs # returns an array of outputs
|
77
|
+
outputs = []
|
78
|
+
last_line = 0
|
79
|
+
git_diff_file_lines.each_with_index do |line, index|
|
80
|
+
next if index < code_line_start
|
81
|
+
next if index <= last_line
|
82
|
+
case line.strip[0]
|
83
|
+
|
84
|
+
when "+"
|
85
|
+
last_line = last_same_line(index)
|
86
|
+
output = Output.new
|
87
|
+
output.description = "Add"
|
88
|
+
output.content = line_block(index, last_line)
|
89
|
+
outputs << output
|
90
|
+
when "-"
|
91
|
+
if line_sign(index + 1) == "+"
|
92
|
+
output = Output.new
|
93
|
+
output.description = "Change"
|
94
|
+
output.content = line_block(index, last_same_line(index))
|
95
|
+
outputs << output
|
96
|
+
last_line = last_same_line(last_same_line(index) + 1)
|
97
|
+
output = Output.new
|
98
|
+
output.description = "To"
|
99
|
+
output.content = line_block(last_same_line(index) + 1, last_line)
|
100
|
+
outputs << output
|
101
|
+
else
|
102
|
+
output = Output.new
|
103
|
+
output.description = "Remove"
|
104
|
+
last_line = last_same_line(index)
|
105
|
+
output.content = line_block(index, last_line)
|
106
|
+
outputs << output
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
outputs
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def ignore_line?(line)
|
117
|
+
line.strip == 'No newline at end of file'
|
118
|
+
end
|
119
|
+
|
120
|
+
def last_same_line(line_index)
|
121
|
+
starting_sign = line_sign(line_index)
|
122
|
+
|
123
|
+
git_diff_file_lines[line_index..-1].each_with_index do |line, index|
|
124
|
+
if line_sign(index + 1 + line_index) != starting_sign
|
125
|
+
return (index + line_index)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def line_block(beginning, ending)
|
131
|
+
lines = []
|
132
|
+
git_diff_file_lines[beginning..ending].each do |line|
|
133
|
+
if ["+", "-"].include?(line[0..0])
|
134
|
+
line = remove_first_character(line)
|
135
|
+
end
|
136
|
+
if !ignore_line?(line)
|
137
|
+
lines << line
|
138
|
+
end
|
139
|
+
end
|
140
|
+
lines
|
141
|
+
end
|
142
|
+
|
143
|
+
def line_sign(line_number)
|
144
|
+
(git_diff_file_lines[line_number] || '').strip[0]
|
145
|
+
end
|
146
|
+
|
147
|
+
def remove_first_character(line)
|
148
|
+
" " + line[1..-1]
|
149
|
+
end
|
150
|
+
|
151
|
+
def code_line_start
|
152
|
+
git_diff_file_lines.each_with_index do |line, index|
|
153
|
+
return (index + 1) if line[0..1] == "@@"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module DocumentGenerator
|
2
|
+
class Output
|
3
|
+
attr_accessor :description, :content
|
4
|
+
|
5
|
+
# TODO: This is due to a bug in maruku. We should create
|
6
|
+
# an issue there--and possibly a PR to fix?
|
7
|
+
def self.no_really_escape(value)
|
8
|
+
value.split("\n").map do |line|
|
9
|
+
if line.strip.size.zero?
|
10
|
+
" "
|
11
|
+
else
|
12
|
+
line
|
13
|
+
end
|
14
|
+
end.join("\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def escaped_content
|
18
|
+
temp = []
|
19
|
+
content.each do |line|
|
20
|
+
temp << line
|
21
|
+
temp << "\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
Output.no_really_escape(CGI.escapeHTML(temp.join.rstrip))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module DocumentGenerator
|
2
|
+
class Repository
|
3
|
+
attr_accessor :url
|
4
|
+
|
5
|
+
def self.menu_dirname
|
6
|
+
'_includes'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.menu_relative_filename
|
10
|
+
File.join(menu_dirname, 'menu.md')
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.default_dirname
|
14
|
+
'_layouts'
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.default_relative_filename
|
18
|
+
File.join(default_dirname, 'default.html')
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(url)
|
22
|
+
@url = url
|
23
|
+
end
|
24
|
+
|
25
|
+
def base_url
|
26
|
+
"https://#{uri.host}#{uri.path}/"
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
uri.path.split('/')[-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate
|
34
|
+
prepare
|
35
|
+
|
36
|
+
File.open(Repository.menu_relative_filename, 'w') do |menu_writer|
|
37
|
+
commits do |commit|
|
38
|
+
menu_writer.write(commit.link)
|
39
|
+
commit.create
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def commits
|
45
|
+
Dir.mktmpdir do |path|
|
46
|
+
repo = Git.clone(url, name, path: path)
|
47
|
+
|
48
|
+
# TODO: Allow options to influence branch, number of commits, etc.
|
49
|
+
repo.log(nil).reverse_each.map do |git_commit|
|
50
|
+
yield Commit.new(base_url, git_commit)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def prepare
|
57
|
+
Dir.mkdir(Repository.menu_dirname) unless Dir.exists?(Repository.menu_dirname)
|
58
|
+
copy_layout
|
59
|
+
end
|
60
|
+
|
61
|
+
def copy_layout
|
62
|
+
return if File.exists?(Repository.default_relative_filename)
|
63
|
+
|
64
|
+
Dir.mkdir(Repository.default_dirname) unless Dir.exists?(Repository.default_dirname)
|
65
|
+
|
66
|
+
src = File.expand_path('../../../assets/_layouts/default.html', __FILE__)
|
67
|
+
dest = Repository.default_relative_filename
|
68
|
+
|
69
|
+
FileUtils.copy_file(src, dest)
|
70
|
+
end
|
71
|
+
|
72
|
+
def normalized_url
|
73
|
+
replacements = [
|
74
|
+
[%r(\Agit@github\.com:), 'git://github.com/'],
|
75
|
+
[%r(\.git\Z), '']
|
76
|
+
]
|
77
|
+
|
78
|
+
replacements.each do |pattern, replacement|
|
79
|
+
url.gsub!(pattern, replacement)
|
80
|
+
end
|
81
|
+
|
82
|
+
url
|
83
|
+
end
|
84
|
+
|
85
|
+
def uri
|
86
|
+
Addressable::URI.parse(normalized_url)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|