how_is 24.0.0 → 25.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github_changelog_generator +0 -1
  3. data/.rubocop.yml +37 -12
  4. data/.travis.yml +6 -3
  5. data/CHANGELOG.md +56 -0
  6. data/CONTRIBUTING.md +34 -0
  7. data/Gemfile +8 -4
  8. data/ISSUES.md +30 -54
  9. data/README.md +16 -91
  10. data/Rakefile +3 -31
  11. data/bin/prerelease-generate-changelog +1 -1
  12. data/bin/setup +0 -0
  13. data/build-debug.rb +20 -0
  14. data/exe/how_is +25 -22
  15. data/fixtures/vcr_cassettes/how-is-example-empty-repository.yml +334 -1
  16. data/fixtures/vcr_cassettes/how-is-example-repository.yml +350 -1
  17. data/fixtures/vcr_cassettes/how-is-from-config-frontmatter.yml +15234 -1
  18. data/fixtures/vcr_cassettes/how-is-how-is-travis-api-repos-builds.yml +2694 -1
  19. data/fixtures/vcr_cassettes/how-is-with-config-file.yml +15234 -1
  20. data/fixtures/vcr_cassettes/how_is_contributions_additions_count.yml +70 -1
  21. data/fixtures/vcr_cassettes/how_is_contributions_all_contributors.yml +70 -1
  22. data/fixtures/vcr_cassettes/how_is_contributions_changed_files.yml +70 -1
  23. data/fixtures/vcr_cassettes/how_is_contributions_changes.yml +70 -1
  24. data/fixtures/vcr_cassettes/how_is_contributions_commits.yml +70 -1
  25. data/fixtures/vcr_cassettes/how_is_contributions_compare_url.yml +70 -1
  26. data/fixtures/vcr_cassettes/how_is_contributions_default_branch.yml +70 -1
  27. data/fixtures/vcr_cassettes/how_is_contributions_deletions_count.yml +70 -1
  28. data/fixtures/vcr_cassettes/how_is_contributions_new_contributors.yml +70 -1
  29. data/fixtures/vcr_cassettes/how_is_contributions_summary.yml +70 -1
  30. data/fixtures/vcr_cassettes/how_is_contributions_summary_2.yml +70 -1
  31. data/how_is.gemspec +12 -6
  32. data/lib/how_is/cacheable.rb +71 -0
  33. data/lib/how_is/cli.rb +121 -124
  34. data/lib/how_is/config.rb +123 -0
  35. data/lib/how_is/constants.rb +9 -0
  36. data/lib/how_is/date_time_helpers.rb +48 -0
  37. data/lib/how_is/frontmatter.rb +14 -9
  38. data/lib/how_is/report.rb +86 -58
  39. data/lib/how_is/report_collection.rb +113 -0
  40. data/lib/how_is/sources/ci/appveyor.rb +88 -0
  41. data/lib/how_is/sources/ci/travis.rb +159 -0
  42. data/lib/how_is/sources/github/contributions.rb +169 -128
  43. data/lib/how_is/sources/github/issue_fetcher.rb +148 -0
  44. data/lib/how_is/sources/github/issues.rb +86 -235
  45. data/lib/how_is/sources/github/pulls.rb +19 -18
  46. data/lib/how_is/sources/github.rb +40 -18
  47. data/lib/how_is/sources/github_helpers.rb +8 -91
  48. data/lib/how_is/sources.rb +2 -0
  49. data/lib/how_is/template.rb +9 -0
  50. data/lib/how_is/templates/contributions_partial.html +1 -0
  51. data/lib/how_is/templates/{issues_or_pulls_partial.html_template → issues_or_pulls_partial.html} +0 -0
  52. data/lib/how_is/templates/new_contributors_partial.html +5 -0
  53. data/lib/how_is/templates/{report.html_template → report.html} +0 -8
  54. data/lib/how_is/templates/{report_partial.html_template → report_partial.html} +3 -3
  55. data/lib/how_is/text.rb +26 -0
  56. data/lib/how_is/version.rb +2 -1
  57. data/lib/how_is.rb +33 -60
  58. metadata +28 -47
  59. data/.hound.yml +0 -2
  60. data/.rubocop_todo.yml +0 -21
  61. data/lib/how_is/sources/travis.rb +0 -37
  62. data/roadmap.markdown +0 -82
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "how_is/text"
5
+
6
+ module HowIs
7
+ HOME_CONFIG = File.join(Dir.home, ".config", "how_is", "config.yml")
8
+
9
+ # Usage:
10
+ # HowIs::Config
11
+ # .load_site_configs("/path/to/config1.yml", "/path/to/config2.yml")
12
+ # .load_file("./repo-config.yml")
13
+ # Or:
14
+ # HowIs::Config
15
+ # .load_defaults
16
+ # .load_file("./repo-config.yml")
17
+ # Or:
18
+ # HowIs::Config
19
+ # .load_defaults
20
+ # .load({"repository" => "how-is/example-repository"})
21
+ class Config < Hash
22
+ attr_reader :site_configs
23
+
24
+ # If the +HOWIS_USE_ENV+ environment variable is set, load config from
25
+ # the environment.
26
+ #
27
+ # Otherwise, load the the default config file.
28
+ #
29
+ # @return [Hash] A Hash representation of the config.
30
+ def load_defaults
31
+ if ENV["HOWIS_USE_ENV"] == "true"
32
+ load_env
33
+ else
34
+ load_site_configs(HOME_CONFIG)
35
+ end
36
+ end
37
+
38
+ def initialize
39
+ super()
40
+ @site_configs = []
41
+ end
42
+
43
+ # Load the config files as specified via +files+.
44
+ #
45
+ # @param files [Array<String>] The path(s) for config files.
46
+ # @return [Config] The config hash. (+self+)
47
+ def load_site_configs(*files)
48
+ # Allows both:
49
+ # load_site_configs('foo', 'bar')
50
+ # load_site_configs(['foo', bar'])
51
+ # but not:
52
+ # load_site_configs(['foo'], 'bar')
53
+ files = files[0] if files.length == 1 && files[0].is_a?(Array)
54
+
55
+ load_files(*files)
56
+ end
57
+
58
+ # TODO: See if this can be consolidated with load_site_configs.
59
+ def load_files(*file_paths)
60
+ files = (site_configs + file_paths).map { |f| Pathname.new(f) }
61
+
62
+ # Keep only files that exist.
63
+ files.select!(&:file?)
64
+
65
+ # Load the YAML files into Hashes.
66
+ configs = files.map { |file| YAML.safe_load(file.read) }
67
+
68
+ # Apply configs.
69
+ load(*configs)
70
+ end
71
+
72
+ # Take a collection of config hashes and cascade them, meaning values
73
+ # in later ones override values in earlier ones.
74
+ #
75
+ # E.g., this results in +{'a'=>'x', 'c'=>'d'}+:
76
+ # load({'a'=>'b'}, {'c'=>'d'}, {'a'=>'x'})
77
+ #
78
+ # And this results in +{'a'=>['b', 'c']}+:
79
+ # load({'a'=>['b']}, {'a'=>['c']})
80
+ #
81
+ # @param [Array<Hash>] The configuration hashes.
82
+ # @return [Config] The final configuration value.
83
+ def load(*configs)
84
+ configs.each do |config|
85
+ config.each do |k, v|
86
+ if self[k] && self[k].is_a?(Array)
87
+ self[k] += v
88
+ else
89
+ self[k] = v
90
+ end
91
+ end
92
+ end
93
+
94
+ self
95
+ end
96
+
97
+ # Load config info from environment variables.
98
+ #
99
+ # Supported environment variables:
100
+ # - HOWIS_GITHUB_TOKEN: a GitHub authentication token.
101
+ # - HOWIS_GITHUB_USERNAME: the GitHub username corresponding to the token.
102
+ #
103
+ # @return [Config] The resulting configuration.
104
+ def load_env
105
+ HowIs::Text.puts "Using configuration from environment variables."
106
+
107
+ gh_token = ENV["HOWIS_GITHUB_TOKEN"]
108
+ gh_username = ENV["HOWIS_GITHUB_USERNAME"]
109
+
110
+ raise "HOWIS_GITHUB_TOKEN environment variable is not set" \
111
+ unless gh_token
112
+ raise "HOWIS_GITHUB_USERNAME environment variable is not set" \
113
+ unless gh_username
114
+
115
+ load({
116
+ "sources/github" => {
117
+ "username" => gh_username,
118
+ "token" => gh_token,
119
+ },
120
+ })
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HowIs
4
+ # The file name used for a report if one isn't specified.
5
+ DEFAULT_REPORT_FILE = "report.html"
6
+
7
+ # Used by things making HTTP requests.
8
+ USER_AGENT = "how_is/#{HowIs::VERSION} (https://github.com/how-is/how_is/)"
9
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "how_is/version"
4
+ require "date"
5
+
6
+ module HowIs
7
+ ##
8
+ # Various helper functions for working with DateTime objects.
9
+ module DateTimeHelpers
10
+ # Check if +left+ is less than or equal to +right+, where both are string
11
+ # representations of a date.
12
+ #
13
+ # @param left [String] A string representation of a date.
14
+ # @param right [String] A string representation of a date.
15
+ # @return [Boolean] True if +left+ is less-than-or-equal to +right+,
16
+ # otherwise false.
17
+ def date_le(left, right)
18
+ left = str_to_dt(left)
19
+ right = str_to_dt(right)
20
+
21
+ left <= right
22
+ end
23
+
24
+ # Check if +left+ is greater than or equal to +right+, where both are string
25
+ # representations of a date.
26
+ #
27
+ # @param left [String] A string representation of a date.
28
+ # @param right [String] A string representation of a date.
29
+ # @return [Boolean] True if +left+ is greater-than-or-equal to +right+,
30
+ # otherwise false.
31
+ def date_ge(left, right)
32
+ left = str_to_dt(left)
33
+ right = str_to_dt(right)
34
+
35
+ left >= right
36
+ end
37
+
38
+ private
39
+
40
+ # Converts a +String+ representation of a date to a +DateTime+.
41
+ #
42
+ # @param str [String] A date.
43
+ # @return [DateTime] A DateTime representation of +str+.
44
+ def str_to_dt(str)
45
+ DateTime.parse(str)
46
+ end
47
+ end
48
+ end
@@ -1,14 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "how_is/version"
4
+ require "okay/warning_helpers"
4
5
 
5
6
  module HowIs
7
+ ##
8
+ # Module for generating YAML frontmatter, as used by Jekyll and other
9
+ # blog engines.
6
10
  module Frontmatter
11
+ extend Okay::WarningHelpers
12
+
7
13
  # Generates YAML frontmatter, as is used in Jekyll and other blog engines.
8
14
  #
9
15
  # E.g.,
10
16
  # generate_frontmatter({'foo' => "bar %{baz}"}, {'baz' => "asdf"})
11
17
  # => "---\nfoo: bar asdf\n"
18
+ #
19
+ # @param frontmatter [Hash] Frontmatter for the report.
20
+ # @param report_data [Hash] The report data itself.
21
+ # @return [String] A YAML dump of the generated frontmatter.
12
22
  def self.generate(frontmatter, report_data)
13
23
  return "" if frontmatter.nil?
14
24
 
@@ -29,18 +39,13 @@ module HowIs
29
39
  # @example
30
40
  # convert_keys({'foo' => 'bar'}, :to_sym)
31
41
  # # => {:foo => 'bar'}
42
+ # @param data [Hash] The input hash.
43
+ # @param method_name [Symbol] The method name used to convert keys.
44
+ # (E.g. :to_s, :to_sym, etc.)
45
+ # @return [Hash] The converted result.
32
46
  def self.convert_keys(data, method_name)
33
47
  data.map { |k, v| [k.send(method_name), v] }.to_h
34
48
  end
35
49
  private_class_method :convert_keys
36
-
37
- def self.silence_warnings(&_block)
38
- old_verbose = $VERBOSE
39
- $VERBOSE = nil # Disable warnings entirely.
40
- yield
41
- ensure
42
- $VERBOSE = old_verbose
43
- end
44
- private_class_method :silence_warnings
45
50
  end
46
51
  end
data/lib/how_is/report.rb CHANGED
@@ -1,15 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "how_is/frontmatter"
4
+ require "how_is/cacheable"
4
5
  require "how_is/sources/github/contributions"
5
6
  require "how_is/sources/github/issues"
6
7
  require "how_is/sources/github/pulls"
7
- require "how_is/sources/travis"
8
+ require "how_is/sources/ci/travis"
9
+ require "how_is/sources/ci/appveyor"
10
+ require "how_is/template"
11
+ require "json"
8
12
 
9
13
  module HowIs
14
+ ##
15
+ # Class for generating a HowIs report.
10
16
  class Report
11
- def initialize(repository, end_date)
12
- @repository = repository
17
+ def initialize(config, end_date)
18
+ @config = config
19
+ @repository = config["repository"]
13
20
 
14
21
  # NOTE: Use DateTime because it defaults to UTC and that's less gross
15
22
  # than trying to get Date to use UTC.
@@ -19,80 +26,50 @@ module HowIs
19
26
  #
20
27
  # (I'm also guessing/hoping that GitHub's URLs use UTC.)
21
28
  end_dt = DateTime.strptime(end_date, "%Y-%m-%d")
22
-
23
- d = end_dt.day
24
- m = end_dt.month
25
- y = end_dt.year
26
- start_year = y
27
- start_month = m - 1
28
- if start_month <= 0
29
- start_month = 12 - start_month
30
- start_year -= 1
31
- end
32
- start_dt = DateTime.new(start_year, start_month, d)
29
+ start_dt = start_dt_from_end_dt(end_dt)
33
30
 
34
31
  @end_date = end_dt.strftime("%Y-%m-%d")
35
32
  @start_date = start_dt.strftime("%Y-%m-%d")
36
-
37
- @gh_contributions = HowIs::Sources::Github::Contributions.new(repository, @start_date, @end_date)
38
- @gh_issues = HowIs::Sources::Github::Issues.new(repository, @start_date, @end_date)
39
- @gh_pulls = HowIs::Sources::Github::Pulls.new(repository, @start_date, @end_date)
40
- @travis = HowIs::Sources::Travis.new(repository, @start_date, @end_date)
41
33
  end
42
34
 
43
- def to_h(frontmatter_data = nil)
44
- @report_hash ||= {
45
- title: "How is #{@repository}?",
46
- repository: @repository,
47
-
48
- contributions_summary: @gh_contributions.to_html,
49
- issues_summary: @gh_issues.to_html,
50
- pulls_summary: @gh_pulls.to_html,
51
- issues_per_label: @gh_issues.issues_per_label_html,
52
-
53
- issues: @gh_issues.to_a,
54
- pulls: @gh_issues.to_a,
55
-
56
- average_issue_age: @gh_issues.average_age,
57
- average_pull_age: @gh_pulls.average_age,
58
-
59
- oldest_issue_link: @gh_issues.oldest["url"],
60
- oldest_issue_date: @gh_issues.oldest["createdAt"],
35
+ def cache
36
+ @cache ||= Cacheable.new(@config, @start_date, @end_date)
37
+ end
61
38
 
62
- newest_issue_link: @gh_issues.newest["url"],
63
- newest_issue_date: @gh_issues.newest["createdAt"],
39
+ def contributions
40
+ @gh_contributions ||= HowIs::Sources::Github::Contributions.new(@config, @start_date, @end_date, cache)
41
+ end
64
42
 
65
- newest_pull_link: @gh_pulls.newest["url"],
66
- newest_pull_date: @gh_pulls.newest["createdAt"],
43
+ def issues
44
+ @gh_issues ||= HowIs::Sources::Github::Issues.new(@config, @start_date, @end_date, cache)
45
+ end
67
46
 
68
- oldest_pull_link: @gh_pulls.oldest["url"],
69
- oldest_pull_date: @gh_pulls.oldest["createdAt"],
47
+ def pulls
48
+ @gh_pulls ||= HowIs::Sources::Github::Pulls.new(@config, @start_date, @end_date, cache)
49
+ end
70
50
 
71
- travis_builds: @travis.builds.to_h,
51
+ def travis
52
+ @travis ||= HowIs::Sources::CI::Travis.new(@config, @start_date, @end_date, cache)
53
+ end
72
54
 
73
- date: @end_date,
74
- }
55
+ def appveyor
56
+ @appveyor ||= HowIs::Sources::CI::Appveyor.new(@config, @start_date, @end_date, cache)
57
+ end
75
58
 
76
- frontmatter =
77
- if frontmatter_data
78
- HowIs::Frontmatter.generate(frontmatter_data, @report_hash)
79
- else
80
- ""
81
- end
59
+ def to_h(frontmatter_data = nil)
60
+ @report_hash ||= report_hash
61
+ frontmatter = HowIs::Frontmatter.generate(frontmatter_data, @report_hash)
82
62
 
83
63
  @report_hash.merge(frontmatter: frontmatter)
84
64
  end
85
65
 
86
66
  def to_html_partial(frontmatter = nil)
87
- template_data = to_h(frontmatter)
88
-
89
- Kernel.format(HowIs.template("report_partial.html_template"), template_data)
67
+ HowIs::Template.apply("report_partial.html", to_h(frontmatter))
90
68
  end
91
69
 
92
70
  def to_html(frontmatter = nil)
93
71
  template_data = to_h(frontmatter).merge({report: to_html_partial})
94
-
95
- Kernel.format(HowIs.template("report.html_template"), template_data)
72
+ HowIs::Template.apply("report.html", template_data)
96
73
  end
97
74
 
98
75
  def to_json(frontmatter = nil)
@@ -104,9 +81,60 @@ module HowIs
104
81
  end
105
82
 
106
83
  def to_format_for(filename)
107
- format = filename.split(".").last
84
+ format = File.extname(filename)[1..-1]
108
85
  send("to_#{format}")
109
86
  end
110
87
  private :to_format_for
88
+
89
+ def start_dt_from_end_dt(end_dt)
90
+ d = end_dt.day
91
+ m = end_dt.month
92
+ y = end_dt.year
93
+ start_year = y
94
+ start_month = m - 1
95
+ if start_month <= 0
96
+ start_month = 12 - start_month
97
+ start_year -= 1
98
+ end
99
+
100
+ DateTime.new(start_year, start_month, d)
101
+ end
102
+
103
+ # rubocop:disable Metrics/AbcSize
104
+ def report_hash
105
+ {
106
+ title: "How is #{@repository}?",
107
+ repository: @repository,
108
+
109
+ contributions_summary: contributions.to_html,
110
+ new_contributors: contributions.new_contributors_html,
111
+ issues_summary: issues.to_html,
112
+ pulls_summary: pulls.to_html,
113
+
114
+ issues: issues.to_a,
115
+ pulls: issues.to_a,
116
+
117
+ average_issue_age: issues.average_age,
118
+ average_pull_age: pulls.average_age,
119
+
120
+ oldest_issue_link: issues.oldest["url"],
121
+ oldest_issue_date: issues.oldest["createdAt"],
122
+
123
+ newest_issue_link: issues.newest["url"],
124
+ newest_issue_date: issues.newest["createdAt"],
125
+
126
+ newest_pull_link: pulls.newest["url"],
127
+ newest_pull_date: pulls.newest["createdAt"],
128
+
129
+ oldest_pull_link: pulls.oldest["url"],
130
+ oldest_pull_date: pulls.oldest["createdAt"],
131
+
132
+ travis_builds: travis.builds,
133
+ appveyor_builds: appveyor.builds,
134
+
135
+ date: @end_date,
136
+ }
137
+ end
138
+ # rubocop:enable Metrics/AbcSize
111
139
  end
112
140
  end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "how_is/report"
4
+ require "okay/warning_helpers"
5
+
6
+ module HowIs
7
+ ##
8
+ # A class representing a collection of Reports.
9
+ class ReportCollection
10
+ include Okay::WarningHelpers
11
+
12
+ def initialize(config, date)
13
+ @config = config
14
+
15
+ # If the config is in the old format, convert it to the new one.
16
+ unless @config["repositories"]
17
+ @config["repositories"] = [{
18
+ "repository" => @config.delete("repository"),
19
+ "reports" => @config.delete("reports"),
20
+ }]
21
+ end
22
+
23
+ @date = date
24
+ @reports = config["repositories"].map(&method(:fetch_report)).to_h
25
+ end
26
+
27
+ # Generates the metadata for the collection of Reports.
28
+ def metadata(repository)
29
+ end_date = DateTime.strptime(@date, "%Y-%m-%d")
30
+ friendly_end_date = end_date.strftime("%B %d, %y")
31
+
32
+ {
33
+ sanitized_repository: repository.tr("/", "-"),
34
+ repository: repository,
35
+ date: end_date,
36
+ friendly_date: friendly_end_date,
37
+ }
38
+ end
39
+ private :metadata
40
+
41
+ def config_for(repo)
42
+ defaults = @config.fetch("default_reports", {})
43
+ config = @config.dup
44
+ repos = config.delete("repositories")
45
+
46
+ # Find the _last_ one that matches, to allow overriding.
47
+ repo_config = repos.reverse.find { |conf| conf["repository"] == repo }
48
+
49
+ # Use values from default_reports, unless overridden.
50
+ config["repository"] = repo
51
+ config["reports"] = defaults.merge(repo_config.fetch("reports", {}))
52
+ config
53
+ end
54
+ private :config_for
55
+
56
+ def fetch_report(repo_config)
57
+ repo = repo_config["repository"]
58
+ report = Report.new(config_for(repo), @date)
59
+ [repo, report]
60
+ end
61
+ private :fetch_report
62
+
63
+ # Converts a ReportCollection to a Hash.
64
+ #
65
+ # Also good for giving programmers nightmares, I suspect.
66
+ def to_h
67
+ results = {}
68
+ defaults = @config["default_reports"] || {}
69
+
70
+ @config["repositories"].map { |repo_config|
71
+ repo = repo_config["repository"]
72
+ config = config_for(repo)
73
+
74
+ config["reports"].map { |format, report_config|
75
+ # Sometimes report_data has unused keys, which generates a warning, but
76
+ # we're okay with it, so we wrap it with silence_warnings {}.
77
+ filename = silence_warnings {
78
+ tmp_filename = report_config["filename"] || defaults[format]["filename"]
79
+ tmp_filename % metadata(repo)
80
+ }
81
+
82
+ directory = report_config["directory"] || defaults[format]["directory"]
83
+ file = File.join(directory, filename)
84
+
85
+ # Export +report+ to the specified +format+ with the specified
86
+ # +frontmatter+.
87
+ frontmatter = report_config["frontmatter"] || {}
88
+ if defaults.has_key?(format) && defaults[format].has_key?("frontmatter")
89
+ frontmatter = defaults[format]["frontmatter"].merge(frontmatter)
90
+ end
91
+ frontmatter = nil if frontmatter == {}
92
+
93
+ export = @reports[repo].send("to_#{format}", frontmatter)
94
+
95
+ results[file] = export
96
+ }
97
+ }
98
+ results
99
+ end
100
+
101
+ # Save all of the reports to the corresponding files.
102
+ #
103
+ # @return [Array<String>] An array of file paths.
104
+ def save_all
105
+ reports = to_h
106
+ reports.each do |file, report|
107
+ File.write(file, report)
108
+ end
109
+
110
+ reports.keys
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "okay/default"
4
+ require "okay/http"
5
+ require "how_is/constants"
6
+ require "how_is/sources"
7
+ require "how_is/sources/github/contributions"
8
+ require "how_is/text"
9
+
10
+ module HowIs
11
+ module Sources
12
+ module CI
13
+ # Fetches metadata about CI builds from appveyor.com.
14
+ class Appveyor
15
+ # @param repository [String] GitHub repository name, of the format user/repo.
16
+ # @param start_date [String] Start date for the report being generated.
17
+ # @param end_date [String] End date for the report being generated.
18
+ # @param cache [Cacheable] Instance of HowIs::Cacheable to cache API calls
19
+ def initialize(config, start_date, end_date, cache)
20
+ @config = config
21
+ @cache = cache
22
+ @repository = config["repository"]
23
+ @start_date = DateTime.parse(start_date)
24
+ @end_date = DateTime.parse(end_date)
25
+ @default_branch = Okay.default
26
+ end
27
+
28
+ # @return [String] The default branch name.
29
+ def default_branch
30
+ return @default_branch unless @default_branch.nil?
31
+
32
+ contributions =
33
+ HowIs::Sources::GitHub::Contributions.new(@config, nil, nil)
34
+
35
+ @default_branch = contributions.default_branch
36
+ end
37
+
38
+ # Fetches builds for the default branch.
39
+ #
40
+ # @return [Hash] Builds for the default branch.
41
+ def builds
42
+ @builds ||=
43
+ fetch_builds["builds"] \
44
+ .map(&method(:normalize_build)) \
45
+ .select(&method(:in_date_range?))
46
+ rescue Net::HTTPServerException
47
+ # It's not elegant, but it works™.
48
+ []
49
+ end
50
+
51
+ private
52
+
53
+ def in_date_range?(build)
54
+ build["started_at"] >= @start_date &&
55
+ build["started_at"] <= @end_date
56
+ end
57
+
58
+ def normalize_build(build)
59
+ build["started_at"] = DateTime.parse(build["created"])
60
+ build["html_url"] = "https://ci.appveyor.com/project/#{@repository}/build/#{build['buildNumber']}"
61
+ build
62
+ end
63
+
64
+ # Returns API result of /api/projects/:repository.
65
+ # FIXME: This doesn't limit results based on the date range.
66
+ #
67
+ # @return [Hash] API results.
68
+ def fetch_builds
69
+ @cache.cached("appveyor_builds") do
70
+ HowIs::Text.print "Fetching Appveyor build data."
71
+
72
+ ret = Okay::HTTP.get(
73
+ "https://ci.appveyor.com/api/projects/#{@repository}/history",
74
+ parameters: {"recordsNumber" => "100"},
75
+ headers: {
76
+ "Accept" => "application/json",
77
+ "User-Agent" => HowIs::USER_AGENT,
78
+ }
79
+ ).or_raise!.from_json
80
+
81
+ HowIs::Text.puts
82
+ ret
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end