class Gem::Version
Version
类将字符串版本处理为可比较的值。版本字符串通常应为一系列用句点分隔的数字。每个部分(用句点分隔的数字)都被视为其自身的数字,这些数字用于排序。例如,3.10 的排序高于 3.2,因为 10 大于 2。
如果任何部分包含字母(目前仅支持 a-z),则该版本被认为是预发布版本。在第 N 部分中具有预发布部分的版本排序低于具有 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 Rational
版本控制¶ ↑
-
版本应由三个非负整数表示,用句点分隔(例如 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 不向后兼容会发生什么?由于使用“>=”,您的东西会损坏。更好的方法是使用“approximate”版本说明符(“~>”)指定您的依赖项。它们有点令人困惑,因此这里介绍了依赖项说明符的工作方式
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
对于最后一个示例,单位数版本会自动用零扩展,以给出合理的结果。
公共类方法
来源
# File lib/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
字符串与 RubyGems 的要求匹配,则为 True。
来源
# File lib/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
或 String
。旨在简化客户端代码。
ver1 = Version.create('1.3.17') # -> (Version object) ver2 = Version.create(ver1) # -> (ver1) ver3 = Version.create(nil) # -> nil
来源
# File lib/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
从 version
字符串构造一个 Version
。版本字符串是由点分隔的一系列数字或 ASCII 字母。
私有类方法
来源
# File lib/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
公共实例方法
来源
# File lib/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
将此版本与 other
进行比较,如果另一个版本大于、相同或小于此版本,则返回 -1、0 或 1。尝试与不是 Gem::Version
或有效版本 String
的内容进行比较将返回 nil
。
来源
# File lib/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
用于 ~> Requirement 的推荐版本。
来源
# File lib/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
返回一个新的版本对象,其中倒数第二个修订号大 1(例如,5.3.1 => 5.4)。
预发布(alpha)部分,例如 5.3.1.b.2 => 5.4,将被忽略。
来源
# File lib/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 lib/rubygems/version.rb, line 402 def freeze prerelease? _segments canonical_segments super end
Object#freeze
来源
# File lib/rubygems/version.rb, line 282 def marshal_dump [@version] end
仅转储原始版本字符串,而不是完整对象。它是一个字符串,用于向后兼容(RubyGems 1.3.5 及更早版本)。
来源
# File lib/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
加载自定义 marshal 格式。它是一个字符串,用于向后兼容(RubyGems 1.3.5 及更早版本)。
来源
# File lib/rubygems/version.rb, line 310 def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end
如果版本包含字母,则认为该版本是预发布版本。
来源
# File lib/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
此版本的发布版本(例如,1.2.0.a -> 1.2.0)。非预发布版本将返回自身。
受保护的实例方法
来源
# File lib/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 lib/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