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 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
+ [![Gem Version](https://badge.fury.io/rb/any_good.svg)](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
+ ![](https://raw.githubusercontent.com/zverok/any_good/master/doc/example.png)
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,6 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative '../lib/any_good'
3
+
4
+ abort 'Usage: `any_good <gemname>`' if ARGV.empty?
5
+
6
+ AnyGood.new(ARGV.shift).fetch.report
@@ -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: []