允许更改位置 perl 模块从配置参数加载

Allowing changing location perl modules are loaded from with configuration argument

我目前有一堆 Perl 代码,其中包含类似 configuration.pm 文件的内容,该文件导出大量其他模块正在使用的变量。同一个模块至少使用了一个模块,称为 Foo,我们在 configuration.pm 提供的一些辅助方法中编写了它(它们应该在不同的模块中,但还没有准备好更改它)。

目前,它加载模块时会在文件顶部附近添加类似这样的内容:

Begin{ push @INC, 'hard/coded/directory'}
use Module::Foo;

我正试图摆脱这个硬编码目录。我已经为它添加了一个默认配置文件来读取数据。我将 import 向下移动了一些并用 require 替换了 use ,就像这样......

$script_directory = $config_data_from_file{'script_directory'};
push @inc, $script_directory;
require Module::Foo;

但是,如果我不想使用默认配置文件,我想向 Main.pl 添加命令行参数以指向不同的配置文件。我的问题是所有其他模块都希望 configuration.pm 加载配置数据并在包含它后立即需要 foo 。所以我不能让 configuration.pm 等到 main.pl 准备好后再初始化。我能想到的最接近的是这样的:

package Configuration;

load_config_file('default/file/location');

sub load_config_file($){

   $config_data_from_file = read_file(@_[0]);

   $script_directory = $config_data_from_file{'script_directory'};
   push @inc, $script_directory;
   require Module::Foo;

  #load the rest
}
如果命令行选项更改了配置文件,

并让 Main.pl 调用 load_config_file。

但这是一个问题,原因有二。首先,如果我的默认脚本位置不存在,我在尝试执行第一次导入时仍然会爆炸。其次,我需要 Foo 两次,覆盖它,如果文件之间存在差异,这可能会导致问题。就此而言,应避免将默认值 script_directory 添加到 @INC。

有几种方法可以解决我看到的问题。一种更干净地加载模块的不同版本以替换旧模块的方法,一种使 Foo 延迟它尝试加载直到第一次在文件中使用它的方法,或者一种延迟 $load_config_file 方法的方法直到我阅读配置文件之后。但是,作为一个 perl 新手,我不知道该怎么做,也没有多少运气能在网上找到方法。

我现在实际上可以做到这一点,加载数据的顺序很脆弱,可以做出假设,或者通过重构数十个脚本跳到更多以更快地实现长期解决方案(但我真的很害怕在我有办法在我的计算机上测试代码之前触摸那么多代码)。但是,我问的部分原因是希望学习更多的 Perl 特性,以后可能会觉得有用;如果我不能进行重构,这将如何解决?

如果你想把配置文件作为第一个参数,你可以这样做:

主要脚本:

  #!perl

  BEGIN {
    use Configuration;
  }

  use Module::Foo;
  ... rest of script ...

Configuration.pm:

  package Configuration;

  load_config_file($ARGV[0] || 'default/file/location');

  sub load_config_file($){

     $config_data_from_file = read_file(@_[0]);

     $script_directory = $config_data_from_file;
     push @INC, $script_directory;

  }

我的一般解决方案是在我的 configuration.pm 中查找我的配置文件的 -f 参数,一旦它被加载并在可能的情况下加载配置文件,同时保持 @ARGV 变量不变这样其他人仍然可以解析它。这意味着我们最终解析命令行参数两次(实际上是 3 次),但这并没有造成任何真正的伤害。我强制在任何使用我的 configuration.pm 的模块中预定义 -f 参数,并且有点要求 configuration.pm 成为我们包含的第一个模块,但我认为这是一笔不小的开支。任何使用我们的 configuration.pm 文件作为配置参数的人都应该希望这种行为。

我发现 AppConfig 是处理这个问题的最佳模块。我的解决方案可以在没有它的情况下完成,但 AppConfig 使它更清晰,因为它结合了从配置文件和命令行加载变量的方法。事实上,纯属偶然,我最终添加了直接从命令行修改任何单个变量的功能,如果他们选择我这样做的话。

我的 configuration.pm 看起来有点像命中(根据记忆重写,不准确)

$conf = AppConfig -> new({ 
          GLOBAL=> {
                EXPAND => AppConfig::Expand_Var,
                ARGCOUNT => AppConfig::ARGCOUNT_ONE
        }})

$conf.define("script_dir", {DEFAULT = "/default/location"});
$conf->define("f", {ALIAS ="file|conf_file"});
...other defines here

#read config file if -f arg exists
parse_commandline_args();
$conf->file($conf->conf_file()) if defined $conf->conf_file()

#reread command line so that arguments on it override those in conf file
parse_commandline_args();

#at this point script_dir should be correct so safely include it.
push @INC $conf->script_dir();

sub parse_commandline_args(){

   $copy_of_args = [@ARGV];
   $conf->args($copy_of_args);
}

我的main.pl几乎没有动过。我在模块顶部附近使用 configuration.pm,其他一切正常。我仍然需要检查并重新定义所有使用脚本来要求它的脚本,以便 configuration.pm 有时间在 INC 运行之前更新它,但除此之外其余的都可以正常工作。我现在想在任何地方使用配置文件中的内容 $conf->variable()

parse_commandline_args 很重要。仅使用 $conf->args() 将删除 @ARGV 的内容,使它们对以后的模块不可用,例如我的 main.pl。通过首先复制数组,我们保留原始@ARGV 不变以备后用。

不确定我是否会从头开始推荐这个,感觉 configuration.pm 自动做所有事情的方式不对,但是为了更新我们丑陋的原型以运行足够长的时间来维护它,直到我们资助编写 合适的 版本,我不会在 perl 中做,它会做。