在Perl发行版中包含数据的3种方法

作为模块作者,能够在Perl发行版中包含数据非常有用。数据可以用于配置和编写数据驱动测试等。以下是包含Perl发行版中数据的三种方法。

编辑: 文章于2014年2月9日更新,包括ExtUtils::MakeMaker解决方案选项3。

使用 __DATA__

“__DATA__”是一个Perl关键字,表示文件中代码的结束。该标记之后出现的任何文本将在运行时自动读取到DATA文件句柄中。例如,让我们在Perl测试文件中包含过去十年Perl TIOBE统计数据的YAML数据

use strict;
use warnings;
use YAML::XS;
use Test::More;

my $yaml = do { local $/; <main::DATA> };
my $data = Load $yaml;

do { ... };

done_testing();

__DATA__
---
2014: 0.917
2013: 2.264
2012: 2.768
2011: 2.857
2010: 3.562
2009: 4.303
2008: 5.247
2007: 6.237
2006: 7.045
2005: 8.861

这里我们使用do块将main::DATA文件句柄拖入$yaml。然后我们使用YAML::XS的“Load”函数将$yaml解码成存储在$data中的Perl数据结构。从这里我们可以自由地使用这些数据在我们的测试中。

关于__DATA__方法的优点是它简单、编码速度快、跨平台功能强大,你永远不会在查找数据时遇到麻烦(与外部文件不同)。__DATA__的缺点是它强制你将数据包含在与代码相同的文件中。如果你有大量数据怎么办?每次模块被使用时,数据都会增加使用该模块的负担,无论这些数据是否真的被使用。此外,__DATA__的内容通常是固定的——只有开发者才能覆盖它。

使用FindBin定位数据文件

FindBin是一个小巧而神奇的模块,它是Perl的核心模块之一,提供了“Bin”函数,该函数返回当前文件目录的绝对路径。这里的模式是在与Perl文件相同的目录中包含一个数据文件,并使用FindBin的Bin函数引用该数据文件。让我们看一个例子

首先,我们有Tiobe Perl YAML数据,保存在文件perl_tiobe.yaml中

---
2014: 0.917
2013: 2.264
2012: 2.768
2011: 2.857
2010: 3.562
2009: 4.303
2008: 5.247
2007: 6.237
2006: 7.045
2005: 8.861

接下来,我们在修改后的测试脚本中引用该文件

use strict;
use warnings;
use YAML::XS;
use Test::More;
use FindBin;

open (my $DATA, '<', "$FindBin::Bin/perl_tiobe.yaml") or die $!;
my $yaml = do { local $/; <$DATA> };
my $data = Load $yaml;

do { ... };

done_testing();

让我们回顾一下与上一个版本相比,此脚本中发生了哪些变化。首先,我们导入Findbin。然后,我们打开一个名为$DATA的文件句柄,该句柄指向FindBin::Bin返回的当前目录加上数据文件名。

如果可以保证数据文件与代码文件在同一位置,FindBin模式效果很好。这使得它非常适合测试文件,因为(按照惯例)它们总是在t目录中,并且不会被作为模块安装的一部分复制到其他地方。您可以在与Perl应用程序一起分发数据文件时使用此模式(例如,在Makefile中,在EXE_FILES指令中同时包含二进制文件和数据文件)。但是,这也意味着数据文件将被复制到目标bin目录,这是一种很快就会引起愤怒的文件污染。

更新Makefile.PL / Build.PL并使用File::Share

另一种在Perl发行版中包含数据文件的方法是将它们放置在发行版根目录中的“share”目录下,更新Makefile.PL / Build.PL以在安装期间复制数据文件,然后使用File::Share来访问文件。

如果您的发行版使用ExtUtils::MakeMaker,您可以在Makefile.PL中使用File::ShareDir::Install来复制数据文件。以下是虚构模块“Data::File”的纯真Makefile.PL

use 5.006;
use strict;
use warnings FATAL => 'all';
use ExtUtils::MakeMaker;
use File::ShareDir::Install;

install_share dist => 'share';

WriteMakefile(
    NAME             => 'Data::Dir',
    AUTHOR           => q{David Farrell },
    VERSION_FROM     => 'lib/Data/Dir.pm',
    ABSTRACT_FROM    => 'lib/Data/Dir.pm',
    LICENSE          => 'Artistic_2_0',
    PL_FILES         => {}, 
    MIN_PERL_VERSION => 5.006,
    CONFIGURE_REQUIRES => {
        'ExtUtils::MakeMaker' => 0,
    },  
    BUILD_REQUIRES => {
        'Test::More' => 0,
    },  
    PREREQ_PM => {
        #'ABC'              => 1.6,
        #'Foo::Bar::Module' => 5.0401,
    },  
    dist  => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
    clean => { FILES => 'Data-Dir-*' },
);

package MY;
use File::ShareDir::Install 'postamble';

在Makefile中,我们导入了File::ShareDir:Install,并将我们的“share”目录作为参数传递给“install_share”函数。Makefile中奇怪的最后一行包括对MY的包声明以及对File::ShareDir::Install的“postamble”方法的导入。务必包含这两行,否则数据文件将不会被复制。

如果您使用Module::Build,请更新Build.PL文件中的share_dir指令。以下是一个虚构模块“Data::File”的普通Build.PL示例:

use 5.006;
use strict;
use warnings FATAL => 'all';
use Module::Build;

my $builder = Module::Build->new(
    module_name         => 'Data::File',
    license             => 'Artistic_2_0',
    dist_author         => q{David Farrell },
    dist_version_from   => 'lib/Data/File.pm',
    release_status      => 'stable',
    configure_requires => {
        'Module::Build' => 0,
    },
    build_requires => {
        'Test::More' => 0,
    },  
    requires => {
        #'ABC'              => 1.6,
        #'Foo::Bar::Module' => 5.0401,
    },  
    add_to_cleanup     => [ 'Data-File-*' ],
    create_makefile_pl => 'traditional',
    share_dir => 'share',
);

$builder->create_build_script();

上面示例中的Build.PL中的“share_dir”指令指示Module::Build在安装时将分发共享目录中的任何文件复制到分发自动目录。

无论您的分发使用Makefile.PL还是Build.PL,访问数据文件现在都是一个代码问题。以下是我们虚构的模块“Data::File”中删除所有内容的File.pm文件。

package Data::File;
use strict;
use warnings;
use YAML::XS;
use File::Share ':all'; 
    
sub read_data {         
    my $data_location = dist_file('Data-File', 'perl_tiobe.yaml');
    open (my $DATA, '<', $data_location) or die $!;
    my $yaml = do { local $/; <$DATA> };
    my $data = Load $yaml; 
    
    do { ... };
}   
        
1;

这部分代码应该看起来很熟悉。在“read_data”子程序中,我们使用File::Share的“dist_file”函数来获取数据文件的绝对文件路径。“dist_file”函数非常棒:它将在测试期间和模块安装后找到数据文件。在那行之后,我们打开文件句柄并像往常一样处理它。

这种方法比前两种需要更多的工作,但回报也很大:我们能够将数据包含在分发中,并在安装和运行时访问它。我们的代码文件不会因为可能不需要的额外数据而变得拥挤,并且我们不仅限于将数据文件包含在与消费代码文件相同的目录中。甚至可以使用“dist_file”(使用分发)从分发共享数据到另一个。

结论

示例主要关注包含YAML数据,但这些解决方案适用于大多数数据类型。将数据包含在Perl分发中并不像应该的那样容易。然而,通过这里描述的三个解决方案,您应该能够应对典型的场景。

喜欢这篇文章吗?请帮助我们转发,转发它!


本文最初发表在PerlTricks.com

标签

David Farrell

David是一位专业程序员,他经常推文并在博客上关于代码和编程艺术发表文章。

查看他们的文章

反馈

这篇文章有什么问题吗?请通过在GitHub上打开问题或拉取请求来帮助我们。