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

  2. 1.0.b1

  3. 1.0.a.2

  4. 0.9

如果您想指定一个包含 1.x 系列的预发布版本和常规发布版本的版本限制,这是最佳方法

s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0'

软件如何更改

用户希望能够指定一个版本约束,该约束可以使他们合理地期望,如果版本约束为真,则库的新版本将与他们的软件一起工作,如果版本约束为假,则不与他们的软件一起工作。换句话说,完美的系统将接受所有兼容的库版本,并拒绝所有不兼容的版本。

库以 3 种方式更改(嗯,不止 3 种,但请专注于此!)。

  1. 更改可能只是一个实现细节,对客户端软件没有影响。

  2. 更改可能会添加新功能,但添加方式是,针对早期版本编写的客户端软件仍然兼容。

  3. 更改可能会以旧软件不再兼容的方式更改库的公共接口。

此时提供一些示例是合适的。假设我有一个支持 pushpop 方法的 Stack 类。

第一类更改的例子:

  • 从基于数组的实现切换到基于链表的实现。

  • 为大型堆栈提供自动(且透明)的后备存储。

第二类更改的例子可能是:

  • 添加一个 depth 方法来返回堆栈的当前深度。

  • 添加一个 top 方法来返回堆栈的当前顶部(而不更改堆栈)。

  • 更改 push,使其返回推送的项目(以前它没有可用的返回值)。

第三类更改的例子可能是:

  • 更改 pop,使其不再返回值(您必须使用 top 来获取堆栈的顶部)。

  • 将方法重命名为 push_itempop_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   ... &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
"~> 3"        3.0   ... 4.0

对于最后一个示例,单位数版本会自动扩展为零,以给出合理的结果。

公共类方法

correct?(version) 单击以切换源

如果 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
create(input) 单击以切换源

创建 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
new(version) 单击以切换源

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

私有类方法

nil_versions_are_discouraged!() 单击以切换源
# 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) 单击以切换源

将此版本与 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
approximate_recommendation() 单击以切换源

一个建议与 ~> 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
bump() 单击以切换源

返回一个新的版本对象,其中倒数第二个修订号大 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
canonical_segments() 单击以切换源

在第一个字母之前或版本末尾删除尾随零段

# 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
eql?(other) 单击以切换源

只有在 Version 指定为相同的精度时,它才与另一个版本 eql?。Version “1.0” 与版本 “1” 不同。

# File rubygems/version.rb, line 262
def eql?(other)
  self.class === other && @version == other.version
end
freeze() 单击以切换源
调用超类方法
# File rubygems/version.rb, line 402
def freeze
  prerelease?
  _segments
  canonical_segments
  super
end
marshal_dump() 单击以切换源

仅转储原始版本字符串,而不是完整的对象。为了向后兼容(RubyGems 1.3.5 及更早版本),它是一个字符串。

# File rubygems/version.rb, line 282
def marshal_dump
  [@version]
end
marshal_load(array) 单击以切换源

加载自定义 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
prerelease?() 单击以切换源

如果版本包含字母,则该版本被视为预发布版本。

# File rubygems/version.rb, line 310
def prerelease?
  unless instance_variable_defined? :@prerelease
    @prerelease = /[a-zA-Z]/.match?(version)
  end
  @prerelease
end
release() 单击以切换源

此版本的发行版(例如,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
to_s()
别名:version
version() 单击以切换源

Version 的字符串表示形式。

# File rubygems/version.rb, line 164
def version
  @version
end
也别名为:to_s

受保护的实例方法

_segments() 单击以切换源
# 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
partition_segments(ver) 单击以切换源
# 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