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.
- checksums.yaml +4 -4
- data/.github_changelog_generator +0 -1
- data/.rubocop.yml +37 -12
- data/.travis.yml +6 -3
- data/CHANGELOG.md +56 -0
- data/CONTRIBUTING.md +34 -0
- data/Gemfile +8 -4
- data/ISSUES.md +30 -54
- data/README.md +16 -91
- data/Rakefile +3 -31
- data/bin/prerelease-generate-changelog +1 -1
- data/bin/setup +0 -0
- data/build-debug.rb +20 -0
- data/exe/how_is +25 -22
- data/fixtures/vcr_cassettes/how-is-example-empty-repository.yml +334 -1
- data/fixtures/vcr_cassettes/how-is-example-repository.yml +350 -1
- data/fixtures/vcr_cassettes/how-is-from-config-frontmatter.yml +15234 -1
- data/fixtures/vcr_cassettes/how-is-how-is-travis-api-repos-builds.yml +2694 -1
- data/fixtures/vcr_cassettes/how-is-with-config-file.yml +15234 -1
- data/fixtures/vcr_cassettes/how_is_contributions_additions_count.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_all_contributors.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_changed_files.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_changes.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_commits.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_compare_url.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_default_branch.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_deletions_count.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_new_contributors.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_summary.yml +70 -1
- data/fixtures/vcr_cassettes/how_is_contributions_summary_2.yml +70 -1
- data/how_is.gemspec +12 -6
- data/lib/how_is/cacheable.rb +71 -0
- data/lib/how_is/cli.rb +121 -124
- data/lib/how_is/config.rb +123 -0
- data/lib/how_is/constants.rb +9 -0
- data/lib/how_is/date_time_helpers.rb +48 -0
- data/lib/how_is/frontmatter.rb +14 -9
- data/lib/how_is/report.rb +86 -58
- data/lib/how_is/report_collection.rb +113 -0
- data/lib/how_is/sources/ci/appveyor.rb +88 -0
- data/lib/how_is/sources/ci/travis.rb +159 -0
- data/lib/how_is/sources/github/contributions.rb +169 -128
- data/lib/how_is/sources/github/issue_fetcher.rb +148 -0
- data/lib/how_is/sources/github/issues.rb +86 -235
- data/lib/how_is/sources/github/pulls.rb +19 -18
- data/lib/how_is/sources/github.rb +40 -18
- data/lib/how_is/sources/github_helpers.rb +8 -91
- data/lib/how_is/sources.rb +2 -0
- data/lib/how_is/template.rb +9 -0
- data/lib/how_is/templates/contributions_partial.html +1 -0
- data/lib/how_is/templates/{issues_or_pulls_partial.html_template → issues_or_pulls_partial.html} +0 -0
- data/lib/how_is/templates/new_contributors_partial.html +5 -0
- data/lib/how_is/templates/{report.html_template → report.html} +0 -8
- data/lib/how_is/templates/{report_partial.html_template → report_partial.html} +3 -3
- data/lib/how_is/text.rb +26 -0
- data/lib/how_is/version.rb +2 -1
- data/lib/how_is.rb +33 -60
- metadata +28 -47
- data/.hound.yml +0 -2
- data/.rubocop_todo.yml +0 -21
- data/lib/how_is/sources/travis.rb +0 -37
- 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
|
data/lib/how_is/frontmatter.rb
CHANGED
@@ -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(
|
12
|
-
@
|
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
|
44
|
-
@
|
45
|
-
|
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
|
-
|
63
|
-
|
39
|
+
def contributions
|
40
|
+
@gh_contributions ||= HowIs::Sources::Github::Contributions.new(@config, @start_date, @end_date, cache)
|
41
|
+
end
|
64
42
|
|
65
|
-
|
66
|
-
|
43
|
+
def issues
|
44
|
+
@gh_issues ||= HowIs::Sources::Github::Issues.new(@config, @start_date, @end_date, cache)
|
45
|
+
end
|
67
46
|
|
68
|
-
|
69
|
-
|
47
|
+
def pulls
|
48
|
+
@gh_pulls ||= HowIs::Sources::Github::Pulls.new(@config, @start_date, @end_date, cache)
|
49
|
+
end
|
70
50
|
|
71
|
-
|
51
|
+
def travis
|
52
|
+
@travis ||= HowIs::Sources::CI::Travis.new(@config, @start_date, @end_date, cache)
|
53
|
+
end
|
72
54
|
|
73
|
-
|
74
|
-
|
55
|
+
def appveyor
|
56
|
+
@appveyor ||= HowIs::Sources::CI::Appveyor.new(@config, @start_date, @end_date, cache)
|
57
|
+
end
|
75
58
|
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
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 =
|
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
|