gemfresh 1.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.
- 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
|
+
|