gems_bond 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/gems_bond/configuration.rb +12 -0
- data/lib/gems_bond/examination_helper.rb +101 -0
- data/lib/gems_bond/fetch_helper.rb +71 -0
- data/lib/gems_bond/fetcher/github.rb +121 -0
- data/lib/gems_bond/fetcher/ruby_gems.rb +58 -0
- data/lib/gems_bond/gem.rb +50 -0
- data/lib/gems_bond/printer.rb +88 -0
- data/lib/gems_bond/railtie.rb +10 -0
- data/lib/gems_bond/scores.yml +32 -0
- data/lib/gems_bond/spy.rb +62 -0
- data/lib/gems_bond/version.rb +5 -0
- data/lib/gems_bond.rb +18 -0
- data/lib/tasks/gems_bond/spy.rake +34 -0
- data/views/diagnosis.erb +116 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bc62800e859ce8dcf0015580a685bfaf8b5410c69843acae6d49e24fae011c4e
|
4
|
+
data.tar.gz: 6ee8416eade996316171af619d7e7fd6f14d3c82817f299c92c793902b5b6076
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d82f72f550a6be300e0d5ca5483b10ef8a7070e10912c89e2900675bdafd712ba6c4014728a5641f7789666d84da8ec126ea2c2b4e0d8a155b7134f68261f952
|
7
|
+
data.tar.gz: ae59181cbfbf9cff164a4c7c5776ccea3ef5c14a773a18d8ae5706be553fc61eff0b9d2a64b54bd11adeb7da8516152cbca5a1cc78daa04f292fdf43391618bb
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GemsBond
|
4
|
+
# Examines gem sanity
|
5
|
+
module ExaminationHelper
|
6
|
+
SCORES = YAML.safe_load(File.read(File.join(File.dirname(__FILE__), "scores.yml")))
|
7
|
+
BOUNDARIES = SCORES["boundaries"]
|
8
|
+
RESULTS = SCORES["results"]
|
9
|
+
|
10
|
+
def version_gap
|
11
|
+
memoize(:version_gap) { calculate_version_gap }
|
12
|
+
end
|
13
|
+
|
14
|
+
RESULTS.each do |result, values|
|
15
|
+
define_method("#{result}_score") do
|
16
|
+
memoize("#{result}_score") do
|
17
|
+
weighted_average(
|
18
|
+
values.map do |key, weighting|
|
19
|
+
[__send__("#{key}_score"), weighting]
|
20
|
+
end
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# Scores getters and calculation
|
29
|
+
|
30
|
+
BOUNDARIES.each_key do |key|
|
31
|
+
# create a _score getter for each key
|
32
|
+
define_method("#{key}_score") do
|
33
|
+
instance_variable_get("@#{key}_score") if instance_variable_defined?("@#{key}_score")
|
34
|
+
|
35
|
+
instance_variable_set("@#{key}_score", __send__("calculate_#{key}_score"))
|
36
|
+
end
|
37
|
+
|
38
|
+
# create a calculation method for each key
|
39
|
+
define_method("calculate_#{key}_score") do
|
40
|
+
value = public_send(key)
|
41
|
+
return unless value
|
42
|
+
|
43
|
+
min = BOUNDARIES[key]["min"]
|
44
|
+
max = BOUNDARIES[key]["max"]
|
45
|
+
|
46
|
+
# when lower the better (last commit at...)
|
47
|
+
if min
|
48
|
+
threshold = Float(BOUNDARIES[key]["threshold"] || 0)
|
49
|
+
return Float(1) if value <= threshold
|
50
|
+
return Float(0) if value >= min
|
51
|
+
|
52
|
+
soften(min - Float(value) + threshold, min + threshold)
|
53
|
+
|
54
|
+
# when higher the better (downloads count...)
|
55
|
+
else
|
56
|
+
return Float(1) if value >= max
|
57
|
+
return Float(0) if value <= 1
|
58
|
+
|
59
|
+
soften(Float(value), max)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def soften(value, comparison)
|
65
|
+
# returns a shaped curve
|
66
|
+
sigmoid = ->(x) { 1 / (1 + Math.exp(-x * 10)) }
|
67
|
+
# simoid boundaries are [0.5, 1]
|
68
|
+
# so remove 0.5 and multiply by 2 to have boundaries [0, 1]
|
69
|
+
(sigmoid.call(value / comparison) - 0.5) * 2
|
70
|
+
end
|
71
|
+
|
72
|
+
# --- COMPUTE
|
73
|
+
|
74
|
+
def weighted_average(scores)
|
75
|
+
acc = 0
|
76
|
+
weight = 0
|
77
|
+
scores.each do |score|
|
78
|
+
value, weighting = score
|
79
|
+
next unless value
|
80
|
+
|
81
|
+
acc += value * weighting
|
82
|
+
weight += weighting
|
83
|
+
end.compact
|
84
|
+
return if weight.zero?
|
85
|
+
|
86
|
+
acc / weight
|
87
|
+
end
|
88
|
+
|
89
|
+
# --- VERSION STATUS
|
90
|
+
|
91
|
+
def calculate_version_gap
|
92
|
+
return unless version && versions
|
93
|
+
|
94
|
+
index = versions.index { |v| v[:number] == version }
|
95
|
+
return unless index
|
96
|
+
|
97
|
+
gap = versions[0..index].count { |v| !v[:prerelease] } - 1
|
98
|
+
gap.positive? ? gap : 0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems_bond/fetcher/ruby_gems"
|
4
|
+
require "gems_bond/fetcher/github"
|
5
|
+
|
6
|
+
module GemsBond
|
7
|
+
# Handles gem data
|
8
|
+
module FetchHelper
|
9
|
+
RUBY_GEM_KEYS = %i[
|
10
|
+
last_version_date
|
11
|
+
downloads_count
|
12
|
+
source_code_uri
|
13
|
+
versions
|
14
|
+
last_version
|
15
|
+
last_version_date
|
16
|
+
days_since_last_version
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
GITHUB_KEYS = %i[
|
20
|
+
contributors_count
|
21
|
+
stars_count
|
22
|
+
forks_count
|
23
|
+
last_commit_date
|
24
|
+
open_issues_count
|
25
|
+
days_since_last_commit
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
KEYS = RUBY_GEM_KEYS + GITHUB_KEYS
|
29
|
+
|
30
|
+
RUBY_GEM_KEYS.each do |key|
|
31
|
+
define_method(key) do
|
32
|
+
memoize(key) do
|
33
|
+
fetch(ruby_gems_fetcher, key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
GITHUB_KEYS.each do |key|
|
39
|
+
define_method(key) do
|
40
|
+
memoize(key) do
|
41
|
+
fetch(github_fetcher, key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch_all(verbose: false)
|
47
|
+
KEYS.each do |key|
|
48
|
+
__send__(key)
|
49
|
+
end
|
50
|
+
verbose && puts(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def fetch(fetcher, key)
|
56
|
+
fetcher&.public_send(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ruby_gems_fetcher
|
60
|
+
return @ruby_gems_fetcher if defined?(@ruby_gems_fetcher)
|
61
|
+
|
62
|
+
@ruby_gems_fetcher = GemsBond::Fetcher::RubyGems.new(name).start
|
63
|
+
end
|
64
|
+
|
65
|
+
def github_fetcher
|
66
|
+
return @github_fetcher if defined?(@github_fetcher)
|
67
|
+
|
68
|
+
@github_fetcher = github_url && GemsBond::Fetcher::Github.new(github_url).start
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "octokit"
|
4
|
+
|
5
|
+
module GemsBond
|
6
|
+
module Fetcher
|
7
|
+
# Fetches data from GitHub
|
8
|
+
class Github
|
9
|
+
REPOSITORY_REGEX = %r{https?://github.com/(?<repository>.*/.*)}.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def valid_url?(url)
|
13
|
+
url&.match?(REPOSITORY_REGEX)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(url)
|
18
|
+
@url = url
|
19
|
+
end
|
20
|
+
|
21
|
+
def source
|
22
|
+
"github"
|
23
|
+
end
|
24
|
+
|
25
|
+
def start
|
26
|
+
parse_url
|
27
|
+
login
|
28
|
+
set_repository
|
29
|
+
self
|
30
|
+
rescue Octokit::Unauthorized, Octokit::InvalidRepository, Octokit::NotFound
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def forks_count
|
35
|
+
@repository["forks"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def stars_count
|
39
|
+
@repository["watchers"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def contributors_count
|
43
|
+
client = Octokit::Client.new(access_token: token, per_page: 1)
|
44
|
+
client.contributors(@repository_path)
|
45
|
+
response = client.last_response
|
46
|
+
links = response.headers[:link]
|
47
|
+
return 0 unless links
|
48
|
+
|
49
|
+
Integer(links.match(/.*page=(?<last>\d+)>; rel="last"/)[:last], 10)
|
50
|
+
end
|
51
|
+
|
52
|
+
def open_issues_count
|
53
|
+
@repository["open_issues"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def last_commit_date
|
57
|
+
date = client.commits(@repository_path).first[:commit][:committer][:date]
|
58
|
+
return unless date
|
59
|
+
|
60
|
+
Date.parse(date.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
def days_since_last_commit
|
64
|
+
return unless last_commit_date
|
65
|
+
|
66
|
+
Date.today - last_commit_date
|
67
|
+
end
|
68
|
+
|
69
|
+
def lib_size
|
70
|
+
contents_size = dir_size("lib")
|
71
|
+
return unless contents_size
|
72
|
+
|
73
|
+
lines_count_estimation = contents_size / 25
|
74
|
+
return lines_count_estimation if lines_count_estimation < 100
|
75
|
+
|
76
|
+
lines_count_estimation - lines_count_estimation % 100
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def parse_url
|
82
|
+
matches = @url.match(REPOSITORY_REGEX)
|
83
|
+
raise Octokit::InvalidRepository unless matches
|
84
|
+
|
85
|
+
path = matches[:repository].split("/")
|
86
|
+
@repository_path = "#{path[0]}/#{path[1]}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def login
|
90
|
+
@login ||= client.user.login
|
91
|
+
end
|
92
|
+
|
93
|
+
def client
|
94
|
+
Octokit::Client.new(access_token: token)
|
95
|
+
end
|
96
|
+
|
97
|
+
def token
|
98
|
+
GemsBond.configuration&.github_token
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_repository
|
102
|
+
@repository = client.repo(@repository_path)
|
103
|
+
end
|
104
|
+
|
105
|
+
def dir_size(dir_path)
|
106
|
+
contents = client.contents(@repository_path, path: dir_path)
|
107
|
+
acc =
|
108
|
+
contents
|
109
|
+
.select { |content| content[:type] == "file" }
|
110
|
+
.sum { |content| content[:size] }
|
111
|
+
acc +=
|
112
|
+
contents
|
113
|
+
.select { |content| content[:type] == "dir" }
|
114
|
+
.sum { |sub_dir| dir_size("#{dir_path}/#{sub_dir[:name]}") }
|
115
|
+
acc
|
116
|
+
rescue Octokit::NotFound
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems"
|
4
|
+
require "rubygems"
|
5
|
+
|
6
|
+
module GemsBond
|
7
|
+
module Fetcher
|
8
|
+
# Fetches data from RubyGems
|
9
|
+
class RubyGems
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def source
|
15
|
+
"ruby gems"
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@info = Gems.info(@name)
|
20
|
+
self
|
21
|
+
rescue Gems::NotFound
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def downloads_count
|
26
|
+
Gems.total_downloads(@name)[:total_downloads]
|
27
|
+
end
|
28
|
+
|
29
|
+
def source_code_uri
|
30
|
+
@info["metadata"]["source_code_uri"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def versions
|
34
|
+
Gems.versions(@name).map do |version|
|
35
|
+
{
|
36
|
+
number: version["number"],
|
37
|
+
created_at: Date.parse(version["created_at"]),
|
38
|
+
prerelease: version["prerelease"]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def last_version
|
44
|
+
versions&.first&.dig(:number)
|
45
|
+
end
|
46
|
+
|
47
|
+
def last_version_date
|
48
|
+
versions&.first&.dig(:created_at)
|
49
|
+
end
|
50
|
+
|
51
|
+
def days_since_last_version
|
52
|
+
return unless last_version_date
|
53
|
+
|
54
|
+
Date.today - last_version_date
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems_bond/fetch_helper"
|
4
|
+
require "gems_bond/examination_helper"
|
5
|
+
|
6
|
+
module GemsBond
|
7
|
+
# Handles gem data
|
8
|
+
class Gem
|
9
|
+
include FetchHelper
|
10
|
+
include ExaminationHelper
|
11
|
+
|
12
|
+
def initialize(dependency)
|
13
|
+
@dependency = dependency
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
memoize(__method__) { @dependency.name }
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
memoize(__method__) { @dependency.description }
|
22
|
+
end
|
23
|
+
|
24
|
+
def version
|
25
|
+
memoize(__method__) { @dependency.to_spec.version.to_s }
|
26
|
+
end
|
27
|
+
|
28
|
+
def homepage
|
29
|
+
memoize(__method__) { @dependency.to_spec.homepage }
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
homepage || source_code_uri
|
34
|
+
end
|
35
|
+
|
36
|
+
def github_url
|
37
|
+
return homepage if GemsBond::Fetcher::Github.valid_url?(homepage)
|
38
|
+
|
39
|
+
source_code_uri if GemsBond::Fetcher::Github.valid_url?(source_code_uri)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def memoize(key)
|
45
|
+
return instance_variable_get("@#{key}") if instance_variable_defined?("@#{key}")
|
46
|
+
|
47
|
+
instance_variable_set("@#{key}", yield)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module GemsBond
|
7
|
+
# Prints gems table
|
8
|
+
class Printer
|
9
|
+
MISSING = "-"
|
10
|
+
|
11
|
+
def initialize(gems)
|
12
|
+
@gems = gems
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
puts "\nPreparing data for printing results..."
|
17
|
+
create_directory
|
18
|
+
File.open("gems_bond/spy.html", "w") do |file|
|
19
|
+
file.puts content
|
20
|
+
end
|
21
|
+
puts "Open file gems_bond/spy.html to display the results."
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_directory
|
27
|
+
return if File.directory?("gems_bond")
|
28
|
+
|
29
|
+
FileUtils.mkdir_p("gems_bond")
|
30
|
+
end
|
31
|
+
|
32
|
+
def template
|
33
|
+
File.read(File.join(File.dirname(__FILE__), "../../views/", "diagnosis.erb"))
|
34
|
+
end
|
35
|
+
|
36
|
+
def content
|
37
|
+
ERB.new(template).result(binding)
|
38
|
+
end
|
39
|
+
|
40
|
+
def sorted_gems
|
41
|
+
# sort with putting gems without average_score at the end
|
42
|
+
@gems.sort do |a, b|
|
43
|
+
if a.average_score && b.average_score
|
44
|
+
a.average_score <=> b.average_score
|
45
|
+
else
|
46
|
+
a.average_score ? -1 : 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def version_color(gap)
|
52
|
+
return "secondary" if gap.nil?
|
53
|
+
return "success" if gap.zero?
|
54
|
+
|
55
|
+
gap < 3 ? "warning" : "danger"
|
56
|
+
end
|
57
|
+
|
58
|
+
def color(score)
|
59
|
+
return "secondary" if score.nil?
|
60
|
+
|
61
|
+
if score < 0.33
|
62
|
+
"danger"
|
63
|
+
elsif score < 0.66
|
64
|
+
"warning"
|
65
|
+
else
|
66
|
+
"success"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def human_date(date)
|
71
|
+
return MISSING if date.nil?
|
72
|
+
|
73
|
+
date.strftime("%F")
|
74
|
+
end
|
75
|
+
|
76
|
+
def human_number(number)
|
77
|
+
return MISSING if number.nil?
|
78
|
+
|
79
|
+
number.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1 ")
|
80
|
+
end
|
81
|
+
|
82
|
+
def human_score(score)
|
83
|
+
return MISSING if score.nil?
|
84
|
+
|
85
|
+
(score * 100).round
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
boundaries:
|
3
|
+
days_since_last_version:
|
4
|
+
min: 730
|
5
|
+
threshold: 90
|
6
|
+
days_since_last_commit:
|
7
|
+
min: 365
|
8
|
+
threshold: 2
|
9
|
+
open_issues_count:
|
10
|
+
max: 300
|
11
|
+
downloads_count:
|
12
|
+
max: 200_000_000
|
13
|
+
contributors_count:
|
14
|
+
max: 350
|
15
|
+
forks_count:
|
16
|
+
max: 15_000
|
17
|
+
stars_count:
|
18
|
+
max: 30_000
|
19
|
+
results:
|
20
|
+
activity:
|
21
|
+
days_since_last_version: 3
|
22
|
+
days_since_last_commit: 5
|
23
|
+
contributors_count: 2
|
24
|
+
open_issues_count: 1
|
25
|
+
popularity:
|
26
|
+
downloads_count: 2
|
27
|
+
contributors_count: 3
|
28
|
+
forks_count: 1
|
29
|
+
stars_count: 1
|
30
|
+
average:
|
31
|
+
activity: 1
|
32
|
+
popularity: 2
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems_bond/gem"
|
4
|
+
require "gems_bond/printer"
|
5
|
+
|
6
|
+
module GemsBond
|
7
|
+
# Inspects gems and outputs the result
|
8
|
+
class Spy
|
9
|
+
RETRIES = 2
|
10
|
+
|
11
|
+
def call
|
12
|
+
chrono do
|
13
|
+
fetch_gems
|
14
|
+
GemsBond::Printer.new(gems).call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def gems_count
|
19
|
+
@gems_count ||= gems.count
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def chrono
|
25
|
+
start_at = Time.now
|
26
|
+
yield
|
27
|
+
seconds = Time.now - start_at
|
28
|
+
time_per_gem_text = "#{(seconds / Float(gems_count)).round(2)} second(s) per gem"
|
29
|
+
puts "\nIt took #{seconds} second(s) to spy #{gems_count} gem(s) (#{time_per_gem_text})."
|
30
|
+
end
|
31
|
+
|
32
|
+
def gems
|
33
|
+
@gems ||=
|
34
|
+
Bundler.load.current_dependencies.map do |dependency|
|
35
|
+
GemsBond::Gem.new(dependency)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch_gems
|
40
|
+
puts "Fetching data for..."
|
41
|
+
gems.each_slice(100) do |batch|
|
42
|
+
threads = []
|
43
|
+
batch.each do |gem|
|
44
|
+
threads << gem_thread(gem)
|
45
|
+
end
|
46
|
+
threads.each(&:join)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def gem_thread(gem)
|
51
|
+
Thread.new do
|
52
|
+
begin
|
53
|
+
retries ||= 0
|
54
|
+
gem.fetch_all(verbose: true)
|
55
|
+
# rescue SocketError, Faraday::ConnectionFailed...
|
56
|
+
rescue StandardError
|
57
|
+
(retries += 1) <= RETRIES ? retry : nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/gems_bond.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems_bond/configuration"
|
4
|
+
require "gems_bond/railtie" if defined?(Rails)
|
5
|
+
|
6
|
+
# Gem module
|
7
|
+
module GemsBond
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :configuration
|
12
|
+
|
13
|
+
def configure
|
14
|
+
self.configuration ||= Configuration.new
|
15
|
+
yield(configuration)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gems_bond/spy"
|
4
|
+
|
5
|
+
begin
|
6
|
+
require "#{Rails.root}/config/initializers/gems_bond" if defined?(Rails)
|
7
|
+
rescue LoadError
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :gems_bond do
|
12
|
+
desc "Investigates gems"
|
13
|
+
task :spy do
|
14
|
+
puts "Welcome, my name is Bond, Gems Bond."\
|
15
|
+
" I will do some spying on your gems for you..."
|
16
|
+
|
17
|
+
GemsBond.configure do |config|
|
18
|
+
# set github_token from ENV if given
|
19
|
+
config.github_token = ENV["GITHUB_TOKEN"] if ENV["GITHUB_TOKEN"]
|
20
|
+
end
|
21
|
+
|
22
|
+
unless GemsBond.configuration.github_token
|
23
|
+
puts "It seems that you didn't provide any GitHub token."\
|
24
|
+
" This is necessary to fetch more data from GitHub,"\
|
25
|
+
" like the last commit date, the forks count, the sise of the repository..."
|
26
|
+
|
27
|
+
puts "Please provide a GitHub token by adding it in the configuration"\
|
28
|
+
" or passing GITHUB_TOKEN=<token> when running the task."
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
|
32
|
+
GemsBond::Spy.new.call
|
33
|
+
end
|
34
|
+
end
|
data/views/diagnosis.erb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<title>Gems Bond Diagnosis</title>
|
6
|
+
<link
|
7
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css"
|
8
|
+
rel="stylesheet"
|
9
|
+
integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1"
|
10
|
+
crossorigin="anonymous"
|
11
|
+
>
|
12
|
+
<script
|
13
|
+
src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js"
|
14
|
+
integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW"
|
15
|
+
crossorigin="anonymous"
|
16
|
+
></script>
|
17
|
+
</head>
|
18
|
+
<body style="margin: 40px;">
|
19
|
+
<div class="container">
|
20
|
+
<div class="row justify-content-center">
|
21
|
+
<div class="list-group">
|
22
|
+
<table class="table table-hover small text-nowrap">
|
23
|
+
<thead>
|
24
|
+
<tr>
|
25
|
+
<th scope="col">#</th>
|
26
|
+
<th scope="col">Name</th>
|
27
|
+
<th scope="col">Version</th>
|
28
|
+
<th scope="col">Activity</th>
|
29
|
+
<th scope="col">Popularity</th>
|
30
|
+
<th scope="col">Diagnosis</th>
|
31
|
+
</tr>
|
32
|
+
</thead>
|
33
|
+
<tbody>
|
34
|
+
<% sorted_gems.each_with_index do |gem, i| %>
|
35
|
+
<tr>
|
36
|
+
<th scope="row"><%= i %></th>
|
37
|
+
<td>
|
38
|
+
<% if gem.url %>
|
39
|
+
<a href="<%= gem.url %>" target="_blank" class="text-decoration-none"><%= gem.name %></a>
|
40
|
+
<% else %>
|
41
|
+
<%= gem.name %>
|
42
|
+
<% end %>
|
43
|
+
</td>
|
44
|
+
<td class="text-wrap">
|
45
|
+
<div>
|
46
|
+
<p class="mb-1"><%= gem.version || "-" %></p>
|
47
|
+
<% if gem.version_gap&.zero? %>
|
48
|
+
<p class="badge bg-<%= version_color(gem.version_gap) %>">
|
49
|
+
up-to-date
|
50
|
+
</p>
|
51
|
+
<% elsif gem.version_gap %>
|
52
|
+
<p class="badge bg-<%= version_color(gem.version_gap) %>">
|
53
|
+
<%= gem.version_gap %> behind <%= gem.last_version %>
|
54
|
+
</p>
|
55
|
+
<% end %>
|
56
|
+
</div>
|
57
|
+
</td>
|
58
|
+
<td>
|
59
|
+
<div class="mb-1">
|
60
|
+
<p class="small m-0 fw-bold">Last version</p>
|
61
|
+
<p class="small m-0"><%= human_date(gem.last_version_date) %></p>
|
62
|
+
</div>
|
63
|
+
<div class="mb-1">
|
64
|
+
<p class="small m-0 fw-bold">Last commit</p>
|
65
|
+
<p class="small m-0"><%= human_date(gem.last_commit_date) %></p>
|
66
|
+
</div>
|
67
|
+
<div class="mb-1">
|
68
|
+
<p class="small m-0 fw-bold">Contributors</p>
|
69
|
+
<p class="small m-0"><%= human_number(gem.contributors_count) %></p>
|
70
|
+
</div>
|
71
|
+
</td>
|
72
|
+
<td>
|
73
|
+
<div class="mb-1">
|
74
|
+
<p class="small m-0 fw-bold">Downloads</p>
|
75
|
+
<p class="small m-0"><%= human_number(gem.downloads_count) %></p>
|
76
|
+
</div>
|
77
|
+
<div class="mb-1">
|
78
|
+
<p class="small m-0 fw-bold">Forks</p>
|
79
|
+
<p class="small m-0"><%= human_number(gem.forks_count) %></p>
|
80
|
+
</div>
|
81
|
+
<div class="mb-1">
|
82
|
+
<p class="small m-0 fw-bold">Stars</p>
|
83
|
+
<p class="small m-0"><%= gem.stars_count || "-" %></p>
|
84
|
+
</div>
|
85
|
+
</td>
|
86
|
+
<td>
|
87
|
+
<div class="row align-items-end">
|
88
|
+
<div class="col-6">
|
89
|
+
<div class="alert alert-<%= color(gem.average_score) %> text-wrap text-center" role="alert">
|
90
|
+
<p class="mb-0">SCORE</p>
|
91
|
+
<p class="mb-0" style="font-size: 40px; font-weight: 600"><%= human_score(gem.average_score) %></p>
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
<div class="col-3">
|
95
|
+
<div class="alert alert-<%= color(gem.activity_score) %> text-wrap text-center" role="alert">
|
96
|
+
<p class="mb-0">ACTIVITY</p>
|
97
|
+
<p class="mb-0" style="font-size: 30px;"><%= human_score(gem.activity_score) %></p>
|
98
|
+
</div>
|
99
|
+
</div>
|
100
|
+
<div class="col-3">
|
101
|
+
<div class="alert alert-<%= color(gem.popularity_score) %> text-wrap text-center" role="alert">
|
102
|
+
<p class="mb-0">POPULARITY</p>
|
103
|
+
<p class="font-weight-bold mb-0" style="font-size: 30px;"><%= human_score(gem.popularity_score) %></p>
|
104
|
+
</div>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
</td>
|
108
|
+
</tr>
|
109
|
+
<% end %>
|
110
|
+
</tbody>
|
111
|
+
</table>
|
112
|
+
</div>
|
113
|
+
</div>
|
114
|
+
</div>
|
115
|
+
</body>
|
116
|
+
</html>
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gems_bond
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edouard Piron
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: gems
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: octokit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
description: Inspect gems relying on data from RubyGems and GitHub
|
42
|
+
email:
|
43
|
+
- edouard.piron@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- lib/gems_bond.rb
|
49
|
+
- lib/gems_bond/configuration.rb
|
50
|
+
- lib/gems_bond/examination_helper.rb
|
51
|
+
- lib/gems_bond/fetch_helper.rb
|
52
|
+
- lib/gems_bond/fetcher/github.rb
|
53
|
+
- lib/gems_bond/fetcher/ruby_gems.rb
|
54
|
+
- lib/gems_bond/gem.rb
|
55
|
+
- lib/gems_bond/printer.rb
|
56
|
+
- lib/gems_bond/railtie.rb
|
57
|
+
- lib/gems_bond/scores.yml
|
58
|
+
- lib/gems_bond/spy.rb
|
59
|
+
- lib/gems_bond/version.rb
|
60
|
+
- lib/tasks/gems_bond/spy.rake
|
61
|
+
- views/diagnosis.erb
|
62
|
+
homepage: https://github.com/BigBigDoudou/gems_bond
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata:
|
66
|
+
homepage_uri: https://github.com/BigBigDoudou/gems_bond
|
67
|
+
source_code_uri: https://github.com/BigBigDoudou/gems_bond
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.4'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubygems_version: 3.0.3
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Inspect gems
|
87
|
+
test_files: []
|