gemfresh 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gemfresh.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'gemfresh'
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
+