gems_bond 1.0.4
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 +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: []
|