From 3fc3e3e143251d83415cdb88625f482888a0e2d2 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 09:21:47 +0300 Subject: [PATCH 01/15] minor changes --- lib/github_changelog_generator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 8a02b9a..08c971b 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -296,12 +296,12 @@ module GitHubChangelogGenerator older_tag_name = older_tag.nil? ? nil : older_tag["name"] if @options[:filter_issues_by_milestone] - # delete excess irrelevant issues (according milestones) + # 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 filtered_issues.empty? && filtered_pull_requests.empty? && newer_tag.nil? + if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty? # do not generate empty unreleased section return "" end From 944adc92cddf9b03e7f98223435af5434668ab0b Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 13:34:01 +0300 Subject: [PATCH 02/15] refacktoring. move separation logic in funciton --- .rubocop_todo.yml | 18 ++++----- lib/github_changelog_generator.rb | 66 +++++++++++++++++++------------ 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b72ff7b..989cb63 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-14 16:51:06 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 13:30:52 +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: 15 +# Offense count: 17 Metrics/AbcSize: - Enabled: false + Max: 73 # Offense count: 2 Metrics/BlockNesting: @@ -16,16 +16,16 @@ Metrics/BlockNesting: # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 337 + Max: 345 # Offense count: 5 Metrics/CyclomaticComplexity: Max: 15 -# Offense count: 22 +# Offense count: 24 # Configuration parameters: CountComments. Metrics/MethodLength: - Enabled: false + Max: 121 # Offense count: 5 Metrics/PerceivedComplexity: @@ -41,7 +41,7 @@ Style/AccessorMethodName: Style/AndOr: Enabled: false -# Offense count: 19 +# Offense count: 18 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. Style/BlockDelimiters: @@ -51,12 +51,12 @@ Style/BlockDelimiters: Style/Documentation: Enabled: false -# Offense count: 5 +# Offense count: 3 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 15 +# Offense count: 14 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 08c971b..45c7909 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -161,12 +161,15 @@ module GitHubChangelogGenerator # @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 { |issue| (issue.labels.map(&:name) & @options[:include_labels]).any? } + filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue| + labels = issue.labels.map(&:name) & @options[:include_labels] + (labels).any? + end if @options[:add_issues_wo_labels] - issues_wo_labels = issues.select { |issue| + issues_wo_labels = issues.select do |issue| !issue.labels.map(&:name).any? - } + end filtered_issues |= issues_wo_labels end filtered_issues @@ -178,7 +181,8 @@ module GitHubChangelogGenerator def exclude_issues_by_labels(issues) unless @options[:exclude_labels].nil? issues = issues.select { |issue| - !(issue.labels.map(&:name) & @options[:exclude_labels]).any? + var = issue.labels.map(&:name) & @options[:exclude_labels] + !(var).any? } end issues @@ -405,28 +409,7 @@ module GitHubChangelogGenerator if @options[:issues] # Generate issues: - issues_a = [] - enhancement_a = [] - bugs_a = [] - - issues.each { |dict| - added = false - dict.labels.each { |label| - if label.name == "bug" - bugs_a.push dict - added = true - next - end - if label.name == "enhancement" - enhancement_a.push dict - added = true - next - end - } - unless added - issues_a.push dict - end - } + 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]) @@ -441,6 +424,37 @@ module GitHubChangelogGenerator log end + # This method sort issues by types + # (bugs, features, or just closed issues) by labels + # + # @param [Array] issues + # @return [Array] tuple of filtered arrays: (Bugs, Enhancements Issues) + def parse_by_sections(issues) + issues_a = [] + enhancement_a = [] + bugs_a = [] + + issues.each { |dict| + added = false + dict.labels.each { |label| + if label.name == "bug" + bugs_a.push dict + added = true + next + end + if label.name == "enhancement" + enhancement_a.push dict + added = true + next + end + } + unless added + issues_a.push dict + end + } + [bugs_a, enhancement_a, issues_a] + end + # @param [Array] issues List of issues on sub-section # @param [String] prefix Nae of sub-section # @return [String] Generate ready-to-go sub-section From 536b39c961854438ecbe1b58289cfb4363e14c7c Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 13:37:06 +0300 Subject: [PATCH 03/15] add doc --- lib/github_changelog_generator/generator.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/github_changelog_generator/generator.rb b/lib/github_changelog_generator/generator.rb index f076f4b..fdc0943 100644 --- a/lib/github_changelog_generator/generator.rb +++ b/lib/github_changelog_generator/generator.rb @@ -28,6 +28,10 @@ module GitHubChangelogGenerator title_with_number end + # Encapsulate characters to make markdown look as expected. + # + # @param [String] string + # @return [String] encapsulated input string def encapsulate_string(string) string.gsub! '\\', '\\\\' From 7f696b6b09e9416bfbe973f5a4cbcbea1e397e5d Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 14:06:48 +0300 Subject: [PATCH 04/15] rename, prepare to moving methods to generator --- .rubocop_todo.yml | 9 +++++++-- bin/github_changelog_generator | 2 +- lib/CHANGELOG.md | 8 ++++++-- lib/github_changelog_generator.rb | 21 +++++++++++++++------ lib/github_changelog_generator/generator.rb | 8 ++++++++ 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 989cb63..2c75cff 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 13:30:52 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 14:06:42 +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 @@ -16,7 +16,7 @@ Metrics/BlockNesting: # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 345 + Max: 350 # Offense count: 5 Metrics/CyclomaticComplexity: @@ -72,3 +72,8 @@ Style/Next: # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. Style/RegexpLiteral: Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Enabled: false diff --git a/bin/github_changelog_generator b/bin/github_changelog_generator index f0c5fd4..5d0d48a 100755 --- a/bin/github_changelog_generator +++ b/bin/github_changelog_generator @@ -1,4 +1,4 @@ #! /usr/bin/env ruby require_relative "../lib/github_changelog_generator" -GitHubChangelogGenerator::ChangelogGenerator.new.compound_changelog +GitHubChangelogGenerator::ChangelogGenerator.new.run diff --git a/lib/CHANGELOG.md b/lib/CHANGELOG.md index a261d8f..3c6e8bd 100644 --- a/lib/CHANGELOG.md +++ b/lib/CHANGELOG.md @@ -1,8 +1,12 @@ # Change Log -## [Unreleased](https://github.com/skywinder/changelog_test/tree/HEAD) +## [0.0.4](https://github.com/skywinder/changelog_test/tree/0.0.4) (2015-05-22) -[Full Changelog](https://github.com/skywinder/changelog_test/compare/v0.0.3...HEAD) +[Full Changelog](https://github.com/skywinder/changelog_test/compare/v0.0.3...0.0.4) + +**Closed issues:** + +- Test issue, that should appear in 0.0.4 [\#3](https://github.com/skywinder/changelog_test/issues/3) **Merged pull requests:** diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 45c7909..21a055a 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -190,6 +190,18 @@ module GitHubChangelogGenerator # The entry point of this script to generate change log # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. + def run + log = compound_changelog + + output_filename = "#{@options[:output]}" + File.open(output_filename, "w") { |file| file.write(log) } + puts "Done!" + puts "Generated log placed in #{Dir.pwd}/#{output_filename}" + end + + # Main function to start change log generation + # + # @return [String] Generated change log file def compound_changelog log = "# Change Log\n\n" @@ -219,11 +231,8 @@ module GitHubChangelogGenerator end log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*" - - output_filename = "#{@options[:output]}" - File.open(output_filename, "w") { |file| file.write(log) } - puts "Done!" - puts "Generated log placed in #{Dir.pwd}/#{output_filename}" + @log = log + @log = log end # The full cycle of generation for whole project @@ -531,6 +540,6 @@ module GitHubChangelogGenerator end if __FILE__ == $PROGRAM_NAME - GitHubChangelogGenerator::ChangelogGenerator.new.compound_changelog + GitHubChangelogGenerator::ChangelogGenerator.new.run end end diff --git a/lib/github_changelog_generator/generator.rb b/lib/github_changelog_generator/generator.rb index fdc0943..345bc81 100644 --- a/lib/github_changelog_generator/generator.rb +++ b/lib/github_changelog_generator/generator.rb @@ -1,5 +1,11 @@ module GitHubChangelogGenerator class Generator + # A Generator responsible for all logic, related with change log generation from ready-to-parse issues + # + # Example: + # generator = GitHubChangelogGenerator::Generator.new + # content = generator.compound_changelog + def initialize(options = nil) @options = options end @@ -42,5 +48,7 @@ module GitHubChangelogGenerator string end + + end end From 4a96a7c0c96f40c0fd796758ab8b43e75caa6772 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 14:11:29 +0300 Subject: [PATCH 05/15] move compund to generator --- .rubocop_todo.yml | 20 +++++++---- lib/github_changelog_generator.rb | 38 +-------------------- lib/github_changelog_generator/generator.rb | 38 ++++++++++++++++++++- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2c75cff..3986315 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 14:06:42 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 14:05:01 +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 @@ -16,7 +16,7 @@ Metrics/BlockNesting: # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 350 + Max: 321 # Offense count: 5 Metrics/CyclomaticComplexity: @@ -51,6 +51,17 @@ Style/BlockDelimiters: Style/Documentation: Enabled: false +# Offense count: 2 +# Cop supports --auto-correct. +Style/EmptyLines: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/EmptyLinesAroundClassBody: + Enabled: false + # Offense count: 3 # Configuration parameters: MinBodyLength. Style/GuardClause: @@ -72,8 +83,3 @@ Style/Next: # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. Style/RegexpLiteral: Enabled: false - -# Offense count: 1 -# Cop supports --auto-correct. -Style/TrailingWhitespace: - Enabled: false diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 21a055a..5dda866 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -191,7 +191,7 @@ module GitHubChangelogGenerator # The entry point of this script to generate change log # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. def run - log = compound_changelog + log = @generator.compound_changelog output_filename = "#{@options[:output]}" File.open(output_filename, "w") { |file| file.write(log) } @@ -199,42 +199,6 @@ module GitHubChangelogGenerator puts "Generated log placed in #{Dir.pwd}/#{output_filename}" end - # Main function to start change log generation - # - # @return [String] Generated change log file - def compound_changelog - log = "# Change Log\n\n" - - if @options[:unreleased_only] - log += generate_log_between_tags(all_tags[0], nil) - elsif @options[:tag1] and @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 - # The full cycle of generation for whole project # @return [String] The complete change log def generate_log_for_all_tags diff --git a/lib/github_changelog_generator/generator.rb b/lib/github_changelog_generator/generator.rb index 345bc81..ac39bd6 100644 --- a/lib/github_changelog_generator/generator.rb +++ b/lib/github_changelog_generator/generator.rb @@ -49,6 +49,42 @@ module GitHubChangelogGenerator string end - + # Main function to start change log generation + # + # @return [String] Generated change log file + def compound_changelog + log = "# Change Log\n\n" + + if @options[:unreleased_only] + log += generate_log_between_tags(all_tags[0], nil) + elsif @options[:tag1] and @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 + + end end From 3c289de79bcfb75901b7cb2bf61e64af168ba7c0 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 15:28:43 +0300 Subject: [PATCH 06/15] move 2 other methods --- lib/github_changelog_generator.rb | 114 ++++++++------------ lib/github_changelog_generator/generator.rb | 35 ++++++ 2 files changed, 77 insertions(+), 72 deletions(-) diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 5dda866..06d89bc 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -43,6 +43,48 @@ module GitHubChangelogGenerator detect_actual_closed_dates end + + # The entry point of this script to generate change log + # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. + def run + log = @generator.compound_changelog + + output_filename = "#{@options[:output]}" + File.open(output_filename, "w") { |file| file.write(log) } + puts "Done!" + puts "Generated log placed in #{Dir.pwd}/#{output_filename}" + end + + # Async fetching of all tags dates + def fetch_tags_dates + if @options[:verbose] + print "Fetching tag dates...\r" + end + + # Async fetching tags: + threads = [] + i = 0 + all = @all_tags.count + @all_tags.each { |tag| + threads << Thread.new { + @fetcher.get_time_of_tag(tag) + if @options[:verbose] + print "Fetching tags dates: #{i + 1}/#{all}\r" + i += 1 + end + } + } + + print " \r" + + threads.each(&:join) + + if @options[:verbose] + puts "Fetching tags dates: #{i}" + end + end + + # Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags # # @return [Array] @@ -188,79 +230,7 @@ module GitHubChangelogGenerator issues end - # The entry point of this script to generate change log - # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. - def run - log = @generator.compound_changelog - output_filename = "#{@options[:output]}" - File.open(output_filename, "w") { |file| file.write(log) } - puts "Done!" - puts "Generated log placed in #{Dir.pwd}/#{output_filename}" - end - - # The full cycle of generation for whole project - # @return [String] The complete change log - def generate_log_for_all_tags - fetch_tags_dates - - if @options[:verbose] - puts "Sorting tags..." - end - - @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! - - if @options[:verbose] - puts "Generating log..." - end - - log = "" - - if @options[:unreleased] && @all_tags.count != 0 - unreleased_log = generate_log_between_tags(all_tags[0], nil) - if unreleased_log - log += unreleased_log - end - end - - (1...all_tags.size).each { |index| - log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) - } - if @all_tags.count != 0 - log += generate_log_between_tags(nil, all_tags.last) - end - - log - end - - # Async fetching of all tags dates - def fetch_tags_dates - if @options[:verbose] - print "Fetching tag dates...\r" - end - - # Async fetching tags: - threads = [] - i = 0 - all = @all_tags.count - @all_tags.each { |tag| - threads << Thread.new { - @fetcher.get_time_of_tag(tag) - if @options[:verbose] - print "Fetching tags dates: #{i + 1}/#{all}\r" - i += 1 - end - } - } - - print " \r" - - threads.each(&:join) - - if @options[:verbose] - puts "Fetching tags dates: #{i}" - end - 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 diff --git a/lib/github_changelog_generator/generator.rb b/lib/github_changelog_generator/generator.rb index ac39bd6..c7bf5a6 100644 --- a/lib/github_changelog_generator/generator.rb +++ b/lib/github_changelog_generator/generator.rb @@ -86,5 +86,40 @@ module GitHubChangelogGenerator end + # The full cycle of generation for whole project + # @return [String] The complete change log + def generate_log_for_all_tags + fetch_tags_dates + + if @options[:verbose] + puts "Sorting tags..." + end + + @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! + + if @options[:verbose] + puts "Generating log..." + end + + log = "" + + if @options[:unreleased] && @all_tags.count != 0 + unreleased_log = generate_log_between_tags(all_tags[0], nil) + if unreleased_log + log += unreleased_log + end + end + + (1...all_tags.size).each { |index| + log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) + } + if @all_tags.count != 0 + log += generate_log_between_tags(nil, all_tags.last) + end + + log + end + + end end From cf7ae57e3db3e23f4cce3699dd0a0e92b57628bd Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 15:55:37 +0300 Subject: [PATCH 07/15] prettify code. fix rubocop waning. move all methods with change log generation to generator --- .rubocop_todo.yml | 13 +- lib/github_changelog_generator.rb | 441 +---------------- lib/github_changelog_generator/generator.rb | 125 ----- .../generator/generator.rb | 442 ++++++++++++++++++ .../generator/generator_fetcher.rb | 14 + .../generator/generator_processor.rb | 110 +++++ 6 files changed, 571 insertions(+), 574 deletions(-) delete mode 100644 lib/github_changelog_generator/generator.rb create mode 100644 lib/github_changelog_generator/generator/generator.rb create mode 100644 lib/github_changelog_generator/generator/generator_fetcher.rb create mode 100644 lib/github_changelog_generator/generator/generator_processor.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3986315..3a25a49 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 14:05:01 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 15:54:38 +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 @@ -16,7 +16,7 @@ Metrics/BlockNesting: # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 321 + Max: 294 # Offense count: 5 Metrics/CyclomaticComplexity: @@ -47,16 +47,11 @@ Style/AndOr: Style/BlockDelimiters: Enabled: false -# Offense count: 4 +# Offense count: 6 Style/Documentation: Enabled: false -# Offense count: 2 -# Cop supports --auto-correct. -Style/EmptyLines: - Enabled: false - -# Offense count: 1 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/EmptyLinesAroundClassBody: diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index 06d89bc..a4fed13 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -6,44 +6,21 @@ require "colorize" require "benchmark" require_relative "github_changelog_generator/parser" -require_relative "github_changelog_generator/generator" +require_relative "github_changelog_generator/generator/generator" require_relative "github_changelog_generator/version" require_relative "github_changelog_generator/reader" -require_relative "github_changelog_generator/fetcher" module GitHubChangelogGenerator - # Default error for ChangelogGenerator - class ChangelogGeneratorError < StandardError - end - # Main class and entry point for this script. class ChangelogGenerator - attr_accessor :options, :all_tags, :github # Class, responsible for whole change log generation cycle # @return initialised instance of ChangelogGenerator def initialize @options = Parser.parse_options - - @fetcher = GitHubChangelogGenerator::Fetcher.new @options - @generator = Generator.new @options - - # @all_tags = get_filtered_tags - @all_tags = @fetcher.get_all_tags - - # TODO: refactor this double asssign of @issues and @pull_requests and move all logic in one method - @issues, @pull_requests = @fetcher.fetch_closed_issues_and_pr - - @pull_requests = @options[:pulls] ? get_filtered_pull_requests : [] - - @issues = @options[:issues] ? get_filtered_issues : [] - - fetch_event_for_issues_and_pr - detect_actual_closed_dates end - # The entry point of this script to generate change log # @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags. def run @@ -55,422 +32,6 @@ module GitHubChangelogGenerator puts "Generated log placed in #{Dir.pwd}/#{output_filename}" end - # Async fetching of all tags dates - def fetch_tags_dates - if @options[:verbose] - print "Fetching tag dates...\r" - end - - # Async fetching tags: - threads = [] - i = 0 - all = @all_tags.count - @all_tags.each { |tag| - threads << Thread.new { - @fetcher.get_time_of_tag(tag) - if @options[:verbose] - print "Fetching tags dates: #{i + 1}/#{all}\r" - i += 1 - end - } - } - - print " \r" - - threads.each(&:join) - - if @options[:verbose] - puts "Fetching tags dates: #{i}" - end - 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 } - end - filtered_tags - end - - def detect_actual_closed_dates - if @options[:verbose] - print "Fetching closed dates for issues...\r" - end - - threads = [] - - @issues.each { |issue| - threads << Thread.new { - find_closed_date_by_commit(issue) - } - } - - @pull_requests.each { |pull_request| - threads << Thread.new { - find_closed_date_by_commit(pull_request) - } - } - threads.each(&:join) - - if @options[:verbose] - puts "Fetching closed dates for issues: Done!" - end - end - - # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. - # @param [Hash] issue - def find_closed_date_by_commit(issue) - unless issue["events"].nil? - # if it's PR -> then find "merged event", in case of usual issue -> fond closed date - compare_string = issue[:merged_at].nil? ? "closed" : "merged" - # reverse! - to find latest closed event. (event goes in date order) - issue["events"].reverse!.each { |event| - if event[:event].eql? compare_string - if event[:commit_id].nil? - issue[:actual_date] = issue[:closed_at] - else - begin - commit = @fetcher.fetch_commit(event) - issue[:actual_date] = commit[:author][:date] - rescue - puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow - issue[:actual_date] = issue[:closed_at] - end - end - break - end - } - end - # TODO: assert issues, that remain without 'actual_date' hash for some reason. - end - - def print_json(json) - puts JSON.pretty_generate(json) - end - - # This method fetches missing params for PR and filter them by specified options - # It include add all PR's with labels from @options[:include_labels] array - # And exclude all from :exclude_labels array. - # @return [Array] filtered PR's - def get_filtered_pull_requests - filter_merged_pull_requests - - filtered_pull_requests = include_issues_by_labels(@pull_requests) - - filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests) - - if @options[:verbose] - puts "Filtered pull requests: #{filtered_pull_requests.count}" - end - - filtered_pull_requests - end - - # This method filter only merged PR and - # fetch missing required attributes for pull requests - # :merged_at - is a date, when issue PR was merged. - # More correct to use merged date, rather than closed date. - def filter_merged_pull_requests - if @options[:verbose] - print "Fetching merged dates...\r" - end - pull_requests = @fetcher.fetch_closed_pull_requests - - @pull_requests.each { |pr| - fetched_pr = pull_requests.find { |fpr| - fpr.number == pr.number - } - pr[:merged_at] = fetched_pr[:merged_at] - pull_requests.delete(fetched_pr) - } - - @pull_requests.select! do |pr| - !pr[:merged_at].nil? - end - 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 - - if @options[:add_issues_wo_labels] - issues_wo_labels = issues.select do |issue| - !issue.labels.map(&:name).any? - end - filtered_issues |= issues_wo_labels - end - filtered_issues - end - - # delete all labels with labels from @options[:exclude_labels] array - # @param [Array] issues - # @return [Array] filtered array - def exclude_issues_by_labels(issues) - unless @options[:exclude_labels].nil? - issues = issues.select { |issue| - var = issue.labels.map(&:name) & @options[:exclude_labels] - !(var).any? - } - end - issues - 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_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"] - 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 "" - end - - create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) - end - - def filter_by_milestone(filtered_issues, newer_tag_name, src_array) - filtered_issues.select! { |issue| - # leave issues without milestones - if issue.milestone.nil? - true - else - # check, that this milestone in tag list: - @all_tags.find { |tag| tag.name == issue.milestone.title }.nil? - end - } - unless newer_tag_name.nil? - - # add missed issues (according milestones) - issues_to_add = src_array.select { |issue| - if issue.milestone.nil? - false - else - # check, that this milestone in tag list: - milestone_is_tag = @all_tags.find { |tag| - tag.name == issue.milestone.title - } - - if milestone_is_tag.nil? - false - else - issue.milestone.title == newer_tag_name - end - end - } - - filtered_issues |= issues_to_add - end - filtered_issues - end - - # Method filter issues, that belong only specified tag range - # @param [Array] array of issues to filter - # @param [Symbol] hash_key key of date value default is :actual_date - # @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 - # @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? - - 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 { |req| - if req[hash_key] - t = 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 - - if newer_tag_time.nil? - 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 - else - false - end - } - 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 [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 - - github_site = options[:github_site] || "https://github.com" - project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}" - - log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url) - - 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]) - end - - if @options[:pulls] - # Generate pull requests: - log += generate_sub_section(pull_requests, @options[:merge_prefix]) - end - - log - end - - # This method sort issues by types - # (bugs, features, or just closed issues) by labels - # - # @param [Array] issues - # @return [Array] tuple of filtered arrays: (Bugs, Enhancements Issues) - def parse_by_sections(issues) - issues_a = [] - enhancement_a = [] - bugs_a = [] - - issues.each { |dict| - added = false - dict.labels.each { |label| - if label.name == "bug" - bugs_a.push dict - added = true - next - end - if label.name == "enhancement" - enhancement_a.push dict - added = true - next - end - } - unless added - issues_a.push dict - end - } - [bugs_a, enhancement_a, issues_a] - end - - # @param [Array] issues List of issues on sub-section - # @param [String] prefix Nae of sub-section - # @return [String] Generate ready-to-go sub-section - def generate_sub_section(issues, prefix) - log = "" - - if options[:simple_list] != true && issues.any? - log += "#{prefix}\n\n" - end - - if issues.any? - issues.each { |issue| - merge_string = @generator.get_string_for_issue(issue) - log += "- #{merge_string}\n\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[:dateformat] - - # Generate tag name and link - if newer_tag_name.equal? @options[:unreleased_label] - log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n" - else - log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{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 - - # Filter issues according labels - # @return [Array] Filtered issues - def get_filtered_issues - filtered_issues = include_issues_by_labels(@issues) - - filtered_issues = exclude_issues_by_labels(filtered_issues) - - if @options[:verbose] - puts "Filtered issues: #{filtered_issues.count}" - end - - filtered_issues - end - - # Fetch event for issues and pull requests - # @return [Array] array of fetched issues - def fetch_event_for_issues_and_pr - if @options[:verbose] - print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r" - end - - # Async fetching events: - - @fetcher.fetch_events_async(@issues + @pull_requests) - end end if __FILE__ == $PROGRAM_NAME diff --git a/lib/github_changelog_generator/generator.rb b/lib/github_changelog_generator/generator.rb deleted file mode 100644 index c7bf5a6..0000000 --- a/lib/github_changelog_generator/generator.rb +++ /dev/null @@ -1,125 +0,0 @@ -module GitHubChangelogGenerator - class Generator - # A Generator responsible for all logic, related with change log generation from ready-to-parse issues - # - # Example: - # generator = GitHubChangelogGenerator::Generator.new - # content = generator.compound_changelog - - def initialize(options = nil) - @options = options - 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](https://github.com/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})" - - unless issue.pull_request.nil? - if @options[:author] - if issue.user.nil? - title_with_number += " ({Null user})" - else - title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))" - end - end - end - title_with_number - end - - # Encapsulate characters to make markdown look as expected. - # - # @param [String] string - # @return [String] encapsulated input string - def encapsulate_string(string) - string.gsub! '\\', '\\\\' - - encpas_chars = %w(> * _ \( \) [ ] #) - encpas_chars.each do |char| - string.gsub! char, "\\#{char}" - end - - string - end - - # Main function to start change log generation - # - # @return [String] Generated change log file - def compound_changelog - log = "# Change Log\n\n" - - if @options[:unreleased_only] - log += generate_log_between_tags(all_tags[0], nil) - elsif @options[:tag1] and @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 - - - # The full cycle of generation for whole project - # @return [String] The complete change log - def generate_log_for_all_tags - fetch_tags_dates - - if @options[:verbose] - puts "Sorting tags..." - end - - @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! - - if @options[:verbose] - puts "Generating log..." - end - - log = "" - - if @options[:unreleased] && @all_tags.count != 0 - unreleased_log = generate_log_between_tags(all_tags[0], nil) - if unreleased_log - log += unreleased_log - end - end - - (1...all_tags.size).each { |index| - log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) - } - if @all_tags.count != 0 - log += generate_log_between_tags(nil, all_tags.last) - end - - log - end - - - end -end diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb new file mode 100644 index 0000000..30c91e5 --- /dev/null +++ b/lib/github_changelog_generator/generator/generator.rb @@ -0,0 +1,442 @@ +require "github_changelog_generator/fetcher" +require_relative "generator_processor" +require_relative "generator_fetcher" + +module GitHubChangelogGenerator + # Default error for ChangelogGenerator + class ChangelogGeneratorError < StandardError + end + + class Generator + attr_accessor :options, :all_tags, :github + + # A Generator responsible for all logic, related with change log generation from ready-to-parse issues + # + # Example: + # generator = GitHubChangelogGenerator::Generator.new + # content = generator.compound_changelog + def initialize(options = nil) + @options = options + + @fetcher = GitHubChangelogGenerator::Fetcher.new @options + # @all_tags = get_filtered_tags + @all_tags = @fetcher.get_all_tags + + # TODO: refactor this double asssign of @issues and @pull_requests and move all logic in one method + @issues, @pull_requests = @fetcher.fetch_closed_issues_and_pr + + @pull_requests = @options[:pulls] ? get_filtered_pull_requests : [] + + @issues = @options[:issues] ? get_filtered_issues : [] + + fetch_event_for_issues_and_pr + detect_actual_closed_dates + 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](https://github.com/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})" + + unless issue.pull_request.nil? + if @options[:author] + if issue.user.nil? + title_with_number += " ({Null user})" + else + title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))" + end + end + end + title_with_number + end + + # Encapsulate characters to make markdown look as expected. + # + # @param [String] string + # @return [String] encapsulated input string + def encapsulate_string(string) + string.gsub! '\\', '\\\\' + + encpas_chars = %w(> * _ \( \) [ ] #) + encpas_chars.each do |char| + string.gsub! char, "\\#{char}" + end + + string + end + + # Main function to start change log generation + # + # @return [String] Generated change log file + def compound_changelog + log = "# Change Log\n\n" + + if @options[:unreleased_only] + log += generate_log_between_tags(all_tags[0], nil) + elsif @options[:tag1] and @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 + + # Async fetching of all tags dates + def fetch_tags_dates + if @options[:verbose] + print "Fetching tag dates...\r" + end + + # Async fetching tags: + threads = [] + i = 0 + all = @all_tags.count + @all_tags.each { |tag| + threads << Thread.new { + @fetcher.get_time_of_tag(tag) + if @options[:verbose] + print "Fetching tags dates: #{i + 1}/#{all}\r" + i += 1 + end + } + } + + print " \r" + + threads.each(&:join) + + if @options[:verbose] + puts "Fetching tags dates: #{i}" + end + 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 } + end + filtered_tags + end + + def detect_actual_closed_dates + if @options[:verbose] + print "Fetching closed dates for issues...\r" + end + + threads = [] + + @issues.each { |issue| + threads << Thread.new { + find_closed_date_by_commit(issue) + } + } + + @pull_requests.each { |pull_request| + threads << Thread.new { + find_closed_date_by_commit(pull_request) + } + } + threads.each(&:join) + + if @options[:verbose] + puts "Fetching closed dates for issues: Done!" + end + end + + # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. + # @param [Hash] issue + def find_closed_date_by_commit(issue) + unless issue["events"].nil? + # if it's PR -> then find "merged event", in case of usual issue -> fond closed date + compare_string = issue[:merged_at].nil? ? "closed" : "merged" + # reverse! - to find latest closed event. (event goes in date order) + issue["events"].reverse!.each { |event| + if event[:event].eql? compare_string + if event[:commit_id].nil? + issue[:actual_date] = issue[:closed_at] + else + begin + commit = @fetcher.fetch_commit(event) + issue[:actual_date] = commit[:author][:date] + rescue + puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow + issue[:actual_date] = issue[:closed_at] + end + end + break + end + } + end + # TODO: assert issues, that remain without 'actual_date' hash for some reason. + end + + def print_json(json) + puts JSON.pretty_generate(json) + end + + # This method fetches missing params for PR and filter them by specified options + # It include add all PR's with labels from @options[:include_labels] array + # And exclude all from :exclude_labels array. + # @return [Array] filtered PR's + def get_filtered_pull_requests + filter_merged_pull_requests + + filtered_pull_requests = include_issues_by_labels(@pull_requests) + + filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests) + + if @options[:verbose] + puts "Filtered pull requests: #{filtered_pull_requests.count}" + end + + filtered_pull_requests + end + + # This method filter only merged PR and + # fetch missing required attributes for pull requests + # :merged_at - is a date, when issue PR was merged. + # More correct to use merged date, rather than closed date. + def filter_merged_pull_requests + if @options[:verbose] + print "Fetching merged dates...\r" + end + pull_requests = @fetcher.fetch_closed_pull_requests + + @pull_requests.each { |pr| + fetched_pr = pull_requests.find { |fpr| + fpr.number == pr.number + } + pr[:merged_at] = fetched_pr[:merged_at] + pull_requests.delete(fetched_pr) + } + + @pull_requests.select! do |pr| + !pr[:merged_at].nil? + end + 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 + + if @options[:add_issues_wo_labels] + issues_wo_labels = issues.select do |issue| + !issue.labels.map(&:name).any? + end + filtered_issues |= issues_wo_labels + end + filtered_issues + end + + # delete all labels with labels from @options[:exclude_labels] array + # @param [Array] issues + # @return [Array] filtered array + def exclude_issues_by_labels(issues) + unless @options[:exclude_labels].nil? + issues = issues.select { |issue| + var = issue.labels.map(&:name) & @options[:exclude_labels] + !(var).any? + } + end + issues + end + + def filter_by_milestone(filtered_issues, newer_tag_name, src_array) + filtered_issues.select! { |issue| + # leave issues without milestones + if issue.milestone.nil? + true + else + # check, that this milestone in tag list: + @all_tags.find { |tag| tag.name == issue.milestone.title }.nil? + end + } + unless newer_tag_name.nil? + + # add missed issues (according milestones) + issues_to_add = src_array.select { |issue| + if issue.milestone.nil? + false + else + # check, that this milestone in tag list: + milestone_is_tag = @all_tags.find { |tag| + tag.name == issue.milestone.title + } + + if milestone_is_tag.nil? + false + else + issue.milestone.title == newer_tag_name + end + end + } + + filtered_issues |= issues_to_add + end + filtered_issues + end + + # Method filter issues, that belong only specified tag range + # @param [Array] array of issues to filter + # @param [Symbol] hash_key key of date value default is :actual_date + # @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 + # @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? + + 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 { |req| + if req[hash_key] + t = 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 + + if newer_tag_time.nil? + 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 + else + false + end + } + 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 [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 + + github_site = options[:github_site] || "https://github.com" + project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}" + + log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url) + + 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]) + end + + if @options[:pulls] + # Generate pull requests: + log += generate_sub_section(pull_requests, @options[:merge_prefix]) + end + + log + end + + # This method sort issues by types + # (bugs, features, or just closed issues) by labels + # + # @param [Array] issues + # @return [Array] tuple of filtered arrays: (Bugs, Enhancements Issues) + def parse_by_sections(issues) + issues_a = [] + enhancement_a = [] + bugs_a = [] + + issues.each { |dict| + added = false + dict.labels.each { |label| + if label.name == "bug" + bugs_a.push dict + added = true + next + end + if label.name == "enhancement" + enhancement_a.push dict + added = true + next + end + } + unless added + issues_a.push dict + end + } + [bugs_a, enhancement_a, issues_a] + end + + # Filter issues according labels + # @return [Array] Filtered issues + def get_filtered_issues + filtered_issues = include_issues_by_labels(@issues) + + filtered_issues = exclude_issues_by_labels(filtered_issues) + + if @options[:verbose] + puts "Filtered issues: #{filtered_issues.count}" + end + + filtered_issues + end + + end +end diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb new file mode 100644 index 0000000..45ec1a2 --- /dev/null +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -0,0 +1,14 @@ +module GitHubChangelogGenerator + class Generator + # Fetch event for issues and pull requests + # @return [Array] array of fetched issues + def fetch_event_for_issues_and_pr + if @options[:verbose] + print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r" + end + + # Async fetching events: + @fetcher.fetch_events_async(@issues + @pull_requests) + end + end +end diff --git a/lib/github_changelog_generator/generator/generator_processor.rb b/lib/github_changelog_generator/generator/generator_processor.rb new file mode 100644 index 0000000..9ff3ea3 --- /dev/null +++ b/lib/github_changelog_generator/generator/generator_processor.rb @@ -0,0 +1,110 @@ +module GitHubChangelogGenerator + class Generator + # @param [Array] issues List of issues on sub-section + # @param [String] prefix Nae of sub-section + # @return [String] Generate ready-to-go sub-section + def generate_sub_section(issues, prefix) + log = "" + + if options[:simple_list] != true && issues.any? + log += "#{prefix}\n\n" + end + + if issues.any? + issues.each { |issue| + merge_string = get_string_for_issue(issue) + log += "- #{merge_string}\n\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[:dateformat] + + # Generate tag name and link + if newer_tag_name.equal? @options[:unreleased_label] + log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n" + else + log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{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_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"] + 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 "" + end + + create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) + end + + # The full cycle of generation for whole project + # @return [String] The complete change log + def generate_log_for_all_tags + fetch_tags_dates + + if @options[:verbose] + puts "Sorting tags..." + end + + @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! + + if @options[:verbose] + puts "Generating log..." + end + + log = "" + + if @options[:unreleased] && @all_tags.count != 0 + unreleased_log = generate_log_between_tags(all_tags[0], nil) + if unreleased_log + log += unreleased_log + end + end + + (1...all_tags.size).each { |index| + log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) + } + if @all_tags.count != 0 + log += generate_log_between_tags(nil, all_tags.last) + end + + log + end + + end +end From 9a24eb1cb3859f399e712e889e81a4636b0d2a05 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 15:59:29 +0300 Subject: [PATCH 08/15] rubocop autofixes --- .rubocop_todo.yml | 32 +------ lib/github_changelog_generator.rb | 2 - lib/github_changelog_generator/fetcher.rb | 12 +-- .../generator/generator.rb | 93 ++++++++----------- .../generator/generator_processor.rb | 25 ++--- 5 files changed, 54 insertions(+), 110 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3a25a49..96a57ef 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 15:54:38 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 15:59:03 +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 @@ -16,13 +16,13 @@ Metrics/BlockNesting: # Offense count: 3 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 294 + Max: 280 # Offense count: 5 Metrics/CyclomaticComplexity: Max: 15 -# Offense count: 24 +# Offense count: 23 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 121 @@ -35,39 +35,15 @@ Metrics/PerceivedComplexity: Style/AccessorMethodName: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/AndOr: - Enabled: false - -# Offense count: 18 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. -Style/BlockDelimiters: - Enabled: false - # Offense count: 6 Style/Documentation: Enabled: false -# Offense count: 4 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/EmptyLinesAroundClassBody: - Enabled: false - -# Offense count: 3 +# Offense count: 1 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 14 -# Cop supports --auto-correct. -# Configuration parameters: MaxLineLength. -Style/IfUnlessModifier: - Enabled: false - # Offense count: 2 # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. Style/Next: diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index a4fed13..dea6253 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -13,7 +13,6 @@ require_relative "github_changelog_generator/reader" module GitHubChangelogGenerator # Main class and entry point for this script. class ChangelogGenerator - # Class, responsible for whole change log generation cycle # @return initialised instance of ChangelogGenerator def initialize @@ -31,7 +30,6 @@ module GitHubChangelogGenerator puts "Done!" puts "Generated log placed in #{Dir.pwd}/#{output_filename}" end - end if __FILE__ == $PROGRAM_NAME diff --git a/lib/github_changelog_generator/fetcher.rb b/lib/github_changelog_generator/fetcher.rb index 863a422..5e08ff9 100644 --- a/lib/github_changelog_generator/fetcher.rb +++ b/lib/github_changelog_generator/fetcher.rb @@ -41,9 +41,7 @@ module GitHubChangelogGenerator def fetch_github_token env_var = @options[:token] ? @options[:token] : (ENV.fetch CHANGELOG_GITHUB_TOKEN, nil) - unless env_var - @logger.warn NO_TOKEN_PROVIDED.yellow - end + @logger.warn NO_TOKEN_PROVIDED.yellow unless env_var env_var end @@ -51,9 +49,7 @@ module GitHubChangelogGenerator # Fetch all tags from repo # @return [Array] array of tags def get_all_tags - if @options[:verbose] - print "Fetching tags...\r" - end + print "Fetching tags...\r" if @options[:verbose] tags = [] @@ -98,9 +94,7 @@ Make sure, that you push tags to remote repo via 'git push --tags'".yellow # (pull request is kind of issue in term of GitHub) # @return [Tuple] with (issues, pull-requests) def fetch_closed_issues_and_pr - if @options[:verbose] - print "Fetching closed issues...\r" - end + print "Fetching closed issues...\r" if @options[:verbose] issues = [] begin diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index 30c91e5..95bc165 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -80,7 +80,7 @@ module GitHubChangelogGenerator if @options[:unreleased_only] log += generate_log_between_tags(all_tags[0], nil) - elsif @options[:tag1] and @options[:tag2] + elsif @options[:tag1] && @options[:tag2] tag1 = @options[:tag1] tag2 = @options[:tag2] tags_strings = [] @@ -110,31 +110,27 @@ module GitHubChangelogGenerator # Async fetching of all tags dates def fetch_tags_dates - if @options[:verbose] - print "Fetching tag dates...\r" - end + print "Fetching tag dates...\r" if @options[:verbose] # Async fetching tags: threads = [] i = 0 all = @all_tags.count - @all_tags.each { |tag| - threads << Thread.new { + @all_tags.each do |tag| + threads << Thread.new do @fetcher.get_time_of_tag(tag) if @options[:verbose] print "Fetching tags dates: #{i + 1}/#{all}\r" i += 1 end - } - } + end + end print " \r" threads.each(&:join) - if @options[:verbose] - puts "Fetching tags dates: #{i}" - end + puts "Fetching tags dates: #{i}" if @options[:verbose] end # Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags @@ -155,28 +151,24 @@ module GitHubChangelogGenerator end def detect_actual_closed_dates - if @options[:verbose] - print "Fetching closed dates for issues...\r" - end + print "Fetching closed dates for issues...\r" if @options[:verbose] threads = [] - @issues.each { |issue| - threads << Thread.new { + @issues.each do |issue| + threads << Thread.new do find_closed_date_by_commit(issue) - } - } + end + end - @pull_requests.each { |pull_request| - threads << Thread.new { + @pull_requests.each do |pull_request| + threads << Thread.new do find_closed_date_by_commit(pull_request) - } - } + end + end threads.each(&:join) - if @options[:verbose] - puts "Fetching closed dates for issues: Done!" - end + puts "Fetching closed dates for issues: Done!" if @options[:verbose] end # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. @@ -186,7 +178,7 @@ module GitHubChangelogGenerator # if it's PR -> then find "merged event", in case of usual issue -> fond closed date compare_string = issue[:merged_at].nil? ? "closed" : "merged" # reverse! - to find latest closed event. (event goes in date order) - issue["events"].reverse!.each { |event| + issue["events"].reverse!.each do |event| if event[:event].eql? compare_string if event[:commit_id].nil? issue[:actual_date] = issue[:closed_at] @@ -201,7 +193,7 @@ module GitHubChangelogGenerator end break end - } + end end # TODO: assert issues, that remain without 'actual_date' hash for some reason. end @@ -233,18 +225,16 @@ module GitHubChangelogGenerator # :merged_at - is a date, when issue PR was merged. # More correct to use merged date, rather than closed date. def filter_merged_pull_requests - if @options[:verbose] - print "Fetching merged dates...\r" - end + print "Fetching merged dates...\r" if @options[:verbose] pull_requests = @fetcher.fetch_closed_pull_requests - @pull_requests.each { |pr| - fetched_pr = pull_requests.find { |fpr| + @pull_requests.each do |pr| + fetched_pr = pull_requests.find do |fpr| fpr.number == pr.number - } + end pr[:merged_at] = fetched_pr[:merged_at] pull_requests.delete(fetched_pr) - } + end @pull_requests.select! do |pr| !pr[:merged_at].nil? @@ -274,16 +264,16 @@ module GitHubChangelogGenerator # @return [Array] filtered array def exclude_issues_by_labels(issues) unless @options[:exclude_labels].nil? - issues = issues.select { |issue| + issues = issues.select do |issue| var = issue.labels.map(&:name) & @options[:exclude_labels] !(var).any? - } + end end issues end def filter_by_milestone(filtered_issues, newer_tag_name, src_array) - filtered_issues.select! { |issue| + filtered_issues.select! do |issue| # leave issues without milestones if issue.milestone.nil? true @@ -291,18 +281,18 @@ module GitHubChangelogGenerator # check, that this milestone in tag list: @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 { |issue| + 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 { |tag| + milestone_is_tag = @all_tags.find do |tag| tag.name == issue.milestone.title - } + end if milestone_is_tag.nil? false @@ -310,7 +300,7 @@ module GitHubChangelogGenerator issue.milestone.title == newer_tag_name end end - } + end filtered_issues |= issues_to_add end @@ -329,7 +319,7 @@ module GitHubChangelogGenerator 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 { |req| + array.select do |req| if req[hash_key] t = Time.parse(req[hash_key]).utc @@ -351,7 +341,7 @@ module GitHubChangelogGenerator else false end - } + end end # Generates log for section with header and body @@ -403,9 +393,9 @@ module GitHubChangelogGenerator enhancement_a = [] bugs_a = [] - issues.each { |dict| + issues.each do |dict| added = false - dict.labels.each { |label| + dict.labels.each do |label| if label.name == "bug" bugs_a.push dict added = true @@ -416,11 +406,9 @@ module GitHubChangelogGenerator added = true next end - } - unless added - issues_a.push dict end - } + issues_a.push dict unless added + end [bugs_a, enhancement_a, issues_a] end @@ -431,12 +419,9 @@ module GitHubChangelogGenerator filtered_issues = exclude_issues_by_labels(filtered_issues) - if @options[:verbose] - puts "Filtered issues: #{filtered_issues.count}" - end + puts "Filtered issues: #{filtered_issues.count}" if @options[:verbose] filtered_issues end - end end diff --git a/lib/github_changelog_generator/generator/generator_processor.rb b/lib/github_changelog_generator/generator/generator_processor.rb index 9ff3ea3..62a8747 100644 --- a/lib/github_changelog_generator/generator/generator_processor.rb +++ b/lib/github_changelog_generator/generator/generator_processor.rb @@ -6,15 +6,13 @@ module GitHubChangelogGenerator def generate_sub_section(issues, prefix) log = "" - if options[:simple_list] != true && issues.any? - log += "#{prefix}\n\n" - end + log += "#{prefix}\n\n" if options[:simple_list] != true && issues.any? if issues.any? - issues.each { |issue| + issues.each do |issue| merge_string = get_string_for_issue(issue) log += "- #{merge_string}\n\n" - } + end end log end @@ -77,34 +75,27 @@ module GitHubChangelogGenerator def generate_log_for_all_tags fetch_tags_dates - if @options[:verbose] - puts "Sorting tags..." - end + puts "Sorting tags..." if @options[:verbose] @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! - if @options[:verbose] - puts "Generating log..." - end + puts "Generating log..." if @options[:verbose] log = "" if @options[:unreleased] && @all_tags.count != 0 unreleased_log = generate_log_between_tags(all_tags[0], nil) - if unreleased_log - log += unreleased_log - end + log += unreleased_log if unreleased_log end - (1...all_tags.size).each { |index| + (1...all_tags.size).each do |index| log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) - } + end if @all_tags.count != 0 log += generate_log_between_tags(nil, all_tags.last) end log end - end end From 3f076b3069483237f5f238cb54acd667ed48da29 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 16:39:08 +0300 Subject: [PATCH 09/15] spread methods by files --- .../generator/generator.rb | 311 +----------------- .../generator/generator_fetcher.rb | 74 +++++ .../generator/generator_generation.rb | 161 +++++++++ .../generator/generator_processor.rb | 219 ++++++++---- 4 files changed, 383 insertions(+), 382 deletions(-) create mode 100644 lib/github_changelog_generator/generator/generator_generation.rb diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index 95bc165..1799958 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -1,6 +1,7 @@ require "github_changelog_generator/fetcher" -require_relative "generator_processor" +require_relative "generator_generation" require_relative "generator_fetcher" +require_relative "generator_processor" module GitHubChangelogGenerator # Default error for ChangelogGenerator @@ -33,30 +34,6 @@ module GitHubChangelogGenerator detect_actual_closed_dates 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](https://github.com/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})" - - unless issue.pull_request.nil? - if @options[:author] - if issue.user.nil? - title_with_number += " ({Null user})" - else - title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))" - end - end - end - title_with_number - end - # Encapsulate characters to make markdown look as expected. # # @param [String] string @@ -72,278 +49,6 @@ module GitHubChangelogGenerator string end - # Main function to start change log generation - # - # @return [String] Generated change log file - def compound_changelog - 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 - - # 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| - threads << Thread.new do - @fetcher.get_time_of_tag(tag) - if @options[:verbose] - print "Fetching tags dates: #{i + 1}/#{all}\r" - i += 1 - end - end - end - - print " \r" - - threads.each(&:join) - - puts "Fetching tags dates: #{i}" if @options[:verbose] - 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 } - end - filtered_tags - end - - def detect_actual_closed_dates - print "Fetching closed dates for issues...\r" if @options[:verbose] - - threads = [] - - @issues.each do |issue| - threads << Thread.new do - find_closed_date_by_commit(issue) - end - end - - @pull_requests.each do |pull_request| - threads << Thread.new do - find_closed_date_by_commit(pull_request) - end - end - threads.each(&:join) - - puts "Fetching closed dates for issues: Done!" if @options[:verbose] - end - - # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. - # @param [Hash] issue - def find_closed_date_by_commit(issue) - unless issue["events"].nil? - # if it's PR -> then find "merged event", in case of usual issue -> fond closed date - compare_string = issue[:merged_at].nil? ? "closed" : "merged" - # reverse! - to find latest closed event. (event goes in date order) - issue["events"].reverse!.each do |event| - if event[:event].eql? compare_string - if event[:commit_id].nil? - issue[:actual_date] = issue[:closed_at] - else - begin - commit = @fetcher.fetch_commit(event) - issue[:actual_date] = commit[:author][:date] - rescue - puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow - issue[:actual_date] = issue[:closed_at] - end - end - break - end - end - end - # TODO: assert issues, that remain without 'actual_date' hash for some reason. - end - - def print_json(json) - puts JSON.pretty_generate(json) - end - - # This method fetches missing params for PR and filter them by specified options - # It include add all PR's with labels from @options[:include_labels] array - # And exclude all from :exclude_labels array. - # @return [Array] filtered PR's - def get_filtered_pull_requests - filter_merged_pull_requests - - filtered_pull_requests = include_issues_by_labels(@pull_requests) - - filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests) - - if @options[:verbose] - puts "Filtered pull requests: #{filtered_pull_requests.count}" - end - - filtered_pull_requests - end - - # This method filter only merged PR and - # fetch missing required attributes for pull requests - # :merged_at - is a date, when issue PR was merged. - # More correct to use merged date, rather than closed date. - def filter_merged_pull_requests - print "Fetching merged dates...\r" if @options[:verbose] - pull_requests = @fetcher.fetch_closed_pull_requests - - @pull_requests.each do |pr| - fetched_pr = pull_requests.find do |fpr| - fpr.number == pr.number - end - pr[:merged_at] = fetched_pr[:merged_at] - pull_requests.delete(fetched_pr) - end - - @pull_requests.select! do |pr| - !pr[:merged_at].nil? - end - 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 - - if @options[:add_issues_wo_labels] - issues_wo_labels = issues.select do |issue| - !issue.labels.map(&:name).any? - end - filtered_issues |= issues_wo_labels - end - filtered_issues - end - - # delete all labels with labels from @options[:exclude_labels] array - # @param [Array] issues - # @return [Array] filtered array - def exclude_issues_by_labels(issues) - unless @options[:exclude_labels].nil? - issues = issues.select do |issue| - var = issue.labels.map(&:name) & @options[:exclude_labels] - !(var).any? - end - end - issues - end - - def filter_by_milestone(filtered_issues, newer_tag_name, src_array) - filtered_issues.select! do |issue| - # leave issues without milestones - if issue.milestone.nil? - true - else - # check, that this milestone in tag list: - @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 - # @param [Array] array of issues to filter - # @param [Symbol] hash_key key of date value default is :actual_date - # @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 - # @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? - - 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 - - if older_tag_time.nil? - tag_in_range_old = true - else - tag_in_range_old = t > older_tag_time - end - - if newer_tag_time.nil? - 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 - else - false - end - end - end - # Generates log for section with header and body # # @param [Array] pull_requests List or PR's in new section @@ -411,17 +116,5 @@ module GitHubChangelogGenerator end [bugs_a, enhancement_a, issues_a] end - - # Filter issues according labels - # @return [Array] Filtered issues - def get_filtered_issues - filtered_issues = include_issues_by_labels(@issues) - - filtered_issues = exclude_issues_by_labels(filtered_issues) - - puts "Filtered issues: #{filtered_issues.count}" if @options[:verbose] - - filtered_issues - end end end diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index 45ec1a2..bf7a2ef 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -10,5 +10,79 @@ module GitHubChangelogGenerator # Async fetching events: @fetcher.fetch_events_async(@issues + @pull_requests) end + + # 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| + threads << Thread.new do + @fetcher.get_time_of_tag(tag) + if @options[:verbose] + print "Fetching tags dates: #{i + 1}/#{all}\r" + i += 1 + end + end + end + + print " \r" + + threads.each(&:join) + + puts "Fetching tags dates: #{i}" if @options[:verbose] + end + + # Find correct closed dates, if issues was closed by commits + def detect_actual_closed_dates + print "Fetching closed dates for issues...\r" if @options[:verbose] + + threads = [] + + @issues.each do |issue| + threads << Thread.new do + find_closed_date_by_commit(issue) + end + end + + @pull_requests.each do |pull_request| + threads << Thread.new do + find_closed_date_by_commit(pull_request) + end + end + threads.each(&:join) + + puts "Fetching closed dates for issues: Done!" if @options[:verbose] + end + + # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. + # @param [Hash] issue + def find_closed_date_by_commit(issue) + unless issue["events"].nil? + # if it's PR -> then find "merged event", in case of usual issue -> fond closed date + compare_string = issue[:merged_at].nil? ? "closed" : "merged" + # reverse! - to find latest closed event. (event goes in date order) + issue["events"].reverse!.each do |event| + if event[:event].eql? compare_string + if event[:commit_id].nil? + issue[:actual_date] = issue[:closed_at] + else + begin + commit = @fetcher.fetch_commit(event) + issue[:actual_date] = commit[:author][:date] + rescue + puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow + issue[:actual_date] = issue[:closed_at] + end + end + break + end + end + end + # TODO: assert issues, that remain without 'actual_date' hash for some reason. + end end end diff --git a/lib/github_changelog_generator/generator/generator_generation.rb b/lib/github_changelog_generator/generator/generator_generation.rb new file mode 100644 index 0000000..c5227d9 --- /dev/null +++ b/lib/github_changelog_generator/generator/generator_generation.rb @@ -0,0 +1,161 @@ +module GitHubChangelogGenerator + class Generator + # Main function to start change log generation + # + # @return [String] Generated change log file + def compound_changelog + 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 + + # @param [Array] issues List of issues on sub-section + # @param [String] prefix Nae of sub-section + # @return [String] Generate ready-to-go sub-section + def generate_sub_section(issues, prefix) + log = "" + + log += "#{prefix}\n\n" if options[:simple_list] != true && issues.any? + + if issues.any? + issues.each do |issue| + merge_string = get_string_for_issue(issue) + log += "- #{merge_string}\n\n" + end + 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[:dateformat] + + # Generate tag name and link + if newer_tag_name.equal? @options[:unreleased_label] + log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n" + else + log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{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_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"] + 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 "" + end + + create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) + end + + # The full cycle of generation for whole project + # @return [String] The complete change log + def generate_log_for_all_tags + fetch_tags_dates + + puts "Sorting tags..." if @options[:verbose] + + @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! + + puts "Generating log..." if @options[:verbose] + + log = "" + + if @options[:unreleased] && @all_tags.count != 0 + unreleased_log = generate_log_between_tags(all_tags[0], nil) + log += unreleased_log if unreleased_log + end + + (1...all_tags.size).each do |index| + log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) + end + if @all_tags.count != 0 + log += generate_log_between_tags(nil, all_tags.last) + 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](https://github.com/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})" + + unless issue.pull_request.nil? + if @options[:author] + if issue.user.nil? + title_with_number += " ({Null user})" + else + title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))" + end + end + end + title_with_number + end + end +end diff --git a/lib/github_changelog_generator/generator/generator_processor.rb b/lib/github_changelog_generator/generator/generator_processor.rb index 62a8747..55e736c 100644 --- a/lib/github_changelog_generator/generator/generator_processor.rb +++ b/lib/github_changelog_generator/generator/generator_processor.rb @@ -1,101 +1,174 @@ module GitHubChangelogGenerator class Generator - # @param [Array] issues List of issues on sub-section - # @param [String] prefix Nae of sub-section - # @return [String] Generate ready-to-go sub-section - def generate_sub_section(issues, prefix) - log = "" - - log += "#{prefix}\n\n" if options[:simple_list] != true && issues.any? - - if issues.any? - issues.each do |issue| - merge_string = get_string_for_issue(issue) - log += "- #{merge_string}\n\n" + # delete all labels with labels from @options[:exclude_labels] array + # @param [Array] issues + # @return [Array] filtered array + def exclude_issues_by_labels(issues) + unless @options[:exclude_labels].nil? + issues = issues.select do |issue| + var = issue.labels.map(&:name) & @options[:exclude_labels] + !(var).any? end end - log + issues 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[:dateformat] - - # Generate tag name and link - if newer_tag_name.equal? @options[:unreleased_label] - log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n" - else - log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{time_string})\n\n" + def filter_by_milestone(filtered_issues, newer_tag_name, src_array) + filtered_issues.select! do |issue| + # leave issues without milestones + if issue.milestone.nil? + true + else + # check, that this milestone in tag list: + @all_tags.find { |tag| tag.name == issue.milestone.title }.nil? + end end + unless newer_tag_name.nil? - if @options[:compare_link] && older_tag_link - # Generate compare link - log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n" + # 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 - - log + filtered_issues end - # Generate log only between 2 specified tags + # Method filter issues, that belong only specified tag range + # @param [Array] array of issues to filter + # @param [Symbol] hash_key key of date value default is :actual_date # @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) + # @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? - newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"] - older_tag_name = older_tag.nil? ? nil : older_tag["name"] + newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag) + older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag) - 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) + array.select do |req| + if req[hash_key] + t = 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 + + if newer_tag_time.nil? + 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 + else + false + end end - - if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty? - # do not generate empty unreleased section - return "" - end - - create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) end - # The full cycle of generation for whole project - # @return [String] The complete change log - def generate_log_for_all_tags - fetch_tags_dates + # This method fetches missing params for PR and filter them by specified options + # It include add all PR's with labels from @options[:include_labels] array + # And exclude all from :exclude_labels array. + # @return [Array] filtered PR's + def get_filtered_pull_requests + filter_merged_pull_requests - puts "Sorting tags..." if @options[:verbose] + filtered_pull_requests = include_issues_by_labels(@pull_requests) - @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! + filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests) - puts "Generating log..." if @options[:verbose] - - log = "" - - if @options[:unreleased] && @all_tags.count != 0 - unreleased_log = generate_log_between_tags(all_tags[0], nil) - log += unreleased_log if unreleased_log + if @options[:verbose] + puts "Filtered pull requests: #{filtered_pull_requests.count}" end - (1...all_tags.size).each do |index| - log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) - end - if @all_tags.count != 0 - log += generate_log_between_tags(nil, all_tags.last) + filtered_pull_requests + end + + # This method filter only merged PR and + # fetch missing required attributes for pull requests + # :merged_at - is a date, when issue PR was merged. + # More correct to use merged date, rather than closed date. + def filter_merged_pull_requests + print "Fetching merged dates...\r" if @options[:verbose] + pull_requests = @fetcher.fetch_closed_pull_requests + + @pull_requests.each do |pr| + fetched_pr = pull_requests.find do |fpr| + fpr.number == pr.number + end + pr[:merged_at] = fetched_pr[:merged_at] + pull_requests.delete(fetched_pr) end - log + @pull_requests.select! do |pr| + !pr[:merged_at].nil? + end + 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 + + if @options[:add_issues_wo_labels] + issues_wo_labels = issues.select do |issue| + !issue.labels.map(&:name).any? + end + filtered_issues |= 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 } + end + filtered_tags + end + + # Filter issues according labels + # @return [Array] Filtered issues + def get_filtered_issues + filtered_issues = include_issues_by_labels(@issues) + + filtered_issues = exclude_issues_by_labels(filtered_issues) + + puts "Filtered issues: #{filtered_issues.count}" if @options[:verbose] + + filtered_issues end end end From b10707b259392a87493e94127c7ddf6a59002fca Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Fri, 22 May 2015 17:44:06 +0300 Subject: [PATCH 10/15] reorganaize issues fetching in more clear way --- .rubocop_todo.yml | 18 ++-- lib/github_changelog_generator.rb | 1 + .../generator/generator.rb | 33 +++++-- .../generator/generator_fetcher.rb | 44 +++++----- .../generator/generator_generation.rb | 22 +++-- .../generator/generator_processor.rb | 88 +++++++++---------- 6 files changed, 113 insertions(+), 93 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 96a57ef..784052c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,28 +1,28 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 15:59:03 +0300 using RuboCop version 0.31.0. +# on 2015-05-22 17:34:14 +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: 17 +# Offense count: 16 Metrics/AbcSize: Max: 73 -# Offense count: 2 +# Offense count: 1 Metrics/BlockNesting: Max: 4 -# Offense count: 3 +# Offense count: 4 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 280 + Max: 162 -# Offense count: 5 +# Offense count: 4 Metrics/CyclomaticComplexity: Max: 15 -# Offense count: 23 +# Offense count: 21 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 121 @@ -31,7 +31,7 @@ Metrics/MethodLength: Metrics/PerceivedComplexity: Max: 18 -# Offense count: 4 +# Offense count: 2 Style/AccessorMethodName: Enabled: false @@ -44,7 +44,7 @@ Style/Documentation: Style/GuardClause: Enabled: false -# Offense count: 2 +# Offense count: 1 # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. Style/Next: Enabled: false diff --git a/lib/github_changelog_generator.rb b/lib/github_changelog_generator.rb index dea6253..c35db79 100755 --- a/lib/github_changelog_generator.rb +++ b/lib/github_changelog_generator.rb @@ -10,6 +10,7 @@ require_relative "github_changelog_generator/generator/generator" require_relative "github_changelog_generator/version" require_relative "github_changelog_generator/reader" +# The main module, where placed all classes (now, at least) module GitHubChangelogGenerator # Main class and entry point for this script. class ChangelogGenerator diff --git a/lib/github_changelog_generator/generator/generator.rb b/lib/github_changelog_generator/generator/generator.rb index 1799958..5b4285f 100644 --- a/lib/github_changelog_generator/generator/generator.rb +++ b/lib/github_changelog_generator/generator/generator.rb @@ -20,18 +20,35 @@ module GitHubChangelogGenerator @options = options @fetcher = GitHubChangelogGenerator::Fetcher.new @options + + fetch_tags + + fetch_issues_and_pr + end + + def fetch_issues_and_pr + issues, pull_requests = @fetcher.fetch_closed_issues_and_pr + + @pull_requests = @options[:pulls] ? get_filtered_pull_requests(pull_requests) : [] + + @issues = @options[:issues] ? get_filtered_issues(issues) : [] + + fetch_events_for_issues_and_pr + detect_actual_closed_dates(@issues + @pull_requests) + end + + def fetch_tags # @all_tags = get_filtered_tags @all_tags = @fetcher.get_all_tags - # TODO: refactor this double asssign of @issues and @pull_requests and move all logic in one method - @issues, @pull_requests = @fetcher.fetch_closed_issues_and_pr + fetch_tags_dates + sort_tags_by_date + end - @pull_requests = @options[:pulls] ? get_filtered_pull_requests : [] - - @issues = @options[:issues] ? get_filtered_issues : [] - - fetch_event_for_issues_and_pr - detect_actual_closed_dates + # 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. diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index bf7a2ef..5e65664 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -2,7 +2,7 @@ module GitHubChangelogGenerator class Generator # Fetch event for issues and pull requests # @return [Array] array of fetched issues - def fetch_event_for_issues_and_pr + def fetch_events_for_issues_and_pr if @options[:verbose] print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r" end @@ -37,28 +37,24 @@ module GitHubChangelogGenerator end # Find correct closed dates, if issues was closed by commits - def detect_actual_closed_dates + def detect_actual_closed_dates(issues) print "Fetching closed dates for issues...\r" if @options[:verbose] + # TODO: implement async fetching with slice! threads = [] - @issues.each do |issue| + issues.each do |issue| threads << Thread.new do find_closed_date_by_commit(issue) end end - @pull_requests.each do |pull_request| - threads << Thread.new do - find_closed_date_by_commit(pull_request) - end - end threads.each(&:join) puts "Fetching closed dates for issues: Done!" if @options[:verbose] end - # Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit. + # Fill :actual_date parameter of specified issue by closed date of the commit, if it was closed by commit. # @param [Hash] issue def find_closed_date_by_commit(issue) unless issue["events"].nil? @@ -67,22 +63,30 @@ module GitHubChangelogGenerator # reverse! - to find latest closed event. (event goes in date order) issue["events"].reverse!.each do |event| if event[:event].eql? compare_string - if event[:commit_id].nil? - issue[:actual_date] = issue[:closed_at] - else - begin - commit = @fetcher.fetch_commit(event) - issue[:actual_date] = commit[:author][:date] - rescue - puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow - issue[:actual_date] = issue[:closed_at] - end - end + set_date_from_event(event, issue) break end end end # TODO: assert issues, that remain without 'actual_date' hash for some reason. end + + # Set closed date from this issue + # + # @param [Hash] event + # @param [Hash] issue + def set_date_from_event(event, issue) + if event[:commit_id].nil? + issue[:actual_date] = issue[:closed_at] + else + begin + commit = @fetcher.fetch_commit(event) + issue[:actual_date] = commit[:author][:date] + rescue + puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow + issue[:actual_date] = issue[:closed_at] + end + end + end end end diff --git a/lib/github_changelog_generator/generator/generator_generation.rb b/lib/github_changelog_generator/generator/generator_generation.rb index c5227d9..66f2a05 100644 --- a/lib/github_changelog_generator/generator/generator_generation.rb +++ b/lib/github_changelog_generator/generator/generator_generation.rb @@ -109,20 +109,9 @@ module GitHubChangelogGenerator # The full cycle of generation for whole project # @return [String] The complete change log def generate_log_for_all_tags - fetch_tags_dates - - puts "Sorting tags..." if @options[:verbose] - - @all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse! - puts "Generating log..." if @options[:verbose] - log = "" - - if @options[:unreleased] && @all_tags.count != 0 - unreleased_log = generate_log_between_tags(all_tags[0], nil) - log += unreleased_log if unreleased_log - end + log = generate_unreleased_section (1...all_tags.size).each do |index| log += generate_log_between_tags(all_tags[index], all_tags[index - 1]) @@ -134,6 +123,15 @@ module GitHubChangelogGenerator log end + def generate_unreleased_section + log = "" + if @options[:unreleased] && @all_tags.count != 0 + unreleased_log = generate_log_between_tags(all_tags[0], nil) + log += unreleased_log if unreleased_log + end + log + end + # Parse issue and generate single line formatted issue line. # # Example output: diff --git a/lib/github_changelog_generator/generator/generator_processor.rb b/lib/github_changelog_generator/generator/generator_processor.rb index 55e736c..cbbaa7f 100644 --- a/lib/github_changelog_generator/generator/generator_processor.rb +++ b/lib/github_changelog_generator/generator/generator_processor.rb @@ -85,45 +85,6 @@ module GitHubChangelogGenerator end end - # This method fetches missing params for PR and filter them by specified options - # It include add all PR's with labels from @options[:include_labels] array - # And exclude all from :exclude_labels array. - # @return [Array] filtered PR's - def get_filtered_pull_requests - filter_merged_pull_requests - - filtered_pull_requests = include_issues_by_labels(@pull_requests) - - filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests) - - if @options[:verbose] - puts "Filtered pull requests: #{filtered_pull_requests.count}" - end - - filtered_pull_requests - end - - # This method filter only merged PR and - # fetch missing required attributes for pull requests - # :merged_at - is a date, when issue PR was merged. - # More correct to use merged date, rather than closed date. - def filter_merged_pull_requests - print "Fetching merged dates...\r" if @options[:verbose] - pull_requests = @fetcher.fetch_closed_pull_requests - - @pull_requests.each do |pr| - fetched_pr = pull_requests.find do |fpr| - fpr.number == pr.number - end - pr[:merged_at] = fetched_pr[:merged_at] - pull_requests.delete(fetched_pr) - end - - @pull_requests.select! do |pr| - !pr[:merged_at].nil? - end - end - # Include issues with labels, specified in :include_labels # @param [Array] issues to filter # @return [Array] filtered array of issues @@ -159,16 +120,55 @@ module GitHubChangelogGenerator filtered_tags end + # General filtered function + # + # @param [Array] all_issues + # @return [Array] filtered issues + def filter_array_by_labels(all_issues) + filtered_issues = include_issues_by_labels(all_issues) + exclude_issues_by_labels(filtered_issues) + end + # Filter issues according labels # @return [Array] Filtered issues - def get_filtered_issues - filtered_issues = include_issues_by_labels(@issues) + def get_filtered_issues(issues) + issues = filter_array_by_labels(issues) + puts "Filtered issues: #{issues.count}" if @options[:verbose] + issues + end - filtered_issues = exclude_issues_by_labels(filtered_issues) + # This method fetches missing params for PR and filter them by specified options + # It include add all PR's with labels from @options[:include_labels] array + # And exclude all from :exclude_labels array. + # @return [Array] filtered PR's + def get_filtered_pull_requests(pull_requests) + pull_requests = filter_array_by_labels(pull_requests) + pull_requests = filter_merged_pull_requests(pull_requests) + puts "Filtered pull requests: #{pull_requests.count}" if @options[:verbose] + pull_requests + end - puts "Filtered issues: #{filtered_issues.count}" if @options[:verbose] + # This method filter only merged PR and + # fetch missing required attributes for pull requests + # :merged_at - is a date, when issue PR was merged. + # More correct to use merged date, rather than closed date. + def filter_merged_pull_requests(pull_requests) + print "Fetching merged dates...\r" if @options[:verbose] + closed_pull_requests = @fetcher.fetch_closed_pull_requests - filtered_issues + pull_requests.each do |pr| + fetched_pr = closed_pull_requests.find do |fpr| + fpr.number == pr.number + end + pr[:merged_at] = fetched_pr[:merged_at] + closed_pull_requests.delete(fetched_pr) + end + + pull_requests.select! do |pr| + !pr[:merged_at].nil? + end + + pull_requests end end end From 4ffb4937872835467509f433fd140d54320a6e10 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Mon, 25 May 2015 09:18:58 +0300 Subject: [PATCH 11/15] slice fetching --- .../generator/generator_fetcher.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator_fetcher.rb b/lib/github_changelog_generator/generator/generator_fetcher.rb index 5e65664..7cb3fd4 100644 --- a/lib/github_changelog_generator/generator/generator_fetcher.rb +++ b/lib/github_changelog_generator/generator/generator_fetcher.rb @@ -40,18 +40,15 @@ module GitHubChangelogGenerator def detect_actual_closed_dates(issues) print "Fetching closed dates for issues...\r" if @options[:verbose] - # TODO: implement async fetching with slice! - threads = [] - - issues.each do |issue| - threads << Thread.new do - find_closed_date_by_commit(issue) + max_thread_number = 50 + issues.each_slice(max_thread_number) do |issues_slice| + threads = [] + issues_slice.each do |issue| + threads << Thread.new { find_closed_date_by_commit(issue) } end + threads.each(&:join) + puts "Fetching closed dates for issues: Done!" if @options[:verbose] end - - threads.each(&:join) - - puts "Fetching closed dates for issues: Done!" if @options[:verbose] end # Fill :actual_date parameter of specified issue by closed date of the commit, if it was closed by commit. From 134c18ba06fd001dd851ef9a21ef71be9148e87a Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Mon, 25 May 2015 09:56:59 +0300 Subject: [PATCH 12/15] typo --- .../generator/generator_generation.rb | 2 +- lib/github_changelog_generator/parser.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/github_changelog_generator/generator/generator_generation.rb b/lib/github_changelog_generator/generator/generator_generation.rb index 66f2a05..f5e6b8f 100644 --- a/lib/github_changelog_generator/generator/generator_generation.rb +++ b/lib/github_changelog_generator/generator/generator_generation.rb @@ -65,7 +65,7 @@ module GitHubChangelogGenerator log = "" # Generate date string: - time_string = newer_tag_time.strftime @options[:dateformat] + time_string = newer_tag_time.strftime @options[:date_format] # Generate tag name and link if newer_tag_name.equal? @options[:unreleased_label] diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index 891fb9f..0070c74 100644 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -9,7 +9,7 @@ module GitHubChangelogGenerator options = { tag1: nil, tag2: nil, - dateformat: "%Y-%m-%d", + date_format: "%Y-%m-%d", output: "CHANGELOG.md", issues: true, add_issues_wo_labels: true, @@ -45,7 +45,7 @@ module GitHubChangelogGenerator options[:token] = last end opts.on("-f", "--date-format [FORMAT]", "Date format. Default is %Y-%m-%d") do |last| - options[:dateformat] = last + options[:date_format] = last end opts.on("-o", "--output [NAME]", "Output file. Default is CHANGELOG.md") do |last| options[:output] = last From c3b9455dfdec26b658fa57944d541035c7c2cf85 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Mon, 25 May 2015 13:02:10 +0300 Subject: [PATCH 13/15] fix regex mess --- .rubocop_todo.yml | 22 +++---- lib/github_changelog_generator/parser.rb | 75 ++++++++++++++---------- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 784052c..7ad00f1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2015-05-22 17:34:14 +0300 using RuboCop version 0.31.0. +# on 2015-05-25 12:59:32 +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 @@ -7,29 +7,25 @@ # Offense count: 16 Metrics/AbcSize: - Max: 73 - -# Offense count: 1 -Metrics/BlockNesting: - Max: 4 + Max: 68 # Offense count: 4 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 162 + Max: 166 -# Offense count: 4 +# Offense count: 3 Metrics/CyclomaticComplexity: - Max: 15 + Max: 9 -# Offense count: 21 +# Offense count: 22 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 121 + Max: 117 -# Offense count: 5 +# Offense count: 4 Metrics/PerceivedComplexity: - Max: 18 + Max: 12 # Offense count: 2 Style/AccessorMethodName: diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index 0070c74..eebb044 100644 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -30,7 +30,7 @@ module GitHubChangelogGenerator issue_prefix: "**Closed issues:**", bug_prefix: "**Fixed bugs:**", enhancement_prefix: "**Implemented enhancements:**", - branch: "origin" + git_remote: "origin" } parser = OptionParser.new do |opts| @@ -123,11 +123,6 @@ module GitHubChangelogGenerator exit end - if ARGV[1] - options[:tag1] = ARGV[0] - options[:tag2] = ARGV[1] - end - if options[:verbose] puts "Performing task with options:" pp options @@ -138,6 +133,18 @@ module GitHubChangelogGenerator end def self.detect_user_and_project(options) + user_project_from_option(options) + if !options[:user] || !options[:project] + if ENV["RUBYLIB"] =~ /ruby-debug-ide/ + options[:user] = "skywinder" + options[:project] = "changelog_test" + else + options[:user], options[:project] = user_project_from_remote(options[:git_remote]) + end + end + end + + def self.user_project_from_option(options) if ARGV[0] && !ARGV[1] github_site = options[:github_site] ? options[:github_site] : "github.com" # this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name @@ -155,37 +162,41 @@ module GitHubChangelogGenerator options[:user] = match[1] options[:project] = match[2] end - end + end - if !options[:user] && !options[:project] - if ENV["RUBYLIB"] =~ /ruby-debug-ide/ - options[:user] = "skywinder" - options[:project] = "changelog_test" - else - remote = `git config --get remote.#{options[:branch]}.url` - # try to find repo in format: - # origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch) - # git@github.com:skywinder/Github-Changelog-Generator.git - match = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/.match(remote) + # Try to find user and project name from git remote + # + # @return [Tuple] user and project + def self.user_project_from_remote(git_remote) + # try to find repo in format: + # origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch) + # git@github.com:skywinder/Github-Changelog-Generator.git + regex1 = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/ - if match && match[1] && match[2] - puts "Detected user:#{match[1]}, project:#{match[2]}" - options[:user] = match[1] - options[:project] = match[2] - else - # try to find repo in format: - # origin https://github.com/skywinder/ChangelogMerger (fetch) - # https://github.com/skywinder/ChangelogMerger - match = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/.match(remote) - if match && match[1] && match[2] - puts "Detected user:#{match[1]}, project:#{match[2]}" - options[:user] = match[1] - options[:project] = match[2] - end - end + # try to find repo in format: + # origin https://github.com/skywinder/ChangelogMerger (fetch) + # https://github.com/skywinder/ChangelogMerger + regex2 = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/ + + remote_structures = [regex1, regex2] + + remote = `git config --get remote.#{git_remote}.url` + user = nil + project = nil + remote_structures.each do |regex| + matches = Regexp.new(regex).match(remote) + + if matches && matches[1] && matches[2] + puts "Detected user:#{matches[1]}, project:#{matches[2]}" + user = matches[1] + project = matches[2] end + + break unless matches.nil? end + + [user, project] end end end From 757f6d40b49661cad90f08cc6bef90bf03ed1223 Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Mon, 25 May 2015 13:34:37 +0300 Subject: [PATCH 14/15] add tests --- lib/github_changelog_generator/parser.rb | 16 +++++++++---- spec/unit/parser_spec.rb | 29 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 spec/unit/parser_spec.rb diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index eebb044..d9bc4bf 100644 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -139,7 +139,8 @@ module GitHubChangelogGenerator options[:user] = "skywinder" options[:project] = "changelog_test" else - options[:user], options[:project] = user_project_from_remote(options[:git_remote]) + remote = `git config --get remote.#{options[:git_remote]}.url` + options[:user], options[:project] = user_project_from_remote(remote) end end end @@ -165,10 +166,11 @@ module GitHubChangelogGenerator end end - # Try to find user and project name from git remote + # Try to find user and project name from git remote output # - # @return [Tuple] user and project - def self.user_project_from_remote(git_remote) + # @param [String] output of git remote command + # @return [Arrajy] user and project + def self.user_project_from_remote(remote) # try to find repo in format: # origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch) # git@github.com:skywinder/Github-Changelog-Generator.git @@ -181,7 +183,6 @@ module GitHubChangelogGenerator remote_structures = [regex1, regex2] - remote = `git config --get remote.#{git_remote}.url` user = nil project = nil remote_structures.each do |regex| @@ -199,4 +200,9 @@ module GitHubChangelogGenerator [user, project] end end + + if __FILE__ == $PROGRAM_NAME + remote = "origin https://github.com/skywinder/Changelog.Merger (fetch)" + p GitHubChangelogGenerator::Parser.user_project_from_remote(remote)[0] + end end diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb new file mode 100644 index 0000000..54d87de --- /dev/null +++ b/spec/unit/parser_spec.rb @@ -0,0 +1,29 @@ +describe GitHubChangelogGenerator::Parser do + describe "#self.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) } + it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) } + end + context "when remote is 2" do + subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) } + end + context "when remote is 3" do + subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) } + end + context "when remote is 4" do + subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin git@github.com:skywinder/ActionSheetPicker-3.0.git (fetch)") } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) } + end + context "when remote is invalid" do + subject { GitHubChangelogGenerator::Parser.user_project_from_remote("some invalid text") } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array([nil, nil]) } + end + end +end From 171e536e76385765e0363f47ce256589f6242c2a Mon Sep 17 00:00:00 2001 From: Petr Korolev Date: Mon, 25 May 2015 14:21:23 +0300 Subject: [PATCH 15/15] add tests for regex --- .rubocop_todo.yml | 2 +- lib/github_changelog_generator/parser.rb | 29 +++++++++++++++--------- spec/unit/parser_spec.rb | 16 +++++++++++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7ad00f1..d033c94 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -12,7 +12,7 @@ Metrics/AbcSize: # Offense count: 4 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 166 + Enabled: false # Offense count: 3 Metrics/CyclomaticComplexity: diff --git a/lib/github_changelog_generator/parser.rb b/lib/github_changelog_generator/parser.rb index d9bc4bf..6e87f7d 100644 --- a/lib/github_changelog_generator/parser.rb +++ b/lib/github_changelog_generator/parser.rb @@ -133,7 +133,7 @@ module GitHubChangelogGenerator end def self.detect_user_and_project(options) - user_project_from_option(options) + options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site]) if !options[:user] || !options[:project] if ENV["RUBYLIB"] =~ /ruby-debug-ide/ options[:user] = "skywinder" @@ -145,31 +145,38 @@ module GitHubChangelogGenerator end end - def self.user_project_from_option(options) - if ARGV[0] && !ARGV[1] - github_site = options[:github_site] ? options[:github_site] : "github.com" + # Try to find user and project name from git remote output + # + # @param [String] output of git remote command + # @return [Array] user and project + def self.user_project_from_option(arg0, arg2, github_site = "github.com") + user = nil + project = nil + + if arg0 && !arg2 # this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name - match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(ARGV[0]) + match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0) begin param = match[2].nil? rescue - puts "Can't detect user and name from first parameter: '#{ARGV[0]}' -> exit'" + puts "Can't detect user and name from first parameter: '#{arg0}' -> exit'" exit end if param exit else - options[:user] = match[1] - options[:project] = match[2] + user = match[1] + project = match[2] end end + [user, project] end # Try to find user and project name from git remote output # # @param [String] output of git remote command - # @return [Arrajy] user and project + # @return [Array] user and project def self.user_project_from_remote(remote) # try to find repo in format: # origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch) @@ -202,7 +209,7 @@ module GitHubChangelogGenerator end if __FILE__ == $PROGRAM_NAME - remote = "origin https://github.com/skywinder/Changelog.Merger (fetch)" - p GitHubChangelogGenerator::Parser.user_project_from_remote(remote)[0] + remote = "invalid reference to project" + p user_project_from_option(ARGV[0], ARGV[1], remote) end end diff --git a/spec/unit/parser_spec.rb b/spec/unit/parser_spec.rb index 54d87de..6f80a6e 100644 --- a/spec/unit/parser_spec.rb +++ b/spec/unit/parser_spec.rb @@ -26,4 +26,20 @@ describe GitHubChangelogGenerator::Parser do it { is_expected.to match_array([nil, nil]) } end end + describe "#self.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 + + context "when option is valid" do + subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil) } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) } + end + context "when option nil" do + subject { GitHubChangelogGenerator::Parser.user_project_from_option(nil, nil) } + it { is_expected.to be_a(Array) } + it { is_expected.to match_array([nil, nil]) } + end + end end