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
@@ -1,274 +1,125 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "how_is/
|
3
|
+
require "how_is/date_time_helpers"
|
4
4
|
require "how_is/sources/github"
|
5
5
|
require "how_is/sources/github_helpers"
|
6
|
+
require "how_is/sources/github/issue_fetcher"
|
7
|
+
require "how_is/template"
|
6
8
|
require "date"
|
7
9
|
|
8
|
-
module HowIs
|
9
|
-
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
query = CGI.escape(raw_query)
|
33
|
-
|
34
|
-
"https://github.com/#{@repository}/#{url_suffix}?q=#{query}"
|
35
|
-
end
|
36
|
-
|
37
|
-
def average_age
|
38
|
-
average_age_for(data)
|
39
|
-
end
|
40
|
-
|
41
|
-
def oldest
|
42
|
-
oldest_for(data) || {}
|
43
|
-
end
|
44
|
-
|
45
|
-
def newest
|
46
|
-
newest_for(data) || {}
|
47
|
-
end
|
48
|
-
|
49
|
-
def summary
|
50
|
-
number_open = to_a.length
|
51
|
-
pretty_number =
|
52
|
-
pluralize(pretty_type, number_open, zero_is_no: true)
|
10
|
+
module HowIs
|
11
|
+
module Sources
|
12
|
+
class Github
|
13
|
+
##
|
14
|
+
# Fetches various information about GitHub Issues.
|
15
|
+
class Issues
|
16
|
+
include HowIs::DateTimeHelpers
|
17
|
+
include HowIs::Sources::GithubHelpers
|
18
|
+
|
19
|
+
attr_reader :config, :start_date, :end_date, :cache
|
20
|
+
|
21
|
+
# @param repository [String] GitHub repository name, of the format user/repo.
|
22
|
+
# @param start_date [String] Start date for the report being generated.
|
23
|
+
# @param end_date [String] End date for the report being generated.
|
24
|
+
# @param cache [Cacheable] Instance of HowIs::Cacheable to cache API calls
|
25
|
+
def initialize(config, start_date, end_date, cache)
|
26
|
+
@config = config
|
27
|
+
@cache = cache
|
28
|
+
@repository = config["repository"]
|
29
|
+
raise "#{self.class}.new() got nil repository." if @repository.nil?
|
30
|
+
@start_date = start_date
|
31
|
+
@end_date = end_date
|
32
|
+
end
|
53
33
|
|
54
|
-
|
55
|
-
|
34
|
+
def url(values = {})
|
35
|
+
defaults = {
|
36
|
+
"is" => singular_type,
|
37
|
+
"created" => "#{@start_date}..#{@end_date}",
|
38
|
+
}
|
39
|
+
values = defaults.merge(values)
|
40
|
+
raw_query = values.map { |k, v|
|
41
|
+
[k, v].join(":")
|
42
|
+
}.join(" ")
|
56
43
|
|
57
|
-
|
58
|
-
summary_ = "<p>#{summary}</p>"
|
44
|
+
query = CGI.escape(raw_query)
|
59
45
|
|
60
|
-
|
46
|
+
"https://github.com/#{@repository}/#{url_suffix}?q=#{query}"
|
47
|
+
end
|
61
48
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
type: type,
|
66
|
-
pretty_type: pretty_type,
|
49
|
+
def average_age
|
50
|
+
average_age_for(data)
|
51
|
+
end
|
67
52
|
|
68
|
-
|
69
|
-
|
53
|
+
def oldest
|
54
|
+
result = oldest_for(data)
|
55
|
+
return {} if result.nil?
|
70
56
|
|
71
|
-
|
72
|
-
newest_date: pretty_date(newest["createdAt"]),
|
73
|
-
}
|
57
|
+
result["date"] = pretty_date(result["createdAt"])
|
74
58
|
|
75
|
-
|
76
|
-
|
59
|
+
result
|
60
|
+
end
|
77
61
|
|
78
|
-
|
62
|
+
def newest
|
63
|
+
result = newest_for(data)
|
64
|
+
return {} if result.nil?
|
79
65
|
|
80
|
-
|
81
|
-
ipl = with_label_links(num_with_label(data), @repository)
|
82
|
-
number_with_no_label = num_with_no_label(data)
|
66
|
+
result["date"] = pretty_date(result["createdAt"])
|
83
67
|
|
84
|
-
|
85
|
-
ipl["(No label)"] = {
|
86
|
-
"name" => "(No label)",
|
87
|
-
"total" => number_with_no_label,
|
88
|
-
}
|
68
|
+
result
|
89
69
|
end
|
90
70
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
<tr>
|
96
|
-
<td style="width: %{label_width}">%{label_text}</td>
|
97
|
-
<td><span class="fill" style="width: %{percentage}%%">%{link_text}</span></td>
|
98
|
-
</tr>
|
99
|
-
EOF
|
71
|
+
def summary
|
72
|
+
number_open = to_a.length
|
73
|
+
pretty_number = pluralize(pretty_type, number_open, zero_is_no: false)
|
74
|
+
was_were = (number_open == 1) ? "was" : "were"
|
100
75
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
return "<p>There are no open issues to graph.</p>" if ipl.empty?
|
76
|
+
"<p>A total of <a href=\"#{url}\">#{pretty_number}</a> #{was_were} opened during this period.</p>"
|
77
|
+
end
|
105
78
|
|
106
|
-
|
107
|
-
|
79
|
+
def to_html
|
80
|
+
return summary if to_a.empty?
|
108
81
|
|
109
|
-
|
110
|
-
|
82
|
+
HowIs::Template.apply("issues_or_pulls_partial.html", {
|
83
|
+
summary: summary,
|
84
|
+
average_age: average_age,
|
85
|
+
pretty_type: pretty_type,
|
111
86
|
|
112
|
-
|
113
|
-
|
114
|
-
label_text = label
|
115
|
-
label_url = label_url_for(info["name"])
|
116
|
-
label_text = '<a href="' + label_url + '">' + label_text + '</a>'
|
87
|
+
oldest_link: oldest["url"],
|
88
|
+
oldest_date: oldest["date"],
|
117
89
|
|
118
|
-
|
119
|
-
|
120
|
-
label_text: label_text,
|
121
|
-
label_link: info["url"],
|
122
|
-
percentage: get_percentage.call(info["total"]),
|
123
|
-
link_text: info["total"].to_s,
|
90
|
+
newest_link: newest["url"],
|
91
|
+
newest_date: newest["date"],
|
124
92
|
})
|
125
|
-
}
|
126
|
-
|
127
|
-
"<table class=\"horizontal-bar-graph\">\n" +
|
128
|
-
parts.join("\n") +
|
129
|
-
"\n</table>"
|
130
|
-
end
|
131
|
-
|
132
|
-
def to_a
|
133
|
-
obj_to_array_of_hashes(data)
|
134
|
-
end
|
135
|
-
|
136
|
-
private
|
137
|
-
|
138
|
-
def url_suffix
|
139
|
-
"issues"
|
140
|
-
end
|
141
|
-
|
142
|
-
def singular_type
|
143
|
-
"issue"
|
144
|
-
end
|
145
|
-
|
146
|
-
def type
|
147
|
-
singular_type + "s"
|
148
|
-
end
|
149
|
-
|
150
|
-
def pretty_type
|
151
|
-
"issue"
|
152
|
-
end
|
153
|
-
|
154
|
-
def data
|
155
|
-
return @data if instance_variable_defined?(:@data)
|
156
|
-
|
157
|
-
@data = []
|
158
|
-
return @data if last_cursor.nil?
|
159
|
-
|
160
|
-
after = nil
|
161
|
-
data = []
|
162
|
-
until after == TERMINATE_GRAPHQL_LOOP
|
163
|
-
after, data = fetch_issues(after, data)
|
164
93
|
end
|
165
94
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
def issue_is_relevant?(issue)
|
170
|
-
if !issue["closedAt"].nil? && date_le(issue["closedAt"], @start_date)
|
171
|
-
false
|
172
|
-
else
|
173
|
-
date_ge(issue["createdAt"], @start_date) && date_le(issue["createdAt"], @end_date)
|
95
|
+
def to_a
|
96
|
+
obj_to_array_of_hashes(data)
|
174
97
|
end
|
175
|
-
end
|
176
|
-
|
177
|
-
def graphql(query_string)
|
178
|
-
query = Okay::GraphQL.query(query_string)
|
179
|
-
headers = {bearer_token: HowIs::Sources::Github::ACCESS_TOKEN}
|
180
|
-
query.submit!(:github, headers).or_raise!.from_json
|
181
|
-
end
|
182
98
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
raw_data = graphql <<~QUERY
|
187
|
-
repository(owner: #{@user.inspect}, name: #{@repo.inspect}) {
|
188
|
-
#{type}(last: 1, orderBy:{field: CREATED_AT, direction: ASC}) {
|
189
|
-
edges {
|
190
|
-
cursor
|
191
|
-
}
|
192
|
-
}
|
193
|
-
}
|
194
|
-
QUERY
|
195
|
-
|
196
|
-
edges = raw_data.dig("data", "repository", type, "edges")
|
197
|
-
@last_cursor =
|
198
|
-
if edges.nil? || edges.empty?
|
199
|
-
nil
|
200
|
-
else
|
201
|
-
edges.last["cursor"]
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def fetch_issues(after, data)
|
206
|
-
data ||= []
|
207
|
-
chunk_size = 100
|
208
|
-
after_str = ", after: #{after.inspect}" unless after.nil?
|
209
|
-
|
210
|
-
raw_data = graphql <<~QUERY
|
211
|
-
repository(owner: #{@user.inspect}, name: #{@repo.inspect}) {
|
212
|
-
#{type}(first: #{chunk_size}#{after_str}, orderBy:{field: CREATED_AT, direction: ASC}) {
|
213
|
-
edges {
|
214
|
-
cursor
|
215
|
-
node {
|
216
|
-
number
|
217
|
-
createdAt
|
218
|
-
closedAt
|
219
|
-
updatedAt
|
220
|
-
state
|
221
|
-
title
|
222
|
-
url
|
223
|
-
labels(first: 100) {
|
224
|
-
nodes {
|
225
|
-
name
|
226
|
-
}
|
227
|
-
}
|
228
|
-
}
|
229
|
-
}
|
230
|
-
}
|
231
|
-
}
|
232
|
-
QUERY
|
233
|
-
|
234
|
-
edges = raw_data.dig("data", "repository", type, "edges")
|
235
|
-
|
236
|
-
current_last_cursor = edges.last["cursor"]
|
237
|
-
|
238
|
-
unless edges.nil?
|
239
|
-
new_data = edges.map { |issue|
|
240
|
-
node = issue["node"]
|
241
|
-
node["labels"] = node["labels"]["nodes"]
|
242
|
-
|
243
|
-
node
|
244
|
-
}
|
245
|
-
|
246
|
-
data += new_data
|
99
|
+
def type
|
100
|
+
singular_type + "s"
|
247
101
|
end
|
248
102
|
|
249
|
-
|
250
|
-
|
103
|
+
def pretty_type
|
104
|
+
"issue"
|
251
105
|
end
|
252
106
|
|
253
|
-
|
254
|
-
end
|
255
|
-
|
256
|
-
def date_le(left, right)
|
257
|
-
left = str_to_dt(left)
|
258
|
-
right = str_to_dt(right)
|
107
|
+
private
|
259
108
|
|
260
|
-
|
261
|
-
|
109
|
+
def url_suffix
|
110
|
+
"issues"
|
111
|
+
end
|
262
112
|
|
263
|
-
|
264
|
-
|
265
|
-
|
113
|
+
def singular_type
|
114
|
+
"issue"
|
115
|
+
end
|
266
116
|
|
267
|
-
|
268
|
-
|
117
|
+
def data
|
118
|
+
return @data if instance_variable_defined?(:@data)
|
269
119
|
|
270
|
-
|
271
|
-
|
120
|
+
fetcher = IssueFetcher.new(self)
|
121
|
+
@data = fetcher.data
|
122
|
+
end
|
272
123
|
end
|
273
124
|
end
|
274
125
|
end
|
@@ -1,27 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "how_is/
|
4
|
-
require "how_is/sources/github"
|
5
|
-
require "how_is/sources/github_helpers"
|
6
|
-
require "date"
|
3
|
+
require "how_is/sources/github/issues"
|
7
4
|
|
8
|
-
module HowIs
|
9
|
-
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
module HowIs
|
6
|
+
module Sources
|
7
|
+
class Github
|
8
|
+
##
|
9
|
+
# Fetches various information about GitHub Pull Requests
|
10
|
+
class Pulls < Issues
|
11
|
+
def url_suffix
|
12
|
+
"pulls"
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def singular_type
|
16
|
+
"pull"
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def type
|
20
|
+
"pullRequests"
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
def pretty_type
|
24
|
+
"pull request"
|
25
|
+
end
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -2,34 +2,56 @@
|
|
2
2
|
|
3
3
|
require "how_is/version"
|
4
4
|
require "how_is/sources"
|
5
|
-
require "github_api"
|
6
5
|
require "okay/graphql"
|
7
6
|
|
8
7
|
module HowIs
|
9
8
|
module Sources
|
10
9
|
# Contains configuration information for GitHub-based sources.
|
11
10
|
class Github
|
12
|
-
|
13
|
-
# is undefined.
|
14
|
-
class ConfigurationError < StandardError
|
15
|
-
def initialize(env_variable)
|
16
|
-
super("environment variable #{env_variable} not defined." \
|
17
|
-
" See README.md for details.")
|
18
|
-
end
|
11
|
+
class ConfigError < StandardError
|
19
12
|
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
raise ConfigurationError, "HOWIS_GITHUB_TOKEN" if ACCESS_TOKEN.nil?
|
14
|
+
def initialize(config)
|
15
|
+
must_have_key!(config, "sources/github")
|
24
16
|
|
25
|
-
|
26
|
-
BASIC_AUTH = ENV["HOWIS_BASIC_AUTH"]
|
27
|
-
raise ConfigurationError, "HOWIS_BASIC_AUTH" if BASIC_AUTH.nil?
|
17
|
+
@config = config["sources/github"]
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
19
|
+
must_have_key!(@config, "username")
|
20
|
+
must_have_key!(@config, "token")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Verify that +hash+ has a particular +key+.
|
24
|
+
#
|
25
|
+
# @raise [ConfigError] If +hash+ does not have the required +key+.
|
26
|
+
def must_have_key!(hash, key)
|
27
|
+
raise ConfigError, "Expected Hash, got #{hash.class}" unless hash.is_a?(Hash)
|
28
|
+
raise ConfigError, "Expected key `#{key}'" unless hash.has_key?(key)
|
29
|
+
end
|
30
|
+
private :must_have_key!
|
31
|
+
|
32
|
+
# The GitHub username used for authenticating with GitHub.
|
33
|
+
def username
|
34
|
+
@config["username"]
|
35
|
+
end
|
36
|
+
|
37
|
+
# A GitHub Personal Access Token which goes with +username+.
|
38
|
+
def access_token
|
39
|
+
@config["token"]
|
40
|
+
end
|
41
|
+
|
42
|
+
# A string containing both the GitHub username and access token,
|
43
|
+
# used in instances where we use Basic Auth.
|
44
|
+
def basic_auth
|
45
|
+
"#{username}:#{access_token}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Submit a GraphQL query, and convert it from JSON to a Ruby object.
|
49
|
+
def graphql(query_string)
|
50
|
+
Okay::GraphQL.query(query_string)
|
51
|
+
.submit!(:github, {bearer_token: access_token})
|
52
|
+
.or_raise!
|
53
|
+
.from_json
|
54
|
+
end
|
33
55
|
end
|
34
56
|
end
|
35
57
|
end
|
@@ -5,46 +5,13 @@ require "date"
|
|
5
5
|
|
6
6
|
module HowIs
|
7
7
|
module Sources
|
8
|
+
##
|
9
|
+
# Helper functions used by GitHub-related sources.
|
8
10
|
module GithubHelpers
|
9
11
|
def obj_to_array_of_hashes(object)
|
10
12
|
object.to_a.map(&:to_h)
|
11
13
|
end
|
12
14
|
|
13
|
-
# Given an Array of issues or pulls, return a Hash specifying how many
|
14
|
-
# issues or pulls use each label.
|
15
|
-
def num_with_label(issues_or_pulls)
|
16
|
-
# Returned hash maps labels to frequency.
|
17
|
-
# E.g., given 10 issues/pulls with label "label1" and 5 with label "label2",
|
18
|
-
# {
|
19
|
-
# "label1" => 10,
|
20
|
-
# "label2" => 5
|
21
|
-
# }
|
22
|
-
|
23
|
-
hash = Hash.new(0)
|
24
|
-
issues_or_pulls.each do |iop|
|
25
|
-
next unless iop["labels"]
|
26
|
-
|
27
|
-
iop["labels"].each do |label|
|
28
|
-
hash[label["name"]] += 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
hash
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns the number of issues with no label.
|
35
|
-
def num_with_no_label(issues)
|
36
|
-
issues.select { |x| x["labels"].empty? }.length
|
37
|
-
end
|
38
|
-
|
39
|
-
# Given an Array of dates, average the timestamps and return the date that
|
40
|
-
# represents.
|
41
|
-
def average_date_for(issues_or_pulls)
|
42
|
-
timestamps = issues_or_pulls.map { |iop| DateTime.parse(iop["createdAt"]).strftime("%s").to_i }
|
43
|
-
average_timestamp = timestamps.reduce(:+) / issues_or_pulls.length
|
44
|
-
|
45
|
-
DateTime.strptime(average_timestamp.to_s, "%s")
|
46
|
-
end
|
47
|
-
|
48
15
|
# Given an Array of issues or pulls, return the average age of them.
|
49
16
|
# Returns nil if no issues or pulls are provided.
|
50
17
|
def average_age_for(issues_or_pulls)
|
@@ -53,19 +20,12 @@ module HowIs
|
|
53
20
|
ages = issues_or_pulls.map { |iop| time_ago_in_seconds(iop["createdAt"]) }
|
54
21
|
average_age_in_seconds = ages.reduce(:+) / ages.length
|
55
22
|
|
56
|
-
values =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
most_significant = values[0, 2].map { |x| x.join(" ") }
|
23
|
+
values =
|
24
|
+
period_pairs_for(average_age_in_seconds) \
|
25
|
+
.reject { |(v, _)| v.zero? } \
|
26
|
+
.map { |(v, k)| pluralize(k, v) }
|
62
27
|
|
63
|
-
value =
|
64
|
-
if most_significant.length < 2
|
65
|
-
most_significant.first
|
66
|
-
else
|
67
|
-
most_significant.join(" and ")
|
68
|
-
end
|
28
|
+
value = values[0, 2].join(" and ")
|
69
29
|
|
70
30
|
"approximately #{value}"
|
71
31
|
end
|
@@ -90,48 +50,13 @@ module HowIs
|
|
90
50
|
sort_iops_by_created_at(issues_or_pulls).last
|
91
51
|
end
|
92
52
|
|
93
|
-
# Given an issue or PR, returns the date it was created.
|
94
|
-
def date_for(issue_or_pull)
|
95
|
-
DateTime.parse(issue_or_pull["createdAt"])
|
96
|
-
end
|
97
|
-
|
98
|
-
def label_url_for(label_name)
|
99
|
-
if label_name == "(No label)"
|
100
|
-
url({"no"=>"label"})
|
101
|
-
else
|
102
|
-
url({"label"=>label_name})
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
53
|
private
|
107
54
|
|
108
|
-
# Takes an Array of labels, and returns amodified list that includes links
|
109
|
-
# to each label.
|
110
|
-
def with_label_links(labels, repository)
|
111
|
-
labels.map { |label, num_issues|
|
112
|
-
label_link = "https://github.com/#{repository}/issues?q=" + CGI.escape("is:open is:issue label:\"#{label}\"")
|
113
|
-
|
114
|
-
[label, {"link" => label_link, "total" => num_issues}]
|
115
|
-
}.to_h
|
116
|
-
end
|
117
|
-
|
118
55
|
# Returns how many seconds ago a date (as a String) was.
|
119
56
|
def time_ago_in_seconds(x)
|
120
57
|
DateTime.now.strftime("%s").to_i - DateTime.parse(x).strftime("%s").to_i
|
121
58
|
end
|
122
59
|
|
123
|
-
def issue_or_pull_to_hash(iop)
|
124
|
-
return nil if iop.nil?
|
125
|
-
|
126
|
-
ret = {}
|
127
|
-
|
128
|
-
ret["html_url"] = iop["html_url"]
|
129
|
-
ret["number"] = iop["number"]
|
130
|
-
ret["date"] = date_for(iop)
|
131
|
-
|
132
|
-
ret
|
133
|
-
end
|
134
|
-
|
135
60
|
SECONDS_IN_A_YEAR = 31_556_926
|
136
61
|
SECONDS_IN_A_MONTH = 2_629_743
|
137
62
|
SECONDS_IN_A_WEEK = 604_800
|
@@ -165,14 +90,6 @@ module HowIs
|
|
165
90
|
"#{number_str} #{string}#{(number == 1) ? '' : 's'}"
|
166
91
|
end
|
167
92
|
|
168
|
-
def are_or_is(number)
|
169
|
-
if number == 1
|
170
|
-
"is"
|
171
|
-
else
|
172
|
-
"are"
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
93
|
def pretty_date(date_or_str)
|
177
94
|
if date_or_str.is_a?(DateTime)
|
178
95
|
date = datetime_or_str
|
@@ -182,7 +99,7 @@ module HowIs
|
|
182
99
|
raise ArgumentError, "expected DateTime or String, got #{date_or_str.class}"
|
183
100
|
end
|
184
101
|
|
185
|
-
date.strftime("%b %
|
102
|
+
date.strftime("%b %d, %Y")
|
186
103
|
end
|
187
104
|
end
|
188
105
|
end
|
data/lib/how_is/sources.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
%{start_text}, %{user}/%{repo} gained <a href="%{compare_url}">%{new_commits}</a>, contributed by %{authors}. There %{additions_count_str} %{additions} and %{deletions} across %{changed_files}.
|
data/lib/how_is/templates/{issues_or_pulls_partial.html_template → issues_or_pulls_partial.html}
RENAMED
File without changes
|
@@ -9,14 +9,6 @@
|
|
9
9
|
max-width: 72ch;
|
10
10
|
margin: auto;
|
11
11
|
}
|
12
|
-
.horizontal-bar-graph {
|
13
|
-
position: relative;
|
14
|
-
width: 100%%; /* lol Kernel.format() disapproves. */
|
15
|
-
}
|
16
|
-
.horizontal-bar-graph .fill {
|
17
|
-
display: inline-block;
|
18
|
-
background: #CCC;
|
19
|
-
}
|
20
12
|
</style>
|
21
13
|
</head>
|
22
14
|
<body>
|