1##
2# The Version class processes string versions into comparable
3# values. A version string should normally be a series of numbers
4# separated by periods. Each part (digits separated by periods) is
5# considered its own number, and these are used for sorting. So for
6# instance, 3.10 sorts higher than 3.2 because ten is greater than
7# two.
8#
9# If any part contains letters (currently only a-z are supported) then
10# that version is considered prerelease. Versions with a prerelease
11# part in the Nth part sort less than versions with N-1
12# parts. Prerelease parts are sorted alphabetically using the normal
13# Ruby string sorting rules. If a prerelease part contains both
14# letters and numbers, it will be broken into multiple parts to
15# provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is
16# greater than 1.0.a9).
17#
18# Prereleases sort between real releases (newest to oldest):
19#
20# 1. 1.0
21# 2. 1.0.b1
22# 3. 1.0.a.2
23# 4. 0.9
24#
25# == How Software Changes
26#
27# Users expect to be able to specify a version constraint that gives them
28# some reasonable expectation that new versions of a library will work with
29# their software if the version constraint is true, and not work with their
30# software if the version constraint is false.  In other words, the perfect
31# system will accept all compatible versions of the library and reject all
32# incompatible versions.
33#
34# Libraries change in 3 ways (well, more than 3, but stay focused here!).
35#
36# 1. The change may be an implementation detail only and have no effect on
37#    the client software.
38# 2. The change may add new features, but do so in a way that client software
39#    written to an earlier version is still compatible.
40# 3. The change may change the public interface of the library in such a way
41#    that old software is no longer compatible.
42#
43# Some examples are appropriate at this point.  Suppose I have a Stack class
44# that supports a <tt>push</tt> and a <tt>pop</tt> method.
45#
46# === Examples of Category 1 changes:
47#
48# * Switch from an array based implementation to a linked-list based
49#   implementation.
50# * Provide an automatic (and transparent) backing store for large stacks.
51#
52# === Examples of Category 2 changes might be:
53#
54# * Add a <tt>depth</tt> method to return the current depth of the stack.
55# * Add a <tt>top</tt> method that returns the current top of stack (without
56#   changing the stack).
57# * Change <tt>push</tt> so that it returns the item pushed (previously it
58#   had no usable return value).
59#
60# === Examples of Category 3 changes might be:
61#
62# * Changes <tt>pop</tt> so that it no longer returns a value (you must use
63#   <tt>top</tt> to get the top of the stack).
64# * Rename the methods to <tt>push_item</tt> and <tt>pop_item</tt>.
65#
66# == RubyGems Rational Versioning
67#
68# * Versions shall be represented by three non-negative integers, separated
69#   by periods (e.g. 3.1.4).  The first integers is the "major" version
70#   number, the second integer is the "minor" version number, and the third
71#   integer is the "build" number.
72#
73# * A category 1 change (implementation detail) will increment the build
74#   number.
75#
76# * A category 2 change (backwards compatible) will increment the minor
77#   version number and reset the build number.
78#
79# * A category 3 change (incompatible) will increment the major build number
80#   and reset the minor and build numbers.
81#
82# * Any "public" release of a gem should have a different version.  Normally
83#   that means incrementing the build number.  This means a developer can
84#   generate builds all day long for himself, but as soon as he/she makes a
85#   public release, the version must be updated.
86#
87# === Examples
88#
89# Let's work through a project lifecycle using our Stack example from above.
90#
91# Version 0.0.1:: The initial Stack class is release.
92# Version 0.0.2:: Switched to a linked=list implementation because it is
93#                 cooler.
94# Version 0.1.0:: Added a <tt>depth</tt> method.
95# Version 1.0.0:: Added <tt>top</tt> and made <tt>pop</tt> return nil
96#                 (<tt>pop</tt> used to return the  old top item).
97# Version 1.1.0:: <tt>push</tt> now returns the value pushed (it used it
98#                 return nil).
99# Version 1.1.1:: Fixed a bug in the linked list implementation.
100# Version 1.1.2:: Fixed a bug introduced in the last fix.
101#
102# Client A needs a stack with basic push/pop capability.  He writes to the
103# original interface (no <tt>top</tt>), so his version constraint looks
104# like:
105#
106#   gem 'stack', '~> 0.0'
107#
108# Essentially, any version is OK with Client A.  An incompatible change to
109# the library will cause him grief, but he is willing to take the chance (we
110# call Client A optimistic).
111#
112# Client B is just like Client A except for two things: (1) He uses the
113# <tt>depth</tt> method and (2) he is worried about future
114# incompatibilities, so he writes his version constraint like this:
115#
116#   gem 'stack', '~> 0.1'
117#
118# The <tt>depth</tt> method was introduced in version 0.1.0, so that version
119# or anything later is fine, as long as the version stays below version 1.0
120# where incompatibilities are introduced.  We call Client B pessimistic
121# because he is worried about incompatible future changes (it is OK to be
122# pessimistic!).
123#
124# == Preventing Version Catastrophe:
125#
126# From: http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
127#
128# Let's say you're depending on the fnord gem version 2.y.z. If you
129# specify your dependency as ">= 2.0.0" then, you're good, right? What
130# happens if fnord 3.0 comes out and it isn't backwards compatible
131# with 2.y.z? Your stuff will break as a result of using ">=". The
132# better route is to specify your dependency with an "approximate" version
133# specifier ("~>"). They're a tad confusing, so here is how the dependency
134# specifiers work:
135#
136#   Specification From  ... To (exclusive)
137#   ">= 3.0"      3.0   ... &infin;
138#   "~> 3.0"      3.0   ... 4.0
139#   "~> 3.0.0"    3.0.0 ... 3.1
140#   "~> 3.5"      3.5   ... 4.0
141#   "~> 3.5.0"    3.5.0 ... 3.6
142
143class Gem::Version
144  autoload :Requirement, 'rubygems/requirement'
145
146  include Comparable
147
148  # FIX: These are only used once, in .correct?. Do they deserve to be
149  # constants?
150  VERSION_PATTERN = '[0-9]+(?>\.[0-9a-zA-Z]+)*' # :nodoc:
151  ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ # :nodoc:
152
153  ##
154  # A string representation of this Version.
155
156  attr_reader :version
157  alias to_s version
158
159  ##
160  # True if the +version+ string matches RubyGems' requirements.
161
162  def self.correct? version
163    version.to_s =~ ANCHORED_VERSION_PATTERN
164  end
165
166  ##
167  # Factory method to create a Version object. Input may be a Version
168  # or a String. Intended to simplify client code.
169  #
170  #   ver1 = Version.create('1.3.17')   # -> (Version object)
171  #   ver2 = Version.create(ver1)       # -> (ver1)
172  #   ver3 = Version.create(nil)        # -> nil
173
174  # REFACTOR: There's no real reason this should be separate from #initialize.
175
176  def self.create input
177    if self === input then # check yourself before you wreck yourself
178      input
179    elsif input.nil? then
180      nil
181    else
182      new input
183    end
184  end
185
186  ##
187  # Constructs a Version from the +version+ string.  A version string is a
188  # series of digits or ASCII letters separated by dots.
189
190  def initialize version
191    raise ArgumentError, "Malformed version number string #{version}" unless
192      self.class.correct?(version)
193
194    @version = version.to_s.dup.strip
195  end
196
197  ##
198  # Return a new version object where the next to the last revision
199  # number is one greater (e.g., 5.3.1 => 5.4).
200  #
201  # Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored.
202
203  def bump
204    segments = self.segments.dup
205    segments.pop while segments.any? { |s| String === s }
206    segments.pop if segments.size > 1
207
208    segments[-1] = segments[-1].succ
209    self.class.new segments.join(".")
210  end
211
212  ##
213  # A Version is only eql? to another version if it's specified to the
214  # same precision. Version "1.0" is not the same as version "1".
215
216  def eql? other
217    self.class === other and @version == other.version
218  end
219
220  def hash # :nodoc:
221    @hash ||= segments.hash
222  end
223
224  def init_with coder # :nodoc:
225    yaml_initialize coder.tag, coder.map
226  end
227
228  def inspect # :nodoc:
229    "#<#{self.class} #{version.inspect}>"
230  end
231
232  ##
233  # Dump only the raw version string, not the complete object. It's a
234  # string for backwards (RubyGems 1.3.5 and earlier) compatibility.
235
236  def marshal_dump
237    [version]
238  end
239
240  ##
241  # Load custom marshal format. It's a string for backwards (RubyGems
242  # 1.3.5 and earlier) compatibility.
243
244  def marshal_load array
245    initialize array[0]
246  end
247
248  def yaml_initialize(tag, map)
249    @version = map['version']
250    @segments = nil
251    @hash = nil
252  end
253
254  def to_yaml_properties
255    ["@version"]
256  end
257
258  def encode_with coder
259    coder.add 'version', @version
260  end
261
262  ##
263  # A version is considered a prerelease if it contains a letter.
264
265  def prerelease?
266    @prerelease ||= !!(@version =~ /[a-zA-Z]/)
267  end
268
269  def pretty_print q # :nodoc:
270    q.text "Gem::Version.new(#{version.inspect})"
271  end
272
273  ##
274  # The release for this version (e.g. 1.2.0.a -> 1.2.0).
275  # Non-prerelease versions return themselves.
276
277  def release
278    return self unless prerelease?
279
280    segments = self.segments.dup
281    segments.pop while segments.any? { |s| String === s }
282    self.class.new segments.join('.')
283  end
284
285  def segments # :nodoc:
286
287    # segments is lazy so it can pick up version values that come from
288    # old marshaled versions, which don't go through marshal_load.
289
290    @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
291      /^\d+$/ =~ s ? s.to_i : s
292    end
293  end
294
295  ##
296  # A recommended version for use with a ~> Requirement.
297
298  def approximate_recommendation
299    segments = self.segments.dup
300
301    segments.pop    while segments.any? { |s| String === s }
302    segments.pop    while segments.size > 2
303    segments.push 0 while segments.size < 2
304
305    "~> #{segments.join(".")}"
306  end
307
308  ##
309  # Compares this version with +other+ returning -1, 0, or 1 if the
310  # other version is larger, the same, or smaller than this
311  # one. Attempts to compare to something that's not a
312  # <tt>Gem::Version</tt> return +nil+.
313
314  def <=> other
315    return unless Gem::Version === other
316    return 0 if @version == other.version
317
318    lhsegments = segments
319    rhsegments = other.segments
320
321    lhsize = lhsegments.size
322    rhsize = rhsegments.size
323    limit  = (lhsize > rhsize ? lhsize : rhsize) - 1
324
325    i = 0
326
327    while i <= limit
328      lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
329      i += 1
330
331      next      if lhs == rhs
332      return -1 if String  === lhs && Numeric === rhs
333      return  1 if Numeric === lhs && String  === rhs
334
335      return lhs <=> rhs
336    end
337
338    return 0
339  end
340end
341