rubygems-update 0.8.3
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.
Potentially problematic release.
This version of rubygems-update might be problematic. Click here for more details.
- data/ChangeLog +2335 -0
- data/README +54 -0
- data/Rakefile +293 -0
- data/Releases +98 -0
- data/TODO +7 -0
- data/bin/gem +11 -0
- data/bin/gem_server +111 -0
- data/bin/generate_yaml_index.rb +58 -0
- data/bin/update_rubygems +18 -0
- data/doc/doc.css +73 -0
- data/doc/makedoc.rb +4 -0
- data/examples/application/an-app.gemspec +26 -0
- data/examples/application/bin/myapp +3 -0
- data/examples/application/lib/somefunctionality.rb +3 -0
- data/gemspecs/README +4 -0
- data/gemspecs/cgikit-1.1.0.gemspec +18 -0
- data/gemspecs/jabber4r.gemspec +26 -0
- data/gemspecs/linguistics.gemspec +22 -0
- data/gemspecs/ook.gemspec +21 -0
- data/gemspecs/progressbar.gemspec +22 -0
- data/gemspecs/redcloth.gemspec +22 -0
- data/gemspecs/rublog.gemspec +23 -0
- data/gemspecs/ruby-doom.gemspec +21 -0
- data/gemspecs/rubyjdwp.gemspec +21 -0
- data/gemspecs/statistics.gemspec +21 -0
- data/lib/rubygems.rb +353 -0
- data/lib/rubygems/builder.rb +54 -0
- data/lib/rubygems/cmd_manager.rb +127 -0
- data/lib/rubygems/command.rb +191 -0
- data/lib/rubygems/config_file.rb +57 -0
- data/lib/rubygems/doc_manager.rb +94 -0
- data/lib/rubygems/format.rb +65 -0
- data/lib/rubygems/gem_commands.rb +925 -0
- data/lib/rubygems/gem_runner.rb +23 -0
- data/lib/rubygems/installer.rb +621 -0
- data/lib/rubygems/loadpath_manager.rb +108 -0
- data/lib/rubygems/old_format.rb +150 -0
- data/lib/rubygems/open-uri.rb +604 -0
- data/lib/rubygems/package.rb +740 -0
- data/lib/rubygems/remote_installer.rb +499 -0
- data/lib/rubygems/rubygems_version.rb +6 -0
- data/lib/rubygems/source_index.rb +130 -0
- data/lib/rubygems/specification.rb +613 -0
- data/lib/rubygems/user_interaction.rb +176 -0
- data/lib/rubygems/validator.rb +148 -0
- data/lib/rubygems/version.rb +279 -0
- data/lib/ubygems.rb +4 -0
- data/pkgs/sources/lib/sources.rb +6 -0
- data/pkgs/sources/sources.gemspec +14 -0
- data/post-install.rb +75 -0
- data/redist/session.gem +433 -0
- data/scripts/buildtests.rb +25 -0
- data/scripts/gemdoc.rb +62 -0
- data/scripts/runtest.rb +17 -0
- data/scripts/specdoc.rb +164 -0
- data/setup.rb +1360 -0
- data/test/bogussources.rb +5 -0
- data/test/data/legacy/keyedlist-0.4.0.ruby +11 -0
- data/test/data/legacy/keyedlist-0.4.0.yaml +16 -0
- data/test/data/lib/code.rb +1 -0
- data/test/data/one/README.one +1 -0
- data/test/data/one/lib/one.rb +3 -0
- data/test/data/one/one.gemspec +17 -0
- data/test/data/one/one.yaml +40 -0
- data/test/functional.rb +145 -0
- data/test/gemenvironment.rb +45 -0
- data/test/gemutilities.rb +18 -0
- data/test/insure_session.rb +46 -0
- data/test/mock/gems/gems/sources-0.0.1/lib/sources.rb +5 -0
- data/test/mock/gems/specifications/sources-0.0.1.gemspec +8 -0
- data/test/mockgemui.rb +45 -0
- data/test/onegem.rb +23 -0
- data/test/simple_gem.rb +66 -0
- data/test/test_builder.rb +13 -0
- data/test/test_cached_fetcher.rb +60 -0
- data/test/test_check_command.rb +28 -0
- data/test/test_command.rb +130 -0
- data/test/test_configfile.rb +36 -0
- data/test/test_format.rb +70 -0
- data/test/test_gemloadpaths.rb +45 -0
- data/test/test_gempaths.rb +115 -0
- data/test/test_loadmanager.rb +40 -0
- data/test/test_local_cache.rb +157 -0
- data/test/test_package.rb +600 -0
- data/test/test_parse_commands.rb +179 -0
- data/test/test_process_commands.rb +21 -0
- data/test/test_remote_fetcher.rb +162 -0
- data/test/test_remote_installer.rb +154 -0
- data/test/test_source_index.rb +58 -0
- data/test/test_specification.rb +286 -0
- data/test/test_validator.rb +53 -0
- data/test/test_version_comparison.rb +204 -0
- data/test/testgem.rc +6 -0
- data/test/user_capture.rb +1 -0
- data/test/yaml_data.rb +59 -0
- metadata +151 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
module Gem
|
2
|
+
|
3
|
+
##
|
4
|
+
# Module that defines the default UserInteraction. Any class
|
5
|
+
# including this module will have access to the +ui+ method that
|
6
|
+
# returns the default UI.
|
7
|
+
module DefaultUserInteraction
|
8
|
+
|
9
|
+
# Return the default UI.
|
10
|
+
def ui
|
11
|
+
DefaultUserInteraction.ui
|
12
|
+
end
|
13
|
+
|
14
|
+
# Set the default UI. If the default UI is never explicity set, a
|
15
|
+
# simple console based UserInteraction will be used automatically.
|
16
|
+
def ui=(new_ui)
|
17
|
+
DefaultUserInteraction.ui = new_ui
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_ui(new_ui, &block)
|
21
|
+
DefaultUserInteraction.use_ui(new_ui, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# The default UI is a class variable of the singleton class for
|
25
|
+
# this module.
|
26
|
+
class << self
|
27
|
+
def ui
|
28
|
+
@ui ||= Gem::ConsoleUI.new
|
29
|
+
end
|
30
|
+
def ui=(new_ui)
|
31
|
+
@ui = new_ui
|
32
|
+
end
|
33
|
+
def use_ui(new_ui)
|
34
|
+
old_ui = @ui
|
35
|
+
@ui = new_ui
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
@ui = old_ui
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Make the default UI accessable without the "ui." prefix. Classes
|
45
|
+
# including this module may use the interaction methods on the
|
46
|
+
# default UI directly. Classes may also reference the +ui+ and
|
47
|
+
# <tt>ui=</tt> methods.
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
#
|
51
|
+
# class X
|
52
|
+
# include Gem::UserInteraction
|
53
|
+
#
|
54
|
+
# def get_answer
|
55
|
+
# n = ask("What is the meaning of life?")
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
module UserInteraction
|
59
|
+
include DefaultUserInteraction
|
60
|
+
[
|
61
|
+
:choose_from_list, :ask, :ask_yes_no, :say, :alert, :alert_warning,
|
62
|
+
:alert_error, :terminate_interaction!, :terminate_interaction
|
63
|
+
].each do |methname|
|
64
|
+
class_eval %{
|
65
|
+
def #{methname}(*args)
|
66
|
+
ui.#{methname}(*args)
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# StreamUI implements a simple stream based user interface.
|
74
|
+
class StreamUI
|
75
|
+
def initialize(in_stream, out_stream, err_stream=STDERR)
|
76
|
+
@ins = in_stream
|
77
|
+
@outs = out_stream
|
78
|
+
@errs = err_stream
|
79
|
+
end
|
80
|
+
|
81
|
+
# Choose from a list of options. +question+ is a prompt displayed
|
82
|
+
# above the list. +list+ is a list of option strings. Returns
|
83
|
+
# the pair [option_name, option_index].
|
84
|
+
def choose_from_list(question, list)
|
85
|
+
@outs.puts question
|
86
|
+
list.each_with_index do |item, index|
|
87
|
+
@outs.puts " #{index+1}. #{item}"
|
88
|
+
end
|
89
|
+
@outs.print "> "
|
90
|
+
@outs.flush
|
91
|
+
result = @ins.gets.strip.to_i - 1
|
92
|
+
return list[result], result
|
93
|
+
end
|
94
|
+
|
95
|
+
# Ask a question. Returns a true for yes, false for no.
|
96
|
+
def ask_yes_no(question, default=nil)
|
97
|
+
qstr = case default
|
98
|
+
when nil
|
99
|
+
'yn'
|
100
|
+
when true
|
101
|
+
'Yn'
|
102
|
+
else
|
103
|
+
'yN'
|
104
|
+
end
|
105
|
+
result = nil
|
106
|
+
while result.nil?
|
107
|
+
result = ask("#{question} [#{qstr}]")
|
108
|
+
result = case result
|
109
|
+
when /^[Yy].*/
|
110
|
+
true
|
111
|
+
when /^[Nn].*/
|
112
|
+
false
|
113
|
+
else
|
114
|
+
default
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return result
|
118
|
+
end
|
119
|
+
|
120
|
+
# Ask a question. Returns an answer.
|
121
|
+
def ask(question)
|
122
|
+
@outs.print(question + " ")
|
123
|
+
@outs.flush
|
124
|
+
result = @ins.gets
|
125
|
+
result.chomp! if result
|
126
|
+
result
|
127
|
+
end
|
128
|
+
|
129
|
+
# Display a statement.
|
130
|
+
def say(statement="")
|
131
|
+
@outs.puts statement
|
132
|
+
end
|
133
|
+
|
134
|
+
# Display an informational alert.
|
135
|
+
def alert(statement, question=nil)
|
136
|
+
@outs.puts "INFO: #{statement}"
|
137
|
+
return ask(question) if question
|
138
|
+
end
|
139
|
+
|
140
|
+
# Display a warning in a location expected to get error messages.
|
141
|
+
def alert_warning(statement, question=nil)
|
142
|
+
@errs.puts "WARNING: #{statement}"
|
143
|
+
ask(question) if question
|
144
|
+
end
|
145
|
+
|
146
|
+
# Display an error message in a location expected to get error
|
147
|
+
# messages.
|
148
|
+
def alert_error(statement, question=nil)
|
149
|
+
@errs.puts "ERROR: #{statement}"
|
150
|
+
ask(question) if question
|
151
|
+
end
|
152
|
+
|
153
|
+
# Terminate the application immediately without running any exit
|
154
|
+
# handlers.
|
155
|
+
def terminate_interaction!(status=-1)
|
156
|
+
exit!(status)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Terminate the appliation normally, running any exit handlers
|
160
|
+
# that might have been defined.
|
161
|
+
def terminate_interaction(status=0)
|
162
|
+
exit(status)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
##
|
168
|
+
# Subclass of StreamUI that instantiates the user interaction using
|
169
|
+
# standard in, out and error.
|
170
|
+
class ConsoleUI < StreamUI
|
171
|
+
def initialize
|
172
|
+
super(STDIN, STDOUT, STDERR)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Gem
|
2
|
+
|
3
|
+
class VerificationError < Gem::Exception; end
|
4
|
+
|
5
|
+
##
|
6
|
+
# Validator performs various gem file and gem database validation
|
7
|
+
class Validator
|
8
|
+
include UserInteraction
|
9
|
+
|
10
|
+
##
|
11
|
+
# Given a gem file's contents, validates against its own MD5 checksum
|
12
|
+
# gem_data:: [String] Contents of the gem file
|
13
|
+
def verify_gem(gem_data)
|
14
|
+
if(gem_data.size == 0) then
|
15
|
+
raise VerificationError.new("Empty Gem file")
|
16
|
+
end
|
17
|
+
require 'md5'
|
18
|
+
unless(gem_data =~ /MD5SUM/m)
|
19
|
+
return # Don't worry about it...this sucks. Need to fix MD5 stuff for
|
20
|
+
# new format
|
21
|
+
# FIXME
|
22
|
+
end
|
23
|
+
unless (MD5.md5(gem_data.gsub(/MD5SUM = "([a-z0-9]+)"/, "MD5SUM = \"" + ("F" * 32) + "\"")) == $1.to_s)
|
24
|
+
raise VerificationError.new("Invalid checksum for Gem file")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Given the path to a gem file, validates against its own MD5 checksum
|
30
|
+
#
|
31
|
+
# gem_path:: [String] Path to gem file
|
32
|
+
def verify_gem_file(gem_path)
|
33
|
+
begin
|
34
|
+
File.open(gem_path, 'rb') do |file|
|
35
|
+
gem_data = file.read
|
36
|
+
verify_gem(gem_data)
|
37
|
+
end
|
38
|
+
rescue Errno::ENOENT
|
39
|
+
raise Gem::VerificationError.new("Missing gem file #{gem_path}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def find_files_for_gem(gem_directory)
|
45
|
+
installed_files = []
|
46
|
+
Find.find(gem_directory) {|file_name|
|
47
|
+
fn = file_name.slice((gem_directory.size)..(file_name.size-1)).sub(/^\//, "")
|
48
|
+
if(!(fn =~ /CVS/ || File.directory?(fn) || fn == "")) then
|
49
|
+
installed_files << fn
|
50
|
+
end
|
51
|
+
|
52
|
+
}
|
53
|
+
installed_files
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
public
|
58
|
+
ErrorData = Struct.new(:path, :problem)
|
59
|
+
|
60
|
+
##
|
61
|
+
# Checks the gem directory for the following potential
|
62
|
+
# inconsistencies/problems:
|
63
|
+
# * Checksum gem itself
|
64
|
+
# * For each file in each gem, check consistency of installed versions
|
65
|
+
# * Check for files that aren't part of the gem but are in the gems directory
|
66
|
+
# * 1 cache - 1 spec - 1 directory.
|
67
|
+
#
|
68
|
+
# returns a hash of ErrorData objects, keyed on the problem gem's name.
|
69
|
+
def alien
|
70
|
+
require 'rubygems/installer'
|
71
|
+
require 'find'
|
72
|
+
require 'md5'
|
73
|
+
errors = {}
|
74
|
+
Gem::SourceIndex.from_installed_gems.each do |gem_name, gem_spec|
|
75
|
+
errors[gem_name] ||= []
|
76
|
+
gem_path = File.join(Gem.dir, "cache", gem_spec.full_name) + ".gem"
|
77
|
+
spec_path = File.join(Gem.dir, "specifications", gem_spec.full_name) + ".gemspec"
|
78
|
+
gem_directory = File.join(Gem.dir, "gems", gem_spec.full_name)
|
79
|
+
installed_files = find_files_for_gem(gem_directory)
|
80
|
+
|
81
|
+
if(!File.exist?(spec_path)) then
|
82
|
+
errors[gem_name] << ErrorData.new(spec_path, "Spec file doesn't exist for installed gem")
|
83
|
+
end
|
84
|
+
|
85
|
+
begin
|
86
|
+
require 'rubygems/format.rb'
|
87
|
+
verify_gem_file(gem_path)
|
88
|
+
File.open(gem_path) do |file|
|
89
|
+
format = Gem::Format.from_file_by_path(gem_path)
|
90
|
+
format.file_entries.each do |entry, data|
|
91
|
+
# Found this file. Delete it from list
|
92
|
+
installed_files.delete remove_leading_dot_dir(entry['path'])
|
93
|
+
File.open(File.join(gem_directory, entry['path']), 'rb') do |f|
|
94
|
+
unless MD5.md5(f.read).to_s == MD5.md5(data).to_s
|
95
|
+
errors[gem_name] << ErrorData.new(entry['path'], "installed file doesn't match original from gem")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
rescue VerificationError => e
|
101
|
+
errors[gem_name] << ErrorData.new(gem_path, e.message)
|
102
|
+
end
|
103
|
+
# Clean out directories that weren't explicitly included in the gemspec
|
104
|
+
# FIXME: This still allows arbitrary incorrect directories.
|
105
|
+
installed_files.delete_if {|potential_directory|
|
106
|
+
File.directory?(File.join(gem_directory, potential_directory))
|
107
|
+
}
|
108
|
+
if(installed_files.size > 0) then
|
109
|
+
errors[gem_name] << ErrorData.new(gem_path, "Unmanaged files in gem: #{installed_files.inspect}")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
errors
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Runs unit tests for a given gem specification
|
117
|
+
def unit_test(gem_spec)
|
118
|
+
start_dir = Dir.pwd
|
119
|
+
Dir.chdir(gem_spec.full_gem_path)
|
120
|
+
$: << File.join(Gem.dir, "gems", gem_spec.full_name)
|
121
|
+
# XXX: why do we need this gem_spec when we've already got 'spec'?
|
122
|
+
test_files = gem_spec.test_files
|
123
|
+
if test_files.empty?
|
124
|
+
say "There are no unit tests to run for #{gem_spec.name}-#{gem_spec.version}"
|
125
|
+
return
|
126
|
+
end
|
127
|
+
require_gem gem_spec.name, "= #{gem_spec.version.version}"
|
128
|
+
test_files.each do |f| require f end
|
129
|
+
require 'test/unit/ui/console/testrunner'
|
130
|
+
suite = Test::Unit::TestSuite.new("#{gem_spec.name}-#{gem_spec.version}")
|
131
|
+
ObjectSpace.each_object(Class) do |klass|
|
132
|
+
suite << klass.suite if (klass < Test::Unit::TestCase)
|
133
|
+
end
|
134
|
+
result = Test::Unit::UI::Console::TestRunner.run(suite, Test::Unit::UI::SILENT)
|
135
|
+
unless result.passed?
|
136
|
+
alert_error(result.to_s)
|
137
|
+
#unless ask_yes_no(result.to_s + "...keep Gem?", true) then
|
138
|
+
#Gem::Uninstaller.new(gem_spec.name, gem_spec.version.version).uninstall
|
139
|
+
#end
|
140
|
+
end
|
141
|
+
Dir.chdir(start_dir)
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove_leading_dot_dir(path)
|
145
|
+
path.sub(/^\.\//, "")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
module Gem
|
2
|
+
|
3
|
+
##
|
4
|
+
# The Dependency class holds a Gem name and Version::Requirement
|
5
|
+
#
|
6
|
+
class Dependency
|
7
|
+
attr_accessor :name, :version_requirements
|
8
|
+
|
9
|
+
##
|
10
|
+
# Constructs the dependency
|
11
|
+
#
|
12
|
+
# name:: [String] name of the Gem
|
13
|
+
# version_requirements:: [String Array] version requirement (e.g. ["> 1.2"])
|
14
|
+
#
|
15
|
+
def initialize(name, version_requirements)
|
16
|
+
@name = name
|
17
|
+
@version_requirements = Version::Requirement.new(version_requirements)
|
18
|
+
@version_requirement = nil # Avoid warnings.
|
19
|
+
end
|
20
|
+
|
21
|
+
undef version_requirements
|
22
|
+
def version_requirements
|
23
|
+
normalize if @version_requirement
|
24
|
+
@version_requirements
|
25
|
+
end
|
26
|
+
|
27
|
+
def requirement_list
|
28
|
+
version_requirements.as_list
|
29
|
+
end
|
30
|
+
|
31
|
+
alias requirements_list requirement_list
|
32
|
+
|
33
|
+
def normalize
|
34
|
+
ver = @version_requirement.instance_eval { @version }
|
35
|
+
@version_requirements = Version::Requirement.new([ver])
|
36
|
+
@version_requirement = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
"#{name} (#{version_requirements})"
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
self.name = other.name and
|
45
|
+
self.version_requirements == other.version_requirements
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# The Version class processes string versions into comparable values
|
51
|
+
#
|
52
|
+
class Version
|
53
|
+
include Comparable
|
54
|
+
|
55
|
+
attr_accessor :version
|
56
|
+
|
57
|
+
NUM_RE = /\s*(\d+(\.\d+)*)*\s*/
|
58
|
+
|
59
|
+
##
|
60
|
+
# Checks if version string is valid format
|
61
|
+
#
|
62
|
+
# str:: [String] the version string
|
63
|
+
# return:: [Boolean] true if the string format is correct, otherwise false
|
64
|
+
#
|
65
|
+
def self.correct?(str)
|
66
|
+
/^#{NUM_RE}$/.match(str)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Factory method to create a Version object. Input may be a Version or a
|
71
|
+
# String. Intended to simplify client code.
|
72
|
+
#
|
73
|
+
# ver1 = Version.create('1.3.17') # -> (Version object)
|
74
|
+
# ver2 = Version.create(ver1) # -> (ver1)
|
75
|
+
# ver3 = Version.create(nil) # -> nil
|
76
|
+
#
|
77
|
+
def self.create(input)
|
78
|
+
if input.respond_to? :version
|
79
|
+
return input
|
80
|
+
elsif input.nil?
|
81
|
+
return nil
|
82
|
+
else
|
83
|
+
return Version.new(input)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Constructs a version from the supplied string
|
89
|
+
#
|
90
|
+
# version:: [String] The version string. Format is digit.digit...
|
91
|
+
#
|
92
|
+
def initialize(version)
|
93
|
+
raise ArgumentError,
|
94
|
+
"Malformed version number string #{version}" unless Version.correct?(version)
|
95
|
+
@version = version
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Returns the text representation of the version
|
100
|
+
#
|
101
|
+
# return:: [String] version as string
|
102
|
+
#
|
103
|
+
def to_s
|
104
|
+
@version
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Convert version to integer array
|
109
|
+
#
|
110
|
+
# return:: [Array] list of integers
|
111
|
+
#
|
112
|
+
def to_ints
|
113
|
+
@version.scan(/\d+/).map {|s| s.to_i}
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Compares two versions
|
118
|
+
#
|
119
|
+
# other:: [Version or .to_ints] other version to compare to
|
120
|
+
# return:: [Fixnum] -1, 0, 1
|
121
|
+
#
|
122
|
+
def <=>(other)
|
123
|
+
return 1 unless other
|
124
|
+
rnums, vnums = to_ints, other.to_ints
|
125
|
+
[rnums.size, vnums.size].max.times {|i|
|
126
|
+
rnums[i] ||= 0
|
127
|
+
vnums[i] ||= 0
|
128
|
+
}
|
129
|
+
|
130
|
+
begin
|
131
|
+
r,v = rnums.shift, vnums.shift
|
132
|
+
end until (r != v || rnums.empty?)
|
133
|
+
|
134
|
+
return r <=> v
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return a new version object where the next to the last revision
|
138
|
+
# number is one greater. (e.g. 5.3.1 => 5.4)
|
139
|
+
def bump
|
140
|
+
ints = to_ints
|
141
|
+
ints.pop if ints.size > 1
|
142
|
+
ints[-1] += 1
|
143
|
+
self.class.new(ints.join("."))
|
144
|
+
end
|
145
|
+
|
146
|
+
##
|
147
|
+
# Requirement version includes a prefaced comparator in addition
|
148
|
+
# to a version number.
|
149
|
+
#
|
150
|
+
# A Requirement object can actually contain multiple, er, requirements, as
|
151
|
+
# in (> 1.2, < 2.0).
|
152
|
+
#
|
153
|
+
class Requirement
|
154
|
+
include Comparable
|
155
|
+
|
156
|
+
OPS = {
|
157
|
+
"=" => lambda { |v, r| v == r },
|
158
|
+
"!=" => lambda { |v, r| v != r },
|
159
|
+
">" => lambda { |v, r| v > r },
|
160
|
+
"<" => lambda { |v, r| v < r },
|
161
|
+
">=" => lambda { |v, r| v >= r },
|
162
|
+
"<=" => lambda { |v, r| v <= r },
|
163
|
+
"~>" => lambda { |v, r| v >= r && v < r.bump }
|
164
|
+
}
|
165
|
+
|
166
|
+
OP_RE = Regexp.new(OPS.keys.collect{|k| Regexp.quote(k)}.join("|"))
|
167
|
+
REQ_RE = /\s*(#{OP_RE})\s*/
|
168
|
+
|
169
|
+
##
|
170
|
+
# Factory method to create a Version::Requirement object. Input may be a
|
171
|
+
# Version, a String, or nil. Intended to simplify client code.
|
172
|
+
#
|
173
|
+
# If the input is "weird", the default version requirement is returned.
|
174
|
+
#
|
175
|
+
def self.create(input)
|
176
|
+
if input.kind_of?(Requirement)
|
177
|
+
return input
|
178
|
+
elsif input.kind_of?(Array)
|
179
|
+
return self.new(input)
|
180
|
+
elsif input.respond_to? :to_str
|
181
|
+
return self.new([input.to_str])
|
182
|
+
else
|
183
|
+
return self.default
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# A default "version requirement" can surely _only_ be '> 0'.
|
189
|
+
#
|
190
|
+
def self.default
|
191
|
+
self.new(['> 0.0.0'])
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Constructs a version requirement instance
|
196
|
+
#
|
197
|
+
# str:: [String Array] the version requirement string (e.g. ["> 1.23"])
|
198
|
+
#
|
199
|
+
def initialize(reqs)
|
200
|
+
@requirements = reqs.collect do |rq|
|
201
|
+
op, version_string = parse(rq)
|
202
|
+
[op, Version.new(version_string)]
|
203
|
+
end
|
204
|
+
@version = nil # Avoid warnings.
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Overrides to check for comparator
|
209
|
+
#
|
210
|
+
# str:: [String] the version requirement string
|
211
|
+
# return:: [Boolean] true if the string format is correct, otherwise false
|
212
|
+
#
|
213
|
+
def correct?(str)
|
214
|
+
/^#{REQ_RE}#{NUM_RE}$/.match(str)
|
215
|
+
end
|
216
|
+
|
217
|
+
def to_s
|
218
|
+
as_list.join(", ")
|
219
|
+
end
|
220
|
+
|
221
|
+
def as_list
|
222
|
+
normalize
|
223
|
+
@requirements.collect { |req|
|
224
|
+
"#{req[0]} #{req[1]}"
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
def normalize
|
229
|
+
return if @version.nil?
|
230
|
+
@requirements = [parse(@version)]
|
231
|
+
@nums = nil
|
232
|
+
@version = nil
|
233
|
+
@op = nil
|
234
|
+
end
|
235
|
+
|
236
|
+
##
|
237
|
+
# Is the requirement satifised by +version+.
|
238
|
+
#
|
239
|
+
# version:: [Gem::Version] the version to compare against
|
240
|
+
# return:: [Boolean] true if this requirement is satisfied by
|
241
|
+
# the version, otherwise false
|
242
|
+
#
|
243
|
+
def satisfied_by?(version)
|
244
|
+
normalize
|
245
|
+
@requirements.all? { |op, rv| satisfy?(op, version, rv) }
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
##
|
251
|
+
# Is "version op required_version" satisfied?
|
252
|
+
#
|
253
|
+
def satisfy?(op, version, required_version)
|
254
|
+
OPS[op].call(version, required_version)
|
255
|
+
end
|
256
|
+
|
257
|
+
##
|
258
|
+
# Parse the version requirement string. Return the operator and
|
259
|
+
# version strings.
|
260
|
+
#
|
261
|
+
def parse(str)
|
262
|
+
if md = /^\s*(#{OP_RE})\s*([0-9.]+)\s*$/.match(str)
|
263
|
+
[md[1], md[2]]
|
264
|
+
elsif md = /^\s*([0-9.]+)\s*$/.match(str)
|
265
|
+
["=", md[1]]
|
266
|
+
elsif md = /^\s*(#{OP_RE})\s*$/.match(str)
|
267
|
+
[md[1], "0"]
|
268
|
+
else
|
269
|
+
fail ArgumentError, "Illformed requirement [#{str}]"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def <=>(other)
|
274
|
+
to_s <=> other.to_s
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|