how_is 8.0.0 → 9.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.
data/lib/how_is.rb CHANGED
@@ -1,56 +1,58 @@
1
- require 'how_is/version'
2
- require 'contracts'
3
- require 'cacert'
4
-
5
- Cacert.set_in_env
6
-
7
- C = Contracts
8
-
9
- module HowIs
10
- include Contracts::Core
11
-
12
- require 'how_is/fetcher'
13
- require 'how_is/analyzer'
14
- require 'how_is/report'
15
-
16
- def self.generate_report_file(report_file:, **kw_args)
17
- analysis = self.generate_analysis(**kw_args)
18
-
19
- Report.export!(analysis, report_file)
20
- end
21
-
22
- def self.generate_report(format:, **kw_args)
23
- analysis = self.generate_analysis(**kw_args)
24
-
25
- Report.export(analysis, format)
26
- end
27
-
28
- def self.supported_formats
29
- report_constants = HowIs.constants.grep(/.Report/) - [:BaseReport]
30
- report_constants.map {|x| x.to_s.split('Report').first.downcase }
31
- end
32
-
33
- def self.can_export_to?(file)
34
- supported_formats.include?(file.split('.').last)
35
- end
36
-
37
- Contract C::KeywordArgs[repository: String,
38
- from_file: C::Optional[C::Or[String, nil]],
39
- fetcher: C::Optional[Class],
40
- analyzer: C::Optional[Class],
41
- github: C::Optional[C::Any]] => C::Any
42
- def self.generate_analysis(repository:,
43
- from_file: nil,
44
- fetcher: Fetcher.new,
45
- analyzer: Analyzer.new,
46
- github: nil)
47
- if from_file
48
- analysis = analyzer.from_file(from_file)
49
- else
50
- raw_data = fetcher.call(repository, github)
51
- analysis = analyzer.call(raw_data)
52
- end
53
-
54
- analysis
55
- end
56
- end
1
+ require 'how_is/version'
2
+ require 'contracts'
3
+ require 'cacert'
4
+
5
+ Cacert.set_in_env
6
+
7
+ C = Contracts
8
+
9
+ module HowIs
10
+ include Contracts::Core
11
+
12
+ require 'how_is/fetcher'
13
+ require 'how_is/analyzer'
14
+ require 'how_is/report'
15
+
16
+ DEFAULT_FORMAT = :html
17
+
18
+ def self.generate_report_file(report:, **kw_args)
19
+ analysis = self.generate_analysis(**kw_args)
20
+
21
+ Report.export!(analysis, report)
22
+ end
23
+
24
+ def self.generate_report(format:, **kw_args)
25
+ analysis = self.generate_analysis(**kw_args)
26
+
27
+ Report.export(analysis, format)
28
+ end
29
+
30
+ def self.supported_formats
31
+ report_constants = HowIs.constants.grep(/.Report/) - [:BaseReport]
32
+ report_constants.map {|x| x.to_s.split('Report').first.downcase }
33
+ end
34
+
35
+ def self.can_export_to?(file)
36
+ supported_formats.include?(file.split('.').last)
37
+ end
38
+
39
+ Contract C::KeywordArgs[repository: String,
40
+ from: C::Optional[C::Or[String, nil]],
41
+ fetcher: C::Optional[Class],
42
+ analyzer: C::Optional[Class],
43
+ github: C::Optional[C::Any]] => C::Any
44
+ def self.generate_analysis(repository:,
45
+ from: nil,
46
+ fetcher: Fetcher.new,
47
+ analyzer: Analyzer.new,
48
+ github: nil)
49
+ if from
50
+ analysis = analyzer.from_file(from)
51
+ else
52
+ raw_data = fetcher.call(repository, github)
53
+ analysis = analyzer.call(raw_data)
54
+ end
55
+
56
+ analysis
57
+ end
58
+ end
@@ -1,170 +1,170 @@
1
- require 'contracts'
2
- require 'ostruct'
3
- require 'date'
4
- require 'json'
5
-
6
- module HowIs
7
- ##
8
- # Represents a completed analysis of the repository being analyzed.
9
- class Analysis < OpenStruct
10
- end
11
-
12
- class Analyzer
13
- include Contracts::Core
14
-
15
- class UnsupportedImportFormat < StandardError
16
- def initialize(format)
17
- super("Unsupported import format: #{format}")
18
- end
19
- end
20
-
21
- Contract Fetcher::Results, C::KeywordArgs[analysis_class: C::Optional[Class]] => Analysis
22
- def call(data, analysis_class: Analysis)
23
- issues = data.issues
24
- pulls = data.pulls
25
-
26
- analysis_class.new(
27
- issues_url: "https://github.com/#{data.repository}/issues",
28
- pulls_url: "https://github.com/#{data.repository}/pulls",
29
-
30
- repository: data.repository,
31
-
32
- number_of_issues: issues.length,
33
- number_of_pulls: pulls.length,
34
-
35
- issues_with_label: with_label_links(num_with_label(issues), data.repository),
36
- issues_with_no_label: {link: nil, total: num_with_no_label(issues)},
37
-
38
- average_issue_age: average_age_for(issues),
39
- average_pull_age: average_age_for(pulls),
40
-
41
- oldest_issue: issue_or_pull_to_hash(oldest_for(issues)),
42
- oldest_pull: issue_or_pull_to_hash(oldest_for(pulls)),
43
- )
44
- end
45
-
46
- def from_file(file)
47
- extension = file.split('.').last
48
- raise UnsupportedImportFormat, extension unless extension == 'json'
49
-
50
- hash = JSON.parse(open(file).read)
51
- hash = hash.map do |k, v|
52
- v = DateTime.parse(v) if k.end_with?('_date')
53
-
54
- [k, v]
55
- end.to_h
56
-
57
- %w[oldest_issue oldest_pull].each do |key|
58
- hash[key]['date'] = DateTime.parse(hash[key]['date'])
59
- end
60
-
61
- Analysis.new(hash)
62
- end
63
-
64
- # Given an Array of issues or pulls, return a Hash specifying how many
65
- # issues or pulls use each label.
66
- def num_with_label(issues_or_pulls)
67
- # Returned hash maps labels to frequency.
68
- # E.g., given 10 issues/pulls with label "label1" and 5 with label "label2",
69
- # {
70
- # "label1" => 10,
71
- # "label2" => 5
72
- # }
73
-
74
- hash = Hash.new(0)
75
- issues_or_pulls.each do |iop|
76
- next unless iop['labels']
77
-
78
- iop['labels'].each do |label|
79
- hash[label['name']] += 1
80
- end
81
- end
82
- hash
83
- end
84
-
85
- def num_with_no_label(issues)
86
- issues.select { |x| x['labels'].empty? }.length
87
- end
88
-
89
- def average_date_for(issues_or_pulls)
90
- timestamps = issues_or_pulls.map { |iop| Date.parse(iop['created_at']).strftime('%s').to_i }
91
- average_timestamp = timestamps.reduce(:+) / issues_or_pulls.length
92
-
93
- DateTime.strptime(average_timestamp.to_s, '%s')
94
- end
95
-
96
- # Given an Array of issues or pulls, return the average age of them.
97
- def average_age_for(issues_or_pulls)
98
- ages = issues_or_pulls.map {|iop| time_ago_in_seconds(iop['created_at'])}
99
- raw_average = ages.reduce(:+) / ages.length
100
-
101
- seconds_in_a_year = 31_556_926
102
- seconds_in_a_month = 2_629_743
103
- seconds_in_a_week = 604_800
104
- seconds_in_a_day = 86_400
105
-
106
- years = raw_average / seconds_in_a_year
107
- years_remainder = raw_average % seconds_in_a_year
108
-
109
- months = years_remainder / seconds_in_a_month
110
- months_remainder = years_remainder % seconds_in_a_month
111
-
112
- weeks = months_remainder / seconds_in_a_week
113
- weeks_remainder = months_remainder % seconds_in_a_week
114
-
115
- days = weeks_remainder / seconds_in_a_day
116
-
117
- values = [
118
- [years, "year"],
119
- [months, "month"],
120
- [weeks, "week"],
121
- [days, "day"],
122
- ].reject {|(v, k)| v == 0}.map{ |(v,k)|
123
- k = k + 's' if v != 1
124
- [v, k]
125
- }
126
-
127
- most_significant = values[0, 2].map {|x| x.join(" ")}
128
-
129
- if most_significant.length < 2
130
- value = most_significant.first
131
- else
132
- value = most_significant.join(" and ")
133
- end
134
-
135
- "approximately #{value}"
136
- end
137
-
138
- # Given an Array of issues or pulls, return the creation date of the oldest.
139
- def oldest_for(issues_or_pulls)
140
- issues_or_pulls.sort_by {|x| DateTime.parse(x['created_at']) }.first
141
- end
142
-
143
- def date_for(issue_or_pull)
144
- DateTime.parse(issue_or_pull['created_at'])
145
- end
146
-
147
- private
148
- def with_label_links(labels, repository)
149
- labels.map do |label, num_issues|
150
- label_link = "https://github.com/#{repository}/issues?q=" + CGI.escape("is:open is:issue label:\"#{label}\"")
151
-
152
- [label, {link: label_link, total: num_issues}]
153
- end.to_h
154
- end
155
-
156
- def time_ago_in_seconds(x)
157
- DateTime.now.strftime("%s").to_i - DateTime.parse(x).strftime("%s").to_i
158
- end
159
-
160
- def issue_or_pull_to_hash(iop)
161
- ret = {}
162
-
163
- ret[:html_url] = iop['html_url']
164
- ret[:number] = iop['number']
165
- ret[:date] = date_for(iop)
166
-
167
- ret
168
- end
169
- end
170
- end
1
+ require 'contracts'
2
+ require 'ostruct'
3
+ require 'date'
4
+ require 'json'
5
+
6
+ module HowIs
7
+ ##
8
+ # Represents a completed analysis of the repository being analyzed.
9
+ class Analysis < OpenStruct
10
+ end
11
+
12
+ class Analyzer
13
+ include Contracts::Core
14
+
15
+ class UnsupportedImportFormat < StandardError
16
+ def initialize(format)
17
+ super("Unsupported import format: #{format}")
18
+ end
19
+ end
20
+
21
+ Contract Fetcher::Results, C::KeywordArgs[analysis_class: C::Optional[Class]] => Analysis
22
+ def call(data, analysis_class: Analysis)
23
+ issues = data.issues
24
+ pulls = data.pulls
25
+
26
+ analysis_class.new(
27
+ issues_url: "https://github.com/#{data.repository}/issues",
28
+ pulls_url: "https://github.com/#{data.repository}/pulls",
29
+
30
+ repository: data.repository,
31
+
32
+ number_of_issues: issues.length,
33
+ number_of_pulls: pulls.length,
34
+
35
+ issues_with_label: with_label_links(num_with_label(issues), data.repository),
36
+ issues_with_no_label: {'link' => nil, 'total' => num_with_no_label(issues)},
37
+
38
+ average_issue_age: average_age_for(issues),
39
+ average_pull_age: average_age_for(pulls),
40
+
41
+ oldest_issue: issue_or_pull_to_hash(oldest_for(issues)),
42
+ oldest_pull: issue_or_pull_to_hash(oldest_for(pulls)),
43
+ )
44
+ end
45
+
46
+ def from_file(file)
47
+ extension = file.split('.').last
48
+ raise UnsupportedImportFormat, extension unless extension == 'json'
49
+
50
+ hash = JSON.parse(open(file).read)
51
+ hash = hash.map do |k, v|
52
+ v = DateTime.parse(v) if k.end_with?('_date')
53
+
54
+ [k, v]
55
+ end.to_h
56
+
57
+ %w[oldest_issue oldest_pull].each do |key|
58
+ hash[key]['date'] = DateTime.parse(hash[key]['date'])
59
+ end
60
+
61
+ Analysis.new(hash)
62
+ end
63
+
64
+ # Given an Array of issues or pulls, return a Hash specifying how many
65
+ # issues or pulls use each label.
66
+ def num_with_label(issues_or_pulls)
67
+ # Returned hash maps labels to frequency.
68
+ # E.g., given 10 issues/pulls with label "label1" and 5 with label "label2",
69
+ # {
70
+ # "label1" => 10,
71
+ # "label2" => 5
72
+ # }
73
+
74
+ hash = Hash.new(0)
75
+ issues_or_pulls.each do |iop|
76
+ next unless iop['labels']
77
+
78
+ iop['labels'].each do |label|
79
+ hash[label['name']] += 1
80
+ end
81
+ end
82
+ hash
83
+ end
84
+
85
+ def num_with_no_label(issues)
86
+ issues.select { |x| x['labels'].empty? }.length
87
+ end
88
+
89
+ def average_date_for(issues_or_pulls)
90
+ timestamps = issues_or_pulls.map { |iop| Date.parse(iop['created_at']).strftime('%s').to_i }
91
+ average_timestamp = timestamps.reduce(:+) / issues_or_pulls.length
92
+
93
+ DateTime.strptime(average_timestamp.to_s, '%s')
94
+ end
95
+
96
+ # Given an Array of issues or pulls, return the average age of them.
97
+ def average_age_for(issues_or_pulls)
98
+ ages = issues_or_pulls.map {|iop| time_ago_in_seconds(iop['created_at'])}
99
+ raw_average = ages.reduce(:+) / ages.length
100
+
101
+ seconds_in_a_year = 31_556_926
102
+ seconds_in_a_month = 2_629_743
103
+ seconds_in_a_week = 604_800
104
+ seconds_in_a_day = 86_400
105
+
106
+ years = raw_average / seconds_in_a_year
107
+ years_remainder = raw_average % seconds_in_a_year
108
+
109
+ months = years_remainder / seconds_in_a_month
110
+ months_remainder = years_remainder % seconds_in_a_month
111
+
112
+ weeks = months_remainder / seconds_in_a_week
113
+ weeks_remainder = months_remainder % seconds_in_a_week
114
+
115
+ days = weeks_remainder / seconds_in_a_day
116
+
117
+ values = [
118
+ [years, "year"],
119
+ [months, "month"],
120
+ [weeks, "week"],
121
+ [days, "day"],
122
+ ].reject {|(v, k)| v == 0}.map{ |(v,k)|
123
+ k = k + 's' if v != 1
124
+ [v, k]
125
+ }
126
+
127
+ most_significant = values[0, 2].map {|x| x.join(" ")}
128
+
129
+ if most_significant.length < 2
130
+ value = most_significant.first
131
+ else
132
+ value = most_significant.join(" and ")
133
+ end
134
+
135
+ "approximately #{value}"
136
+ end
137
+
138
+ # Given an Array of issues or pulls, return the creation date of the oldest.
139
+ def oldest_for(issues_or_pulls)
140
+ issues_or_pulls.sort_by {|x| DateTime.parse(x['created_at']) }.first
141
+ end
142
+
143
+ def date_for(issue_or_pull)
144
+ DateTime.parse(issue_or_pull['created_at'])
145
+ end
146
+
147
+ private
148
+ def with_label_links(labels, repository)
149
+ labels.map do |label, num_issues|
150
+ label_link = "https://github.com/#{repository}/issues?q=" + CGI.escape("is:open is:issue label:\"#{label}\"")
151
+
152
+ [label, {'link' => label_link, 'total' => num_issues}]
153
+ end.to_h
154
+ end
155
+
156
+ def time_ago_in_seconds(x)
157
+ DateTime.now.strftime("%s").to_i - DateTime.parse(x).strftime("%s").to_i
158
+ end
159
+
160
+ def issue_or_pull_to_hash(iop)
161
+ ret = {}
162
+
163
+ ret['html_url'] = iop['html_url']
164
+ ret['number'] = iop['number']
165
+ ret['date'] = date_for(iop)
166
+
167
+ ret
168
+ end
169
+ end
170
+ end