aboutsummaryrefslogtreecommitdiffstats
path: root/lib/rubygems/version.rb
diff options
context:
space:
mode:
authornobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-02-22 02:52:35 +0000
committernobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2010-02-22 02:52:35 +0000
commitb551e8c8b36766651be4e782e09e3b02e7d14a10 (patch)
treee164a1ef908bd4451568abf05b688f1593915b81 /lib/rubygems/version.rb
parent65544f575b25b18dc27f9364f973556ddb48538f (diff)
downloadruby-b551e8c8b36766651be4e782e09e3b02e7d14a10.tar.gz
* lib/rubygems: update to 1.3.6.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@26728 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rubygems/version.rb')
-rw-r--r--lib/rubygems/version.rb357
1 files changed, 206 insertions, 151 deletions
diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb
index f959429846..77403ff32b 100644
--- a/lib/rubygems/version.rb
+++ b/lib/rubygems/version.rb
@@ -1,9 +1,3 @@
-#--
-# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
-# All rights reserved.
-# See LICENSE.txt for permissions.
-#++
-
##
# The Version class processes string versions into comparable
# values. A version string should normally be a series of numbers
@@ -24,72 +18,153 @@
# 2. 1.0.b
# 3. 1.0.a
# 4. 0.9
+#
+# == How Software Changes
+#
+# Users expect to be able to specify a version constraint that gives them
+# some reasonable expectation that new versions of a library will work with
+# their software if the version constraint is true, and not work with their
+# software if the version constraint is false. In other words, the perfect
+# system will accept all compatible versions of the library and reject all
+# incompatible versions.
+#
+# Libraries change in 3 ways (well, more than 3, but stay focused here!).
+#
+# 1. The change may be an implementation detail only and have no effect on
+# the client software.
+# 2. The change may add new features, but do so in a way that client software
+# written to an earlier version is still compatible.
+# 3. The change may change the public interface of the library in such a way
+# that old software is no longer compatible.
+#
+# Some examples are appropriate at this point. Suppose I have a Stack class
+# that supports a <tt>push</tt> and a <tt>pop</tt> method.
+#
+# === Examples of Category 1 changes:
+#
+# * Switch from an array based implementation to a linked-list based
+# implementation.
+# * Provide an automatic (and transparent) backing store for large stacks.
+#
+# === Examples of Category 2 changes might be:
+#
+# * Add a <tt>depth</tt> method to return the current depth of the stack.
+# * Add a <tt>top</tt> method that returns the current top of stack (without
+# changing the stack).
+# * Change <tt>push</tt> so that it returns the item pushed (previously it
+# had no usable return value).
+#
+# === Examples of Category 3 changes might be:
+#
+# * Changes <tt>pop</tt> so that it no longer returns a value (you must use
+# <tt>top</tt> to get the top of the stack).
+# * Rename the methods to <tt>push_item</tt> and <tt>pop_item</tt>.
+#
+# == RubyGems Rational Versioning
+#
+# * Versions shall be represented by three non-negative integers, separated
+# by periods (e.g. 3.1.4). The first integers is the "major" version
+# number, the second integer is the "minor" version number, and the third
+# integer is the "build" number.
+#
+# * A category 1 change (implementation detail) will increment the build
+# number.
+#
+# * A category 2 change (backwards compatible) will increment the minor
+# version number and reset the build number.
+#
+# * A category 3 change (incompatible) will increment the major build number
+# and reset the minor and build numbers.
+#
+# * Any "public" release of a gem should have a different version. Normally
+# that means incrementing the build number. This means a developer can
+# generate builds all day long for himself, but as soon as he/she makes a
+# public release, the version must be updated.
+#
+# === Examples
+#
+# Let's work through a project lifecycle using our Stack example from above.
+#
+# Version 0.0.1:: The initial Stack class is release.
+# Version 0.0.2:: Switched to a linked=list implementation because it is
+# cooler.
+# Version 0.1.0:: Added a <tt>depth</tt> method.
+# Version 1.0.0:: Added <tt>top</tt> and made <tt>pop</tt> return nil
+# (<tt>pop</tt> used to return the old top item).
+# Version 1.1.0:: <tt>push</tt> now returns the value pushed (it used it
+# return nil).
+# Version 1.1.1:: Fixed a bug in the linked list implementation.
+# Version 1.1.2:: Fixed a bug introduced in the last fix.
+#
+# Client A needs a stack with basic push/pop capability. He writes to the
+# original interface (no <tt>top</tt>), so his version constraint looks
+# like:
+#
+# gem 'stack', '~> 0.0'
+#
+# Essentially, any version is OK with Client A. An incompatible change to
+# the library will cause him grief, but he is willing to take the chance (we
+# call Client A optimistic).
+#
+# Client B is just like Client A except for two things: (1) He uses the
+# <tt>depth</tt> method and (2) he is worried about future
+# incompatibilities, so he writes his version constraint like this:
+#
+# gem 'stack', '~> 0.1'
+#
+# The <tt>depth</tt> method was introduced in version 0.1.0, so that version
+# or anything later is fine, as long as the version stays below version 1.0
+# where incompatibilities are introduced. We call Client B pessimistic
+# because he is worried about incompatible future changes (it is OK to be
+# pessimistic!).
+#
+# == Preventing Version Catastrophe:
+#
+# From: http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
+#
+# Let's say you're depending on the fnord gem version 2.y.z. If you
+# specify your dependency as ">= 2.0.0" then, you're good, right? What
+# happens if fnord 3.0 comes out and it isn't backwards compatible
+# with 2.y.z? Your stuff will break as a result of using ">=". The
+# better route is to specify your dependency with a "spermy" version
+# specifier. They're a tad confusing, so here is how the dependency
+# specifiers work:
+#
+# Specification From ... To (exclusive)
+# ">= 3.0" 3.0 ... &infin;
+# "~> 3.0" 3.0 ... 4.0
+# "~> 3.0.0" 3.0.0 ... 3.1
+# "~> 3.5" 3.5 ... 4.0
+# "~> 3.5.0" 3.5.0 ... 3.6
class Gem::Version
-
- class Part
- include Comparable
-
- attr_reader :value
-
- def initialize(value)
- @value = (value =~ /\A\d+\z/) ? value.to_i : value
- end
-
- def to_s
- self.value.to_s
- end
-
- def inspect
- @value
- end
-
- def alpha?
- String === value
- end
-
- def numeric?
- Fixnum === value
- end
-
- def <=>(other)
- if self.numeric? && other.alpha? then
- 1
- elsif self.alpha? && other.numeric? then
- -1
- else
- self.value <=> other.value
- end
- end
-
- def succ
- self.class.new(self.value.succ)
- end
- end
-
include Comparable
- VERSION_PATTERN = '[0-9]+(\.[0-9a-z]+)*'
+ VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc:
+ ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
+
+ ##
+ # A string representation of this Version.
attr_reader :version
+ alias to_s version
- def self.correct?(version)
- pattern = /\A\s*(#{VERSION_PATTERN})*\s*\z/
+ ##
+ # True if the +version+ string matches RubyGems' requirements.
- version.is_a? Integer or
- version =~ pattern or
- version.to_s =~ pattern
+ def self.correct? version
+ version.to_s =~ ANCHORED_VERSION_PATTERN
end
##
- # Factory method to create a Version object. Input may be a Version or a
- # String. Intended to simplify client code.
+ # Factory method to create a Version object. Input may be a Version
+ # or a String. Intended to simplify client code.
#
# ver1 = Version.create('1.3.17') # -> (Version object)
# ver2 = Version.create(ver1) # -> (ver1)
# ver3 = Version.create(nil) # -> nil
- def self.create(input)
+ def self.create input
if input.respond_to? :version then
input
elsif input.nil? then
@@ -103,149 +178,129 @@ class Gem::Version
# Constructs a Version from the +version+ string. A version string is a
# series of digits or ASCII letters separated by dots.
- def initialize(version)
+ def initialize version
raise ArgumentError, "Malformed version number string #{version}" unless
self.class.correct?(version)
- self.version = version
- end
+ @version = version.to_s
+ @version.strip!
- def inspect # :nodoc:
- "#<#{self.class} #{@version.inspect}>"
+ segments # prime @segments
end
##
- # Dump only the raw version string, not the complete object
+ # Return a new version object where the next to the last revision
+ # number is one greater (e.g., 5.3.1 => 5.4).
+ #
+ # Pre-release (alpha) parts, e.g, 5.3.1.b2 => 5.4, are ignored.
- def marshal_dump
- [@version]
+ def bump
+ segments = self.segments.dup
+ segments.pop while segments.any? { |s| String === s }
+ segments.pop if segments.size > 1
+
+ segments[-1] = segments[-1].succ
+ self.class.new segments.join(".")
end
##
- # Load custom marshal format
+ # A Version is only eql? to another version if it's specified to the
+ # same precision. Version "1.0" is not the same as version "1".
- def marshal_load(array)
- self.version = array[0]
+ def eql? other
+ self.class === other and segments == other.segments
end
- def parts
- @parts ||= normalize
+ def hash # :nodoc:
+ segments.hash
end
- ##
- # Strip ignored trailing zeros.
-
- def normalize
- parts_arr = parse_parts_from_version_string
- if parts_arr.length != 1
- parts_arr.pop while parts_arr.last && parts_arr.last.value == 0
- parts_arr = [Part.new(0)] if parts_arr.empty?
- end
- parts_arr
+ def inspect # :nodoc:
+ "#<#{self.class} #{version.inspect}>"
end
##
- # Returns the text representation of the version
+ # Dump only the raw version string, not the complete object. It's a
+ # string for backwards (RubyGems 1.3.5 and earlier) compatibility.
- def to_s
- @version
+ def marshal_dump
+ [version]
end
- def to_yaml_properties
- ['@version']
- end
+ ##
+ # Load custom marshal format. It's a string for backwards (RubyGems
+ # 1.3.5 and earlier) compatibility.
- def version=(version)
- @version = version.to_s.strip
- normalize
+ def marshal_load array
+ initialize array[0]
end
-
##
- # A version is considered a prerelease if any part contains a letter.
+ # A version is considered a prerelease if it contains a letter.
def prerelease?
- parts.any? { |part| part.alpha? }
- end
-
- ##
- # The release for this version (e.g. 1.2.0.a -> 1.2.0)
- # Non-prerelease versions return themselves
- def release
- return self unless prerelease?
- rel_parts = parts.dup
- rel_parts.pop while rel_parts.any? { |part| part.alpha? }
- self.class.new(rel_parts.join('.'))
+ @prerelease ||= segments.any? { |s| String === s }
end
- def yaml_initialize(tag, values)
- self.version = values['version']
+ def pretty_print q # :nodoc:
+ q.text "Gem::Version.new(#{version.inspect})"
end
-
##
- # Compares this version with +other+ returning -1, 0, or 1 if the other
- # version is larger, the same, or smaller than this one.
+ # The release for this version (e.g. 1.2.0.a -> 1.2.0).
+ # Non-prerelease versions return themselves.
- def <=>(other)
- return nil unless self.class === other
- return 1 unless other
- mine, theirs = balance(self.parts.dup, other.parts.dup)
- mine <=> theirs
- end
+ def release
+ return self unless prerelease?
- def balance(a, b)
- a << Part.new(0) while a.size < b.size
- b << Part.new(0) while b.size < a.size
- [a, b]
+ segments = self.segments.dup
+ segments.pop while segments.any? { |s| String === s }
+ self.class.new segments.join('.')
end
- ##
- # A Version is only eql? to another version if it has the same version
- # string. "1.0" is not the same version as "1".
+ def segments # :nodoc:
- def eql?(other)
- self.class === other and @version == other.version
- end
+ # @segments is lazy so it can pick up @version values that come
+ # from old marshaled versions, which don't go through
+ # marshal_load. +segments+ is called in +initialize+ to "prime
+ # the pump" in normal cases.
- def hash # :nodoc:
- @version.hash
+ @segments ||= @version.scan(/[0-9a-z]+/i).map do |s|
+ /^\d+$/ =~ s ? s.to_i : s
+ end
end
##
- # Return a new version object where the next to the last revision number is
- # one greater. (e.g. 5.3.1 => 5.4)
- #
- # Pre-release (alpha) parts are ignored. (e.g 5.3.1.b2 => 5.4)
+ # A recommended version for use with a ~> Requirement.
- def bump
- parts = parse_parts_from_version_string
- parts.pop while parts.any? { |part| part.alpha? }
- parts.pop if parts.size > 1
- parts[-1] = parts[-1].succ
- self.class.new(parts.join("."))
- end
+ def spermy_recommendation
+ segments = self.segments.dup
- def parse_parts_from_version_string # :nodoc:
- @version.to_s.scan(/[0-9a-z]+/i).map { |s| Part.new(s) }
- end
+ segments.pop while segments.any? { |s| String === s }
+ segments.pop while segments.size > 2
+ segments.push 0 while segments.size < 2
- def pretty_print(q) # :nodoc:
- q.text "Gem::Version.new(#{@version.inspect})"
+ "~> #{segments.join(".")}"
end
- #:stopdoc:
+ ##
+ # Compares this version with +other+ returning -1, 0, or 1 if the other
+ # version is larger, the same, or smaller than this one.
- require 'rubygems/requirement'
+ def <=> other
+ return 1 unless other # HACK: comparable with nil? why?
+ return nil unless self.class === other
- ##
- # Gem::Requirement's original definition is nested in Version.
- # Although an inappropriate place, current gems specs reference the nested
- # class name explicitly. To remain compatible with old software loading
- # gemspecs, we leave a copy of original definition in Version, but define an
- # alias Gem::Requirement for use everywhere else.
+ lhsize = segments.size
+ rhsize = other.segments.size
+ limit = (lhsize > rhsize ? lhsize : rhsize) - 1
- Requirement = ::Gem::Requirement
+ 0.upto(limit) do |i|
+ lhs, rhs = segments[i] || 0, other.segments[i] || 0
- # :startdoc:
+ return -1 if String === lhs && Numeric === rhs
+ return 1 if Numeric === lhs && String === rhs
+ return lhs <=> rhs if lhs != rhs
+ end
+ return 0
+ end
end
-