内容

名称

version::Internals - Perl 扩展,用于版本对象

描述

针对所有现代版本的 Perl 重载版本对象。本文档记录了 version.pm 的内部数据表示和底层代码。有关日常使用,请参阅 version.pod。本文档仅对有兴趣了解血腥细节的用户有用。

什么是版本?

对于本模块而言,版本“号”是由一个或多个小数点和一个下划线(可选)分隔的正整数值序列。这对应于 Perl 本身用于版本的内容,并扩展了 Camel 书籍的各个版本中讨论的“版本作为数字”。

实际上有两种不同的版本对象

十进制版本

任何“看起来像数字”的版本,请参阅"十进制版本"。这也包括带有一个小数点和一个嵌入式下划线的版本,请参阅"Alpha 版本",即使必须引用这些版本以保留下划线格式。

点分十进制版本

也称为“点分整数”,它们包含多个小数点,并且可能有一个可选的嵌入式下划线,请参阅"点分十进制版本"。这是大多数开源软件中通常用作“外部”版本(用作标签或 tar 文件名的一部分)的内容。现在需要一个前导“v”字符,如果缺少,将发出警告。

这两种方法都会产生类似的版本对象,因为只有在需要时,默认字符串化才会产生版本"规范形式"

$v  = version->new(1.002);     # 1.002, but compares like 1.2.0
$v  = version->new(1.002003);  # 1.002003
$v2 = version->new("v1.2.3");  # v1.2.3

具体来说,初始化为"十进制版本"的版本号将按其最初创建时的形式进行字符串化(即传递给new()的字符串)。初始化为"点分十进制版本"的版本号将被字符串化为"规范形式"

十进制版本

这些对应于 Perl 本身在 5.6.0 之前的历史版本,以及所有其他遵循 $VERSION 标量的 Camel 规则的模块。十进制版本使用看起来像浮点数的数字进行初始化。前导零重要的,尾随零是隐含的,因此子版本之间至少保持三位。这意味着任何包含少于三位数字的子版本(小数点右边的数字)都将添加尾随零以弥补差异,但仅用于与其他版本对象进行比较。例如

                                 # Prints     Equivalent to
$v = version->new(      1.2);    # 1.2        v1.200.0
$v = version->new(     1.02);    # 1.02       v1.20.0
$v = version->new(    1.002);    # 1.002      v1.2.0
$v = version->new(   1.0023);    # 1.0023     v1.2.300
$v = version->new(  1.00203);    # 1.00203    v1.2.30
$v = version->new( 1.002003);    # 1.002003   v1.2.3

无论输入值是否带引号,前面的所有示例都是正确的。重要的特性是输入值仅包含一个小数。另请参阅"Alpha 版本"

重要提示:如上所示,如果您的十进制版本的小数点后包含 3 个以上有效数字,它将按 3 的倍数拆分,因此 1.0003 等于 v1.0.300,因为需要保持与 Perl 自身的 5.005_03 == 5.5.30 解释兼容。任何尾随零在数学比较中都会被忽略。

点分十进制版本

这些是最新形式的版本,对应于从 5.6.0 开始的 Perl 自身的版本样式。从 Perl 5.10.0 开始,很可能是 Perl 6,这可能是首选形式。此方法通常要求输入参数加引号,尽管 Perl 5.8.1 之后的版本可以使用 v 字符串作为一种特殊形式的引号,但这极不推荐。

“十进制版本”不同,点分十进制版本有多个小数点,例如:

                                 # Prints
$v = version->new( "v1.200");    # v1.200.0
$v = version->new("v1.20.0");    # v1.20.0
$v = qv("v1.2.3");               # v1.2.3
$v = qv("1.2.3");                # v1.2.3
$v = qv("1.20");                 # v1.20.0

一般来说,点分十进制版本允许最大程度的自由来指定版本,而十进制版本则强制执行一定的统一性。

就像“十进制版本”一样,点分十进制版本可以用作“Alpha 版本”

Alpha 版本

对于使用 CPAN 的模块作者,惯例是在版本字符串中用下划线来表示不稳定的版本。(请参阅CPAN。)version.pm 遵循此惯例,alpha 版本将测试为比最近的稳定版本更新,但比下一个稳定版本更低。只有最后一个元素可以用下划线分隔

# Declaring
use version 0.77; our $VERSION = version->declare("v1.2_3");

# Parsing
$v1 = version->parse("v1.2_3");
$v1 = version->parse("1.002_003");

请注意,在编写 alpha 十进制版本时,您必须引用该版本。十进制版本的字符串化形式将始终是用于初始化版本对象的相同字符串。

用于版本解析的正则表达式

版本字符串的合法形式的正式定义包含在 version::regex 类中。基本元素包含在常见元素中,尽管它们的作用域仅限于文件,因此它们仅对参考目的有用。有两个公开可用的标量,可以在其他代码中使用(未导出)

$version::LAX

此正则表达式涵盖了当前版本字符串解析器允许的所有合法形式。这并不是说所有这些形式都是推荐的,其中一些形式只能在引用时使用。

对于带点的十进制数

v1.2
1.2345.6
v1.23_4

如果出现两个或更多的小数,则前导“v”是可选的。如果只包含一个小数,则需要前导“v”来触发带点小数的解析。允许前导零,但不推荐(除非引用),因为 Perl 有可能将该数字视为八进制数。尾随下划线加一个或多个数字表示 alpha 或开发版本(并且必须引用才能正确解析)。

对于十进制版本

1
1.2345
1.2345_01

需要一个整数部分、一个可选的小数点,以及小数点右侧的一个或多个数字(可选)。允许尾随下划线,并且允许前导零。与宽松的带点小数版本一样,引用值对于正确解析 alpha/开发形式是必需的。

$version::STRICT

此正则表达式涵盖了一组更有限的格式,并构成了初始化版本对象的最佳实践。但是,您选择使用十进制数还是带点小数是个人喜好。

v1.234.5

对于带点小数版本,需要一个前导“v”,其中包含三个或更多个不超过三位数的子版本。还禁止在第一个子版本(在上面的示例中为“1”)之前使用前导 0(零)。

2.3456

对于十进制版本,需要一个整数部分(没有前导 0)、一个小数点以及小数点右侧的一个或多个数字。

提供的两个标量都已编译为正则表达式,并且不包含锚点或隐式分组,因此可以自由地包含在您自己的正则表达式中。例如,考虑以下代码

($pkg, $ver) =~ /
	^[ \t]*
	use [ \t]+($PKGNAME)
	(?:[ \t]+($version::STRICT))?
	[ \t]*;
/x;

这将匹配以下形式的一行

use Foo::Bar::Baz v1.2.3;	# legal only in Perl 5.8.1+

其中 $PKGNAME 是另一个正则表达式,用于定义包名的合法形式。

实现详情

十进制版本与点分十进制版本之间的等价性

当 Perl 5.6.0 发布时,决定在旧式十进制版本和新式点分十进制版本之间提供转换

5.6.0    == 5.006000
5.005_04 == 5.5.40

浮点数首先在小数点处拆分,然后小数点右侧每组三个数字构成下一个数字,依此类推,直到用尽有效数字,加上足够的尾随零以达到下一个三的倍数。

这也是 version.pm 采用的方法。一些示例可能会有所帮助

                          equivalent
decimal    zero-padded    dotted-decimal
-------    -----------    --------------
1.2        1.200          v1.200.0
1.02       1.020          v1.20.0
1.002      1.002          v1.2.0
1.0023     1.002300       v1.2.300
1.00203    1.002030       v1.2.30
1.002003   1.002003       v1.2.3

引用规则

由于 Perl 解析和标记例程的性质,某些初始化值必须加上引号才能正确解析为预期版本,尤其是在使用 declare"qv()" 方法时。在创建版本对象时不必对十进制数加上引号,但使用 version.pm 方法时始终对所有初始化值加上引号是安全的,因为这将确保键入的内容得到使用。

此外,如果对初始化值加上引号,那么在打印 (字符串化) $VERSION 时,传入的带引号值将与输出的内容完全相同。如果不给值加上引号,则 Perl 的普通数字处理就会发挥作用,您可能无法得到预期结果。

如果您使用解析为浮点数的数学公式,则需要依赖 Perl 的转换例程来生成您预期的版本。例如,通过 10 的幂相除是相当安全的,但其他运算不太可能是您想要的。例如

$VERSION = version->new((qw$Revision: 1.4)[1]/10);
print $VERSION;          # yields 0.14
$V2 = version->new(100/9); # Integer overflow in decimal number
print $V2;               # yields something like 11.111.111.100

Perl 5.8.1 及更高版本能够自动给 v 字符串加上引号,但在早期版本的 Perl 中这是不可能的。换句话说

$version = version->new("v2.5.4");  # legal in all versions of Perl
$newvers = version->new(v2.5.4);    # legal only in Perl >= 5.8.1

关于 v 字符串

有两种方法可以输入 v 字符串:带有两个或更多小数点的裸数字,或带有一个小数点和一个前导“v”字符 (也是裸的) 的裸数字。例如

$vs1 = 1.2.3; # encoded as \1\2\3
$vs2 = v1.2;  # encoded as \1\2

但是,强烈不建议在任何情况下使用裸 v 字符串来初始化版本对象。此外,在 5.8.1 之前的任何 Perl 版本中都不完全支持裸 v 字符串。

如果您坚持在 Perl > 5.6.0 中使用裸 v 字符串,请注意以下限制

1) 对于 Perl 版本 5.6.0 到 5.8.0,v 字符串代码仅根据 v 字符串的一些特征进行猜测。您必须使用三部分版本,例如 1.2.3 或 v1.2.3,才能使此启发式方法成功。

2) 对于 Perl 版本 5.8.1 及更高版本,v 字符串已在 Perl 核心更改为神奇的,这意味着 version.pm 代码可以自动确定是否使用了 v 字符串编码。

3) 在所有情况下,使用 v 字符串创建的版本都会有一个字符串化形式,该形式具有前导“v”字符,原因很简单,有时无法判断最初是否存在该字符。

版本对象内部

version.pm 提供了一个重载版本对象,该对象旨在封装作者预期的 $VERSION 分配,并使其完全自然地将这些对象用作数字(例如,用于比较)。为此,版本对象既包含作者键入的原始表示形式,也包含解析后的表示形式以简化比较。版本对象采用 重载 方法来简化需要比较、打印等对象的代码。

版本对象的内部结构是一个包含多个组件的受祝福的哈希

    bless( {
      'original' => 'v1.2.3_4',
      'alpha' => 1,
      'qv' => 1,
      'version' => [
	1,
	2,
	3,
	4
      ]
    }, 'version' );
original

用于初始化此版本对象的值的忠实表示。唯一不会与源文件中存在的字符完全相同的情况是,如果使用了像 v1.2 这样的短点分十进制版本(在这种情况下,它将包含 'v1.2')。强烈不建议使用此形式,因为它会让您和您的用户感到困惑。

qv

一个布尔值,表示这是一个十进制版本还是点分十进制版本。请参见 version 中的“is_qv()”

alpha

一个布尔值,表示这是一个 alpha 版本。注意:下划线只能出现在最后一个位置。请参见 version 中的“is_alpha()”

version

一个非负整数数组,用于与其他版本对象进行比较。

Replacement UNIVERSAL::VERSION

除了版本对象之外,此模块还用一个使用版本对象进行比较的函数替换了核心 UNIVERSAL::VERSION 函数。此运算符的返回值始终是作为简单标量(即不是对象)的字符串化形式,但生成的警告消息包括字符串化形式或正常形式,具体取决于它的调用方式。

例如

package Foo;
$VERSION = 1.2;

package Bar;
$VERSION = "v1.3.5"; # works with all Perl's (since it is quoted)

package main;
use version;

print $Foo::VERSION; # prints 1.2

print $Bar::VERSION; # prints 1.003005

eval "use foo 10";
print $@; # prints "foo version 10 required..."
eval "use foo 1.3.5; # work in Perl 5.6.1 or better
print $@; # prints "foo version 1.3.5 required..."

eval "use bar 1.3.6";
print $@; # prints "bar version 1.3.6 required..."
eval "use bar 1.004"; # note Decimal version
print $@; # prints "bar version 1.004 required..."

重要提示:这可能意味着需要更改搜索特定字符串(以确定给定模块是否可用)的代码。始终最好使用 userequire 中隐含的内置比较,而不是手动检查 class->VERSION,然后自己进行比较。

当用作函数时,替换的 UNIVERSAL::VERSION 如下所示

print $module->VERSION;

也将专门返回字符串化形式。有关更多详细信息,请参见 “Stringification”

USAGE DETAILS

Using modules that use version.pm

尽可能,version.pm 模块仍然与所有当前代码兼容。但是,如果您的模块正在使用已使用 version 类定义 $VERSION 的模块,则需要注意以下几点。为了讨论的目的,我们将假设已安装以下模块

package Example;
use version;  $VERSION = qv('1.2.2');
...module code here...
1;
Decimal versions always work

形式的代码

use Example 1.002003;

始终正常工作。use 将使用模块名称后作为第一个术语给出的浮点数(例如,高于 1.002.003)执行自动 $VERSION 比较。在这种情况下,已安装的模块对于请求的行来说太旧,因此您会看到类似于

Example version 1.002003 (v1.2.3) required--this is only version 1.002002 (v1.2.2)...
点分十进制版本有时有效

对于 Perl >= 5.6.2,您还可以使用如下行

use Example 1.2.3;

它将再次起作用(即给出如上所示的错误消息),即使对于通常不支持 v 字符串的 Perl 版本也是如此(请参见上面的 "关于 v 字符串?")。这与 use 仅检查第二个术语看起来是否像数字并将其传递给替换 UNIVERSAL::VERSION 的事实有关。然而,在 Perl 5.005_04 中并非如此,因此强烈建议您始终在代码中使用十进制版本,即使对于支持点分十进制版本的 Perl 版本也是如此。

对象方法

new()

与许多 OO 接口一样,new() 方法用于初始化版本对象。如果将两个参数传递给 new(),则第二个参数将被用作带有前缀“v”一样。这是为了支持使用 qw 运算符和 CVS 变量 $Revision 的历史用法,该变量在每次将文件提交到存储库时都会由 CVS 自动递增。

为了促进此功能,可以使用以下代码

$VERSION = version->new(qw$Revision: 2.7 $);

版本对象将被创建,就像使用了以下代码一样

$VERSION = version->new("v2.7");

换句话说,版本将自动从字符串中解析出来,并且它将被引用以保留 CVS 通常对版本所具有的含义。CVS $Revision$ 的递增方式与十进制版本不同(即 1.10 紧跟 1.9),因此必须将其处理为点分十进制版本。

可以将新版本对象创建为现有版本对象的副本,作为类方法

$v1 = version->new(12.3);
$v2 = version->new($v1);

或作为对象方法

$v1 = version->new(12.3);
$v2 = $v1->new(12.3);

并且在每种情况下,$v1 和 $v2 都将相同。注意:如果您使用现有对象创建新对象,如下所示

$v2 = $v1->new();

新对象不会是现有对象的克隆。在示例案例中,$v2 将是与 $v1 相同类型的空对象。

qv()

创建新版本对象的一种替代方法是通过导出的 qv() 子。这与其他 q? 运算符(如 qq、qw)严格不同,因为唯一支持的分隔符是括号(或空格)。这是在不触发浮点解释的情况下初始化短版本对象的最佳方法。例如

$v1 = qv(1.2);         # v1.2.0
$v2 = qv("1.2");       # also v1.2.0

如您所见,通常可以互换使用裸数字或引号括起来的字符串,除了尾随零的情况,必须加上引号才能正确转换。因此,强烈建议 qv() 的所有初始化器都使用引号括起来的字符串,而不是裸数字。

要防止将 qv() 函数导出到调用者的命名空间,请使用带有空参数的版本

use version ();

或只需要求版本,如下所示

require version;

这两种方法都将阻止 import() 方法触发并导出 qv() 子。

对于后续示例,将使用以下三个对象

$ver   = version->new("1.2.3.4"); # see "Quoting Rules"
$alpha = version->new("1.2.3_4"); # see "Alpha Versions"
$nver  = version->new(1.002);     # see "Decimal Versions"
标准形式

对于使用多个小数位(带引号或可能的 v 字符串)初始化的任何版本对象,或使用 qv() 运算符初始化的版本对象,字符串化表示形式将以标准化或简化形式(无多余零)返回,并带有前导“v”

print $ver->normal;         # prints as v1.2.3.4
print $ver->stringify;      # ditto
print $ver;                 # ditto
print $nver->normal;        # prints as v1.2.0
print $nver->stringify;     # prints as 1.002,
                            # see "Stringification"

为了保留已处理版本的含义,标准化表示形式将始终包含至少三个子项。换句话说,以下内容始终为真

my $newver = version->new($ver->stringify);
if ($newver eq $ver ) # always true
  {...}
数字转换

尽管默认情况下禁止对版本对象进行所有数学运算,但可以通过使用 $obj->numify 方法检索与版本对象相对应的数字。出于格式化目的,在显示与版本对象相对应的数字时,假定所有子版本都具有三个小数位。例如

print $ver->numify;         # prints 1.002003004
print $nver->numify;        # prints 1.002

与字符串化运算符不同,无需附加尾随零来保留正确的版本值。

字符串化

版本对象的默认字符串化返回与用于创建它的字符串完全相同的字符串,无论您使用 new() 还是 qv(),只有一个例外。唯一的例外是如果使用 qv() 创建对象,并且初始化器没有两个小数位或前导“v”(两者都是可选的),那么字符串化形式将有一个前导“v”,以便支持往返处理。

例如

Initialized as          Stringifies to
==============          ==============
version->new("1.2")       1.2
version->new("v1.2")     v1.2
qv("1.2.3")               1.2.3
qv("v1.3.5")             v1.3.5
qv("1.2")                v1.2   ### exceptional case

另请参见 UNIVERSAL::VERSION,因为当用作类方法时,它也会返回字符串化形式。

重要提示:上表中显示了一个特殊情况,其中“初始化器”与字符串化表示形式在字符串上不等价。如果您对没有前导“v”只有一个小数位的版本使用 qv() 运算符,则字符串化输出将有一个前导“v”,以保留含义。有关更多详细信息,请参见 "qv()" 运算符。

重要提示 2:尝试通过手动应用 numify()normal() 绕过正常的字符串化规则,有时会产生令人惊讶的结果

print version->new(version->new("v1.0")->numify)->normal; # v1.0.0

原因在于 numify() 运算符会将“v1.0”转换为等效字符串“1.000000”。强制外部版本对象为 normal() 形式将显示数学等价的“v1.0.0”。

正如 "new()" 中的示例所示,你可以始终通过非常简洁的

$v2 = $v1->new($v1);

创建现有版本对象的副本,其值相同

比较运算符

cmp<=> 运算符在项之间执行相同的比较(自动升级到版本对象)。Perl 根据这两个运算符自动生成所有其他比较运算符。除了下面列出的显而易见的相等性之外,追加一个尾随 0 项不会改变版本的值以进行比较。换句话说,“v1.2”和“1.2.0”的比较结果是相同的。

例如,以下关系成立

As Number        As String           Truth Value
-------------    ----------------    -----------
$ver >  1.0      $ver gt "1.0"       true
$ver <  2.5      $ver lt             true
$ver != 1.3      $ver ne "1.3"       true
$ver == 1.2      $ver eq "1.2"       false
$ver == 1.2.3.4  $ver eq "1.2.3.4"   see discussion below

为了减少混淆,最好选择十进制表示法或字符串表示法并坚持使用它。Perl6 版本对象可能仅支持十进制比较。另请参见 "引用规则"

警告:比较小数点数量不等的版本(无论显式还是隐式初始化)乍一看可能会产生意外的结果。例如,以下不等式成立

version->new(0.96)     > version->new(0.95); # 0.960.0 > 0.950.0
version->new("0.96.1") < version->new(0.95); # 0.096.1 < 0.950.0

因此,最好仅使用 "十进制版本""点分十进制版本",其中有多个小数点。

逻辑运算符

如果你需要测试版本对象是否已初始化,你可以直接对其进行测试

$vobj = version->new($something);
if ( $vobj )   # true only if $something was non-blank

你还可以测试版本对象是否是 alpha 版本,例如为了防止使用主版本中不存在的某些功能

$vobj = version->new("1.2_3"); # MUST QUOTE
...later...
if ( $vobj->is_alpha )       # True

作者

John Peacock <[email protected]>

另请参见

perl.