Merge branch 'release/1.5.0'

This commit is contained in:
Petr Korolev 2015-05-26 16:06:54 +03:00
commit a2b23e0f69
13 changed files with 408 additions and 217 deletions

View File

@ -6,3 +6,11 @@ Metrics/LineLength:
#http://viget.com/extend/just-use-double-quoted-ruby-strings #http://viget.com/extend/just-use-double-quoted-ruby-strings
Style/StringLiterals: Style/StringLiterals:
EnforcedStyle: double_quotes EnforcedStyle: double_quotes
# Configuration parameters: CountComments.
Metrics/ClassLength:
Enabled: false
# Configuration parameters: CountComments.
Metrics/MethodLength:
Enabled: false

View File

@ -1,37 +1,27 @@
# This configuration was generated by `rubocop --auto-gen-config` # This configuration was generated by `rubocop --auto-gen-config`
# on 2015-05-25 12:59:32 +0300 using RuboCop version 0.31.0. # on 2015-05-26 16:00:55 +0300 using RuboCop version 0.31.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again. # versions of RuboCop, may require this file to be generated again.
# Offense count: 16 # Offense count: 14
Metrics/AbcSize: Metrics/AbcSize:
Max: 68 Max: 59
# Offense count: 4 # Offense count: 1
# Configuration parameters: CountComments.
Metrics/ClassLength:
Enabled: false
# Offense count: 3
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Max: 9 Max: 7
# Offense count: 22 # Offense count: 1
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 117
# Offense count: 4
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Max: 12 Max: 8
# Offense count: 2 # Offense count: 2
Style/AccessorMethodName: Style/AccessorMethodName:
Enabled: false Enabled: false
# Offense count: 6 # Offense count: 8
Style/Documentation: Style/Documentation:
Enabled: false Enabled: false

View File

@ -82,30 +82,35 @@ As output you will get `CHANGELOG.md` file with pretty *Markdown-formatted* chan
### Params ### Params
Type `github_changelog_generator --help` for detailed usage. Type `github_changelog_generator --help` for detailed usage.
Usage: changelog_generator [options] Usage: github_changelog_generator [options]
-u, --user [USER] Username of the owner of target GitHub repo -u, --user [USER] Username of the owner of target GitHub repo
-p, --project [PROJECT] Name of project on GitHub -p, --project [PROJECT] Name of project on GitHub
-t, --token [TOKEN] To make more than 50 requests per hour your GitHub token required. You can generate it here: https://github.com/settings/tokens/new -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
-f, --date-format [FORMAT] Date format. Default is %d/%m/%y -f, --date-format [FORMAT] Date format. Default is %Y-%m-%d
-o, --output [NAME] Output file. Default is CHANGELOG.md -o, --output [NAME] Output file. Default is CHANGELOG.md
--[no-]verbose Run verbosely. Default is true --[no-]issues Include closed issues in changelog. Default is true
--[no-]issues Include closed issues to changelog. Default is true --[no-]issues-wo-labels Include closed issues without labels in changelog. Default is true
--[no-]issues-wo-labels Include closed issues without labels to changelog. Default is true --[no-]pr-wo-labels Include pull requests without labels in changelog. Default is true
--[no-]pr-wo-labels Include pull requests without labels to changelog. Default is true --[no-]pull-requests Include pull-requests in changelog. Default is true
--[no-]pull-requests Include pull-requests to changelog. Default is true --[no-]filter-by-milestone Use milestone to detect when issue was resolved. Default is true
--[no-]filter-by-milestone Use milestone to detect when issue was resolved. Default is true --[no-]author Add author of pull-request in the end. Default is true
--[no-]author Add author of pull-request in the end. Default is true --unreleased-only Generate log from unreleased closed issues only.
--unreleased-only Generate log from unreleased closed issues only. --[no-]unreleased Add to log unreleased closed issues. Default is true
--[no-]unreleased Add to log unreleased closed issues. Default is true --unreleased-label [label] Add to log unreleased closed issues. Default is true
--[no-]compare-link Include compare link between older version and newer version. Default is true --[no-]compare-link Include compare link (Full Changelog) between older version and newer version. Default is true
--include-labels x,y,z Issues only with that labels will be included to changelog. Default is 'bug,enhancement' --include-labels x,y,z Only issues with the specified labels will be included in the changelog. Default is 'bug,enhancement'
--exclude-labels x,y,z Issues with that labels will be always excluded from changelog. Default is 'duplicate,question,invalid,wontfix' --exclude-labels x,y,z Issues with the specified labels will be always excluded from changelog. Default is 'duplicate,question,invalid,wontfix'
--max-issues [NUMBER] Max number of issues to fetch from GitHub. Default is unlimited. --between-tags x,y,z Change log will be filled only between specified tags
--github-site [URL] The Enterprise Github site on which your project is hosted. --exclude-tags x,y,z Change log will be exclude specified tags
--github-api [URL] The enterprise endpoint to use for your Github API. --max-issues [NUMBER] Max number of issues to fetch from GitHub. Default is unlimited
--future-release [RELEASE-VERSION] Put the unreleased changes in the specified release number. --github-site [URL] The Enterprise Github site on which your project is hosted.
-v, --version Print version number --github-api [URL] The enterprise endpoint to use for your Github API.
-h, --help Displays Help --simple-list Create simple list from issues and pull requests. Default is false.
--future-release [RELEASE-VERSION]
Put the unreleased changes in the specified release number.
--[no-]verbose Run verbosely. Default is true
-v, --version Print version number
-h, --help Displays Help
### GitHub token ### GitHub token

View File

@ -15,7 +15,7 @@ module GitHubChangelogGenerator
"This script can make only 50 requests to GitHub API per hour without token!" "This script can make only 50 requests to GitHub API per hour without token!"
def initialize(options = {}) def initialize(options = {})
@options = options @options = options || {}
@logger = Logger.new(STDOUT) @logger = Logger.new(STDOUT)
@logger.formatter = proc do |_severity, _datetime, _progname, msg| @logger.formatter = proc do |_severity, _datetime, _progname, msg|
@ -28,8 +28,8 @@ module GitHubChangelogGenerator
@tag_times_hash = {} @tag_times_hash = {}
github_options = { per_page: PER_PAGE_NUMBER } github_options = { per_page: PER_PAGE_NUMBER }
github_options[:oauth_token] = @github_token unless @github_token.nil? github_options[:oauth_token] = @github_token unless @github_token.nil?
github_options[:endpoint] = options[:github_endpoint] unless options[:github_endpoint].nil? github_options[:endpoint] = @options[:github_endpoint] unless @options[:github_endpoint].nil?
github_options[:site] = options[:github_endpoint] unless options[:github_site].nil? github_options[:site] = @options[:github_endpoint] unless @options[:github_site].nil?
@github = check_github_response { Github.new github_options } @github = check_github_response { Github.new github_options }
end end

View File

@ -2,6 +2,7 @@ require "github_changelog_generator/fetcher"
require_relative "generator_generation" require_relative "generator_generation"
require_relative "generator_fetcher" require_relative "generator_fetcher"
require_relative "generator_processor" require_relative "generator_processor"
require_relative "generator_tags"
module GitHubChangelogGenerator module GitHubChangelogGenerator
# Default error for ChangelogGenerator # Default error for ChangelogGenerator
@ -17,13 +18,9 @@ module GitHubChangelogGenerator
# generator = GitHubChangelogGenerator::Generator.new # generator = GitHubChangelogGenerator::Generator.new
# content = generator.compound_changelog # content = generator.compound_changelog
def initialize(options = nil) def initialize(options = nil)
@options = options @options = options || {}
@fetcher = GitHubChangelogGenerator::Fetcher.new @options @fetcher = GitHubChangelogGenerator::Fetcher.new @options
fetch_tags
fetch_issues_and_pr
end end
def fetch_issues_and_pr def fetch_issues_and_pr
@ -37,20 +34,6 @@ module GitHubChangelogGenerator
detect_actual_closed_dates(@issues + @pull_requests) detect_actual_closed_dates(@issues + @pull_requests)
end end
def fetch_tags
# @all_tags = get_filtered_tags
@all_tags = @fetcher.get_all_tags
fetch_tags_dates
sort_tags_by_date
end
# Sort all tags by date
def sort_tags_by_date
puts "Sorting tags..." if @options[:verbose]
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
end
# Encapsulate characters to make markdown look as expected. # Encapsulate characters to make markdown look as expected.
# #
# @param [String] string # @param [String] string
@ -74,14 +57,7 @@ module GitHubChangelogGenerator
# @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag. # @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag.
# @return [String] Ready and parsed section # @return [String] Ready and parsed section
def create_log(pull_requests, issues, newer_tag, older_tag_name = nil) def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag) newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
if newer_tag.nil? && @options[:future_release]
newer_tag_name = @options[:future_release]
newer_tag_link = @options[:future_release]
else
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
end
github_site = options[:github_site] || "https://github.com" github_site = options[:github_site] || "https://github.com"
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}" project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
@ -90,11 +66,7 @@ module GitHubChangelogGenerator
if @options[:issues] if @options[:issues]
# Generate issues: # Generate issues:
bugs_a, enhancement_a, issues_a = parse_by_sections(issues) log += issues_to_log(issues)
log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
log += generate_sub_section(bugs_a, @options[:bug_prefix])
log += generate_sub_section(issues_a, @options[:issue_prefix])
end end
if @options[:pulls] if @options[:pulls]
@ -105,6 +77,20 @@ module GitHubChangelogGenerator
log log
end end
# Generate ready-to-paste log from list of issues.
#
# @param [Array] issues
# @return [String] generated log for issues
def issues_to_log(issues)
log = ""
bugs_a, enhancement_a, issues_a = parse_by_sections(issues)
log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
log += generate_sub_section(bugs_a, @options[:bug_prefix])
log += generate_sub_section(issues_a, @options[:issue_prefix])
log
end
# This method sort issues by types # This method sort issues by types
# (bugs, features, or just closed issues) by labels # (bugs, features, or just closed issues) by labels
# #

View File

@ -14,25 +14,19 @@ module GitHubChangelogGenerator
# Async fetching of all tags dates # Async fetching of all tags dates
def fetch_tags_dates def fetch_tags_dates
print "Fetching tag dates...\r" if @options[:verbose] print "Fetching tag dates...\r" if @options[:verbose]
# Async fetching tags: # Async fetching tags:
threads = [] threads = []
i = 0 i = 0
all = @all_tags.count all = @all_tags.count
@all_tags.each do |tag| @all_tags.each do |tag|
print " \r"
threads << Thread.new do threads << Thread.new do
@fetcher.get_time_of_tag(tag) @fetcher.get_time_of_tag(tag)
if @options[:verbose] print "Fetching tags dates: #{i + 1}/#{all}\r" if @options[:verbose]
print "Fetching tags dates: #{i + 1}/#{all}\r" i += 1
i += 1
end
end end
end end
print " \r"
threads.each(&:join) threads.each(&:join)
puts "Fetching tags dates: #{i}" if @options[:verbose] puts "Fetching tags dates: #{i}" if @options[:verbose]
end end

View File

@ -4,36 +4,42 @@ module GitHubChangelogGenerator
# #
# @return [String] Generated change log file # @return [String] Generated change log file
def compound_changelog def compound_changelog
fetch_and_filter_tags
fetch_issues_and_pr
log = "# Change Log\n\n" log = "# Change Log\n\n"
if @options[:unreleased_only] if @options[:unreleased_only]
log += generate_log_between_tags(all_tags[0], nil) log += generate_log_between_tags(all_tags[0], nil)
elsif @options[:tag1] && @options[:tag2]
tag1 = @options[:tag1]
tag2 = @options[:tag2]
tags_strings = []
all_tags.each { |x| tags_strings.push(x["name"]) }
if tags_strings.include?(tag1)
if tags_strings.include?(tag2)
to_a = tags_strings.map.with_index.to_a
hash = Hash[to_a]
index1 = hash[tag1]
index2 = hash[tag2]
log += generate_log_between_tags(all_tags[index1], all_tags[index2])
else
fail ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
end
else
fail ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
end
else else
log += generate_log_for_all_tags log += generate_log_for_all_tags
end end
log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*" log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
@log = log @log = log
@log = log end
# @return [String] temp method should be removed soon
def generate_for_2_tags(log)
tag1 = @options[:tag1]
tag2 = @options[:tag2]
tags_strings = []
all_tags.each { |x| tags_strings.push(x["name"]) }
if tags_strings.include?(tag1)
if tags_strings.include?(tag2)
to_a = tags_strings.map.with_index.to_a
hash = Hash[to_a]
index1 = hash[tag1]
index2 = hash[tag2]
log += generate_log_between_tags(all_tags[index1], all_tags[index2])
else
fail ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
end
else
fail ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
end
log
end end
# @param [Array] issues List of issues on sub-section # @param [Array] issues List of issues on sub-section
@ -86,18 +92,10 @@ module GitHubChangelogGenerator
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag # @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 # @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) def generate_log_between_tags(older_tag, newer_tag)
filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag) filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
older_tag_name = older_tag.nil? ? nil : older_tag["name"] older_tag_name = older_tag.nil? ? nil : older_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
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty? if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
# do not generate empty unreleased section # do not generate empty unreleased section
return "" return ""
@ -106,6 +104,23 @@ module GitHubChangelogGenerator
create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
end end
# Apply all filters to issues and pull requests
#
# @return [Array] filtered issues and pull requests
def filter_issues_for_tags(newer_tag, older_tag)
filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag)
filtered_issues = delete_by_time(@issues, :actual_date, 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 # The full cycle of generation for whole project
# @return [String] The complete change log # @return [String] The complete change log
def generate_log_for_all_tags def generate_log_for_all_tags
@ -125,7 +140,7 @@ module GitHubChangelogGenerator
def generate_unreleased_section def generate_unreleased_section
log = "" log = ""
if @options[:unreleased] && @all_tags.count != 0 if @options[:unreleased]
unreleased_log = generate_log_between_tags(all_tags[0], nil) unreleased_log = generate_log_between_tags(all_tags[0], nil)
log += unreleased_log if unreleased_log log += unreleased_log if unreleased_log
end end

View File

@ -13,7 +13,44 @@ module GitHubChangelogGenerator
issues issues
end end
def filter_by_milestone(filtered_issues, newer_tag_name, src_array) # @return [Array] filtered issues accourding milestone
def filter_by_milestone(filtered_issues, tag_name, all_issues)
remove_issues_in_milestones(filtered_issues)
unless tag_name.nil?
# add missed issues (according milestones)
issues_to_add = find_issues_to_add(all_issues, tag_name)
filtered_issues |= issues_to_add
end
filtered_issues
end
# Add all issues, that should be in that tag, according milestone
#
# @param [Array] all_issues
# @param [String] tag_name
# @return [Array] issues with milestone #tag_name
def find_issues_to_add(all_issues, tag_name)
all_issues.select do |issue|
if issue.milestone.nil?
false
else
# check, that this milestone in tag list:
milestone_is_tag = @all_tags.find do |tag|
tag.name == issue.milestone.title
end
if milestone_is_tag.nil?
false
else
issue.milestone.title == tag_name
end
end
end
end
# @return [Array] array with removed issues, that contain milestones with same name as a tag
def remove_issues_in_milestones(filtered_issues)
filtered_issues.select! do |issue| filtered_issues.select! do |issue|
# leave issues without milestones # leave issues without milestones
if issue.milestone.nil? if issue.milestone.nil?
@ -23,29 +60,6 @@ module GitHubChangelogGenerator
@all_tags.find { |tag| tag.name == issue.milestone.title }.nil? @all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
end end
end end
unless newer_tag_name.nil?
# add missed issues (according milestones)
issues_to_add = src_array.select do |issue|
if issue.milestone.nil?
false
else
# check, that this milestone in tag list:
milestone_is_tag = @all_tags.find do |tag|
tag.name == issue.milestone.title
end
if milestone_is_tag.nil?
false
else
issue.milestone.title == newer_tag_name
end
end
end
filtered_issues |= issues_to_add
end
filtered_issues
end end
# Method filter issues, that belong only specified tag range # Method filter issues, that belong only specified tag range
@ -55,26 +69,19 @@ module GitHubChangelogGenerator
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section # @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
# @return [Array] filtered issues # @return [Array] filtered issues
def delete_by_time(array, hash_key = :actual_date, older_tag = nil, newer_tag = nil) def delete_by_time(array, hash_key = :actual_date, older_tag = nil, newer_tag = nil)
fail ChangelogGeneratorError, "At least one of the tags should be not nil!".red if older_tag.nil? && newer_tag.nil? # in case if not tags specified - return unchanged array
return array if older_tag.nil? && newer_tag.nil?
newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag) newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag)
older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag) older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag)
array.select do |req| array.select do |req|
if req[hash_key] if req[hash_key]
t = Time.parse(req[hash_key]).utc time = Time.parse(req[hash_key]).utc
if older_tag_time.nil? tag_in_range_old = tag_newer_old_tag?(older_tag_time, time)
tag_in_range_old = true
else
tag_in_range_old = t > older_tag_time
end
if newer_tag_time.nil? tag_in_range_new = tag_older_new_tag?(newer_tag_time, time)
tag_in_range_new = true
else
tag_in_range_new = t <= newer_tag_time
end
tag_in_range = (tag_in_range_old) && (tag_in_range_new) tag_in_range = (tag_in_range_old) && (tag_in_range_new)
@ -85,39 +92,50 @@ module GitHubChangelogGenerator
end end
end end
def tag_older_new_tag?(newer_tag_time, time)
if newer_tag_time.nil?
tag_in_range_new = true
else
tag_in_range_new = time <= newer_tag_time
end
tag_in_range_new
end
def tag_newer_old_tag?(older_tag_time, t)
if older_tag_time.nil?
tag_in_range_old = true
else
tag_in_range_old = t > older_tag_time
end
tag_in_range_old
end
# Include issues with labels, specified in :include_labels # Include issues with labels, specified in :include_labels
# @param [Array] issues to filter # @param [Array] issues to filter
# @return [Array] filtered array of issues # @return [Array] filtered array of issues
def include_issues_by_labels(issues) def include_issues_by_labels(issues)
filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue| filtered_issues = filter_by_include_labels(issues)
labels = issue.labels.map(&:name) & @options[:include_labels] filtered_issues |= filter_wo_labels(issues)
(labels).any? filtered_issues
end end
# @return [Array] issues without labels or empty array if add_issues_wo_labels is false
def filter_wo_labels(issues)
if @options[:add_issues_wo_labels] if @options[:add_issues_wo_labels]
issues_wo_labels = issues.select do |issue| issues_wo_labels = issues.select do |issue|
!issue.labels.map(&:name).any? !issue.labels.map(&:name).any?
end end
filtered_issues |= issues_wo_labels return issues_wo_labels
end end
filtered_issues []
end end
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags def filter_by_include_labels(issues)
# filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue|
# @return [Array] labels = issue.labels.map(&:name) & @options[:include_labels]
def get_filtered_tags (labels).any?
all_tags = @fetcher.get_all_tags
filtered_tags = []
if @options[:between_tags]
@options[:between_tags].each do |tag|
unless all_tags.include? tag
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
end
end
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
end end
filtered_tags filtered_issues
end end
# General filtered function # General filtered function

View File

@ -0,0 +1,70 @@
module GitHubChangelogGenerator
class Generator
# fetch, filter tags, fetch dates and sort them in time order
def fetch_and_filter_tags
@all_tags = get_filtered_tags(@fetcher.get_all_tags)
fetch_tags_dates
sort_tags_by_date
end
# Sort all tags by date
def sort_tags_by_date
puts "Sorting tags..." if @options[:verbose]
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
end
# Detect link, name and time for specified tag.
#
# @param [Hash] newer_tag newer tag. Can be nil, if it's Unreleased section.
# @return [Array] link, name and time of the tag
def detect_link_tag_time(newer_tag)
# if tag is nil - set current time
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag)
# if it's future release tag - set this value
if newer_tag.nil? && @options[:future_release]
newer_tag_name = @options[:future_release]
newer_tag_link = @options[:future_release]
else
# put unreleased label if there is no name for the tag
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
end
[newer_tag_link, newer_tag_name, newer_tag_time]
end
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
#
# @return [Array]
def get_filtered_tags(all_tags)
filtered_tags = filter_between_tags(all_tags)
filter_excluded_tags(filtered_tags)
end
def filter_between_tags(all_tags)
filtered_tags = all_tags
if @options[:between_tags]
@options[:between_tags].each do |tag|
unless all_tags.include? tag
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
end
end
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
end
filtered_tags
end
def filter_excluded_tags(all_tags)
filtered_tags = all_tags
if @options[:exclude_tags]
@options[:exclude_tags].each do |tag|
unless all_tags.include? tag
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
end
end
filtered_tags = all_tags.reject { |tag| @options[:exclude_tags].include? tag }
end
filtered_tags
end
end
end

View File

@ -5,34 +5,32 @@ require_relative "version"
module GitHubChangelogGenerator module GitHubChangelogGenerator
class Parser class Parser
# parse options with optparse
def self.parse_options def self.parse_options
options = { options = get_default_options
tag1: nil,
tag2: nil,
date_format: "%Y-%m-%d",
output: "CHANGELOG.md",
issues: true,
add_issues_wo_labels: true,
add_pr_wo_labels: true,
pulls: true,
filter_issues_by_milestone: true,
author: true,
unreleased: true,
unreleased_label: "Unreleased",
compare_link: true,
include_labels: %w(bug enhancement),
exclude_labels: %w(duplicate question invalid wontfix),
max_issues: nil,
simple_list: false,
verbose: true,
merge_prefix: "**Merged pull requests:**", parser = setup_parser(options)
issue_prefix: "**Closed issues:**",
bug_prefix: "**Fixed bugs:**",
enhancement_prefix: "**Implemented enhancements:**",
git_remote: "origin"
}
parser.parse!
detect_user_and_project(options)
if !options[:user] || !options[:project]
puts parser.banner
exit
end
if options[:verbose]
puts "Performing task with options:"
pp options
puts ""
end
options
end
# setup parsing options
def self.setup_parser(options)
parser = OptionParser.new do |opts| parser = OptionParser.new do |opts|
opts.banner = "Usage: github_changelog_generator [options]" opts.banner = "Usage: github_changelog_generator [options]"
opts.on("-u", "--user [USER]", "Username of the owner of target GitHub repo") do |last| opts.on("-u", "--user [USER]", "Username of the owner of target GitHub repo") do |last|
@ -86,6 +84,12 @@ module GitHubChangelogGenerator
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| 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|
options[:exclude_labels] = list options[:exclude_labels] = list
end end
opts.on("--between-tags x,y,z", Array, "Change log will be filled only between specified tags") do |list|
options[:between_tags] = list
end
opts.on("--exclude-tags x,y,z", Array, "Change log will be exclude specified tags") do |list|
options[:exclude_tags] = list
end
opts.on("--max-issues [NUMBER]", Integer, "Max number of issues to fetch from GitHub. Default is unlimited") do |max| opts.on("--max-issues [NUMBER]", Integer, "Max number of issues to fetch from GitHub. Default is unlimited") do |max|
options[:max_issues] = max options[:max_issues] = max
end end
@ -113,25 +117,41 @@ module GitHubChangelogGenerator
exit exit
end end
end end
parser
end
parser.parse! # just get default options
def self.get_default_options
detect_user_and_project(options) options = {
tag1: nil,
if !options[:user] || !options[:project] tag2: nil,
puts parser.banner date_format: "%Y-%m-%d",
exit output: "CHANGELOG.md",
end issues: true,
add_issues_wo_labels: true,
if options[:verbose] add_pr_wo_labels: true,
puts "Performing task with options:" pulls: true,
pp options filter_issues_by_milestone: true,
puts "" author: true,
end unreleased: true,
unreleased_label: "Unreleased",
compare_link: true,
include_labels: %w(bug enhancement),
exclude_labels: %w(duplicate question invalid wontfix),
max_issues: nil,
simple_list: false,
verbose: true,
merge_prefix: "**Merged pull requests:**",
issue_prefix: "**Closed issues:**",
bug_prefix: "**Fixed bugs:**",
enhancement_prefix: "**Implemented enhancements:**",
git_remote: "origin"
}
options options
end end
# Detects user and project from git
def self.detect_user_and_project(options) def self.detect_user_and_project(options)
options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site]) options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site])
if !options[:user] || !options[:project] if !options[:user] || !options[:project]
@ -149,12 +169,13 @@ module GitHubChangelogGenerator
# #
# @param [String] output of git remote command # @param [String] output of git remote command
# @return [Array] user and project # @return [Array] user and project
def self.user_project_from_option(arg0, arg2, github_site = "github.com") def self.user_project_from_option(arg0, arg1, github_site = nil)
user = nil user = nil
project = nil project = nil
github_site ||= "github.com"
if arg0 && !arg2 if arg0 && !arg1
# this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name # this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name
puts arg0
match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0) match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0)
begin begin

View File

@ -1,3 +1,3 @@
module GitHubChangelogGenerator module GitHubChangelogGenerator
VERSION = "1.4.1" VERSION = "1.5.0"
end end

View File

@ -0,0 +1,69 @@
describe GitHubChangelogGenerator::Generator do
describe "#filter_between_tags" do
context "when between_tags nil" do
before do
@generator = GitHubChangelogGenerator::Generator.new(between_tags: nil)
end
subject do
@generator.get_filtered_tags(%w(1 2 3))
end
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(%w(1 2 3)) }
end
context "when between_tags same as input array" do
before do
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2 3))
end
subject do
@generator.get_filtered_tags(%w(1 2 3))
end
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(%w(1 2 3)) }
end
context "when between_tags filled with correct values" do
before do
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2))
end
subject do
@generator.get_filtered_tags(%w(1 2 3))
end
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(%w(1 2)) }
end
context "when between_tags filled with invalid values" do
before do
@generator = GitHubChangelogGenerator::Generator.new(between_tags: %w(1 q w))
end
subject do
@generator.get_filtered_tags(%w(1 2 3))
end
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(%w(1)) }
end
end
describe "#get_filtered_tags" do
subject { generator.get_filtered_tags(%w(1 2 3 4 5)) }
# before { generator.get_filtered_tags(%w(1 2 3 4 5)) }
context "with excluded and between tags" do
let(:generator) { GitHubChangelogGenerator::Generator.new(between_tags: %w(1 2 3), exclude_tags: %w(2)) }
it { is_expected.to be_a Array }
it { is_expected.to match_array(%w(1 3)) }
end
end
describe "#filter_excluded_tags" do
subject { generator.filter_excluded_tags(%w(1 2 3)) }
context "with valid excluded tags" do
let(:generator) { GitHubChangelogGenerator::Generator.new(exclude_tags: %w(3)) }
it { is_expected.to be_a Array }
it { is_expected.to match_array(%w(1 2)) }
end
end
end

View File

@ -1,5 +1,5 @@
describe GitHubChangelogGenerator::Parser do describe GitHubChangelogGenerator::Parser do
describe "#self.user_project_from_remote" do describe ".user_project_from_remote" do
context "when remote is 1" do context "when remote is 1" do
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin https://github.com/skywinder/ActionSheetPicker-3.0 (fetch)") } subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin https://github.com/skywinder/ActionSheetPicker-3.0 (fetch)") }
it { is_expected.to be_a(Array) } it { is_expected.to be_a(Array) }
@ -26,7 +26,7 @@ describe GitHubChangelogGenerator::Parser do
it { is_expected.to match_array([nil, nil]) } it { is_expected.to match_array([nil, nil]) }
end end
end end
describe "#self.user_project_from_option" do describe ".user_project_from_option" do
# context "when option is invalid" do # context "when option is invalid" do
# it("should exit") { expect { GitHubChangelogGenerator::Parser.user_project_from_option("blah", nil) }.to raise_error(SystemExit) } # it("should exit") { expect { GitHubChangelogGenerator::Parser.user_project_from_option("blah", nil) }.to raise_error(SystemExit) }
# end # end
@ -41,5 +41,20 @@ describe GitHubChangelogGenerator::Parser do
it { is_expected.to be_a(Array) } it { is_expected.to be_a(Array) }
it { is_expected.to match_array([nil, nil]) } it { is_expected.to match_array([nil, nil]) }
end end
context "when site is nil" do
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, nil) }
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
end
context "when site is valid" do
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, "https://codeclimate.com") }
it { is_expected.to be_a(Array) }
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
end
context "when second arg is not nil" do
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", "blah", nil) }
it { is_expected.to be_a(Array) }
it { is_expected.to match_array([nil, nil]) }
end
end end
end end