更佳编程之路: 第七章 -- 顶级控制流和配置_VMware, Unix及操作系统讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  VMware, Unix及操作系统讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3413 | 回复: 0   主题: 更佳编程之路: 第七章 -- 顶级控制流和配置        下一篇 
谁是天蝎
注册用户
等级:大元帅
经验:90210
发帖:106
精华:0
注册:2011-7-21
状态:离线
发送短消息息给谁是天蝎 加好友    发送短消息息给谁是天蝎 发消息
发表于: IP:您无权察看 2011-8-25 16:33:43 | [全部帖] [楼主帖] 楼主

编写 cfperl 解释器的前几个步骤是编写主循环,确定控制流以及采用与 cfengine 配置样式(尽可能)相似的配置样式。

因为 cfperl 是一个小项目,所以自顶向下方法是可行的。我将主循环放在首位,接着按顺序开发解释器周期的每个部分。此外,为了我和他人的利益,我在脚本的结束部分以 POD 形式对主循环编写了文档。

控制流由我选择的解析模块确定。使用 Parse::RecDescent 允许我进行 2 级解析:首先使用通用解析器,然后(通过使用以节为键的文法)使用适合于具体情况的文法或使用缺省文法。

主循环

主 cfperl 循环如下所示:

  1. 模块
  2. 常量
  3. 初始化、全局变量、文法
  4. cfrun 循环

实际上,在模块之前,要做两件事情。第一件事是导言(程序标识、GPL 许可证)。然后,声明 $VERSION 变量,并将它初始化成与 CVS 修订本相同。请注意 $Revision: 1.9 $和 $Id: c10.xml,v 1.9 2002/06/09 23:27:55 lifelogs Exp $ 的使用,它们是在检入时由 CVS 自动填充的。这里引入了 $VERSION,因为(从概念上讲)它是元变量:不是由 cfperl 本身直接使用,但反映了有关 cfperl 的状态信息。

清单 1. cfperl 导言

#!/usr/bin/perl -w
# cfperl.pl: cfengine parser in Perl
# by tzz@iglou.com $Id: c10.xml,v 1.9 2002/06/09 23:27:55 lifelogs Exp $
# ... license omitted ...
my $VERSION = sprintf('%d.%02d', (q$Revision: 1.9 $ =~ /\d+/g));


接下来是模块节。我使用由 Emacs 提供的可折叠方式; # {{{和 # }}} 字符串分别指示节的开始和结束。

清单 2. cfperl 模块

# {{{ modules
                  use Data::Dumper;
                  use English;
                  use strict;
                  use POSIX;
                  use Parse::RecDescent;
                  use Carp;
                  use File::Basename;
                  use AppConfig qw/:expand :argcount/;
                  use IO::File;
                  use Sys::Hostname;
            # }}}
            


接下来是常量节。我用符号名称为每个专门处理的 cfengine 配置节命名,这些符号名称可以在整个 cfperl 源代码中使用,而不仅限于字符串常量。另外,“any”类的名称在这里都被定义成常量,并设置缺省全局配置文件。常量使源代码的维护更为方便;因此请始终尽量使用常量而不是变量。

清单 3. cfperl 常量

# {{{ constants
                  use constant GLOBAL_CONFIG_FILE => '/etc/cfengine/cfengine.conf';
                  use constant ANY_CLASS => 'any';
                  use constant GROUPS_SECTION => 'groups';
                  use constant IMPORT_SECTION => 'import';
                  use constant CONTROL_SECTION => 'control';
                  use constant DEFAULT_SECTION => 'default';
                  use constant CRON_SECTION => 'cron';
            # }}}
            


接下来是初始化、全局变量和文法部分。因为我们将在以后的章节中研究文法,所以我在这里的代码样本中省略它们。

自动刷新输出和使 Data::Dumper 模块输出可读是辅助调试的重要设置。我们将在 配置选项一节中说明 AppConfig 选项。拥有全局 $config 对象将大大有助于管理配置选项。

如果全局变量是标量,则将它们设置成缺省值(避免不明显错误的最佳实践是始终初始化变量 — 但决不要假设它们已

初始化)。cfrun 队列、次序以及类散列将在以后章节中说明。因为它们可以在整个 cfperl 中使用,所以将它们定义为全局性的,以更便于访问。

清单 4. cfperl 初始化和全局变量

# {{{ initialization settings
                  $| = 1; # auto-flush the output
                  $Data::Dumper::Terse = 1; # produce human-readable Data::Dumper output
                  $Data::Dumper::Indent = 0; # produce human-readable Data::Dumper output
                  my $config = AppConfig->new();
                  $config->define(
                  # see the section on configuration options for the full AppConfig definitions
                   );
            # }}}
            # {{{ globals
                              my $current_section = 'control';# the current section while parsing
                              my $current_classes = 'any'; # the current classes while parsing, starts out as 'any'
                              my @cfrun_queue; # the cfrun queue (cfrun atoms, see add_line)
                              my %classes; # the list of defined classes
                              my @cfrun_order; # the defined order of execution
                        # }}}
                        


在进一步初始化已定义的类和处理配置选项(我们将在 配置选项一节中说明配置选项)之后,根据给予 cfperl 的 cfengine 配置调用 process_line()和 cfrun() 函数。注: -exec(或 -e)选项将根据加上 -e 的参数先运行 process_line()和 cfrun()。

清单 5. cfperl 主循环

my $input_line;
while ($input_line = <$config_file>)
{
       chomp $input_line;
       process_line($input_line);
}
cfrun();





控制流

在 cfperl 本身的 POD 节中简要地概述了 cfperl 控制流。

清单 6. cfperl 控制流文档

First, a top-level parser is applied, preprocessing everything into a
cfrun queue (a queue of actions, tagged with a section and some classes).
Second, the 'import', 'control' and 'groups' sections are parsed (first
the imports, then the control and group statements). The actionsequence
(called a cfqueue) is defined.
Third, the remaining statements (everything not processed in the second
step) are processed.


处理这些步骤的函数是:

  • 顶级解析器: parse_line()
  • 导入: load_file()
  • 控制和分组: dispatch()
  • 所有其它节: dispatch()

除了顶级解析外,所有这些步骤都在 cfrun() 内部完成。实际上, cfrun()是真正解释器,而 parse_line() 是用于挑选出节和导入的准备阶段。导入必须在解释之前进行,因为其内容会影响解释。

注:控制流的文档在程序本身内部!它以相当简单的术语说明,有 cfengine 方面经验的人应该会理解。



命令行开关和其它配置选项

应用程序配置有许多 CPAN 模块。我选取了 AppConfig,因为我曾经使用过它,而且因为它可以模拟 cfengine 配置开关的行为。

每个开关都被赋予一个别名。例如, -define和 -D 的意义相同,这需要将 -v用作 -debug 的快捷方式。我本来还可以使用大小写来区分它们。

help( -h)和 input( -i)选项不带参数。debug( -v)、fileread( -f,将其命名为 fileread,因为 AppConfig 有一个内部 file()方法)和 exec( -e)都是具有一个参数的选项。define( -D)是唯一列表选项,所以用户可以写成“ -D Alpha -D Beta”,从而同时定义 Alpha 和 Beta。

我使用 AppConfig args() 方法来读取 @ARGV 中的参数。

清单 7. cfperl 命令行开关定义

my $config = AppConfig->new();
$config->define(
'HELP' => { ARGCOUNT => ARGCOUNT_NONE,
       DEFAULT => 0,
ALIAS => 'h'},
'DEBUG' => { ARGCOUNT => ARGCOUNT_ONE,
DEFAULT => 0,
ALIAS => 'v'},
'DEFINE' => { ARGCOUNT => ARGCOUNT_LIST,
ALIAS => 'D'},
'FILEREAD' => { ARGCOUNT => ARGCOUNT_ONE,
DEFAULT => 0,
ALIAS => 'f' },
'EXEC' => { ARGCOUNT => ARGCOUNT_ONE,
       DEFAULT => 0,
ALIAS => 'e' },
'INPUT' => { ARGCOUNT => ARGCOUNT_NONE,
DEFAULT => 0,
ALIAS => 'i' },
);
# now read the command-line options from @ARGV
out(0, "main: Invalid options passed, ignoring") unless $config->args();


在主循环开始时,事情变得有点儿棘手了。我们必须处理所有的命令行开关。首先,使用 -D 选项定义所有给定的类。

清单 8. cfperl 命令行开关处理:-define 选项

foreach my $class (@{$config->DEFINE})
{
       out(1, "Defining class $class on user request");
$classes{$class} = 1;
}


现在,处理输入文件。我们将 $config_file 定义为 IO::File 对象(由于许多原因,它处理文件数据要比标准的 Perl FILE 句柄可靠得多)。如果 -f选项指定了一个 可读文件,那么我们将 $config_file 设置成该文件。否则,如果给定 -i选项,则将 $config_file 设置成标准的 cfperl 输入。否则,如果给定 -e 选项,那么我们只要处理该选项的参数,运行快速 cfrun(),然后退出。最后,如果所有情况都失败,我们使用在常量节中定义的 GLOBAL_CONFIG_FILE。

cfperl 用户可以使用的复杂选项模拟了可与 cfengine 一起使用的选项,但它们不一样,也不该一样。cfperl 不打算替换 cfengine,只是为它添加功能而已。复制 cfengine 的所有行为和命令行开关是不必要的而且很困难。

清单 9. cfperl 命令行开关处理:文件处理

my $config_file = new IO::File;
if (-r $config->FILEREAD) # we can read the -f argument
{
       $config_file->open('< ' . $config->FILEREAD);
}
elsif ($config->INPUT) # -i means read configuration interactively
{
       $config_file->fdopen(fileno(STDIN),"r");
}
elsif ($config->EXEC) # just run the one line
{
       process_line($config->EXEC) && cfrun();
       exit;
}
else # none of the above, use GLOBAL_CONFIG_FILE
{
       $config_file->open('< ' . GLOBAL_CONFIG_FILE);
}
exit unless $config_file->opened();
# we continue to a parse_line/cfrun loop done on $config_file


下次,我们将完成 cfperl 的代码 — 到时候见。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论