diff --git a/.rubocop.yml b/.rubocop.yml index 5a712a8..d9eac99 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,3 +6,11 @@ Metrics/LineLength: #http://viget.com/extend/just-use-double-quoted-ruby-strings Style/StringLiterals: EnforcedStyle: double_quotes + +# Configuration parameters: CountComments. +Metrics/ClassLength: + Enabled: false + +# Configuration parameters: CountComments. +Metrics/MethodLength: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d033c94..0e98934 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,37 +1,27 @@ # 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 # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 16 +# Offense count: 14 Metrics/AbcSize: - Max: 68 + Max: 59 -# Offense count: 4 -# Configuration parameters: CountComments. -Metrics/ClassLength: - Enabled: false - -# Offense count: 3 +# Offense count: 1 Metrics/CyclomaticComplexity: - Max: 9 + Max: 7 -# Offense count: 22 -# Configuration parameters: CountComments. -Metrics/MethodLength: - Max: 117 - -# Offense count: 4 +# Offense count: 1 Metrics/PerceivedComplexity: - Max: 12 + Max: 8 # Offense count: 2 Style/AccessorMethodName: Enabled: false -# Offense count: 6 +# Offense count: 8 Style/Documentation: Enabled: false diff --git a/README.md b/README.md index 4152ab7..9747349 100644 --- a/README.md +++ b/README.md @@ -82,30 +82,35 @@ As output you will get `CHANGELOG.md` file with pretty *Markdown-formatted* chan ### Params Type `github_changelog_generator --help` for detailed usage. - Usage: changelog_generator [options] - -u, --user [USER] Username of the owner of target GitHub repo - -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 - -f, --date-format [FORMAT] Date format. Default is %d/%m/%y - -o, --output [NAME] Output file. Default is CHANGELOG.md - --[no-]verbose Run verbosely. Default is true - --[no-]issues Include closed issues to 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 to 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-]author Add author of pull-request in the end. Default is true - --unreleased-only Generate log from unreleased closed issues only. - --[no-]unreleased Add to log unreleased closed issues. Default is true - --[no-]compare-link Include compare link 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' - --exclude-labels x,y,z Issues with that 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. - --github-site [URL] The Enterprise Github site on which your project is hosted. - --github-api [URL] The enterprise endpoint to use for your Github API. - --future-release [RELEASE-VERSION] Put the unreleased changes in the specified release number. - -v, --version Print version number - -h, --help Displays Help + Usage: github_changelog_generator [options] + -u, --user [USER] Username of the owner of target GitHub repo + -p, --project [PROJECT] Name of project on GitHub + -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 %Y-%m-%d + -o, --output [NAME] Output file. Default is CHANGELOG.md + --[no-]issues Include closed issues in changelog. Default is true + --[no-]issues-wo-labels Include closed issues without labels in changelog. Default is true + --[no-]pr-wo-labels Include pull requests without labels in changelog. Default is true + --[no-]pull-requests Include pull-requests in changelog. 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 + --unreleased-only Generate log from unreleased closed issues only. + --[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 (Full Changelog) between older version and newer version. Default is true + --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 the specified labels will be always excluded from changelog. Default is 'duplicate,question,invalid,wontfix' + --between-tags x,y,z Change log will be filled only between specified tags + --exclude-tags x,y,z Change log will be exclude specified tags + --max-issues [NUMBER] Max number of issues to fetch from GitHub. Default is unlimited + --github-site [URL] The Enterprise Github site on which your project is hosted. + --github-api [URL] The enterprise endpoint to use for your Github API. + --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 diff --git a/lib/github_changelog_generator/fetcher.rb b/lib/github_changelog_generator/fetcher.rb index 5e08ff9..5fb3c57 100644 --- a/lib/github_changelog_generator/fetcher.rb +++ b/lib/github_changelog_generator/fetcher.rb @@ -15,7 +15,7 @@ module GitHubChangelogGenerator "This script can make only 50 requests to GitHub API per hour without token!" def initialize(options = {}) - @options = options + @options = options || {} @logger = Logger.new(STDOUT) @logger.formatter = proc do |_severity, _datetime, _progname, msg| @@ -28,8 +28,8 @@ module GitHubChangelogGenerator @tag_times_hash = {} github_options = { per_page: PER_PAGE_NUMBER } github_options[:oauth_token] = @github_token unless @github_token.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[:endpoint] = @options[:github_endpoint] unless @options[:github_endpoint].nil? + github_options[:site] = @options[:github_endpoint] unless @options[:github_site].nil? @github = check_github_response { Github.new github_options } end diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index 5b4285f..3ac896d 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -2,6 +2,7 @@ require "github_changelog_generator/fetcher" require_relative "generator_generation" require_relative "generator_fetcher" require_relative "generator_processor" +require_relative "generator_tags" module GitHubChangelogGenerator # Default error for ChangelogGenerator @@ -17,13 +18,9 @@ module GitHubChangelogGenerator # generator = GitHubChangelogGenerator::Generator.new # content = generator.compound_changelog def initialize(options = nil) - @options = options + @options = options || {} @fetcher = GitHubChangelogGenerator::Fetcher.new @options - - fetch_tags - - fetch_issues_and_pr end def fetch_issues_and_pr @@ -37,20 +34,6 @@ module GitHubChangelogGenerator detect_actual_closed_dates(@issues + @pull_requests) 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. # # @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. # @return [String] Ready and parsed section 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) - 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 + 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]}" @@ -90,11 +66,7 @@ module GitHubChangelogGenerator if @options[:issues] # Generate issues: - 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 += issues_to_log(issues) end if @options[:pulls] @@ -105,6 +77,20 @@ module GitHubChangelogGenerator log 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 # (bugs, features, or just closed issues) by labels # diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index 7cb3fd4..3b15769 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -14,25 +14,19 @@ module GitHubChangelogGenerator # Async fetching of all tags dates def fetch_tags_dates print "Fetching tag dates...\r" if @options[:verbose] - # Async fetching tags: threads = [] i = 0 all = @all_tags.count @all_tags.each do |tag| + print " \r" threads << Thread.new do @fetcher.get_time_of_tag(tag) - if @options[:verbose] - print "Fetching tags dates: #{i + 1}/#{all}\r" - i += 1 - end + print "Fetching tags dates: #{i + 1}/#{all}\r" if @options[:verbose] + i += 1 end end - - print " \r" - threads.each(&:join) - puts "Fetching tags dates: #{i}" if @options[:verbose] end diff --git a/lib/github_changelog_generator/generator/generator_generation.rb b/lib/github_changelog_generator/generator/generator_generation.rb index f5e6b8f..5571359 100644 --- a/lib/github_changelog_generator/generator/generator_generation.rb +++ b/lib/github_changelog_generator/generator/generator_generation.rb @@ -4,36 +4,42 @@ module GitHubChangelogGenerator # # @return [String] Generated change log file def compound_changelog + fetch_and_filter_tags + fetch_issues_and_pr + log = "# Change Log\n\n" if @options[:unreleased_only] 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 log += generate_log_for_all_tags end log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*" @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 # @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] 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_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) + filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag) - newer_tag_name = newer_tag.nil? ? nil : newer_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? # do not generate empty unreleased section return "" @@ -106,6 +104,23 @@ module GitHubChangelogGenerator create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) 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 # @return [String] The complete change log def generate_log_for_all_tags @@ -125,7 +140,7 @@ module GitHubChangelogGenerator def generate_unreleased_section log = "" - if @options[:unreleased] && @all_tags.count != 0 + if @options[:unreleased] unreleased_log = generate_log_between_tags(all_tags[0], nil) log += unreleased_log if unreleased_log end diff --git a/lib/github_changelog_generator/generator/generator_processor.rb b/lib/github_changelog_generator/generator/generator_processor.rb index cbbaa7f..f95142c 100644 --- a/lib/github_changelog_generator/generator/generator_processor.rb +++ b/lib/github_changelog_generator/generator/generator_processor.rb @@ -13,7 +13,44 @@ module GitHubChangelogGenerator issues 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| # leave issues without milestones if issue.milestone.nil? @@ -23,29 +60,6 @@ module GitHubChangelogGenerator @all_tags.find { |tag| tag.name == issue.milestone.title }.nil? 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 # 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 # @return [Array] filtered issues 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) older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag) array.select do |req| 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 = true - else - tag_in_range_old = t > older_tag_time - end + tag_in_range_old = tag_newer_old_tag?(older_tag_time, time) - if newer_tag_time.nil? - tag_in_range_new = true - else - tag_in_range_new = t <= newer_tag_time - end + tag_in_range_new = tag_older_new_tag?(newer_tag_time, time) tag_in_range = (tag_in_range_old) && (tag_in_range_new) @@ -85,39 +92,50 @@ module GitHubChangelogGenerator 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 # @param [Array] issues to filter # @return [Array] filtered array of issues def include_issues_by_labels(issues) - filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue| - labels = issue.labels.map(&:name) & @options[:include_labels] - (labels).any? - end + filtered_issues = filter_by_include_labels(issues) + filtered_issues |= filter_wo_labels(issues) + filtered_issues + 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] issues_wo_labels = issues.select do |issue| !issue.labels.map(&:name).any? end - filtered_issues |= issues_wo_labels + return issues_wo_labels end - filtered_issues + [] end - # Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags - # - # @return [Array] - def get_filtered_tags - 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 } + def filter_by_include_labels(issues) + filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue| + labels = issue.labels.map(&:name) & @options[:include_labels] + (labels).any? end - filtered_tags + filtered_issues end # General filtered function diff --git a/lib/github_changelog_generator/generator/generator_tags.rb b/lib/github_changelog_generator/generator/generator_tags.rb new file mode 100644 index 0000000..6e4cc7d --- /dev/null +++ b/lib/github_changelog_generator/generator/generator_tags.rb @@ -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 diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index 6e87f7d..683b294 100644 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -5,34 +5,32 @@ require_relative "version" module GitHubChangelogGenerator class Parser + # parse options with optparse def self.parse_options - 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, + options = get_default_options - merge_prefix: "**Merged pull requests:**", - issue_prefix: "**Closed issues:**", - bug_prefix: "**Fixed bugs:**", - enhancement_prefix: "**Implemented enhancements:**", - git_remote: "origin" - } + parser = setup_parser(options) + 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| opts.banner = "Usage: github_changelog_generator [options]" 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| options[:exclude_labels] = list 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| options[:max_issues] = max end @@ -113,25 +117,41 @@ module GitHubChangelogGenerator exit end end + parser + end - 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 + # just get default options + def self.get_default_options + 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:**", + issue_prefix: "**Closed issues:**", + bug_prefix: "**Fixed bugs:**", + enhancement_prefix: "**Implemented enhancements:**", + git_remote: "origin" + } options end + # Detects user and project from git def self.detect_user_and_project(options) options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site]) if !options[:user] || !options[:project] @@ -149,12 +169,13 @@ module GitHubChangelogGenerator # # @param [String] output of git remote command # @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 project = nil - - if arg0 && !arg2 + github_site ||= "github.com" + 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 + puts arg0 match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0) begin diff --git a/lib/github_changelog_generator/version.rb b/lib/github_changelog_generator/version.rb index 65eb6cc..c453d07 100644 --- a/lib/github_changelog_generator/version.rb +++ b/lib/github_changelog_generator/version.rb @@ -1,3 +1,3 @@ module GitHubChangelogGenerator - VERSION = "1.4.1" + VERSION = "1.5.0" end diff --git a/spec/unit/generator/generator_tags_spec.rb b/spec/unit/generator/generator_tags_spec.rb new file mode 100644 index 0000000..f3ffca7 --- /dev/null +++ b/spec/unit/generator/generator_tags_spec.rb @@ -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 diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index 6f80a6e..6bc9d8a 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -1,5 +1,5 @@ describe GitHubChangelogGenerator::Parser do - describe "#self.user_project_from_remote" do + describe ".user_project_from_remote" do context "when remote is 1" do subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin https://github.com/skywinder/ActionSheetPicker-3.0 (fetch)") } it { is_expected.to be_a(Array) } @@ -26,7 +26,7 @@ describe GitHubChangelogGenerator::Parser do it { is_expected.to match_array([nil, nil]) } end end - describe "#self.user_project_from_option" do + describe ".user_project_from_option" do # context "when option is invalid" do # it("should exit") { expect { GitHubChangelogGenerator::Parser.user_project_from_option("blah", nil) }.to raise_error(SystemExit) } # end @@ -41,5 +41,20 @@ describe GitHubChangelogGenerator::Parser do it { is_expected.to be_a(Array) } it { is_expected.to match_array([nil, nil]) } 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