any_good 0.0.1
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/LICENSE.txt +22 -0
- data/README.md +61 -0
- data/exe/any_good +6 -0
- data/lib/any_good/meters.rb +73 -0
- data/lib/any_good.rb +94 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2fe801250f6e51612d255456a8ad2d7e7cea083b
|
4
|
+
data.tar.gz: b8db3eb675a42e1ca7bc9f9ece8e70ddd275da63
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 985c456c6133c922c904176719795fc1fc737deba31b90fe33c5c514fb5e504f8015019b39452638c2f21283e78375055c2b129d087f5284eb00ef9b1b213f51
|
7
|
+
data.tar.gz: d457b7e29bf41e278beac08b949c9c473d9e917f879548ffe063ab04b31430d493563c91492475c24426a9c09d07bdf355ccf4ec0f2556d87632fca1874d9854
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014-15 Victor 'Zverok' Shepelev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
[](http://badge.fury.io/rb/any_good)
|
2
|
+
|
3
|
+
## What's this?
|
4
|
+
|
5
|
+
A thing to quickly evaluate Ruby gem maturity and answer "Is it any good?". Like this:
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
Just a report of some numbers and facts from rubygems.org and GitHub repo of the gem in
|
10
|
+
question, to understand how risky it would be to use it.
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
```
|
15
|
+
$ gem install any_good
|
16
|
+
$ any_good <gem_name>
|
17
|
+
```
|
18
|
+
|
19
|
+
## Why?
|
20
|
+
|
21
|
+
I find myself constantly repeating this process for some new gems I spotted somewhere: going to
|
22
|
+
their gem page and repo page to understand how well it maintained or is it abandoned, and is it
|
23
|
+
something new that still have to get its ways and popularity.
|
24
|
+
|
25
|
+
This gem is just a quick one-evening experiment on whether it can be automated in a helpful manner.
|
26
|
+
|
27
|
+
## What are the colors? Are you criticizing gems?..
|
28
|
+
|
29
|
+
The colors (green-yellow-red) are just based on my own subjective "thresholds". Maybe they could become
|
30
|
+
configurable in future versions, if any. "Yellow" and "red" aren't, in fact, "bad", it is "point
|
31
|
+
of attention" when judging whether you'll give a chance to some gem, and how important are they,
|
32
|
+
depends on the situation.
|
33
|
+
|
34
|
+
The colors DO NOT mean to "score" the gems (this gem is bad) or to compare "which gem is better",
|
35
|
+
but the typically DO give some insights on the gem's current status in the community.
|
36
|
+
|
37
|
+
Those insights aren't that accurate: for example, [tzinfo](https://rubygems.org/gems/tzinfo)
|
38
|
+
is used by literally everyone, yet its GitHub repo has just ~200 stars. For another,
|
39
|
+
[inflecto](https://rubygems.org/gems/inflecto) is explicitly abandoned by its author, the last version
|
40
|
+
was released four years ago, yet it is robust and widely used.
|
41
|
+
|
42
|
+
## GitHub?
|
43
|
+
|
44
|
+
A large part of stats is taken from gem's GitHub repo.
|
45
|
+
|
46
|
+
Yes, the gem is not required to have the link to sources published. Yes, there are gems with sources
|
47
|
+
on GitLab, BitBucket, or even SourceForge, God forbid. But again, as with colors, my _subjective_
|
48
|
+
experience says me to check its GitHub if it is accessible. So the `any_good` does.
|
49
|
+
|
50
|
+
BTW, `any_good` connects to GitHub API anonymously, and anonymous connections are subject to harsh
|
51
|
+
rate limiting, so if you use it a lot through one day, you may want to provide `GITHUB_ACCESS_TOKEN`
|
52
|
+
environment variable (tokens are obtained [here](https://github.com/settings/tokens)).
|
53
|
+
|
54
|
+
## Is it any good?
|
55
|
+
|
56
|
+
Well, it is a quick one-evening experiment. So, no tests, no docs except this README, no config,
|
57
|
+
and just hard-coded thresholds. But it works for me.
|
58
|
+
|
59
|
+
## Who are you anyways?
|
60
|
+
|
61
|
+
Just a humble [@zverok](http://zverok.github.io).
|
data/exe/any_good
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
class AnyGood
|
2
|
+
class Meter < Struct.new(:name, :thresholds, :block)
|
3
|
+
def call(data)
|
4
|
+
Metric.new(name, value(data), *thresholds)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def value(data)
|
12
|
+
@data = data
|
13
|
+
instance_eval(&block)
|
14
|
+
rescue NoMethodError
|
15
|
+
nil
|
16
|
+
ensure
|
17
|
+
@data = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Metric
|
22
|
+
attr_reader :value, :name, :color
|
23
|
+
|
24
|
+
def initialize(name, value, *thresholds)
|
25
|
+
@name = name
|
26
|
+
@value = value
|
27
|
+
@color = deduce_color(*thresholds)
|
28
|
+
end
|
29
|
+
|
30
|
+
def format
|
31
|
+
'%20s: %s' % [name, colorized_value]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def colorized_value
|
37
|
+
Pastel.new.send(color, formatted_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def formatted_value
|
41
|
+
case value
|
42
|
+
when nil
|
43
|
+
'—'
|
44
|
+
when String
|
45
|
+
value
|
46
|
+
when Numeric
|
47
|
+
value.to_s.chars.reverse.each_slice(3).to_a.map(&:join).join(',').reverse
|
48
|
+
when Date, Time
|
49
|
+
diff = TimeMath.measure(value, Time.now)
|
50
|
+
unit, num = diff.detect { |_, v| !v.zero? }
|
51
|
+
"#{num} #{unit} ago"
|
52
|
+
else
|
53
|
+
fail ArgumentError, "Unformattable #{value.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def deduce_color(yellow = nil, red = nil)
|
58
|
+
return :dark if value.nil?
|
59
|
+
return :white if !yellow # no thresholds given
|
60
|
+
|
61
|
+
# special trick to tell "lower is better" from "higher is better" situations
|
62
|
+
val = red.is_a?(Numeric) && red < 0 ? -value : value
|
63
|
+
|
64
|
+
if val < red
|
65
|
+
:red
|
66
|
+
elsif val < yellow
|
67
|
+
:yellow
|
68
|
+
else
|
69
|
+
:green
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/any_good.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'gems'
|
2
|
+
require 'octokit'
|
3
|
+
require 'pp'
|
4
|
+
require 'date'
|
5
|
+
require 'time_math'
|
6
|
+
require 'pastel'
|
7
|
+
|
8
|
+
class AnyGood
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@github_client =
|
14
|
+
if (token = ENV['GITHUB_ACCESS_TOKEN'])
|
15
|
+
Octokit::Client.new(access_token: token).tap { |client| client.user.login }
|
16
|
+
else
|
17
|
+
Octokit::Client.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch
|
22
|
+
data = {
|
23
|
+
gem: Gems.info(name),
|
24
|
+
gem_versions: Gems.versions(name),
|
25
|
+
gem_rev_deps: Gems.reverse_dependencies(name)
|
26
|
+
}
|
27
|
+
|
28
|
+
repo_id = [data[:gem]['source_code_uri'], data[:gem]['homepage_uri']]
|
29
|
+
.grep(%r{^https?://github\.com/}).first&.sub(%r{^https?://github\.com/}, '')
|
30
|
+
|
31
|
+
if repo_id
|
32
|
+
data.merge!(
|
33
|
+
repo: @github_client.repository(repo_id).to_h,
|
34
|
+
open_issues: @github_client.issues(repo_id, state: 'open', per_page: 50).map(&:to_h),
|
35
|
+
closed_issues: @github_client.issues(repo_id, state: 'closed', per_page: 50).map(&:to_h),
|
36
|
+
last_commit: @github_client.commits(repo_id, per_page: 1).first.to_h
|
37
|
+
# open_prs: @github_client.issues(repo_id, state: 'open').map(&:to_h)
|
38
|
+
# closed_prs: @github_client.issues(repo_id, state: 'closed').map(&:to_h)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
@data = OpenStruct.new(data)
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def report
|
48
|
+
self.class.meters.each do |meter|
|
49
|
+
puts meter.call(@data).format
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
require_relative 'any_good/meters'
|
55
|
+
|
56
|
+
def self.meters
|
57
|
+
@meters ||= []
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.metric(name, thresholds: nil, &block)
|
61
|
+
meters << Meter.new(name, thresholds, block)
|
62
|
+
end
|
63
|
+
|
64
|
+
T = TimeMath
|
65
|
+
now = Time.now
|
66
|
+
|
67
|
+
metric('Downloads', thresholds: [5_000, 10_000]) { data.gem['downloads'] }
|
68
|
+
metric('Latest version', thresholds: [T.month.decrease(now, 2), T.year.decrease(now)]) {
|
69
|
+
Time.parse(data.gem_versions.first['created_at'])
|
70
|
+
}
|
71
|
+
metric('Used by', thresholds: [10, 100]) { data.gem_rev_deps.count }
|
72
|
+
|
73
|
+
metric('Stars', thresholds: [100, 500]) { data.repo[:stargazers_count] }
|
74
|
+
metric('Forks', thresholds: [5, 20]) { data.repo[:forks_count] }
|
75
|
+
metric('Last commit', thresholds: [T.month.decrease(now), T.month.decrease(now, 4)]) {
|
76
|
+
data.last_commit.dig(:commit, :committer, :date)
|
77
|
+
}
|
78
|
+
|
79
|
+
metric('Open issues') {
|
80
|
+
res = data.open_issues.count
|
81
|
+
res == 50 ? '50+' : res
|
82
|
+
}
|
83
|
+
metric('...without reaction', thresholds: [-5, -20]) {
|
84
|
+
data.open_issues.reject { |i| i[:labels].any? || i[:comments] > 0 }.count
|
85
|
+
}
|
86
|
+
metric('...last reaction', thresholds: [T.week.decrease(now), T.month.decrease(now)]) {
|
87
|
+
data.open_issues.detect { |i| i[:labels].any? || i[:comments] > 0 }&.fetch(:updated_at)
|
88
|
+
}
|
89
|
+
metric('Closed issues') {
|
90
|
+
res = data.closed_issues.count
|
91
|
+
res == 50 ? '50+' : res
|
92
|
+
}
|
93
|
+
metric('...last closed') { data.closed_issues.first&.fetch(:closed_at) }
|
94
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: any_good
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Victor Shepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pastel
|
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: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gems
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: time_math2
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubygems-tasks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email: zverok.offline@gmail.com
|
99
|
+
executables:
|
100
|
+
- any_good
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- exe/any_good
|
107
|
+
- lib/any_good.rb
|
108
|
+
- lib/any_good/meters.rb
|
109
|
+
homepage: https://github.com/zverok/any_good
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: 2.3.0
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.6.10
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Is that gem any good?
|
133
|
+
test_files: []
|