Merge pull request #587 from eputnam/config_sections
Add option --configure-sections, --add-sections, --include-merged
This commit is contained in:
commit
e2ddb73373
237
lib/github_changelog_generator/generator/entry.rb
Normal file
237
lib/github_changelog_generator/generator/entry.rb
Normal file
|
@ -0,0 +1,237 @@
|
|||
require "github_changelog_generator/generator/section"
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
# This class generates the content for a single changelog entry. An entry is
|
||||
# generally either for a specific tagged release or the collection of
|
||||
# unreleased changes.
|
||||
#
|
||||
# An entry is comprised of header text followed by a series of sections
|
||||
# relating to the entry.
|
||||
#
|
||||
# @see GitHubChangelogGenerator::Generator
|
||||
# @see GitHubChangelogGenerator::Section
|
||||
class Entry
|
||||
attr_reader :content
|
||||
|
||||
def initialize(options = Options.new({}))
|
||||
@content = ""
|
||||
@options = Options.new(options)
|
||||
end
|
||||
|
||||
# Generates log entry with header and body
|
||||
#
|
||||
# @param [Array] pull_requests List or PR's in new section
|
||||
# @param [Array] issues List of issues in new section
|
||||
# @param [String] newer_tag_name Name of the newer tag. Could be nil for `Unreleased` section.
|
||||
# @param [String] newer_tag_link Name of the newer tag. Could be "HEAD" for `Unreleased` section.
|
||||
# @param [Time] newer_tag_time Time of the newer tag
|
||||
# @param [Hash, nil] older_tag Older tag, used for the links. Could be nil for last tag.
|
||||
# @return [String] Ready and parsed section
|
||||
def create_entry_for_tag(pull_requests, issues, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name) # rubocop:disable Metrics/ParameterLists
|
||||
github_site = @options[:github_site] || "https://github.com"
|
||||
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
|
||||
|
||||
set_sections_and_maps
|
||||
|
||||
@content = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
||||
|
||||
@content += generate_body(pull_requests, issues)
|
||||
|
||||
@content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Creates section objects and the label and section maps needed for
|
||||
# sorting
|
||||
def set_sections_and_maps
|
||||
@sections = if @options.configure_sections?
|
||||
parse_sections(@options[:configure_sections])
|
||||
elsif @options.add_sections?
|
||||
default_sections.concat parse_sections(@options[:add_sections])
|
||||
else
|
||||
default_sections
|
||||
end
|
||||
|
||||
@lmap = label_map
|
||||
@smap = section_map
|
||||
end
|
||||
|
||||
# Turns a string from the commandline into an array of Section objects
|
||||
#
|
||||
# @param [String, Hash] either string or hash describing sections
|
||||
# @return [Array] array of Section objects
|
||||
def parse_sections(sections_desc)
|
||||
require "json"
|
||||
|
||||
sections_desc = sections_desc.to_json if sections_desc.class == Hash
|
||||
|
||||
begin
|
||||
sections_json = JSON.parse(sections_desc)
|
||||
rescue JSON::ParserError => e
|
||||
raise "There was a problem parsing your JSON string for sections: #{e}"
|
||||
end
|
||||
|
||||
sections_json.collect do |name, v|
|
||||
Section.new(name: name.to_s, prefix: v["prefix"], labels: v["labels"], options: @options)
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a hash map of labels => section objects
|
||||
#
|
||||
# @return [Hash] map of labels => section objects
|
||||
def label_map
|
||||
@sections.each_with_object({}) do |section_obj, memo|
|
||||
section_obj.labels.each do |label|
|
||||
memo[label] = section_obj.name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a hash map of 'section name' => section object
|
||||
#
|
||||
# @return [Hash] map of 'section name' => section object
|
||||
def section_map
|
||||
@sections.each_with_object({}) do |section, memo|
|
||||
memo[section.name] = section
|
||||
end
|
||||
end
|
||||
|
||||
# It generates header text for an entry with specific parameters.
|
||||
#
|
||||
# @param [String] newer_tag_name - name of newer tag
|
||||
# @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
|
||||
# @param [Time] newer_tag_time - time, when newer tag created
|
||||
# @param [String] older_tag_name - tag name, used for links.
|
||||
# @param [String] project_url - url for current project.
|
||||
# @return [String] - Header text for a changelog entry.
|
||||
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
||||
header = ""
|
||||
|
||||
# Generate date string:
|
||||
time_string = newer_tag_time.strftime(@options[:date_format])
|
||||
|
||||
# Generate tag name and link
|
||||
release_url = if @options[:release_url]
|
||||
format(@options[:release_url], newer_tag_link)
|
||||
else
|
||||
"#{project_url}/tree/#{newer_tag_link}"
|
||||
end
|
||||
header += if newer_tag_name.equal?(@options[:unreleased_label])
|
||||
"## [#{newer_tag_name}](#{release_url})\n\n"
|
||||
else
|
||||
"## [#{newer_tag_name}](#{release_url}) (#{time_string})\n\n"
|
||||
end
|
||||
|
||||
if @options[:compare_link] && older_tag_name
|
||||
# Generate compare link
|
||||
header += "[Full Changelog](#{project_url}/compare/#{older_tag_name}...#{newer_tag_link})\n\n"
|
||||
end
|
||||
|
||||
header
|
||||
end
|
||||
|
||||
# Generates complete body text for a tag (without a header)
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @param [Array] issues
|
||||
# @returns [String] ready-to-go tag body
|
||||
def generate_body(pull_requests, issues)
|
||||
body = ""
|
||||
body += main_sections_to_log(pull_requests, issues)
|
||||
body += merged_section_to_log(pull_requests) if @options[:pulls] && @options[:add_pr_wo_labels]
|
||||
body
|
||||
end
|
||||
|
||||
# Generates main sections for a tag
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @param [Array] issues
|
||||
# @return [string] ready-to-go sub-sections
|
||||
def main_sections_to_log(pull_requests, issues)
|
||||
if @options[:issues]
|
||||
sections_to_log = parse_by_sections(pull_requests, issues)
|
||||
|
||||
sections_to_log.map(&:generate_content).join
|
||||
end
|
||||
end
|
||||
|
||||
# Generates section for prs with no labels (for a tag)
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @return [string] ready-to-go sub-section
|
||||
def merged_section_to_log(pull_requests)
|
||||
merged = Section.new(name: "merged", prefix: @options[:merge_prefix], labels: [], issues: pull_requests, options: @options)
|
||||
@sections << merged unless @sections.find { |section| section.name == "merged" }
|
||||
merged.generate_content
|
||||
end
|
||||
|
||||
# Set of default sections for backwards-compatibility/defaults
|
||||
#
|
||||
# @return [Array] array of Section objects
|
||||
def default_sections
|
||||
[
|
||||
Section.new(name: "breaking", prefix: @options[:breaking_prefix], labels: @options[:breaking_labels], options: @options),
|
||||
Section.new(name: "enhancements", prefix: @options[:enhancement_prefix], labels: @options[:enhancement_labels], options: @options),
|
||||
Section.new(name: "bugs", prefix: @options[:bug_prefix], labels: @options[:bug_labels], options: @options),
|
||||
Section.new(name: "issues", prefix: @options[:issue_prefix], labels: @options[:issue_labels], options: @options)
|
||||
]
|
||||
end
|
||||
|
||||
# This method sorts issues by types
|
||||
# (bugs, features, or just closed issues) by labels
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @param [Array] issues
|
||||
# @return [Hash] Mapping of filtered arrays: (Bugs, Enhancements, Breaking stuff, Issues)
|
||||
def parse_by_sections(pull_requests, issues)
|
||||
issues.each do |dict|
|
||||
added = false
|
||||
|
||||
dict["labels"].each do |label|
|
||||
break if @lmap[label["name"]].nil?
|
||||
@smap[@lmap[label["name"]]].issues << dict
|
||||
added = true
|
||||
|
||||
break if added
|
||||
end
|
||||
if @smap["issues"]
|
||||
@sections.find { |sect| sect.name == "issues" }.issues << dict unless added
|
||||
end
|
||||
end
|
||||
sort_pull_requests(pull_requests)
|
||||
end
|
||||
|
||||
# This method iterates through PRs and sorts them into sections
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @param [Hash] sections
|
||||
# @return [Hash] sections
|
||||
def sort_pull_requests(pull_requests)
|
||||
added_pull_requests = []
|
||||
pull_requests.each do |pr|
|
||||
added = false
|
||||
|
||||
pr["labels"].each do |label|
|
||||
break if @lmap[label["name"]].nil?
|
||||
@smap[@lmap[label["name"]]].issues << pr
|
||||
added_pull_requests << pr
|
||||
added = true
|
||||
|
||||
break if added
|
||||
end
|
||||
end
|
||||
added_pull_requests.each { |req| pull_requests.delete(req) }
|
||||
@sections
|
||||
end
|
||||
|
||||
def line_labels_for(issue)
|
||||
labels = if @options[:issue_line_labels] == ["ALL"]
|
||||
issue["labels"]
|
||||
else
|
||||
issue["labels"].select { |label| @options[:issue_line_labels].include?(label["name"]) }
|
||||
end
|
||||
labels.map { |label| " \[[#{label['name']}](#{label['url'].sub('api.github.com/repos', 'github.com')})\]" }.join("")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "github_changelog_generator/octo_fetcher"
|
||||
require "github_changelog_generator/generator/generator_generation"
|
||||
require "github_changelog_generator/generator/generator_fetcher"
|
||||
require "github_changelog_generator/generator/generator_processor"
|
||||
require "github_changelog_generator/generator/generator_tags"
|
||||
require "github_changelog_generator/generator/entry"
|
||||
require "github_changelog_generator/generator/section"
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
# Default error for ChangelogGenerator
|
||||
class ChangelogGeneratorError < StandardError
|
||||
end
|
||||
|
||||
# This class is the high-level code for gathering issues and PRs for a github
|
||||
# repository and generating a CHANGELOG.md file. A changelog is made up of a
|
||||
# series of "entries" of all tagged releases, plus an extra entry for the
|
||||
# unreleased changes. Entries are made up of various organizational
|
||||
# "sections," and sections contain the github issues and PRs.
|
||||
#
|
||||
# So the changelog contains entries, entries contain sections, and sections
|
||||
# contain issues and PRs.
|
||||
#
|
||||
# @see GitHubChangelogGenerator::Entry
|
||||
# @see GitHubChangelogGenerator::Section
|
||||
class Generator
|
||||
attr_accessor :options, :filtered_tags, :github, :tag_section_mapping, :sorted_tags
|
||||
attr_accessor :options, :filtered_tags, :tag_section_mapping, :sorted_tags
|
||||
|
||||
# A Generator responsible for all logic, related with changelog generation from ready-to-parse issues
|
||||
#
|
||||
|
@ -23,6 +35,104 @@ module GitHubChangelogGenerator
|
|||
@options = options
|
||||
@tag_times_hash = {}
|
||||
@fetcher = GitHubChangelogGenerator::OctoFetcher.new(options)
|
||||
@sections = []
|
||||
end
|
||||
|
||||
# Main function to start changelog generation
|
||||
#
|
||||
# @return [String] Generated changelog file
|
||||
def compound_changelog
|
||||
options.load_custom_ruby_files
|
||||
fetch_and_filter_tags
|
||||
fetch_issues_and_pr
|
||||
|
||||
log = ""
|
||||
log += options[:frontmatter] if options[:frontmatter]
|
||||
log += "#{options[:header]}\n\n"
|
||||
|
||||
log += if options[:unreleased_only]
|
||||
generate_entry_between_tags(filtered_tags[0], nil)
|
||||
else
|
||||
generate_entries_for_all_tags
|
||||
end
|
||||
|
||||
log += File.read(options[:base]) if File.file?(options[:base])
|
||||
|
||||
credit_line = "\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
|
||||
log.gsub!(credit_line, "") # Remove old credit lines
|
||||
log += credit_line
|
||||
|
||||
@log = log
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Generate log only between 2 specified tags
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
def generate_entry_between_tags(older_tag, newer_tag)
|
||||
filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
|
||||
|
||||
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
|
||||
# do not generate empty unreleased section
|
||||
return ""
|
||||
end
|
||||
|
||||
newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
|
||||
|
||||
# If the older tag is nil, go back in time from the latest tag and find
|
||||
# the SHA for the first commit.
|
||||
older_tag_name =
|
||||
if older_tag.nil?
|
||||
@fetcher.commits_before(newer_tag_time).last["sha"]
|
||||
else
|
||||
older_tag["name"]
|
||||
end
|
||||
|
||||
Entry.new(options).create_entry_for_tag(filtered_pull_requests, filtered_issues, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name)
|
||||
end
|
||||
|
||||
# Filters issues and pull requests based on, respectively, `closed_at` and `merged_at`
|
||||
# timestamp fields.
|
||||
#
|
||||
# @return [Array] filtered issues and pull requests
|
||||
def filter_issues_for_tags(newer_tag, older_tag)
|
||||
filtered_pull_requests = delete_by_time(@pull_requests, "merged_at", older_tag, newer_tag)
|
||||
filtered_issues = delete_by_time(@issues, "closed_at", older_tag, newer_tag)
|
||||
|
||||
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
||||
|
||||
if options[:filter_issues_by_milestone]
|
||||
# delete excess irrelevant issues (according milestones). Issue #22.
|
||||
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
||||
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
||||
end
|
||||
[filtered_issues, filtered_pull_requests]
|
||||
end
|
||||
|
||||
# The full cycle of generation for whole project
|
||||
# @return [String] All entries in the changelog
|
||||
def generate_entries_for_all_tags
|
||||
puts "Generating entry..." if options[:verbose]
|
||||
|
||||
entries = generate_unreleased_entry
|
||||
|
||||
@tag_section_mapping.each_pair do |_tag_section, left_right_tags|
|
||||
older_tag, newer_tag = left_right_tags
|
||||
entries += generate_entry_between_tags(older_tag, newer_tag)
|
||||
end
|
||||
|
||||
entries
|
||||
end
|
||||
|
||||
def generate_unreleased_entry
|
||||
entry = ""
|
||||
if options[:unreleased]
|
||||
start_tag = filtered_tags[0] || sorted_tags.last
|
||||
unreleased_entry = generate_entry_between_tags(start_tag, nil)
|
||||
entry += unreleased_entry if unreleased_entry
|
||||
end
|
||||
entry
|
||||
end
|
||||
|
||||
def fetch_issues_and_pr
|
||||
|
@ -35,144 +145,5 @@ module GitHubChangelogGenerator
|
|||
fetch_events_for_issues_and_pr
|
||||
detect_actual_closed_dates(@issues + @pull_requests)
|
||||
end
|
||||
|
||||
ENCAPSULATED_CHARACTERS = %w(< > * _ \( \) [ ] #)
|
||||
|
||||
# Encapsulate characters to make Markdown look as expected.
|
||||
#
|
||||
# @param [String] string
|
||||
# @return [String] encapsulated input string
|
||||
def encapsulate_string(string)
|
||||
string = string.gsub('\\', '\\\\')
|
||||
|
||||
ENCAPSULATED_CHARACTERS.each do |char|
|
||||
string = string.gsub(char, "\\#{char}")
|
||||
end
|
||||
|
||||
string
|
||||
end
|
||||
|
||||
# Generates log for section with header and body
|
||||
#
|
||||
# @param [Array] pull_requests List or PR's in new section
|
||||
# @param [Array] issues List of issues in new section
|
||||
# @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
|
||||
# @param [Hash, nil] older_tag Older tag, used for the links. Could be nil for last tag.
|
||||
# @return [String] Ready and parsed section
|
||||
def create_log_for_tag(pull_requests, issues, newer_tag, older_tag = nil)
|
||||
newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
|
||||
|
||||
github_site = options[:github_site] || "https://github.com"
|
||||
project_url = "#{github_site}/#{options[:user]}/#{options[:project]}"
|
||||
|
||||
# If the older tag is nil, go back in time from the latest tag and find
|
||||
# the SHA for the first commit.
|
||||
older_tag_name =
|
||||
if older_tag.nil?
|
||||
@fetcher.commits_before(newer_tag_time).last["sha"]
|
||||
else
|
||||
older_tag["name"]
|
||||
end
|
||||
|
||||
log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
||||
|
||||
if options[:issues]
|
||||
# Generate issues:
|
||||
log += issues_to_log(issues, pull_requests)
|
||||
end
|
||||
|
||||
if options[:pulls] && options[:add_pr_wo_labels]
|
||||
# Generate pull requests:
|
||||
log += generate_sub_section(pull_requests, options[:merge_prefix])
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Generate ready-to-paste log from list of issues and pull requests.
|
||||
#
|
||||
# @param [Array] issues
|
||||
# @param [Array] pull_requests
|
||||
# @return [String] generated log for issues
|
||||
def issues_to_log(issues, pull_requests)
|
||||
sections = parse_by_sections(issues, pull_requests)
|
||||
|
||||
log = ""
|
||||
log += generate_sub_section(sections[:breaking], options[:breaking_prefix])
|
||||
log += generate_sub_section(sections[:enhancements], options[:enhancement_prefix])
|
||||
log += generate_sub_section(sections[:bugs], options[:bug_prefix])
|
||||
log += generate_sub_section(sections[:issues], options[:issue_prefix])
|
||||
log
|
||||
end
|
||||
|
||||
# This method sort issues by types
|
||||
# (bugs, features, or just closed issues) by labels
|
||||
#
|
||||
# @param [Array] issues
|
||||
# @param [Array] pull_requests
|
||||
# @return [Hash] Mapping of filtered arrays: (Bugs, Enhancements, Breaking stuff, Issues)
|
||||
def parse_by_sections(issues, pull_requests)
|
||||
sections = {
|
||||
issues: [],
|
||||
enhancements: [],
|
||||
bugs: [],
|
||||
breaking: []
|
||||
}
|
||||
|
||||
issues.each do |dict|
|
||||
added = false
|
||||
|
||||
dict["labels"].each do |label|
|
||||
if options[:bug_labels].include?(label["name"])
|
||||
sections[:bugs] << dict
|
||||
added = true
|
||||
elsif options[:enhancement_labels].include?(label["name"])
|
||||
sections[:enhancements] << dict
|
||||
added = true
|
||||
elsif options[:breaking_labels].include?(label["name"])
|
||||
sections[:breaking] << dict
|
||||
added = true
|
||||
end
|
||||
|
||||
break if added
|
||||
end
|
||||
|
||||
sections[:issues] << dict unless added
|
||||
end
|
||||
|
||||
sort_pull_requests(pull_requests, sections)
|
||||
end
|
||||
|
||||
# This method iterates through PRs and sorts them into sections
|
||||
#
|
||||
# @param [Array] pull_requests
|
||||
# @param [Hash] sections
|
||||
# @return [Hash] sections
|
||||
def sort_pull_requests(pull_requests, sections)
|
||||
added_pull_requests = []
|
||||
pull_requests.each do |pr|
|
||||
added = false
|
||||
|
||||
pr["labels"].each do |label|
|
||||
if options[:bug_labels].include?(label["name"])
|
||||
sections[:bugs] << pr
|
||||
added_pull_requests << pr
|
||||
added = true
|
||||
elsif options[:enhancement_labels].include?(label["name"])
|
||||
sections[:enhancements] << pr
|
||||
added_pull_requests << pr
|
||||
added = true
|
||||
elsif options[:breaking_labels].include?(label["name"])
|
||||
sections[:breaking] << pr
|
||||
added_pull_requests << pr
|
||||
added = true
|
||||
end
|
||||
|
||||
break if added
|
||||
end
|
||||
end
|
||||
added_pull_requests.each { |p| pull_requests.delete(p) }
|
||||
sections
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
# Main function to start changelog generation
|
||||
#
|
||||
# @return [String] Generated changelog file
|
||||
def compound_changelog
|
||||
options.load_custom_ruby_files
|
||||
fetch_and_filter_tags
|
||||
fetch_issues_and_pr
|
||||
|
||||
log = ""
|
||||
log += options[:frontmatter] if options[:frontmatter]
|
||||
log += "#{options[:header]}\n\n"
|
||||
|
||||
log += if options[:unreleased_only]
|
||||
generate_log_between_tags(filtered_tags[0], nil)
|
||||
else
|
||||
generate_log_for_all_tags
|
||||
end
|
||||
|
||||
log += File.read(options[:base]) if File.file?(options[:base])
|
||||
|
||||
credit_line = "\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
|
||||
log.gsub!(credit_line, "") # Remove old credit lines
|
||||
log += credit_line
|
||||
|
||||
@log = log
|
||||
end
|
||||
|
||||
# @param [Array] issues List of issues on sub-section
|
||||
# @param [String] prefix Name of sub-section
|
||||
# @return [String] Generate ready-to-go sub-section
|
||||
def generate_sub_section(issues, prefix)
|
||||
log = ""
|
||||
|
||||
if issues.any?
|
||||
log += "#{prefix}\n\n" unless options[:simple_list]
|
||||
issues.each do |issue|
|
||||
merge_string = get_string_for_issue(issue)
|
||||
log += "- #{merge_string}\n"
|
||||
end
|
||||
log += "\n"
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# It generate one header for section with specific parameters.
|
||||
#
|
||||
# @param [String] newer_tag_name - name of newer tag
|
||||
# @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
|
||||
# @param [Time] newer_tag_time - time, when newer tag created
|
||||
# @param [String] older_tag_link - tag name, used for links.
|
||||
# @param [String] project_url - url for current project.
|
||||
# @return [String] - Generate one ready-to-add section.
|
||||
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_link, project_url)
|
||||
log = ""
|
||||
|
||||
# Generate date string:
|
||||
time_string = newer_tag_time.strftime(options[:date_format])
|
||||
|
||||
# Generate tag name and link
|
||||
release_url = if options[:release_url]
|
||||
format(options[:release_url], newer_tag_link)
|
||||
else
|
||||
"#{project_url}/tree/#{newer_tag_link}"
|
||||
end
|
||||
log += if newer_tag_name.equal?(options[:unreleased_label])
|
||||
"## [#{newer_tag_name}](#{release_url})\n\n"
|
||||
else
|
||||
"## [#{newer_tag_name}](#{release_url}) (#{time_string})\n\n"
|
||||
end
|
||||
|
||||
if options[:compare_link] && older_tag_link
|
||||
# Generate compare link
|
||||
log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n"
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Generate log only between 2 specified tags
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
def generate_log_between_tags(older_tag, newer_tag)
|
||||
filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
|
||||
|
||||
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
|
||||
# do not generate empty unreleased section
|
||||
return ""
|
||||
end
|
||||
|
||||
create_log_for_tag(filtered_pull_requests, filtered_issues, newer_tag, older_tag)
|
||||
end
|
||||
|
||||
# Filters issues and pull requests based on, respectively, `closed_at` and `merged_at`
|
||||
# timestamp fields.
|
||||
#
|
||||
# @return [Array] filtered issues and pull requests
|
||||
def filter_issues_for_tags(newer_tag, older_tag)
|
||||
filtered_pull_requests = delete_by_time(@pull_requests, "merged_at", older_tag, newer_tag)
|
||||
filtered_issues = delete_by_time(@issues, "closed_at", older_tag, newer_tag)
|
||||
|
||||
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
||||
|
||||
if options[:filter_issues_by_milestone]
|
||||
# delete excess irrelevant issues (according milestones). Issue #22.
|
||||
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
||||
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
||||
end
|
||||
[filtered_issues, filtered_pull_requests]
|
||||
end
|
||||
|
||||
# The full cycle of generation for whole project
|
||||
# @return [String] The complete changelog
|
||||
def generate_log_for_all_tags
|
||||
puts "Generating log..." if options[:verbose]
|
||||
|
||||
log = generate_unreleased_section
|
||||
|
||||
@tag_section_mapping.each_pair do |_tag_section, left_right_tags|
|
||||
older_tag, newer_tag = left_right_tags
|
||||
log += generate_log_between_tags(older_tag, newer_tag)
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
def generate_unreleased_section
|
||||
log = ""
|
||||
if options[:unreleased]
|
||||
start_tag = filtered_tags[0] || sorted_tags.last
|
||||
unreleased_log = generate_log_between_tags(start_tag, nil)
|
||||
log += unreleased_log if unreleased_log
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# Parse issue and generate single line formatted issue line.
|
||||
#
|
||||
# Example output:
|
||||
# - Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) (@skywinder)
|
||||
#
|
||||
# @param [Hash] issue Fetched issue from GitHub
|
||||
# @return [String] Markdown-formatted single issue
|
||||
def get_string_for_issue(issue)
|
||||
encapsulated_title = encapsulate_string issue["title"]
|
||||
|
||||
title_with_number = "#{encapsulated_title} [\\##{issue['number']}](#{issue['html_url']})"
|
||||
if options[:issue_line_labels].present?
|
||||
title_with_number = "#{title_with_number}#{line_labels_for(issue)}"
|
||||
end
|
||||
issue_line_with_user(title_with_number, issue)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def line_labels_for(issue)
|
||||
labels = if options[:issue_line_labels] == ["ALL"]
|
||||
issue["labels"]
|
||||
else
|
||||
issue["labels"].select { |label| options[:issue_line_labels].include?(label["name"]) }
|
||||
end
|
||||
labels.map { |label| " \[[#{label['name']}](#{label['url'].sub('api.github.com/repos', 'github.com')})\]" }.join("")
|
||||
end
|
||||
|
||||
def issue_line_with_user(line, issue)
|
||||
return line if !options[:author] || issue["pull_request"].nil?
|
||||
|
||||
user = issue["user"]
|
||||
return "#{line} ({Null user})" unless user
|
||||
|
||||
if options[:usernames_as_github_logins]
|
||||
"#{line} (@#{user['login']})"
|
||||
else
|
||||
"#{line} ([#{user['login']}](#{user['html_url']}))"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
83
lib/github_changelog_generator/generator/section.rb
Normal file
83
lib/github_changelog_generator/generator/section.rb
Normal file
|
@ -0,0 +1,83 @@
|
|||
module GitHubChangelogGenerator
|
||||
# This class generates the content for a single section of a changelog entry.
|
||||
# It turns the tagged issues and PRs into a well-formatted list of changes to
|
||||
# be later incorporated into a changelog entry.
|
||||
#
|
||||
# @see GitHubChangelogGenerator::Entry
|
||||
class Section
|
||||
attr_accessor :name, :prefix, :issues, :labels
|
||||
|
||||
def initialize(opts = {})
|
||||
@name = opts[:name]
|
||||
@prefix = opts[:prefix]
|
||||
@labels = opts[:labels] || []
|
||||
@issues = opts[:issues] || []
|
||||
@options = opts[:options] || Options.new({})
|
||||
end
|
||||
|
||||
# @param [Array] issues List of issues on sub-section
|
||||
# @param [String] prefix Name of sub-section
|
||||
# @return [String] Generate section content
|
||||
def generate_content
|
||||
content = ""
|
||||
|
||||
if @issues.any?
|
||||
content += "#{@prefix}\n\n" unless @options[:simple_list]
|
||||
@issues.each do |issue|
|
||||
merge_string = get_string_for_issue(issue)
|
||||
content += "- #{merge_string}\n"
|
||||
end
|
||||
content += "\n"
|
||||
end
|
||||
content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Parse issue and generate single line formatted issue line.
|
||||
#
|
||||
# Example output:
|
||||
# - Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) (@skywinder)
|
||||
#
|
||||
# @param [Hash] issue Fetched issue from GitHub
|
||||
# @return [String] Markdown-formatted single issue
|
||||
def get_string_for_issue(issue)
|
||||
encapsulated_title = encapsulate_string issue["title"]
|
||||
|
||||
title_with_number = "#{encapsulated_title} [\\##{issue['number']}](#{issue['html_url']})"
|
||||
if @options[:issue_line_labels].present?
|
||||
title_with_number = "#{title_with_number}#{line_labels_for(issue)}"
|
||||
end
|
||||
issue_line_with_user(title_with_number, issue)
|
||||
end
|
||||
|
||||
def issue_line_with_user(line, issue)
|
||||
return line if !@options[:author] || issue["pull_request"].nil?
|
||||
|
||||
user = issue["user"]
|
||||
return "#{line} ({Null user})" unless user
|
||||
|
||||
if @options[:usernames_as_github_logins]
|
||||
"#{line} (@#{user['login']})"
|
||||
else
|
||||
"#{line} ([#{user['login']}](#{user['html_url']}))"
|
||||
end
|
||||
end
|
||||
|
||||
ENCAPSULATED_CHARACTERS = %w(< > * _ \( \) [ ] #)
|
||||
|
||||
# Encapsulate characters to make Markdown look as expected.
|
||||
#
|
||||
# @param [String] string
|
||||
# @return [String] encapsulated input string
|
||||
def encapsulate_string(string)
|
||||
string = string.gsub('\\', '\\\\')
|
||||
|
||||
ENCAPSULATED_CHARACTERS.each do |char|
|
||||
string = string.gsub(char, "\\#{char}")
|
||||
end
|
||||
|
||||
string
|
||||
end
|
||||
end
|
||||
end
|
|
@ -15,6 +15,7 @@ module GitHubChangelogGenerator
|
|||
KNOWN_OPTIONS = %i[
|
||||
add_issues_wo_labels
|
||||
add_pr_wo_labels
|
||||
add_sections
|
||||
author
|
||||
base
|
||||
between_tags
|
||||
|
@ -29,6 +30,7 @@ module GitHubChangelogGenerator
|
|||
enhancement_prefix
|
||||
breaking_labels
|
||||
breaking_prefix
|
||||
configure_sections
|
||||
exclude_labels
|
||||
exclude_tags
|
||||
exclude_tags_regex
|
||||
|
@ -100,6 +102,16 @@ module GitHubChangelogGenerator
|
|||
puts ""
|
||||
end
|
||||
|
||||
# Boolean method for whether the user is using configure_sections
|
||||
def configure_sections?
|
||||
!self[:configure_sections].nil? && !self[:configure_sections].empty?
|
||||
end
|
||||
|
||||
# Boolean method for whether the user is using add_sections
|
||||
def add_sections?
|
||||
!self[:add_sections].nil? && !self[:add_sections].empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def values
|
||||
|
|
|
@ -71,6 +71,12 @@ module GitHubChangelogGenerator
|
|||
opts.on("--header-label [LABEL]", "Setup custom header label. Default is \"# Changelog\"") do |v|
|
||||
options[:header] = v
|
||||
end
|
||||
opts.on("--configure-sections [Hash, String]", "Define your own set of sections which overrides all default sections") do |v|
|
||||
options[:configure_sections] = v
|
||||
end
|
||||
opts.on("--add-sections [Hash, String]", "Add new sections but keep the default sections") do |v|
|
||||
options[:add_sections] = v
|
||||
end
|
||||
opts.on("--front-matter [JSON]", "Add YAML front matter. Formatted as JSON because it's easier to add on the command line") do |v|
|
||||
options[:frontmatter] = JSON.parse(v).to_yaml + "---\n"
|
||||
end
|
||||
|
@ -209,6 +215,8 @@ module GitHubChangelogGenerator
|
|||
bug_labels: ["bug", "Bug", "Type: Bug"],
|
||||
exclude_labels: ["duplicate", "question", "invalid", "wontfix", "Duplicate", "Question", "Invalid", "Wontfix", "Meta: Exclude From Changelog"],
|
||||
breaking_labels: %w[backwards-incompatible breaking],
|
||||
configure_sections: {},
|
||||
add_sections: {},
|
||||
issue_line_labels: [],
|
||||
max_issues: nil,
|
||||
simple_list: false,
|
||||
|
|
|
@ -10,7 +10,6 @@ module GitHubChangelogGenerator
|
|||
|
||||
OPTIONS = %w[ user project token date_format output
|
||||
bug_prefix enhancement_prefix issue_prefix
|
||||
breaking_labels issue_line_labels
|
||||
header merge_prefix issues
|
||||
add_issues_wo_labels add_pr_wo_labels
|
||||
pulls filter_issues_by_milestone author
|
||||
|
@ -20,7 +19,7 @@ module GitHubChangelogGenerator
|
|||
between_tags exclude_tags exclude_tags_regex since_tag max_issues
|
||||
github_site github_endpoint simple_list
|
||||
future_release release_branch verbose release_url
|
||||
base ]
|
||||
base configure_sections add_sections]
|
||||
|
||||
OPTIONS.each do |o|
|
||||
attr_accessor o.to_sym
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.\" generated with Ronn/v0.7.3
|
||||
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
||||
.
|
||||
.TH "GIT\-GENERATE\-CHANGELOG" "1" "October 2017" "" ""
|
||||
.TH "GIT\-GENERATE\-CHANGELOG" "1" "December 2017" "" ""
|
||||
.
|
||||
.SH "NAME"
|
||||
\fBgit\-generate\-changelog\fR \- Generate changelog from github
|
||||
|
@ -284,6 +284,18 @@ Paths to Ruby file(s) to require before generating changelog\.
|
|||
Run verbosely\. Default is true
|
||||
.
|
||||
.P
|
||||
\-\-configure\-sections [HASH, STRING]
|
||||
.
|
||||
.P
|
||||
Define your own set of sections which overrides all default sections") do |v|
|
||||
.
|
||||
.P
|
||||
\-\-add\-sections [HASH, STRING]
|
||||
.
|
||||
.P
|
||||
Add new sections but keep the default sections"
|
||||
.
|
||||
.P
|
||||
\-v, \-\-version
|
||||
.
|
||||
.P
|
||||
|
|
|
@ -261,6 +261,14 @@
|
|||
|
||||
<p> Run verbosely. Default is true</p>
|
||||
|
||||
<p> --configure-sections [HASH, STRING]</p>
|
||||
|
||||
<p> Define your own set of sections which overrides all default sections") do |v|</p>
|
||||
|
||||
<p> --add-sections [HASH, STRING]</p>
|
||||
|
||||
<p> Add new sections but keep the default sections"</p>
|
||||
|
||||
<p> -v, --version</p>
|
||||
|
||||
<p> Print version number</p>
|
||||
|
@ -286,7 +294,7 @@
|
|||
|
||||
<ol class='man-decor man-foot man foot'>
|
||||
<li class='tl'></li>
|
||||
<li class='tc'>October 2017</li>
|
||||
<li class='tc'>December 2017</li>
|
||||
<li class='tr'>git-generate-changelog(1)</li>
|
||||
</ol>
|
||||
|
||||
|
|
|
@ -220,6 +220,18 @@
|
|||
|
||||
<p> Put the unreleased changes in the specified release number.</p>
|
||||
|
||||
<p> --configure-sections [HASH, STRING]</p>
|
||||
|
||||
<p> Define your own set of sections which overrides all default sections") do |v|</p>
|
||||
|
||||
<p> --add-sections [HASH, STRING]</p>
|
||||
|
||||
<p> Add new sections but keep the default sections"</p>
|
||||
|
||||
<p> --include-merged</p>
|
||||
|
||||
<p> If configure_sections is set, use this to restore the merged pull requests sections</p>
|
||||
|
||||
<p> --[no-]verbose</p>
|
||||
|
||||
<p> Run verbosely. Default is true</p>
|
||||
|
|
|
@ -187,6 +187,14 @@ Automatically generate changelog from your tags, issues, labels and pull request
|
|||
|
||||
Run verbosely. Default is true
|
||||
|
||||
--configure-sections [HASH, STRING]
|
||||
|
||||
Define your own set of sections which overrides all default sections") do |v|
|
||||
|
||||
--add-sections [HASH, STRING]
|
||||
|
||||
Add new sections but keep the default sections"
|
||||
|
||||
-v, --version
|
||||
|
||||
Print version number
|
||||
|
|
363
spec/unit/generator/entry_spec.rb
Normal file
363
spec/unit/generator/entry_spec.rb
Normal file
|
@ -0,0 +1,363 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Metrics/ModuleLength
|
||||
module GitHubChangelogGenerator
|
||||
RSpec.describe Entry do
|
||||
def label(name)
|
||||
{ "name" => name }
|
||||
end
|
||||
|
||||
def issue(title, labels, number = "1", user = { "login" => "user" })
|
||||
{
|
||||
"title" => "issue #{title}",
|
||||
"labels" => labels.map { |l| label(l) },
|
||||
"number" => number,
|
||||
"html_url" => "https://github.com/owner/repo/issue/#{number}",
|
||||
"user" => user
|
||||
}
|
||||
end
|
||||
|
||||
def pr(title, labels, number = "1", user = { "login" => "user" })
|
||||
{
|
||||
"pull_request" => true,
|
||||
"title" => "pr #{title}",
|
||||
"labels" => labels.map { |l| label(l) },
|
||||
"number" => number,
|
||||
"html_url" => "https://github.com/owner/repo/pull/#{number}",
|
||||
"user" => user.merge("html_url" => "https://github.com/#{user['login']}")
|
||||
}
|
||||
end
|
||||
|
||||
def titles_for(issues)
|
||||
issues.map { |issue| issue["title"] }
|
||||
end
|
||||
|
||||
def default_sections
|
||||
%w[enhancements bugs breaking issues]
|
||||
end
|
||||
|
||||
describe "#create_entry_for_tag" do
|
||||
let(:options) do
|
||||
Parser.default_options.merge(
|
||||
user: "owner",
|
||||
project: "repo",
|
||||
bug_labels: ["bug"],
|
||||
enhancement_labels: ["enhancement"],
|
||||
breaking_labels: ["breaking"]
|
||||
)
|
||||
end
|
||||
|
||||
let(:issues) do
|
||||
[
|
||||
issue("no labels", [], "5", "login" => "user1"),
|
||||
issue("enhancement", ["enhancement"], "6", "login" => "user2"),
|
||||
issue("bug", ["bug"], "7", "login" => "user1"),
|
||||
issue("breaking", ["breaking"], "8", "login" => "user5"),
|
||||
issue("all the labels", %w[enhancement bug breaking], "9", "login" => "user9")
|
||||
]
|
||||
end
|
||||
|
||||
let(:pull_requests) do
|
||||
[
|
||||
pr("no labels", [], "10", "login" => "user1"),
|
||||
pr("enhancement", ["enhancement"], "11", "login" => "user5"),
|
||||
pr("bug", ["bug"], "12", "login" => "user5"),
|
||||
pr("breaking", ["breaking"], "13", "login" => "user5"),
|
||||
pr("all the labels", %w[enhancement bug breaking], "14", "login" => "user5")
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.new(options) }
|
||||
|
||||
it "generates a header and body" do
|
||||
expect(subject.create_entry_for_tag(pull_requests, issues, "1.0.1", "1.0.1", Time.new(2017, 12, 4), "1.0.0")).to eq(<<-CHANGELOG.gsub(/^ {8}/, "")
|
||||
## [1.0.1](https://github.com/owner/repo/tree/1.0.1) (2017-12-04)
|
||||
|
||||
[Full Changelog](https://github.com/owner/repo/compare/1.0.0...1.0.1)
|
||||
|
||||
**Breaking changes:**
|
||||
|
||||
- issue breaking [\\#8](https://github.com/owner/repo/issue/8)
|
||||
- pr breaking [\\#13](https://github.com/owner/repo/pull/13) ([user5](https://github.com/user5))
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- issue enhancement [\\#6](https://github.com/owner/repo/issue/6)
|
||||
- issue all the labels [\\#9](https://github.com/owner/repo/issue/9)
|
||||
- pr enhancement [\\#11](https://github.com/owner/repo/pull/11) ([user5](https://github.com/user5))
|
||||
- pr all the labels [\\#14](https://github.com/owner/repo/pull/14) ([user5](https://github.com/user5))
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- issue bug [\\#7](https://github.com/owner/repo/issue/7)
|
||||
- pr bug [\\#12](https://github.com/owner/repo/pull/12) ([user5](https://github.com/user5))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- issue no labels [\\#5](https://github.com/owner/repo/issue/5)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- pr no labels [\\#10](https://github.com/owner/repo/pull/10) ([user1](https://github.com/user1))
|
||||
|
||||
CHANGELOG
|
||||
)
|
||||
end
|
||||
end
|
||||
describe "#parse_sections" do
|
||||
before do
|
||||
subject { described_class.new }
|
||||
end
|
||||
context "valid json" do
|
||||
let(:sections_string) { "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}" }
|
||||
|
||||
let(:sections_array) do
|
||||
[
|
||||
Section.new(name: "foo", prefix: "foofix", labels: %w[test1 test2]),
|
||||
Section.new(name: "bar", prefix: "barfix", labels: %w[test3 test4])
|
||||
]
|
||||
end
|
||||
|
||||
it "returns an array with 2 objects" do
|
||||
arr = subject.send(:parse_sections, sections_string)
|
||||
expect(arr.size).to eq 2
|
||||
arr.each { |section| expect(section).to be_an_instance_of Section }
|
||||
end
|
||||
|
||||
it "returns correctly constructed sections" do
|
||||
require "json"
|
||||
|
||||
sections_json = JSON.parse(sections_string)
|
||||
sections_array.each_index do |i|
|
||||
aggregate_failures "checks each component" do
|
||||
expect(sections_array[i].name).to eq sections_json.first[0]
|
||||
expect(sections_array[i].prefix).to eq sections_json.first[1]["prefix"]
|
||||
expect(sections_array[i].labels).to eq sections_json.first[1]["labels"]
|
||||
expect(sections_array[i].issues).to eq []
|
||||
end
|
||||
sections_json.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
context "hash" do
|
||||
let(:sections_hash) do
|
||||
{
|
||||
enhancements: {
|
||||
prefix: "**Enhancements**",
|
||||
labels: %w[feature enhancement]
|
||||
},
|
||||
breaking: {
|
||||
prefix: "**Breaking**",
|
||||
labels: ["breaking"]
|
||||
},
|
||||
bugs: {
|
||||
prefix: "**Bugs**",
|
||||
labels: ["bug"]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:sections_array) do
|
||||
[
|
||||
Section.new(name: "enhancements", prefix: "**Enhancements**", labels: %w[feature enhancement]),
|
||||
Section.new(name: "breaking", prefix: "**Breaking**", labels: ["breaking"]),
|
||||
Section.new(name: "bugs", prefix: "**Bugs**", labels: ["bug"])
|
||||
]
|
||||
end
|
||||
|
||||
it "returns an array with 3 objects" do
|
||||
arr = subject.send(:parse_sections, sections_hash)
|
||||
expect(arr.size).to eq 3
|
||||
arr.each { |section| expect(section).to be_an_instance_of Section }
|
||||
end
|
||||
|
||||
it "returns correctly constructed sections" do
|
||||
sections_array.each_index do |i|
|
||||
aggregate_failures "checks each component" do
|
||||
expect(sections_array[i].name).to eq sections_hash.first[0].to_s
|
||||
expect(sections_array[i].prefix).to eq sections_hash.first[1][:prefix]
|
||||
expect(sections_array[i].labels).to eq sections_hash.first[1][:labels]
|
||||
expect(sections_array[i].issues).to eq []
|
||||
end
|
||||
sections_hash.shift
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse_by_sections" do
|
||||
context "default sections" do
|
||||
let(:options) do
|
||||
{
|
||||
bug_labels: ["bug"],
|
||||
enhancement_labels: ["enhancement"],
|
||||
breaking_labels: ["breaking"]
|
||||
}
|
||||
end
|
||||
|
||||
let(:issues) do
|
||||
[
|
||||
issue("no labels", []),
|
||||
issue("enhancement", ["enhancement"]),
|
||||
issue("bug", ["bug"]),
|
||||
issue("breaking", ["breaking"]),
|
||||
issue("all the labels", %w[enhancement bug breaking])
|
||||
]
|
||||
end
|
||||
|
||||
let(:pull_requests) do
|
||||
[
|
||||
pr("no labels", []),
|
||||
pr("enhancement", ["enhancement"]),
|
||||
pr("bug", ["bug"]),
|
||||
pr("breaking", ["breaking"]),
|
||||
pr("all the labels", %w[enhancement bug breaking])
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.new(options) }
|
||||
|
||||
before do
|
||||
subject.send(:set_sections_and_maps)
|
||||
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
||||
end
|
||||
|
||||
it "returns 4 sections" do
|
||||
expect(@arr.size).to eq 4
|
||||
end
|
||||
|
||||
it "returns default sections" do
|
||||
default_sections.each { |default_section| expect(@arr.select { |section| section.name == default_section }.size).to eq 1 }
|
||||
end
|
||||
|
||||
it "assigns issues to the correct sections" do
|
||||
breaking_section = @arr.select { |section| section.name == "breaking" }[0]
|
||||
enhancement_section = @arr.select { |section| section.name == "enhancements" }[0]
|
||||
issue_section = @arr.select { |section| section.name == "issues" }[0]
|
||||
bug_section = @arr.select { |section| section.name == "bugs" }[0]
|
||||
|
||||
expect(titles_for(breaking_section.issues)).to eq(["issue breaking", "pr breaking"])
|
||||
expect(titles_for(enhancement_section.issues)).to eq(["issue enhancement", "issue all the labels", "pr enhancement", "pr all the labels"])
|
||||
expect(titles_for(issue_section.issues)).to eq(["issue no labels"])
|
||||
expect(titles_for(bug_section.issues)).to eq(["issue bug", "pr bug"])
|
||||
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
||||
end
|
||||
end
|
||||
context "configure sections" do
|
||||
let(:options) do
|
||||
{
|
||||
configure_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}"
|
||||
}
|
||||
end
|
||||
|
||||
let(:issues) do
|
||||
[
|
||||
issue("no labels", []),
|
||||
issue("test1", ["test1"]),
|
||||
issue("test3", ["test3"]),
|
||||
issue("test4", ["test4"]),
|
||||
issue("all the labels", %w[test1 test2 test3 test4])
|
||||
]
|
||||
end
|
||||
|
||||
let(:pull_requests) do
|
||||
[
|
||||
pr("no labels", []),
|
||||
pr("test1", ["test1"]),
|
||||
pr("test3", ["test3"]),
|
||||
pr("test4", ["test4"]),
|
||||
pr("all the labels", %w[test1 test2 test3 test4])
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.new(options) }
|
||||
|
||||
before do
|
||||
subject.send(:set_sections_and_maps)
|
||||
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
||||
end
|
||||
|
||||
it "returns 2 sections" do
|
||||
expect(@arr.size).to eq 2
|
||||
end
|
||||
|
||||
it "returns only configured sections" do
|
||||
expect(@arr.select { |section| section.name == "foo" }.size).to eq 1
|
||||
expect(@arr.select { |section| section.name == "bar" }.size).to eq 1
|
||||
end
|
||||
|
||||
it "assigns issues to the correct sections" do
|
||||
foo_section = @arr.select { |section| section.name == "foo" }[0]
|
||||
bar_section = @arr.select { |section| section.name == "bar" }[0]
|
||||
|
||||
aggregate_failures "checks all sections" do
|
||||
expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "pr test1", "pr all the labels"])
|
||||
expect(titles_for(bar_section.issues)).to eq(["issue test3", "issue test4", "pr test3", "pr test4"])
|
||||
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
||||
end
|
||||
end
|
||||
end
|
||||
context "add sections" do
|
||||
let(:options) do
|
||||
{
|
||||
bug_labels: ["bug"],
|
||||
enhancement_labels: ["enhancement"],
|
||||
breaking_labels: ["breaking"],
|
||||
add_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}}"
|
||||
}
|
||||
end
|
||||
|
||||
let(:issues) do
|
||||
[
|
||||
issue("no labels", []),
|
||||
issue("test1", ["test1"]),
|
||||
issue("bugaboo", ["bug"]),
|
||||
issue("all the labels", %w[test1 test2 enhancement bug])
|
||||
]
|
||||
end
|
||||
|
||||
let(:pull_requests) do
|
||||
[
|
||||
pr("no labels", []),
|
||||
pr("test1", ["test1"]),
|
||||
pr("enhance", ["enhancement"]),
|
||||
pr("all the labels", %w[test1 test2 enhancement bug])
|
||||
]
|
||||
end
|
||||
|
||||
subject { described_class.new(options) }
|
||||
|
||||
before do
|
||||
subject.send(:set_sections_and_maps)
|
||||
@arr = subject.send(:parse_by_sections, pull_requests, issues)
|
||||
end
|
||||
|
||||
it "returns 5 sections" do
|
||||
expect(@arr.size).to eq 5
|
||||
end
|
||||
|
||||
it "returns default sections" do
|
||||
default_sections.each { |default_section| expect(@arr.select { |section| section.name == default_section }.size).to eq 1 }
|
||||
end
|
||||
|
||||
it "returns added section" do
|
||||
expect(@arr.select { |section| section.name == "foo" }.size).to eq 1
|
||||
end
|
||||
|
||||
it "assigns issues to the correct sections" do
|
||||
foo_section = @arr.select { |section| section.name == "foo" }[0]
|
||||
enhancement_section = @arr.select { |section| section.name == "enhancements" }[0]
|
||||
bug_section = @arr.select { |section| section.name == "bugs" }[0]
|
||||
|
||||
aggregate_failures "checks all sections" do
|
||||
expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "pr test1", "pr all the labels"])
|
||||
expect(titles_for(enhancement_section.issues)).to eq(["pr enhance"])
|
||||
expect(titles_for(bug_section.issues)).to eq(["issue bugaboo"])
|
||||
expect(titles_for(pull_requests)).to eq(["pr no labels"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ModuleLength
|
|
@ -1,73 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
describe Generator do
|
||||
describe "#get_string_for_issue" do
|
||||
let(:issue) do
|
||||
{ "title" => "Bug in code" }
|
||||
end
|
||||
|
||||
it "formats an issue according to options" do
|
||||
expect do
|
||||
described_class.new.get_string_for_issue(issue)
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse_by_sections" do
|
||||
def label(name)
|
||||
{ "name" => name }
|
||||
end
|
||||
|
||||
def issue(title, labels)
|
||||
{ "title" => "issue #{title}", "labels" => labels.map { |l| label(l) } }
|
||||
end
|
||||
|
||||
def pr(title, labels)
|
||||
{ "title" => "pr #{title}", "labels" => labels.map { |l| label(l) } }
|
||||
end
|
||||
|
||||
def get_titles(issues)
|
||||
issues.map { |issue| issue["title"] }
|
||||
end
|
||||
|
||||
let(:options) do
|
||||
{
|
||||
bug_labels: ["bug"],
|
||||
enhancement_labels: ["enhancement"],
|
||||
breaking_labels: ["breaking"]
|
||||
}
|
||||
end
|
||||
|
||||
let(:issues) do
|
||||
[
|
||||
issue("no labels", []),
|
||||
issue("enhancement", ["enhancement"]),
|
||||
issue("bug", ["bug"]),
|
||||
issue("breaking", ["breaking"]),
|
||||
issue("all the labels", %w[enhancement bug breaking])
|
||||
]
|
||||
end
|
||||
|
||||
let(:pull_requests) do
|
||||
[
|
||||
pr("no labels", []),
|
||||
pr("enhancement", ["enhancement"]),
|
||||
pr("bug", ["bug"]),
|
||||
pr("breaking", ["breaking"]),
|
||||
pr("all the labels", %w[enhancement bug breaking])
|
||||
]
|
||||
end
|
||||
|
||||
it "works" do
|
||||
sections = described_class.new(options).parse_by_sections(issues, pull_requests)
|
||||
|
||||
expect(get_titles(sections[:issues])).to eq(["issue no labels"])
|
||||
expect(get_titles(sections[:enhancements])).to eq(["issue enhancement", "issue all the labels", "pr enhancement", "pr all the labels"])
|
||||
expect(get_titles(sections[:bugs])).to eq(["issue bug", "pr bug"])
|
||||
expect(get_titles(sections[:breaking])).to eq(["issue breaking", "pr breaking"])
|
||||
expect(get_titles(pull_requests)).to eq(["pr no labels"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user