github_changelog_generator 1.15.0 → 1.16.3

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +32 -54
  3. data/Rakefile +1 -1
  4. data/lib/github_changelog_generator/argv_parser.rb +225 -0
  5. data/lib/github_changelog_generator/generator/entry.rb +10 -10
  6. data/lib/github_changelog_generator/generator/generator.rb +41 -19
  7. data/lib/github_changelog_generator/generator/generator_fetcher.rb +5 -9
  8. data/lib/github_changelog_generator/generator/generator_processor.rb +23 -20
  9. data/lib/github_changelog_generator/generator/generator_tags.rb +15 -9
  10. data/lib/github_changelog_generator/generator/section.rb +27 -7
  11. data/lib/github_changelog_generator/helper.rb +1 -1
  12. data/lib/github_changelog_generator/octo_fetcher.rb +190 -126
  13. data/lib/github_changelog_generator/options.rb +4 -0
  14. data/lib/github_changelog_generator/parser.rb +70 -248
  15. data/lib/github_changelog_generator/parser_file.rb +29 -14
  16. data/lib/github_changelog_generator/reader.rb +2 -2
  17. data/lib/github_changelog_generator/ssl_certs/cacert.pem +851 -1680
  18. data/lib/github_changelog_generator/task.rb +3 -2
  19. data/lib/github_changelog_generator/version.rb +1 -1
  20. data/man/git-generate-changelog.1 +46 -34
  21. data/man/git-generate-changelog.1.html +39 -31
  22. data/man/git-generate-changelog.html +19 -19
  23. data/man/git-generate-changelog.md +39 -31
  24. data/spec/files/config_example +5 -0
  25. data/spec/spec_helper.rb +1 -1
  26. data/spec/unit/generator/entry_spec.rb +37 -31
  27. data/spec/unit/generator/generator_processor_spec.rb +99 -44
  28. data/spec/unit/generator/generator_spec.rb +47 -0
  29. data/spec/unit/generator/generator_tags_spec.rb +46 -3
  30. data/spec/unit/generator/section_spec.rb +34 -0
  31. data/spec/unit/octo_fetcher_spec.rb +45 -2
  32. data/spec/unit/parser_spec.rb +50 -0
  33. metadata +45 -9
@@ -25,6 +25,7 @@ module GitHubChangelogGenerator
25
25
  bug_prefix
26
26
  cache_file
27
27
  cache_log
28
+ config_file
28
29
  compare_link
29
30
  configure_sections
30
31
  date_format
@@ -37,6 +38,7 @@ module GitHubChangelogGenerator
37
38
  exclude_tags
38
39
  exclude_tags_regex
39
40
  filter_issues_by_milestone
41
+ issues_of_open_milestones
40
42
  frontmatter
41
43
  future_release
42
44
  github_endpoint
@@ -44,6 +46,7 @@ module GitHubChangelogGenerator
44
46
  header
45
47
  http_cache
46
48
  include_labels
49
+ include_tags_regex
47
50
  issue_prefix
48
51
  issue_line_labels
49
52
  issue_line_body
@@ -62,6 +65,7 @@ module GitHubChangelogGenerator
62
65
  security_prefix
63
66
  simple_list
64
67
  since_tag
68
+ since_commit
65
69
  ssl_ca_file
66
70
  summary_labels
67
71
  summary_prefix
@@ -1,267 +1,89 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "optparse"
5
- require "github_changelog_generator/version"
6
4
  require "github_changelog_generator/helper"
5
+ require "github_changelog_generator/argv_parser"
6
+ require "github_changelog_generator/parser_file"
7
7
 
8
8
  module GitHubChangelogGenerator
9
9
  class Parser
10
- # parse options with optparse
11
- def self.parse_options
12
- options = default_options
10
+ class << self
11
+ PARSERS = [
12
+ ArgvParser, # Parse arguments first to get initial options populated
13
+ FileParserChooser, # Then parse possible configuration files
14
+ ArgvParser # Lastly parse arguments again to keep the given arguments the strongest
15
+ ].freeze
13
16
 
14
- ParserFile.new(options).parse!
17
+ def parse_options(argv = ARGV)
18
+ options = default_options
15
19
 
16
- parser = setup_parser(options)
17
- begin parser.parse!
18
- rescue OptionParser::InvalidOption => e
19
- abort [e, parser].join("\n")
20
- end
20
+ PARSERS.each do |parser|
21
+ parser.new(options).parse!(argv)
22
+ end
21
23
 
22
- unless options[:user] && options[:project]
23
- warn "Configure which user and project to work on."
24
- warn "Options --user and --project, or settings to that effect. See --help for more."
25
- abort(parser.banner)
24
+ abort_if_user_and_project_not_given!(options)
25
+
26
+ options.print_options
27
+
28
+ options
26
29
  end
27
30
 
28
- options.print_options
31
+ def abort_if_user_and_project_not_given!(options)
32
+ return if options[:user] && options[:project]
29
33
 
30
- options
31
- end
34
+ warn "Configure which user and project to work on."
35
+ warn "Options --user and --project, or settings to that effect. See --help for more."
36
+ warn ArgvParser.banner
32
37
 
33
- # Setup parsing options
34
- #
35
- # @param options [Options]
36
- # @return [OptionParser]
37
- def self.setup_parser(options)
38
- OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
39
- opts.banner = "Usage: github_changelog_generator --user USER --project PROJECT [options]"
40
- opts.on("-u", "--user USER", "Username of the owner of target GitHub repo.") do |last|
41
- options[:user] = last
42
- end
43
- opts.on("-p", "--project PROJECT", "Name of project on GitHub.") do |last|
44
- options[:project] = last
45
- end
46
- opts.on("-t", "--token [TOKEN]", "To make more than 50 requests per hour your GitHub token is required. You can generate it at: https://github.com/settings/tokens/new") do |last|
47
- options[:token] = last
48
- end
49
- opts.on("-f", "--date-format FORMAT", "Date format. Default is %Y-%m-%d.") do |last|
50
- options[:date_format] = last
51
- end
52
- opts.on("-o", "--output [NAME]", "Output file. To print to STDOUT instead, use blank as path. Default is CHANGELOG.md") do |last|
53
- options[:output] = last
54
- end
55
- opts.on("-b", "--base [NAME]", "Optional base file to append generated changes to.") do |last|
56
- options[:base] = last
57
- end
58
- opts.on("--summary-label [LABEL]", "Set up custom label for the release summary section. Default is \"\".") do |v|
59
- options[:summary_prefix] = v
60
- end
61
- opts.on("--breaking-label [LABEL]", "Set up custom label for the breaking changes section. Default is \"**Breaking changes:**\".") do |v|
62
- options[:breaking_prefix] = v
63
- end
64
- opts.on("--enhancement-label [LABEL]", "Set up custom label for enhancements section. Default is \"**Implemented enhancements:**\".") do |v|
65
- options[:enhancement_prefix] = v
66
- end
67
- opts.on("--bugs-label [LABEL]", "Set up custom label for bug-fixes section. Default is \"**Fixed bugs:**\".") do |v|
68
- options[:bug_prefix] = v
69
- end
70
- opts.on("--deprecated-label [LABEL]", "Set up custom label for the deprecated changes section. Default is \"**Deprecated:**\".") do |v|
71
- options[:deprecated_prefix] = v
72
- end
73
- opts.on("--removed-label [LABEL]", "Set up custom label for the removed changes section. Default is \"**Removed:**\".") do |v|
74
- options[:removed_prefix] = v
75
- end
76
- opts.on("--security-label [LABEL]", "Set up custom label for the security changes section. Default is \"**Security fixes:**\".") do |v|
77
- options[:security_prefix] = v
78
- end
79
- opts.on("--issues-label [LABEL]", "Set up custom label for closed-issues section. Default is \"**Closed issues:**\".") do |v|
80
- options[:issue_prefix] = v
81
- end
82
- opts.on("--header-label [LABEL]", "Set up custom header label. Default is \"# Changelog\".") do |v|
83
- options[:header] = v
84
- end
85
- opts.on("--configure-sections [Hash, String]", "Define your own set of sections which overrides all default sections.") do |v|
86
- options[:configure_sections] = v
87
- end
88
- opts.on("--add-sections [Hash, String]", "Add new sections but keep the default sections.") do |v|
89
- options[:add_sections] = v
90
- end
91
- opts.on("--front-matter [JSON]", "Add YAML front matter. Formatted as JSON because it's easier to add on the command line.") do |v|
92
- options[:frontmatter] = JSON.parse(v).to_yaml + "---\n"
93
- end
94
- opts.on("--pr-label [LABEL]", "Set up custom label for pull requests section. Default is \"**Merged pull requests:**\".") do |v|
95
- options[:merge_prefix] = v
96
- end
97
- opts.on("--[no-]issues", "Include closed issues in changelog. Default is true.") do |v|
98
- options[:issues] = v
99
- end
100
- opts.on("--[no-]issues-wo-labels", "Include closed issues without labels in changelog. Default is true.") do |v|
101
- options[:add_issues_wo_labels] = v
102
- end
103
- opts.on("--[no-]pr-wo-labels", "Include pull requests without labels in changelog. Default is true.") do |v|
104
- options[:add_pr_wo_labels] = v
105
- end
106
- opts.on("--[no-]pull-requests", "Include pull-requests in changelog. Default is true.") do |v|
107
- options[:pulls] = v
108
- end
109
- opts.on("--[no-]filter-by-milestone", "Use milestone to detect when issue was resolved. Default is true.") do |last|
110
- options[:filter_issues_by_milestone] = last
111
- end
112
- opts.on("--[no-]author", "Add author of pull request at the end. Default is true.") do |author|
113
- options[:author] = author
114
- end
115
- opts.on("--usernames-as-github-logins", "Use GitHub tags instead of Markdown links for the author of an issue or pull-request.") do |v|
116
- options[:usernames_as_github_logins] = v
117
- end
118
- opts.on("--unreleased-only", "Generate log from unreleased closed issues only.") do |v|
119
- options[:unreleased_only] = v
120
- end
121
- opts.on("--[no-]unreleased", "Add to log unreleased closed issues. Default is true.") do |v|
122
- options[:unreleased] = v
123
- end
124
- opts.on("--unreleased-label [label]", "Set up custom label for unreleased closed issues section. Default is \"**Unreleased:**\".") do |v|
125
- options[:unreleased_label] = v
126
- end
127
- opts.on("--[no-]compare-link", "Include compare link (Full Changelog) between older version and newer version. Default is true.") do |v|
128
- options[:compare_link] = v
129
- end
130
- opts.on("--include-labels x,y,z", Array, "Of the labeled issues, only include the ones with the specified labels.") do |list|
131
- options[:include_labels] = list
132
- end
133
- opts.on("--exclude-labels x,y,z", Array, "Issues with the specified labels will be excluded from changelog. Default is 'duplicate,question,invalid,wontfix'.") do |list|
134
- options[:exclude_labels] = list
135
- end
136
- opts.on("--summary-labels x,y,z", Array, 'Issues with these labels will be added to a new section, called "Release Summary". The section display only body of issues. Default is \'release-summary,summary\'.') do |list|
137
- options[:summary_labels] = list
138
- end
139
- opts.on("--breaking-labels x,y,z", Array, 'Issues with these labels will be added to a new section, called "Breaking changes". Default is \'backwards-incompatible,breaking\'.') do |list|
140
- options[:breaking_labels] = list
141
- end
142
- opts.on("--enhancement-labels x,y,z", Array, 'Issues with the specified labels will be added to "Implemented enhancements" section. Default is \'enhancement,Enhancement\'.') do |list|
143
- options[:enhancement_labels] = list
144
- end
145
- opts.on("--bug-labels x,y,z", Array, 'Issues with the specified labels will be added to "Fixed bugs" section. Default is \'bug,Bug\'.') do |list|
146
- options[:bug_labels] = list
147
- end
148
- opts.on("--deprecated-labels x,y,z", Array, 'Issues with the specified labels will be added to a section called "Deprecated". Default is \'deprecated,Deprecated\'.') do |list|
149
- options[:deprecated_labels] = list
150
- end
151
- opts.on("--removed-labels x,y,z", Array, 'Issues with the specified labels will be added to a section called "Removed". Default is \'removed,Removed\'.') do |list|
152
- options[:removed_labels] = list
153
- end
154
- opts.on("--security-labels x,y,z", Array, 'Issues with the specified labels will be added to a section called "Security fixes". Default is \'security,Security\'.') do |list|
155
- options[:security_labels] = list
156
- end
157
- opts.on("--issue-line-labels x,y,z", Array, 'The specified labels will be shown in brackets next to each matching issue. Use "ALL" to show all labels. Default is [].') do |list|
158
- options[:issue_line_labels] = list
159
- end
160
- opts.on("--exclude-tags x,y,z", Array, "Changelog will exclude specified tags") do |list|
161
- options[:exclude_tags] = list
162
- end
163
- opts.on("--exclude-tags-regex [REGEX]", "Apply a regular expression on tag names so that they can be excluded, for example: --exclude-tags-regex \".*\+\d{1,}\".") do |last|
164
- options[:exclude_tags_regex] = last
165
- end
166
- opts.on("--since-tag x", "Changelog will start after specified tag.") do |v|
167
- options[:since_tag] = v
168
- end
169
- opts.on("--due-tag x", "Changelog will end before specified tag.") do |v|
170
- options[:due_tag] = v
171
- end
172
- opts.on("--max-issues [NUMBER]", Integer, "Maximum number of issues to fetch from GitHub. Default is unlimited.") do |max|
173
- options[:max_issues] = max
174
- end
175
- opts.on("--release-url [URL]", "The URL to point to for release links, in printf format (with the tag as variable).") do |url|
176
- options[:release_url] = url
177
- end
178
- opts.on("--github-site [URL]", "The Enterprise GitHub site where your project is hosted.") do |last|
179
- options[:github_site] = last
180
- end
181
- opts.on("--github-api [URL]", "The enterprise endpoint to use for your GitHub API.") do |last|
182
- options[:github_endpoint] = last
183
- end
184
- opts.on("--simple-list", "Create a simple list from issues and pull requests. Default is false.") do |v|
185
- options[:simple_list] = v
186
- end
187
- opts.on("--future-release [RELEASE-VERSION]", "Put the unreleased changes in the specified release number.") do |future_release|
188
- options[:future_release] = future_release
189
- end
190
- opts.on("--release-branch [RELEASE-BRANCH]", "Limit pull requests to the release branch, such as master or release.") do |release_branch|
191
- options[:release_branch] = release_branch
192
- end
193
- opts.on("--[no-]http-cache", "Use HTTP Cache to cache GitHub API requests (useful for large repos). Default is true.") do |http_cache|
194
- options[:http_cache] = http_cache
195
- end
196
- opts.on("--cache-file [CACHE-FILE]", "Filename to use for cache. Default is github-changelog-http-cache in a temporary directory.") do |cache_file|
197
- options[:cache_file] = cache_file
198
- end
199
- opts.on("--cache-log [CACHE-LOG]", "Filename to use for cache log. Default is github-changelog-logger.log in a temporary directory.") do |cache_log|
200
- options[:cache_log] = cache_log
201
- end
202
- opts.on("--ssl-ca-file [PATH]", "Path to cacert.pem file. Default is a bundled lib/github_changelog_generator/ssl_certs/cacert.pem. Respects SSL_CA_PATH.") do |ssl_ca_file|
203
- options[:ssl_ca_file] = ssl_ca_file
204
- end
205
- opts.on("--require x,y,z", Array, "Path to Ruby file(s) to require before generating changelog.") do |paths|
206
- options[:require] = paths
207
- end
208
- opts.on("--[no-]verbose", "Run verbosely. Default is true.") do |v|
209
- options[:verbose] = v
210
- end
211
- opts.on("-v", "--version", "Print version number.") do |_v|
212
- puts "Version: #{GitHubChangelogGenerator::VERSION}"
213
- exit
214
- end
215
- opts.on("-h", "--help", "Displays Help.") do
216
- puts opts
217
- exit
218
- end
38
+ Kernel.abort
219
39
  end
220
- end
221
40
 
222
- # @return [Options] Default options
223
- def self.default_options
224
- Options.new(
225
- date_format: "%Y-%m-%d",
226
- output: "CHANGELOG.md",
227
- base: "HISTORY.md",
228
- issues: true,
229
- add_issues_wo_labels: true,
230
- add_pr_wo_labels: true,
231
- pulls: true,
232
- filter_issues_by_milestone: true,
233
- author: true,
234
- unreleased: true,
235
- unreleased_label: "Unreleased",
236
- compare_link: true,
237
- exclude_labels: ["duplicate", "question", "invalid", "wontfix", "Duplicate", "Question", "Invalid", "Wontfix", "Meta: Exclude From Changelog"],
238
- summary_labels: ["Release summary", "release-summary", "Summary", "summary"],
239
- breaking_labels: ["backwards-incompatible", "Backwards incompatible", "breaking"],
240
- enhancement_labels: ["enhancement", "Enhancement", "Type: Enhancement"],
241
- bug_labels: ["bug", "Bug", "Type: Bug"],
242
- deprecated_labels: ["deprecated", "Deprecated", "Type: Deprecated"],
243
- removed_labels: ["removed", "Removed", "Type: Removed"],
244
- security_labels: ["security", "Security", "Type: Security"],
245
- configure_sections: {},
246
- add_sections: {},
247
- issue_line_labels: [],
248
- max_issues: nil,
249
- simple_list: false,
250
- ssl_ca_file: nil,
251
- verbose: true,
252
- header: "# Changelog",
253
- merge_prefix: "**Merged pull requests:**",
254
- issue_prefix: "**Closed issues:**",
255
- summary_prefix: "",
256
- breaking_prefix: "**Breaking changes:**",
257
- enhancement_prefix: "**Implemented enhancements:**",
258
- bug_prefix: "**Fixed bugs:**",
259
- deprecated_prefix: "**Deprecated:**",
260
- removed_prefix: "**Removed:**",
261
- security_prefix: "**Security fixes:**",
262
- http_cache: true,
263
- require: []
264
- )
41
+ # @return [Options] Default options
42
+ def default_options
43
+ Options.new(
44
+ date_format: "%Y-%m-%d",
45
+ output: "CHANGELOG.md",
46
+ base: "HISTORY.md",
47
+ issues: true,
48
+ add_issues_wo_labels: true,
49
+ add_pr_wo_labels: true,
50
+ pulls: true,
51
+ filter_issues_by_milestone: true,
52
+ issues_of_open_milestones: true,
53
+ author: true,
54
+ unreleased: true,
55
+ unreleased_label: "Unreleased",
56
+ compare_link: true,
57
+ exclude_labels: ["duplicate", "question", "invalid", "wontfix", "Duplicate", "Question", "Invalid", "Wontfix", "Meta: Exclude From Changelog"],
58
+ summary_labels: ["Release summary", "release-summary", "Summary", "summary"],
59
+ breaking_labels: ["backwards-incompatible", "Backwards incompatible", "breaking"],
60
+ enhancement_labels: ["enhancement", "Enhancement", "Type: Enhancement"],
61
+ bug_labels: ["bug", "Bug", "Type: Bug"],
62
+ deprecated_labels: ["deprecated", "Deprecated", "Type: Deprecated"],
63
+ removed_labels: ["removed", "Removed", "Type: Removed"],
64
+ security_labels: ["security", "Security", "Type: Security"],
65
+ configure_sections: {},
66
+ add_sections: {},
67
+ issue_line_labels: [],
68
+ max_issues: nil,
69
+ simple_list: false,
70
+ ssl_ca_file: nil,
71
+ verbose: true,
72
+ header: "# Changelog",
73
+ merge_prefix: "**Merged pull requests:**",
74
+ issue_prefix: "**Closed issues:**",
75
+ summary_prefix: "",
76
+ breaking_prefix: "**Breaking changes:**",
77
+ enhancement_prefix: "**Implemented enhancements:**",
78
+ bug_prefix: "**Fixed bugs:**",
79
+ deprecated_prefix: "**Deprecated:**",
80
+ removed_prefix: "**Removed:**",
81
+ security_prefix: "**Security fixes:**",
82
+ http_cache: true,
83
+ require: [],
84
+ config_file: ".github_changelog_generator"
85
+ )
86
+ end
265
87
  end
266
88
  end
267
89
  end
@@ -5,6 +5,28 @@ require "pathname"
5
5
  module GitHubChangelogGenerator
6
6
  ParserError = Class.new(StandardError)
7
7
 
8
+ class FileParserChooser
9
+ def initialize(options)
10
+ @options = options
11
+ @config_file = Pathname.new(options[:config_file])
12
+ end
13
+
14
+ def parse!(_argv)
15
+ return nil unless (path = resolve_path)
16
+
17
+ ParserFile.new(@options, File.open(path)).parse!
18
+ end
19
+
20
+ def resolve_path
21
+ return @config_file if @config_file.exist?
22
+
23
+ path = @config_file.expand_path
24
+ return path if File.exist?(path)
25
+
26
+ nil
27
+ end
28
+ end
29
+
8
30
  # ParserFile is a configuration file reader which sets options in the
9
31
  # given Hash.
10
32
  #
@@ -22,29 +44,22 @@ module GitHubChangelogGenerator
22
44
  #
23
45
  class ParserFile
24
46
  # @param options [Hash] options to be configured from file contents
25
- # @param file [nil,IO] configuration file handle, defaults to opening `.github_changelog_generator`
26
- def initialize(options, file = open_settings_file)
47
+ # @param io [nil, IO] configuration file handle
48
+ def initialize(options, io = nil)
27
49
  @options = options
28
- @file = file
50
+ @io = io
29
51
  end
30
52
 
31
53
  # Sets options using configuration file content
32
54
  def parse!
33
- return unless @file
55
+ return unless @io
34
56
 
35
- @file.each_with_index { |line, i| parse_line!(line, i + 1) }
36
- @file.close
57
+ @io.each_with_index { |line, i| parse_line!(line, i + 1) }
58
+ @io.close
37
59
  end
38
60
 
39
61
  private
40
62
 
41
- FILENAME = ".github_changelog_generator"
42
-
43
- def open_settings_file
44
- path = Pathname(File.expand_path(FILENAME))
45
- File.open(path) if path.exist?
46
- end
47
-
48
63
  def parse_line!(line, line_number)
49
64
  return if non_configuration_line?(line)
50
65
 
@@ -56,7 +71,7 @@ module GitHubChangelogGenerator
56
71
 
57
72
  # Returns true if the line starts with a pound sign or a semi-colon.
58
73
  def non_configuration_line?(line)
59
- line =~ /^[\#;]/ || line =~ /^[\s]+$/
74
+ line =~ /^[\#;]/ || line =~ /^\s+$/
60
75
  end
61
76
 
62
77
  # Returns a the option name as a symbol and its string value sans newlines.
@@ -29,8 +29,8 @@ module GitHubChangelogGenerator
29
29
  defaults = {
30
30
  heading_level: "##",
31
31
  heading_structures: [
32
- /^## \[(?<version>.+?)\]\((?<url>.+?)\)( \((?<date>.+?)\))?$/,
33
- /^## (?<version>.+?)( \((?<date>.+?)\))?$/
32
+ /^## \[(?<version>.+?)\]\((?<url>.+?)\)( \((?<date>.+?)\))?$/, # rubocop:disable Lint/MixedRegexpCaptureTypes
33
+ /^## (?<version>.+?)( \((?<date>.+?)\))?$/ # rubocop:disable Lint/MixedRegexpCaptureTypes
34
34
  ]
35
35
  }
36
36