class Gem::Version
Version
类将字符串版本处理为可比较的值。版本字符串通常应为一系列由句点分隔的数字。每个部分(由句点分隔的数字)都被视为其自身的数字,这些数字用于排序。因此,例如,3.10 的排序高于 3.2,因为十大于二。
如果任何部分包含字母(目前仅支持 a-z),则该版本被视为预发布版本。Nth 部分中包含预发布部分的版本排序低于具有 N-1 部分的版本。预发布部分使用正常的 Ruby 字符串排序规则按字母顺序排序。如果预发布部分包含字母和数字,它将被分解为多个部分以提供预期的排序行为 (1.0.a10 变为 1.0.a.10,并且大于 1.0.a9)。
预发布版本在实际发布版本之间排序(从最新到最旧)
-
1.0
-
1.0.b1
-
1.0.a.2
-
0.9
如果您想指定一个包含 1.x 系列的预发布版本和常规发布版本的版本限制,这是最佳方法
s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0'
软件如何更改¶ ↑
用户希望能够指定一个版本约束,该约束可以使他们合理地期望,如果版本约束为真,则库的新版本将与他们的软件一起工作,如果版本约束为假,则不与他们的软件一起工作。换句话说,完美的系统将接受所有兼容的库版本,并拒绝所有不兼容的版本。
库以 3 种方式更改(嗯,不止 3 种,但请专注于此!)。
-
更改可能只是一个实现细节,对客户端软件没有影响。
-
更改可能会添加新功能,但添加方式是,针对早期版本编写的客户端软件仍然兼容。
-
更改可能会以旧软件不再兼容的方式更改库的公共接口。
此时提供一些示例是合适的。假设我有一个支持 push
和 pop
方法的 Stack 类。
第一类更改的例子:¶ ↑
-
从基于数组的实现切换到基于链表的实现。
-
为大型堆栈提供自动(且透明)的后备存储。
第二类更改的例子可能是:¶ ↑
-
添加一个
depth
方法来返回堆栈的当前深度。 -
添加一个
top
方法来返回堆栈的当前顶部(而不更改堆栈)。 -
更改
push
,使其返回推送的项目(以前它没有可用的返回值)。
第三类更改的例子可能是:¶ ↑
-
更改
pop
,使其不再返回值(您必须使用top
来获取堆栈的顶部)。 -
将方法重命名为
push_item
和pop_item
。
RubyGems 合理版本控制¶ ↑
-
版本应由三个非负整数表示,并用句点分隔(例如 3.1.4)。第一个整数是“主要”版本号,第二个整数是“次要”版本号,第三个整数是“构建”号。
-
第一类更改(实现细节)将递增构建号。
-
第二类更改(向后兼容)将递增次要版本号并重置构建号。
-
第三类更改(不兼容)将递增主要构建号并重置次要和构建号。
-
gem 的任何“公共”发行版都应具有不同的版本。通常这意味着递增构建号。这意味着开发人员可以整天生成构建,但是一旦他们进行公共发布,就必须更新版本。
例子¶ ↑
让我们使用上面 Stack 示例完成一个项目生命周期。
版本
0.0.1-
初始 Stack 类已发布。
版本
0.0.2-
切换到链表实现,因为它更酷。
版本
0.1.0-
添加了
depth
方法。 版本
1.0.0-
添加了
top
,并使pop
返回 nil (pop
以前返回旧的顶部项)。 版本
1.1.0-
push
现在返回推送的值(它过去返回 nil)。 版本
1.1.1-
修复了链表实现中的一个错误。
版本
1.1.2-
修复了上次修复中引入的错误。
客户端 A 需要一个具有基本 push/pop 功能的堆栈。它们编写到原始接口(没有 top
),因此它们的版本约束类似于
gem 'stack', '>= 0.0'
本质上,任何版本对于客户端 A 都是可以接受的。对库的不兼容更改将给他们带来麻烦,但他们愿意冒险(我们称客户端 A 为乐观)。
客户端 B 与客户端 A 相同,除了两件事:(1) 它们使用 depth
方法,并且 (2) 它们担心未来的不兼容性,因此它们像这样编写其版本约束
gem 'stack', '~> 0.1'
depth
方法是在 0.1.0 版本中引入的,因此该版本或任何更高版本都可以,只要版本保持在引入不兼容性的 1.0 版本以下即可。我们称客户端 B 为悲观,因为它们担心未来不兼容的更改(悲观是可以接受的!)。
防止 Version
灾难:¶ ↑
来自:www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html
假设您依赖于 fnord gem 版本 2.y.z。如果您将依赖项指定为“>= 2.0.0”,那么,您就没事了,对吗?如果 fnord 3.0 发布,并且它与 2.y.z 不向后兼容,会发生什么?由于使用了“>=”,您的东西会因此而损坏。更好的方法是使用“近似”版本说明符 (“~>”) 指定依赖项。它们有点令人困惑,因此以下是依赖项说明符的工作方式
Specification From ... To (exclusive) ">= 3.0" 3.0 ... ∞ "~> 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 "~> 3" 3.0 ... 4.0
对于最后一个示例,单位数版本会自动扩展为零,以给出合理的结果。
公共类方法
如果 version
字符串与 RubyGems 的要求匹配,则为 true。
# File rubygems/version.rb, line 173 def self.correct?(version) nil_versions_are_discouraged! if version.nil? ANCHORED_VERSION_PATTERN.match?(version.to_s) end
创建 Version
对象的工厂方法。输入可以是 Version
或字符串。旨在简化客户端代码。
ver1 = Version.create('1.3.17') # -> (Version object) ver2 = Version.create(ver1) # -> (ver1) ver3 = Version.create(nil) # -> nil
# File rubygems/version.rb, line 187 def self.create(input) if self === input # check yourself before you wreck yourself input elsif input.nil? nil_versions_are_discouraged! nil else new input end end
从 version
字符串构造 Version
。版本字符串是由点分隔的一系列数字或 ASCII 字母。
# File rubygems/version.rb, line 221 def initialize(version) unless self.class.correct?(version) raise ArgumentError, "Malformed version number string #{version}" end # If version is an empty string convert it to 0 version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version) @version = version.to_s # optimization to avoid allocation when given an integer, since we know # it's to_s won't have any spaces or dashes unless version.is_a?(Integer) @version = @version.strip @version.gsub!("-",".pre.") end @version = -@version @segments = nil end
私有类方法
# File rubygems/version.rb, line 209 def self.nil_versions_are_discouraged! unless Gem::Deprecate.skip warn "nil versions are discouraged and will be deprecated in Rubygems 4" end end
公共实例方法
将此版本与 other
进行比较,如果另一个版本大于、相同或小于此版本,则返回 -1、0 或 1。尝试与不是 Gem::Version
或有效版本字符串的内容进行比较返回 nil
。
# File rubygems/version.rb, line 360 def <=>(other) return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments lhsegments = canonical_segments rhsegments = other.canonical_segments lhsize = lhsegments.size rhsize = rhsegments.size limit = (lhsize > rhsize ? lhsize : rhsize) - 1 i = 0 while i <= limit lhs = lhsegments[i] || 0 rhs = rhsegments[i] || 0 i += 1 next if lhs == rhs return -1 if String === lhs && Numeric === rhs return 1 if Numeric === lhs && String === rhs return lhs <=> rhs end 0 end
一个建议与 ~> Requirement 一起使用的版本。
# File rubygems/version.rb, line 342 def approximate_recommendation segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 recommendation = "~> #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end
返回一个新的版本对象,其中倒数第二个修订号大 1(例如,5.3.1 => 5.4)。
预发布(alpha)部分,例如,5.3.1.b.2 => 5.4,将被忽略。
# File rubygems/version.rb, line 247 def bump @@bump[self] ||= begin segments = self.segments 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 end
在第一个字母之前或版本末尾删除尾随零段
# File rubygems/version.rb, line 391 def canonical_segments @canonical_segments ||= begin # remove trailing 0 segments, using dot or letter as anchor # may leave a trailing dot which will be ignored by partition_segments canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "") # remove 0 segments before the first letter in a prerelease version canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease? partition_segments(canonical_version) end end
# File rubygems/version.rb, line 402 def freeze prerelease? _segments canonical_segments super end
仅转储原始版本字符串,而不是完整的对象。为了向后兼容(RubyGems 1.3.5 及更早版本),它是一个字符串。
# File rubygems/version.rb, line 282 def marshal_dump [@version] end
加载自定义 marshal 格式。为了向后兼容(RubyGems 1.3.5 及更早版本),它是一个字符串。
# File rubygems/version.rb, line 290 def marshal_load(array) string = array[0] raise TypeError, "wrong version string" unless string.is_a?(String) initialize string end
如果版本包含字母,则该版本被视为预发布版本。
# File rubygems/version.rb, line 310 def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end
此版本的发行版(例如,1.2.0.a -> 1.2.0)。非预发布版本返回自身。
# File rubygems/version.rb, line 325 def release @@release[self] ||= if prerelease? segments = self.segments segments.pop while segments.any? {|s| String === s } self.class.new segments.join(".") else self end end
受保护的实例方法
# File rubygems/version.rb, line 411 def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load. # since this version object is cached in @@all, its @segments should be frozen @segments ||= partition_segments(@version) end
# File rubygems/version.rb, line 418 def partition_segments(ver) ver.scan(/\d+|[a-z]+/i).map! do |s| /\A\d/.match?(s) ? s.to_i : -s end.freeze end