gemfresh 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +20 -0
- data/Rakefile +1 -0
- data/bin/gemfresh +2 -0
- data/gemfresh.gemspec +17 -0
- data/gemfresh.rb +234 -0
- data/support.rb +84 -0
- metadata +91 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jon Williams, http://jonathannen.com/
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Gemfresh
|
2
|
+
Scans your bundler Gemfile and lets you know how up-to-date your gems are.
|
3
|
+
|
4
|
+
## Installation and Usage
|
5
|
+
|
6
|
+
To install, simply grab the gem:
|
7
|
+
gem install gemfresh
|
8
|
+
|
9
|
+
Change a project directory with a Gemfile (e.g. Your Rails v3+ project) and
|
10
|
+
type:
|
11
|
+
gemfresh
|
12
|
+
|
13
|
+
This will output a list of current, updateable and obsolete gems. For more
|
14
|
+
information on what this means, run gemfresh with the --help option:
|
15
|
+
gemfresh --help
|
16
|
+
|
17
|
+
## License
|
18
|
+
MIT Licensed. See MIT-LICENSE.txt for more information.
|
19
|
+
|
20
|
+
Thanks [@jonathannen](http://twitter.com/jonathannen).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/gemfresh
ADDED
data/gemfresh.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'gemfresh'
|
3
|
+
s.version = '1.0.1'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ['Jon Williams']
|
6
|
+
s.email = ['jon@jonathannen.com']
|
7
|
+
s.homepage = 'https://github.com/jonathannen/gemfresh'
|
8
|
+
s.summary = 'Checks the freshness of your Gemfile.'
|
9
|
+
s.description = 'Scans Gemfiles to check for obsolete and updateable gems.'
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
s.require_paths = ['.']
|
15
|
+
|
16
|
+
s.add_runtime_dependency 'bundler', '~> 1.0.18'
|
17
|
+
end
|
data/gemfresh.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
require 'time'
|
5
|
+
require File.dirname(__FILE__) + '/support'
|
6
|
+
|
7
|
+
# Handle ARGV
|
8
|
+
if ARGV.include?('--help')
|
9
|
+
puts <<-HELP
|
10
|
+
Usage:
|
11
|
+
gemfresh [GEMFILE] [LOCKFILE]
|
12
|
+
|
13
|
+
Both GEMFILE and LOCKFILE will default to "Gemfile" and "Gemfile.lock" in
|
14
|
+
your current directory. Generally you'll simply invoke gemfresh from your
|
15
|
+
Rails (or similar) project directory.
|
16
|
+
|
17
|
+
Gemfresh will list three categories of gems. "Current" gems are up-to-date.
|
18
|
+
"Obsolete" gems
|
19
|
+
|
20
|
+
"Updateable" gems that have a 'fuzzy' gemspec - e.g. '~> 2.2.0' is a fuzzy
|
21
|
+
match for 2.2.1, 2.2.2, etc. Running bundle update will attempt to update
|
22
|
+
your gems. If something is listed at updateable, you have an older version
|
23
|
+
- e.g. "2.2.1", when the current is "2.2.2".
|
24
|
+
|
25
|
+
Just because a gem is updateable or obsolete, doesn't mean it can be
|
26
|
+
updated. There might be dependencies that limit you to specific versions.
|
27
|
+
|
28
|
+
Check the bundler documentation (http://gembundler.com/) for more
|
29
|
+
information on Gemfiles.
|
30
|
+
HELP
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check for gemfile and lockfiles
|
35
|
+
gemfile = ARGV[0] || './Gemfile'
|
36
|
+
lockfile = ARGV[1] || './Gemfile.lock'
|
37
|
+
unless File.exists?(gemfile)
|
38
|
+
puts "Couldn't find #{gemfile}.\nRun gemfresh with --help if you need more information."
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
unless File.exists?(lockfile)
|
42
|
+
puts "Couldn't find #{lockfile}.\nRun gemfresh with --help if you need more information."
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
|
46
|
+
# Front for RubyGems
|
47
|
+
class RubyGemReader < Struct.new('RubyGemReader', :uri)
|
48
|
+
def get(path, data={}, content_type='application/x-www-form-urlencoded')
|
49
|
+
request = Net::HTTP::Get.new(path)
|
50
|
+
request.add_field 'Connection', 'keep-alive'
|
51
|
+
request.add_field 'Keep-Alive', '30'
|
52
|
+
request.add_field 'User-Agent', 'github.com/jonathannen/gemfresh'
|
53
|
+
response = connection.request request
|
54
|
+
response.body
|
55
|
+
end
|
56
|
+
private
|
57
|
+
# A persistent connection
|
58
|
+
def connection(host = Gem.host)
|
59
|
+
return @connection unless @connection.nil?
|
60
|
+
@connection = Net::HTTP.new self.uri.host, self.uri.port
|
61
|
+
@connection.start
|
62
|
+
@connection
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Start in earnets
|
67
|
+
puts "Checking the freshness of your Gemfile.\n"
|
68
|
+
|
69
|
+
# Get the data from bundler
|
70
|
+
Bundler.settings[:frozen] = true
|
71
|
+
bundle = Bundler::Dsl.evaluate('./Gemfile', './Gemfile.lock', {})
|
72
|
+
|
73
|
+
# Set up the top level values
|
74
|
+
deps = bundle.dependencies
|
75
|
+
specs = bundle.resolve
|
76
|
+
sources = {}
|
77
|
+
results = { :current => [], :update => [], :obsolete => [] }
|
78
|
+
count = 0
|
79
|
+
prereleases = 0
|
80
|
+
|
81
|
+
# Map dependencies to their specs, then select RubyGem sources
|
82
|
+
dep_specs = deps.map { |dep| [dep, specs.find { |spec| spec.name == dep.name }] }
|
83
|
+
dep_specs = dep_specs.select { |dep, spec| !spec.nil? && (spec.source.class == Bundler::Source::Rubygems) }
|
84
|
+
|
85
|
+
# Do we have any deps?
|
86
|
+
if deps.empty?
|
87
|
+
puts "No top-level RubyGem dependencies found in your Gemfile.\nRun gemfresh with --help if you need more information."
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
|
91
|
+
# Iterate through the deps, checking the spec against the latest version
|
92
|
+
print "Hitting up your RubyGems sources: "
|
93
|
+
dep_specs.each do |dep, spec|
|
94
|
+
name = dep.name
|
95
|
+
# version = spec.version.to_s
|
96
|
+
|
97
|
+
# Get a connection to the rubygem repository, reusing if we can
|
98
|
+
remote = spec.source.remotes.first
|
99
|
+
next if remote.nil?
|
100
|
+
reader = sources[remote]
|
101
|
+
reader = sources[remote] = RubyGemReader.new(remote) if reader.nil?
|
102
|
+
|
103
|
+
# Get the RubyGems data
|
104
|
+
# lookup = RubyGems.get("/api/v1/gems/#{name}.yaml")
|
105
|
+
gemdata = reader.get("/api/v1/gems/#{name}.yaml")
|
106
|
+
gemdata = YAML.load(gemdata)
|
107
|
+
|
108
|
+
# Get the versions list as well
|
109
|
+
versions = reader.get("/api/v1/versions/#{name}.yaml")
|
110
|
+
versions = YAML.load(versions)
|
111
|
+
|
112
|
+
# Store the result as a diff object
|
113
|
+
diff = SpecDiff.new(dep, spec, gemdata, versions)
|
114
|
+
results[diff.classify] << diff
|
115
|
+
|
116
|
+
# Stats
|
117
|
+
prereleases +=1 if diff.prerelease?
|
118
|
+
count += 1
|
119
|
+
|
120
|
+
# Get the dates of the given and current versions
|
121
|
+
# version_date = versions.find { |v| v['number'] == version }
|
122
|
+
# version_date = Time.parse(version_date['built_at']) unless version_date.nil?
|
123
|
+
# current_date = versions.find { |v| v['number'] == current }
|
124
|
+
# current_date = Time.parse(current_date['built_at']) unless current_date.nil?
|
125
|
+
|
126
|
+
# Exact match or directly updatable? If so, we can move on
|
127
|
+
# prerelease = false
|
128
|
+
# match = case
|
129
|
+
# when (version == current) then :current
|
130
|
+
# when (dep.match?(dep.name, current)) then :update
|
131
|
+
# else nil
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# # Not exact or updatable - we need to check if you're on a pre-release version
|
135
|
+
# if match.nil?
|
136
|
+
# match = :obsolete
|
137
|
+
# versions = versions.select { |v| v['prerelease']}.map { |v| v['number'] }
|
138
|
+
# prerelease = versions.include?(version)
|
139
|
+
# # If it's a prerelease determine what kind
|
140
|
+
# if prerelease
|
141
|
+
# prereleases += 1
|
142
|
+
# current = versions.first # Big assumption
|
143
|
+
# match = case
|
144
|
+
# when (version == current) then :current
|
145
|
+
# when (dep.match?(dep.name, current)) then :update
|
146
|
+
# else :obsolete
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
# end
|
150
|
+
|
151
|
+
# Got our result
|
152
|
+
# results[match] << SpecDiff.new(dep, spec, versions)
|
153
|
+
# [dep, spec, current, prerelease, version_date, current_date]
|
154
|
+
print "."
|
155
|
+
STDOUT.flush
|
156
|
+
end
|
157
|
+
puts " Done!"
|
158
|
+
|
159
|
+
# Let the user about prereleases
|
160
|
+
if prereleases > 0
|
161
|
+
puts "\nYou have #{prereleases} prerelease gem#{prereleases == 1 ? '' : 's'}. Prereleases will be marked with a '*'."
|
162
|
+
end
|
163
|
+
|
164
|
+
# Output Gem Ages
|
165
|
+
puts "\nThe following Gems are:"
|
166
|
+
ages = results.values.flatten.group_by(&:build_age)
|
167
|
+
{:month1 => 'less than a month', :month6 => '6 months or less', :year1 => 'less than a year', :more => 'more than a year'}.each_pair do |key, value|
|
168
|
+
next if ages[key].nil?
|
169
|
+
puts "-- #{value} old:"
|
170
|
+
puts ages[key].map(&:to_s).join(', ')
|
171
|
+
end
|
172
|
+
|
173
|
+
# Output Current Gems
|
174
|
+
if results[:current].empty?
|
175
|
+
puts "\nYou don't have any current gems."
|
176
|
+
else
|
177
|
+
puts "\nThe following gems at the most current version: "
|
178
|
+
puts results[:current].map(&:to_s).join(', ')
|
179
|
+
end
|
180
|
+
|
181
|
+
# Output Updatable Gems
|
182
|
+
if results[:update].empty?
|
183
|
+
puts "\nYou don't have any updatable gems."
|
184
|
+
else
|
185
|
+
puts "\nThe following gems are locked to older versions, but your Gemfile allows for the current version: "
|
186
|
+
results[:update].each do |diff|
|
187
|
+
puts " #{diff}, with #{diff.dep.requirement} could allow #{diff.version_available}"
|
188
|
+
end
|
189
|
+
puts "Barring dependency issues, these gems could be updated to current using 'bundle update'."
|
190
|
+
end
|
191
|
+
|
192
|
+
# Output Obsolete Gems
|
193
|
+
if results[:obsolete].empty?
|
194
|
+
puts "\nYou don't have any obsolete gems."
|
195
|
+
else
|
196
|
+
puts "\nThe following gems are obsolete: "
|
197
|
+
results[:obsolete].each do |diff|
|
198
|
+
released = diff.version_build_date(diff.version_available)
|
199
|
+
released = released.nil? ? '.' : ", #{released.strftime('%d %b %Y')}."
|
200
|
+
|
201
|
+
suggest = diff.suggest
|
202
|
+
suggest = suggest.nil? ? '' : "Also consider version #{suggest}."
|
203
|
+
|
204
|
+
puts " #{diff} is now at #{diff.version_available}#{released} #{suggest}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
|
data/support.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
class SpecDiff < Struct.new(:dep, :spec, :gemdata, :versions)
|
2
|
+
|
3
|
+
# Configure the diff
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
# Check for prerelease - Gem rules are that a letter indicates a prerelease
|
7
|
+
# See http://rubygems.rubyforge.org/rubygems-update/Gem/Version.html#method-i-prerelease-3F
|
8
|
+
@prerelease = version_in_use =~ /[a-zA-Z]/
|
9
|
+
end
|
10
|
+
|
11
|
+
# Return a :month1, :month6, :year1, :more depending on the
|
12
|
+
# build age of the available version
|
13
|
+
def build_age
|
14
|
+
build_date = version_build_date(version_in_use)
|
15
|
+
return :more if build_date.nil?
|
16
|
+
days = ((Time.now.utc - build_date)/(24 * 60 * 60)).round
|
17
|
+
case
|
18
|
+
when days < 31 then :month1
|
19
|
+
when days < 182 then :month6
|
20
|
+
when days < 366 then :year1
|
21
|
+
else :more
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Classify this as :current, :update or :obsolete
|
26
|
+
def classify
|
27
|
+
case
|
28
|
+
when (version_available == version_in_use) then :current
|
29
|
+
when (dep.match?(name, version_available)) then :update
|
30
|
+
else :obsolete
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def prerelease?; @prerelease; end
|
35
|
+
|
36
|
+
def name; dep.name; end
|
37
|
+
|
38
|
+
# Is there a suggested version - e.g. if you're using rails 3.0.8, the most
|
39
|
+
# current might be 3.1.0. However, the suggested version would be 3.0.10 --
|
40
|
+
# this will suggest the best version within your current minor version tree.
|
41
|
+
# May return nil if you're at the current suggestion, or if there is no
|
42
|
+
# reasonable match
|
43
|
+
def suggest
|
44
|
+
match = nil
|
45
|
+
head = version_in_use.rpartition('.').first
|
46
|
+
versions.sort_by { |v| v['built_at'] }.reverse.each do |ver|
|
47
|
+
ver = ver['number']
|
48
|
+
match = ver and break if ver.start_with?(head)
|
49
|
+
end
|
50
|
+
(match == version_in_use) || (match == version_available) ? nil : match
|
51
|
+
end
|
52
|
+
|
53
|
+
# String representation is the basic spec form 'gename (version)',
|
54
|
+
# with a start appended for prereleases.
|
55
|
+
def to_s
|
56
|
+
"#{spec}#{prerelease? ? '*' : ''}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return the version data for a given string
|
60
|
+
def version_data(version)
|
61
|
+
return nil if versions.nil? || versions.empty?
|
62
|
+
version = versions.find { |v| v['number'] == version }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return the build date for a given version string (e.g. '1.2.1')
|
66
|
+
def version_build_date(version)
|
67
|
+
return nil if versions.nil? || versions.empty?
|
68
|
+
version_date = version_data(version)['built_at']
|
69
|
+
version_date.nil? ? nil : Time.parse(version_date)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Best version available according to RubyGems data
|
73
|
+
def version_available
|
74
|
+
return gemdata["version"].to_s unless prerelease?
|
75
|
+
|
76
|
+
# Depends if it's a prerelease or not
|
77
|
+
prereleases = versions.select { |v| v['prerelease']}.map { |v| v['number'] }
|
78
|
+
prereleases.first # Big Assumption, but appears correct on data so far
|
79
|
+
end
|
80
|
+
|
81
|
+
# The version currently in use according to the lockfile
|
82
|
+
def version_in_use; spec.version.to_s; end
|
83
|
+
|
84
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gemfresh
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 1.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jon Williams
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-29 00:00:00 +10:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: bundler
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 51
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 18
|
34
|
+
version: 1.0.18
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: Scans Gemfiles to check for obsolete and updateable gems.
|
38
|
+
email:
|
39
|
+
- jon@jonathannen.com
|
40
|
+
executables:
|
41
|
+
- gemfresh
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- Gemfile
|
49
|
+
- MIT-LICENSE.txt
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- bin/gemfresh
|
53
|
+
- gemfresh.gemspec
|
54
|
+
- gemfresh.rb
|
55
|
+
- support.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: https://github.com/jonathannen/gemfresh
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- .
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.6.2
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Checks the freshness of your Gemfile.
|
90
|
+
test_files: []
|
91
|
+
|