github_changelog_generator 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +11 -43
- data/CHANGELOG.md +24 -4
- data/README.md +29 -24
- data/bin/github_changelog_generator +1 -1
- data/lib/CHANGELOG.md +6 -2
- data/lib/github_changelog_generator.rb +5 -488
- data/lib/github_changelog_generator/fetcher.rb +6 -12
- data/lib/github_changelog_generator/generator/generator.rb +123 -0
- data/lib/github_changelog_generator/generator/generator_fetcher.rb +83 -0
- data/lib/github_changelog_generator/generator/generator_generation.rb +174 -0
- data/lib/github_changelog_generator/generator/generator_processor.rb +192 -0
- data/lib/github_changelog_generator/generator/generator_tags.rb +70 -0
- data/lib/github_changelog_generator/parser.rb +122 -77
- data/lib/github_changelog_generator/version.rb +1 -1
- data/spec/unit/generator/generator_tags_spec.rb +69 -0
- data/spec/unit/parser_spec.rb +60 -0
- metadata +11 -3
- data/lib/github_changelog_generator/generator.rb +0 -42
@@ -0,0 +1,70 @@
|
|
1
|
+
module GitHubChangelogGenerator
|
2
|
+
class Generator
|
3
|
+
# fetch, filter tags, fetch dates and sort them in time order
|
4
|
+
def fetch_and_filter_tags
|
5
|
+
@all_tags = get_filtered_tags(@fetcher.get_all_tags)
|
6
|
+
fetch_tags_dates
|
7
|
+
sort_tags_by_date
|
8
|
+
end
|
9
|
+
|
10
|
+
# Sort all tags by date
|
11
|
+
def sort_tags_by_date
|
12
|
+
puts "Sorting tags..." if @options[:verbose]
|
13
|
+
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
|
14
|
+
end
|
15
|
+
|
16
|
+
# Detect link, name and time for specified tag.
|
17
|
+
#
|
18
|
+
# @param [Hash] newer_tag newer tag. Can be nil, if it's Unreleased section.
|
19
|
+
# @return [Array] link, name and time of the tag
|
20
|
+
def detect_link_tag_time(newer_tag)
|
21
|
+
# if tag is nil - set current time
|
22
|
+
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag)
|
23
|
+
|
24
|
+
# if it's future release tag - set this value
|
25
|
+
if newer_tag.nil? && @options[:future_release]
|
26
|
+
newer_tag_name = @options[:future_release]
|
27
|
+
newer_tag_link = @options[:future_release]
|
28
|
+
else
|
29
|
+
# put unreleased label if there is no name for the tag
|
30
|
+
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
|
31
|
+
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
|
32
|
+
end
|
33
|
+
[newer_tag_link, newer_tag_name, newer_tag_time]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
|
37
|
+
#
|
38
|
+
# @return [Array]
|
39
|
+
def get_filtered_tags(all_tags)
|
40
|
+
filtered_tags = filter_between_tags(all_tags)
|
41
|
+
filter_excluded_tags(filtered_tags)
|
42
|
+
end
|
43
|
+
|
44
|
+
def filter_between_tags(all_tags)
|
45
|
+
filtered_tags = all_tags
|
46
|
+
if @options[:between_tags]
|
47
|
+
@options[:between_tags].each do |tag|
|
48
|
+
unless all_tags.include? tag
|
49
|
+
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
|
50
|
+
end
|
51
|
+
end
|
52
|
+
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
|
53
|
+
end
|
54
|
+
filtered_tags
|
55
|
+
end
|
56
|
+
|
57
|
+
def filter_excluded_tags(all_tags)
|
58
|
+
filtered_tags = all_tags
|
59
|
+
if @options[:exclude_tags]
|
60
|
+
@options[:exclude_tags].each do |tag|
|
61
|
+
unless all_tags.include? tag
|
62
|
+
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
|
63
|
+
end
|
64
|
+
end
|
65
|
+
filtered_tags = all_tags.reject { |tag| @options[:exclude_tags].include? tag }
|
66
|
+
end
|
67
|
+
filtered_tags
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -5,34 +5,32 @@ require_relative "version"
|
|
5
5
|
|
6
6
|
module GitHubChangelogGenerator
|
7
7
|
class Parser
|
8
|
+
# parse options with optparse
|
8
9
|
def self.parse_options
|
9
|
-
options =
|
10
|
-
tag1: nil,
|
11
|
-
tag2: nil,
|
12
|
-
dateformat: "%Y-%m-%d",
|
13
|
-
output: "CHANGELOG.md",
|
14
|
-
issues: true,
|
15
|
-
add_issues_wo_labels: true,
|
16
|
-
add_pr_wo_labels: true,
|
17
|
-
pulls: true,
|
18
|
-
filter_issues_by_milestone: true,
|
19
|
-
author: true,
|
20
|
-
unreleased: true,
|
21
|
-
unreleased_label: "Unreleased",
|
22
|
-
compare_link: true,
|
23
|
-
include_labels: %w(bug enhancement),
|
24
|
-
exclude_labels: %w(duplicate question invalid wontfix),
|
25
|
-
max_issues: nil,
|
26
|
-
simple_list: false,
|
27
|
-
verbose: true,
|
10
|
+
options = get_default_options
|
28
11
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
12
|
+
parser = setup_parser(options)
|
13
|
+
|
14
|
+
parser.parse!
|
15
|
+
|
16
|
+
detect_user_and_project(options)
|
17
|
+
|
18
|
+
if !options[:user] || !options[:project]
|
19
|
+
puts parser.banner
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
23
|
+
if options[:verbose]
|
24
|
+
puts "Performing task with options:"
|
25
|
+
pp options
|
26
|
+
puts ""
|
27
|
+
end
|
28
|
+
|
29
|
+
options
|
30
|
+
end
|
35
31
|
|
32
|
+
# setup parsing options
|
33
|
+
def self.setup_parser(options)
|
36
34
|
parser = OptionParser.new do |opts|
|
37
35
|
opts.banner = "Usage: github_changelog_generator [options]"
|
38
36
|
opts.on("-u", "--user [USER]", "Username of the owner of target GitHub repo") do |last|
|
@@ -45,7 +43,7 @@ module GitHubChangelogGenerator
|
|
45
43
|
options[:token] = last
|
46
44
|
end
|
47
45
|
opts.on("-f", "--date-format [FORMAT]", "Date format. Default is %Y-%m-%d") do |last|
|
48
|
-
options[:
|
46
|
+
options[:date_format] = last
|
49
47
|
end
|
50
48
|
opts.on("-o", "--output [NAME]", "Output file. Default is CHANGELOG.md") do |last|
|
51
49
|
options[:output] = last
|
@@ -86,6 +84,12 @@ module GitHubChangelogGenerator
|
|
86
84
|
opts.on("--exclude-labels x,y,z", Array, 'Issues with the specified labels will be always excluded from changelog. Default is \'duplicate,question,invalid,wontfix\'') do |list|
|
87
85
|
options[:exclude_labels] = list
|
88
86
|
end
|
87
|
+
opts.on("--between-tags x,y,z", Array, "Change log will be filled only between specified tags") do |list|
|
88
|
+
options[:between_tags] = list
|
89
|
+
end
|
90
|
+
opts.on("--exclude-tags x,y,z", Array, "Change log will be exclude specified tags") do |list|
|
91
|
+
options[:exclude_tags] = list
|
92
|
+
end
|
89
93
|
opts.on("--max-issues [NUMBER]", Integer, "Max number of issues to fetch from GitHub. Default is unlimited") do |max|
|
90
94
|
options[:max_issues] = max
|
91
95
|
end
|
@@ -113,79 +117,120 @@ module GitHubChangelogGenerator
|
|
113
117
|
exit
|
114
118
|
end
|
115
119
|
end
|
120
|
+
parser
|
121
|
+
end
|
116
122
|
|
117
|
-
|
123
|
+
# just get default options
|
124
|
+
def self.get_default_options
|
125
|
+
options = {
|
126
|
+
tag1: nil,
|
127
|
+
tag2: nil,
|
128
|
+
date_format: "%Y-%m-%d",
|
129
|
+
output: "CHANGELOG.md",
|
130
|
+
issues: true,
|
131
|
+
add_issues_wo_labels: true,
|
132
|
+
add_pr_wo_labels: true,
|
133
|
+
pulls: true,
|
134
|
+
filter_issues_by_milestone: true,
|
135
|
+
author: true,
|
136
|
+
unreleased: true,
|
137
|
+
unreleased_label: "Unreleased",
|
138
|
+
compare_link: true,
|
139
|
+
include_labels: %w(bug enhancement),
|
140
|
+
exclude_labels: %w(duplicate question invalid wontfix),
|
141
|
+
max_issues: nil,
|
142
|
+
simple_list: false,
|
143
|
+
verbose: true,
|
144
|
+
merge_prefix: "**Merged pull requests:**",
|
145
|
+
issue_prefix: "**Closed issues:**",
|
146
|
+
bug_prefix: "**Fixed bugs:**",
|
147
|
+
enhancement_prefix: "**Implemented enhancements:**",
|
148
|
+
git_remote: "origin"
|
149
|
+
}
|
118
150
|
|
119
|
-
|
151
|
+
options
|
152
|
+
end
|
120
153
|
|
154
|
+
# Detects user and project from git
|
155
|
+
def self.detect_user_and_project(options)
|
156
|
+
options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site])
|
121
157
|
if !options[:user] || !options[:project]
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
|
131
|
-
if options[:verbose]
|
132
|
-
puts "Performing task with options:"
|
133
|
-
pp options
|
134
|
-
puts ""
|
158
|
+
if ENV["RUBYLIB"] =~ /ruby-debug-ide/
|
159
|
+
options[:user] = "skywinder"
|
160
|
+
options[:project] = "changelog_test"
|
161
|
+
else
|
162
|
+
remote = `git config --get remote.#{options[:git_remote]}.url`
|
163
|
+
options[:user], options[:project] = user_project_from_remote(remote)
|
164
|
+
end
|
135
165
|
end
|
136
|
-
|
137
|
-
options
|
138
166
|
end
|
139
167
|
|
140
|
-
|
141
|
-
|
142
|
-
|
168
|
+
# Try to find user and project name from git remote output
|
169
|
+
#
|
170
|
+
# @param [String] output of git remote command
|
171
|
+
# @return [Array] user and project
|
172
|
+
def self.user_project_from_option(arg0, arg1, github_site = nil)
|
173
|
+
user = nil
|
174
|
+
project = nil
|
175
|
+
github_site ||= "github.com"
|
176
|
+
if arg0 && !arg1
|
143
177
|
# this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name
|
144
|
-
|
178
|
+
puts arg0
|
179
|
+
match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0)
|
145
180
|
|
146
181
|
begin
|
147
182
|
param = match[2].nil?
|
148
183
|
rescue
|
149
|
-
puts "Can't detect user and name from first parameter: '#{
|
184
|
+
puts "Can't detect user and name from first parameter: '#{arg0}' -> exit'"
|
150
185
|
exit
|
151
186
|
end
|
152
187
|
if param
|
153
188
|
exit
|
154
189
|
else
|
155
|
-
|
156
|
-
|
190
|
+
user = match[1]
|
191
|
+
project = match[2]
|
157
192
|
end
|
158
|
-
|
159
193
|
end
|
194
|
+
[user, project]
|
195
|
+
end
|
160
196
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
197
|
+
# Try to find user and project name from git remote output
|
198
|
+
#
|
199
|
+
# @param [String] output of git remote command
|
200
|
+
# @return [Array] user and project
|
201
|
+
def self.user_project_from_remote(remote)
|
202
|
+
# try to find repo in format:
|
203
|
+
# origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch)
|
204
|
+
# git@github.com:skywinder/Github-Changelog-Generator.git
|
205
|
+
regex1 = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/
|
206
|
+
|
207
|
+
# try to find repo in format:
|
208
|
+
# origin https://github.com/skywinder/ChangelogMerger (fetch)
|
209
|
+
# https://github.com/skywinder/ChangelogMerger
|
210
|
+
regex2 = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/
|
211
|
+
|
212
|
+
remote_structures = [regex1, regex2]
|
213
|
+
|
214
|
+
user = nil
|
215
|
+
project = nil
|
216
|
+
remote_structures.each do |regex|
|
217
|
+
matches = Regexp.new(regex).match(remote)
|
218
|
+
|
219
|
+
if matches && matches[1] && matches[2]
|
220
|
+
puts "Detected user:#{matches[1]}, project:#{matches[2]}"
|
221
|
+
user = matches[1]
|
222
|
+
project = matches[2]
|
187
223
|
end
|
224
|
+
|
225
|
+
break unless matches.nil?
|
188
226
|
end
|
227
|
+
|
228
|
+
[user, project]
|
189
229
|
end
|
190
230
|
end
|
231
|
+
|
232
|
+
if __FILE__ == $PROGRAM_NAME
|
233
|
+
remote = "invalid reference to project"
|
234
|
+
p user_project_from_option(ARGV[0], ARGV[1], remote)
|
235
|
+
end
|
191
236
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
describe GitHubChangelogGenerator::Generator do
|
2
|
+
describe "#filter_between_tags" do
|
3
|
+
context "when between_tags nil" do
|
4
|
+
before do
|
5
|
+
@generator = GitHubChangelogGenerator::Generator.new(between_tags: nil)
|
6
|
+
end
|
7
|
+
|
8
|
+
subject do
|
9
|
+
@generator.get_filtered_tags(%w(1 2 3))
|
10
|
+
end
|
11
|
+
it { is_expected.to be_a(Array) }
|
12
|
+
it { is_expected.to match_array(%w(1 2 3)) }
|
13
|
+
end
|
14
|
+
context "when between_tags same as input array" do
|
15
|
+
before do
|
16
|
+
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2 3))
|
17
|
+
end
|
18
|
+
subject do
|
19
|
+
@generator.get_filtered_tags(%w(1 2 3))
|
20
|
+
end
|
21
|
+
it { is_expected.to be_a(Array) }
|
22
|
+
it { is_expected.to match_array(%w(1 2 3)) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when between_tags filled with correct values" do
|
26
|
+
before do
|
27
|
+
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2))
|
28
|
+
end
|
29
|
+
subject do
|
30
|
+
@generator.get_filtered_tags(%w(1 2 3))
|
31
|
+
end
|
32
|
+
it { is_expected.to be_a(Array) }
|
33
|
+
it { is_expected.to match_array(%w(1 2)) }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when between_tags filled with invalid values" do
|
37
|
+
before do
|
38
|
+
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 q w))
|
39
|
+
end
|
40
|
+
|
41
|
+
subject do
|
42
|
+
@generator.get_filtered_tags(%w(1 2 3))
|
43
|
+
end
|
44
|
+
it { is_expected.to be_a(Array) }
|
45
|
+
it { is_expected.to match_array(%w(1)) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#get_filtered_tags" do
|
50
|
+
subject { generator.get_filtered_tags(%w(1 2 3 4 5)) }
|
51
|
+
# before { generator.get_filtered_tags(%w(1 2 3 4 5)) }
|
52
|
+
|
53
|
+
context "with excluded and between tags" do
|
54
|
+
let(:generator) { GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2 3), exclude_tags: %w(2)) }
|
55
|
+
it { is_expected.to be_a Array }
|
56
|
+
it { is_expected.to match_array(%w(1 3)) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#filter_excluded_tags" do
|
61
|
+
subject { generator.filter_excluded_tags(%w(1 2 3)) }
|
62
|
+
|
63
|
+
context "with valid excluded tags" do
|
64
|
+
let(:generator) { GitHubChangelogGenerator::Generator.new(exclude_tags: %w(3)) }
|
65
|
+
it { is_expected.to be_a Array }
|
66
|
+
it { is_expected.to match_array(%w(1 2)) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
describe GitHubChangelogGenerator::Parser do
|
2
|
+
describe ".user_project_from_remote" do
|
3
|
+
context "when remote is 1" do
|
4
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin https://github.com/skywinder/ActionSheetPicker-3.0 (fetch)") }
|
5
|
+
it { is_expected.to be_a(Array) }
|
6
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
7
|
+
end
|
8
|
+
context "when remote is 2" do
|
9
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") }
|
10
|
+
it { is_expected.to be_a(Array) }
|
11
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
12
|
+
end
|
13
|
+
context "when remote is 3" do
|
14
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") }
|
15
|
+
it { is_expected.to be_a(Array) }
|
16
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
17
|
+
end
|
18
|
+
context "when remote is 4" do
|
19
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin git@github.com:skywinder/ActionSheetPicker-3.0.git (fetch)") }
|
20
|
+
it { is_expected.to be_a(Array) }
|
21
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
22
|
+
end
|
23
|
+
context "when remote is invalid" do
|
24
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("some invalid text") }
|
25
|
+
it { is_expected.to be_a(Array) }
|
26
|
+
it { is_expected.to match_array([nil, nil]) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
describe ".user_project_from_option" do
|
30
|
+
# context "when option is invalid" do
|
31
|
+
# it("should exit") { expect { GitHubChangelogGenerator::Parser.user_project_from_option("blah", nil) }.to raise_error(SystemExit) }
|
32
|
+
# end
|
33
|
+
|
34
|
+
context "when option is valid" do
|
35
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil) }
|
36
|
+
it { is_expected.to be_a(Array) }
|
37
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
38
|
+
end
|
39
|
+
context "when option nil" do
|
40
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_option(nil, nil) }
|
41
|
+
it { is_expected.to be_a(Array) }
|
42
|
+
it { is_expected.to match_array([nil, nil]) }
|
43
|
+
end
|
44
|
+
context "when site is nil" do
|
45
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, nil) }
|
46
|
+
it { is_expected.to be_a(Array) }
|
47
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
48
|
+
end
|
49
|
+
context "when site is valid" do
|
50
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, "https://codeclimate.com") }
|
51
|
+
it { is_expected.to be_a(Array) }
|
52
|
+
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
53
|
+
end
|
54
|
+
context "when second arg is not nil" do
|
55
|
+
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", "blah", nil) }
|
56
|
+
it { is_expected.to be_a(Array) }
|
57
|
+
it { is_expected.to match_array([nil, nil]) }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|