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 ... ∞ 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