Perl Tips

13 January 2006


查找所用模块的路径

use Data::Dumper;
print $INC{'Data/Dumper.pm'};
C:/usr/lib/Data/Dumper.pm
use %INC;

制作 ppm 专用的 ppd 文件

E:\Fayland\CPAN> perl Build.PL
E:\Fayland\CPAN> perl Build
E:\Fayland\CPAN> perl Build test
E:\Fayland\CPAN> perl Build ppmdist
会升成一个类如 Lingua-Han-Stroke.ppd 和 PPM-Lingua-Han-Stroke-0.04.tar.gz 文件。
他人安装的话使用 ppm install Lingua-Han-Stroke.ppd 就可以了,把这两个文件给他传过去。
这对 Win32 下没有 nmake 的人有点用。

Plan for 2006: hang myself on the tree of Perl

30 December 2005


The year of 2006 would be a turn in the course of events.
I'm going to receive my bachelor's degree in advertisement next June, and I'll attend the post-graduate entrance exam in January 14th-15th.
So the future seems so unclear that I'm not sure what will I do and where will I go. Maybe go to work related with Perl, maybe stay at home doing SOHO work concerned with internet and Perl, or maybe continue my study in Tsinghua university, which I like best.

Although there is so many possibilities, yet one thing is certain: keep abreast with Perl.
There is no plan for a new language such as Python, Ruby, Java or C/C++/C#.
Ya, maybe a functional language like ML or Haskell, I'm not sure.
The Perl world is so immense that I still need to learn a lot.

    The planned stuff are as follows:
  • Perl 6. yes, I think Autrijus will release an official Perl 6 implement - Pugs, it's worth time to learn Perl 6 and play with it.
  • Parrot. the PIR language. I like the idea of parrot.
  • modperl. It's still popular and quicker than what you expected. I hate to consider speed when I write perl code.
  • POE. I know you guys are tired of the threads thing in Perl, me too. and event-driven is great.
  • take part in more open-source projects. It's a goold place to practise and improve my skill.
  • use Catalyst to write a script of Forum and other script. more than 3,000 lines, I suppose.
  • part time jobs are welcome. I do need more money to live better.
Woo, so many things. I just wonder whether I get enough time to dig the Perl treasure.
Ya, it's my plan. I'll do my best to adhere to my plan.

HTML::Parser 简单解释

29 December 2005


HTML::Parser 是一个非常强大的用于处理 html 解析的模块。

HTML::Parser 的文档没有一个完整的例子。所以我就把下面的我写在 ShellWeb 里的例子摘取出来,并简单的解释一下。

use HTML::Parser;

my $parser = HTML::Parser->new( api_version => 3,
   start_h => [\&start, "self, tagname, attr, text"],
   text_h => [\&text, "self, dtext"],
   end_h => [\&end, "self, tagname"],
   ignore_elements => [qw(script style)],
);

$parser->parse($content);

new 用于建立实例,api_version => 3 用于指定版本,因为 HTML::Parser 还有老的版本 2,可能以后还会有新的版本。而不同的版本有不同的 API, 所以指定这个是必须的。
start_h, text_h, end_h 是三个事件处理过程。分别对应标签的开始,文字(就是没有标签的地方但不包括注释等),标签的关闭。

其实完整的事件处理包括 text, start, end, declaration, comment, process, start_document, end_document 和 default.
一般用到的只有 text start end.

    指定事件所对应的处理方法和参数有如下几种方法:
  • 第一种就是如上面所看到的,在事件后面加上 _h, 如 text_h, start_h 这样。
  • 第二种也是在 new 里做为参数。
    $p = HTML::Parser->new(api_version => 3,
    handlers => { text => [\@array, "event,text"],
    comment => [\@array, "event,text"],
    });
    用 handlers 来定义。
  • 第三种是在 new 里不定义,在后面用 handler 来定义。
    $p = HTML::Parser->new(...);
    $p->handler(start => \&start, 'attr, attrseq, text' );
这三者接受参数的方法是一样的。第一个为处理方法,第二个为参数。
完整的参数参考 perldoc HTML::Parser
最最常用的为 self, tagname, attr, text
不过注意的是不同的事件有不同的参数限制。如果 start 事件可以有 attr 但是 end 事件就没有 attr 了。(因为结束标签里是不会有属性的。)

如最开始的例子中我们使用了 start_h => [\&start, "self, tagname, attr, text"],
那么接下来我们就定义我们自己的 start 子程序:

sub start {
   my ($self, $tag, $attr, $text) = @_;
   if ($tag eq 'img') {
       $buffer->insert($iter, "[IMG - $attr->{src}]");
   } elsif ($tag eq 'pre') {
       $self->{'_pre'} = 1;
   } elsif ($tag eq 'title') {
       $self->{'_tilte'} = 1;
   } else {
       $buffer->insert($iter, $text);
   }
}
start 子程序的第一行就是 start_h => [\&start, "self, tagname, attr, text"], 后面所对应的参数。分别对照为 self, 标签名(如 a/img 等),属性(这是一个散列,比如 img 必定有 $attr->{src}),text 指的是整个标签的完整原始内容。
看起来就是这么简单。接下来定义 text end 子程序:
sub text {
   my ($self, $text) = @_;
   $text =~ s/\r\n//sg unless ( defined($self->{'_pre'}) );
   if ( defined($self->{'_tilte'}) ) {
       $window->set_title("$text - ShellWeb");
   } else {
       $buffer->insert($iter, $text);
   }
}

sub end {
   my ($self, $tag) = @_;
   if ($tag eq 'li') {
       $buffer->insert($iter, "\n");
   } elsif ($tag eq 'p') {
       $buffer->insert($iter, "\n");
   }
   $self->{'_tilte'} = undef if ($tag eq 'title');
   $self->{'_pre'} = undef if ($tag eq 'pre');
}

这个完整的程序中间我们加了一点点小小的技巧。因为 text 事件是不知道这个文本外面的标签名的(也就是 text_h 没有 tagname 参数)。
这样我们必须在 title/pre 标签的开始时用 $self->{'_title'} 保存它开始了,在 end 时将它释放。
这样我们就在 text 事件中知道刚文本是不是包围在 title 或 pre 里面。

上面就是大致的 HTML::Parser 分析过程。最重要的是实践,自己设定个结果,然后用 HTML::Parser 实现它。
实现不了出了问题看 perldoc HTML::Parser 或者给我发 mail. ;-)


用 IE 来浏览 perldoc

28 December 2005


perldoc 黑忽忽的界面浏览起来不是很方便。我一向习惯用浏览器来浏览 perldoc, 但当我安装了很多模块的时候,perldoc 的 TOC/Table Of Content 将变得很长,找一个想要的模块将要拉很长的浏览器,比较不方便。
于是写了一个简单的文件用 IE 浏览器打开模块 html 文件。
#!/usr/bin/perl
use strict;
use Config;
use File::Spec;

my ($module) = @ARGV;

die 'not a module' unless ($module =~ /^[\w\:]+$/);

# determine it weather it is in /lib or /site/lib
my $file = File::Spec->catfile($Config{installhtmldir}, 'lib', split(/\:+/, $module)) . '.html';
$file = File::Spec->catfile($Config{installhtmldir}, 'site', 'lib', split(/\:+/, $module)) . '.html' unless (-e $file);
# if not in /lib and site/lib, check the lib/Pod, for such as perlfunc
$file = File::Spec->catfile($Config{installhtmldir}, 'lib', 'Pod', split(/\:+/, $module)) . '.html' unless (-e $file);
# and /bin
$file = File::Spec->catfile($Config{installhtmldir}, 'bin', split(/\:+/, $module)) . '.html' unless (-e $file);
die 'no such html' unless (-e $file);

`"C:/Program Files/Internet Explorer/IEXPLORE.EXE" $file`;

再用 pl2bat 将其转化为 bat 文件
pl2bat pd.pl
最后将这个 bat 文件拖进某 PATH 目录下就可以了。
这下我们可以用
pd Module::Build
来打开 Module::Build 的 html 文件。

写模块给 CPAN 要注意的地方

21 December 2005


Perl 最让其他语言的人嫉妒的地方就是 CPAN. 有了 CPAN 我们几乎能找到所需要的任何东西。
在索取的同时,我们也应当力所能及地向 CPAN 贡献点什么。
如果你不知道怎么向 CPAN 写一个 module, 你可以参与让自己做为一个测试者.
如果你希望向 CPAN 贡献某个模块,那就再好不过了。

    在贡献模块之前,或许你应该注意下面的东西:(当然,这不是权威的说法)
  • 写一个模块之前,永远先在 http://search.cpan.org/ 上搜索,很有可能就有某人先你而写了这个模块。
  • 模块的命名。搜索该模块,先确定没有其他人使用了该名字。注意 CPAN 的命名还是有其规则的。
    比如语言方面的一般以 Lingua:: 开头,时间方面有关日期的用 Date:: 时间的用 Time::
    我最初写一个身份证验证模块的时候,把它命名为 'China::IdentityCard::Validate', 虽然这可能很明显。但 Adam Kennedy 告诉我,一般不以 China 这种国家开头来命名一个模块。后来在他的建议下,我才改为了 Business::CN::IdentityCard
    一般你要取一个模块名的时候,先搜索类似的模块,看看它们是怎么命名的。或者去 comp.lang.perl.modules 新闻组上咨询意见比较妥当。
  • 一般向 CPAN 贡献的单模块中有两种。一种是用 new 来构建 OO 另一种是用 Exporter 来导出某些函数。
    • 写 OO 模块之前,第一件事就是必须确定下你那模块的 API 接口。因为目前的 Perl 5 不支持 use Module-0.01 这样专门指定版本号的做法(Perl 6 中将可以 use 模块+版本号+作者,这样就不会冲突了)。因为 Perl 5 的这个缺陷,所以你如果经常改变你模块的 API 的时候,别人将会怨恨你的。(比如我昨天使用你的模块可以导出 $baby->kiss('u'), 你更新的模块把 kissme 方法去掉了或者传递参数数目改变了,那当我这程序就 broken 了。)
      所以一般牢记,先一个 OO 模块之前,定义你自己的 API 接口,然后尽量不改变它。
    • 如果你是用 Exporter 导入函数的话,最好使用 @EXPORT_OK 而不是 @EXPORT
      @EXPORT 将你的函数导出后很可能会跟程序里定义的某函数冲突(当然我们可以用 use Module (); 来避免冲突)。
      而 @EXPORT_OK 只有在指定后才导出。这样就不会有任何明显的冲突。
  • 写模块时永远用在前面加 use strict; 或许还有 use warnings;
    use strict; 可以让避免模块里的变量与程序里的冲突。跟这模块相邻的一般都使用 use vars qw/$VERSION @EXPORT @EXPORT_OK/;
上面就是我写模块过程中所碰到的误区或经历。Hope this helps. Thanks.

检测中国的假期模块

20 December 2005


前几天看 use.perl 的时候看到 jonasbn 写了个 journal: Date::Holidays::* - coming to your country?.
觉得我也可以参加,就琢磨着写了个 Date::Holidays::CN

perldoc

目前应该可以在 CPAN 上找到。找不到的话可以访问 http://www.fayland.org/CPAN/
use Date::Holidays::CN;

my ($year, $month, $day) = (localtime)[ 5, 4, 3 ];
$year += 1900;
$month += 1;
if (my $holidayname = is_cn_holiday( $year, $month, $day )) {
print "这是个 $holidayname";
}

my $h = cn_holidays($year);
printf "10 月 1 日是 '%s'\n", $h->{'1001'};

上面这一段代码是为了符合 Date::Holidays 的 API 而写的。
# suggested
use Date::Holidays::CN qw/is_cn_solar_holiday is_cn_lunar_holiday/;
my $holidayname = is_cn_solar_holiday( 2005, 10, 1 ); # $day = '国庆节'
my $is_holiday = is_cn_lunar_holiday( 2005, 9, 18 ); # $day = '中秋节'
这一段我比较推荐。因为中国的特殊国情。:-)

瑕疵

目前 cn_holidays 里没有阴历的日子。打算下一版本加进去。
另外因为采用了 DateTime::Calendar::Chinese 所以这速度实在是有点不可忍受。不过我找不到另外的处理阴历的模块,只好忍忍了。

过程

在写的过程中碰到了很奇怪的问题。这问题我发到了 PerlChina/BBS 和 PerlChina/Maillist 上了。
后来用 from_object 代替了 from_epoch, 没想到就搞定了。
my $dt2 = DateTime->new(
   year => $year,
   month => $month,
   day => $day,
   hour => 0,
   minute => 0,
   second => 0,
   nanosecond => 500000000,
   time_zone => 'Asia/Shanghai',
);
# ouch! the DateTime::Calendar::Chinese is a bit too slow!
my $dt = DateTime::Calendar::Chinese->from_object(object => $dt2);
return $LUNAR->{sprintf "%02d%02d", $dt->month, $dt->day};
如果诸位以后有机会用到这 DateTime::Calendar::Chinese 模块的话,小心用 from_object 可能比较好。

Enjoy!

如果有任何问题,可以联系我。比如说要增加某一节日。:->
my mail: fayland at gmail.com

modperl Filter Part1

18 December 2005


我写过 Template builtin FiltersTemplate customized Filters,这是 TT 的过滤器。
modperl 也有它自己的过滤器。过滤器的含义都是一样的,接受一些数据,改变它,然后返回这些数据。
这在你不能对原始数据做一些改变时特别有用。
我的打算是先介绍 modperl 的内置案例,然后自己写几个 filter.

首先 modperl 的 filter 分为两种:PerlInputFilterHandler 和 PerlOutputFilterHandler 。
我先用官方文档的 Input and Output Filters 来解释一下 filter 的一般写法。

#file:MyApache2/FilterObfuscate.pm
#--------------------------------
package MyApache2::FilterObfuscate;

use strict;
use warnings;
use Apache2::Filter ();
use Apache2::RequestRec ();
use APR::Table ();
use Apache2::Const -compile => qw(OK);

use constant BUFF_LEN => 1024;

sub handler {
   my $f = shift;

   unless ($f->ctx) {
       $f->r->headers_out->unset('Content-Length');
       $f->ctx(1);
   }

   while ($f->read(my $buffer, BUFF_LEN)) {
       $buffer =~ s/[\r\n]//g;
       $f->print($buffer);
   }

   return Apache2::Const::OK;
}
1;

这个过滤器是将文件里的 \r\n 换行去掉。实际上一般你要去掉 \r\n 的时候要注意如 pre 这样的标签时必须将 \r\n 转为 br 或者不去掉。这里我们只是简单的写一下,没什么实际意义。
应用的话:
<Files ~ "\.html">
   PerlOutputFilterHandler MyApache2::FilterObfuscate
</Files>
对于所有的 html 使用此过滤器。
    下面解释下:
  • my $f = shift; 这里是用 $f 而不是 $r 是因为这里传进来的是 filter 对象而不是 request 对象。
  • $f->ctx 用于设置 filter 的上下文变量。它在每一个请求时初始化,请求结束时 undef 掉。如果有多个 filter 的话,它可以在这几个 filter 共享一些变量。
    unless ($f->ctx) {
       $f->r->headers_out->unset('Content-Length');
       $f->ctx(1);
    }
    第一行测试是否已经设置了 ctx, 如果没有设置的话,说明是第一次访问,因为我们将在接下来对内容做一些缩水(去掉了换行),所以必须重新设置 Content-Length, 否则的话浏览器将期待更多的内容,这样会出错。当 unset Content-Length 后,我们将 ctx 设置为 1. 这样下次访问就不需要重新 unset Content-Length 了。
    关于 $f->ctx 的更多解释,查看 Apache2::Filter
  • while ($f->read(my $buffer, BUFF_LEN)) {
    read 用于读进 BUFF_LEN 长度的内容。设置 BUFF_LEN 是怕内容太大,一次读入可能会死机。这里的 BUFF_LEN 已经用
    use constant BUFF_LEN => 1024; 设置过了。
    这个和处理 CGI 上传文件是相似的。
  • $buffer =~ s/[\r\n]//g;$f->print($buffer); 做一些改变,然后输出它。
  • return Apache2::Const::OK; 返回值。这里的返回值可以是 Apache2::Const::OK 或者是 Apache2::Const::DECLINED。二者的区别可以查看 modperl 服务器的运行阶段和句柄

一次请求中调用了多次 filter 句柄

在继续之前,我们得学习下 Apache 处理输出的内在性质。
Apache 并不是将所有的内容存在一个块中,而是将内容分为很多个 buckets,然后用一个所谓的 Bucket Brigades 连起来。详细的查看官方文档,Bucket Brigades. 我也不是很清楚。
诸位只需要知道在一次请求中,我们会调用多次同一个 filter 句柄就可以了。
这就是为什么我们要设置 $f->ctx(1); 的原因。
如果你想知道到底调用了多少次该句柄的话,可以这么写 handler:
sub handler {
my $f = shift;

my $ctx = $f->ctx;
$ctx->{invoked}++;
$f->ctx($ctx);
warn "filter was invoked $ctx->{invoked} times\n";

return Apache2::Const::DECLINED;
}

$f->ctx->{invoked} 来纪录调用了多少次,每调用一次增加一。
先读取 my $ctx = $f->ctx; 再增加 $ctx->{invoked}++; 最后赋值纪录回去 $f->ctx($ctx);
这就是 $f->ctx 的一般用法。

$f->seen_eos

我们可以用 $f->ctx 来测试是否是第一次访问,同时我们可以用 $f->seen_eos 来测试是否是最后一次访问该过滤器句柄。可能的代码如下:
if ($f->seen_eos) {
finalize($f);
}
自定义子过程 finalize 用于处理一些结尾工作。

Stay stun

See u next time!

Lingua::Han::Utils 和 Encode::Guess

17 December 2005


首先介绍下 Encode::Guess 的用途。
我们知道一个文件有很多种编码,最常见的就是 ASCII 和 utf8
如果你从外面读入一个文件,想把它 decode 时却不知道该指定何种编码时非常有用。
一般的代码是:
use Encode;
use Encode::Guess qw/euc-cn/;

my $word = "直到";
my $enc = Encode::Guess->guess($word);
die $! unless ref($enc);
print $enc->name;

如果该代码文件是 utf8 模式下编辑时,它输出的是 utf8; 不在 Unicode Editing 模式下很可能输出 euc-cn
然后你要解码的话就可以指定它的编码了:
$word = decode($enc->name, $word);
可是今天发现 Encode::Guess 不太美好的地方。
当我测试 $word = "直到" 时,在 utf8 和非 Unicode Editing 时都解码正常。
但测试 $word = "直" 时,在非 Unicode Editing 时却出错,它没有猜测成功。

我本来在 Lingua::Han::Utils 里就是用 die $! unless ref($enc); 的,所以测试 Unihan_value("直") 时会出错。
后来想想,把它改成了:

my $encoding;
unless (ref($enc)) {
   $encoding = 'euc-cn'; # use 'enc-cn' by default
} else {
   $encoding = $enc->name;
}
$word = decode($encoding, $word);
默认没有猜测成功的话就指定该编码为 enc-cn.
因为我认为猜测 utf8 总是会成功的,不成功的话就应该是 enc-cn.
试了下 Unihan_value("直") 一切 OK!

:-)


Catalyst Advent Calendar 中文版

15 December 2005


Catalyst 是个非常火爆的 Perl MVC framework.

chunzi 兄在今天 launch 一个新的 project -- Catalyst Advent Calendar 中文版, 我们使用 Subversion 来协作翻译 Catalyst Advent Calendar.

不过就我个人的感觉而言,Catalyst Advent Calendar 太短资料太少了。就匆匆而过很难得到一些很深入的理解。
就像我刚翻译的 Day 9 YAML! 一样,初学者很难跟着这个 tip 来应用。而 Catalyst 专家早就知道这个 tip 了。市场范围比较小。我更倾向于写一篇完整的文章来详细介绍这个过程。

不过 Advent 的作用本来就是提一下,让你认识到你可以这么做。

我非常喜欢看以前的 Perl Advent ,它能让我认识到很多我从来没用过的非常棒的模块。可惜这年的似乎没了。

any way, it's a nice work.


[DRAFT]如何处理 Perl 中的时间

13 December 2005


如果你能比较流畅地阅读英文,可以试着看:
http://www.perl.com/lpt/a/2003/03/13/datetime.html

我只翻译了一些我认为应该知道的内容。

基础概念

  • UTC/GMT/Z
    UTC 是 Coordinated Universal Time 的缩写
    GMT 是 Greenwich Mean Time 的缩写。格林威治时间。
    Z 是美国军方所用的。所以你如果在时间里看到里面有个 Z 字,比如 2005-12-13T14:59:37Z 就说明它是个 GMT 时间。
    Perl 中的 gmtime 就是属于这方面的。
  • 与之相对的是 Local Time, Perl 中对应的是 localtime
    为什么有 gmtime 和 localtime 这是因为地球是圆的和还有太阳的关系。在英国中午十二点在中国并不是中午十二点。
    这就涉及到另外一个概念。Time Zone 时区。更复杂的是时区的加加减减不是固定的。
    其中还涉及到 Daylight Saving Time,在 Daylight Saving Time 的影响下某地相对英国可能是相差五个小时,而没有这个影响的话只有四个小时。
    当然我们不用管这个东西,我地理也学得不是很好。学得好的同学已经把这个搞定了,我直接拿来用就是了。(不要怪我不好学,实在是没兴趣再去学习一次地理了。)
  • 上面的是现实中的问题,接下来是电脑的问题了。英文中涉及到的 epoch 的意思经常是从某一时间开始算起的秒数。
    比如 Unix 系统下的 epoch 就是从 1970 年一月一日的 GMT 时间开始算秒。但是并不是所有的系统都是从这个时间开始算起的。
    Perl 中获取这个 epoch 我们用 time 函数就可以了。
    另外我们还有个电脑问题。因为现在我们大部分人都在用 32 位的处理器,把 32 位的最大值做为秒,然后换算一下也只有 136 年。所以现在的大部分 Unix 系统都只能处理 1902-2038 这个时间段。
    这就是所谓的 epoch 问题。有些模块是有这个问题的(比如 Class::Date ),而有些模块是没这个问题的(比如 Date::Simple )。如果你发现某个模块处理不了这个时间段外的年,那就说明是有这个问题的。写代码的时候要小心一点。
    因为 epoch 有这些问题,所以一般而言最好不要用这个来保存你的时间。(除非不得已)
  • 日历。一般我们都使用阳历,即所谓的 The Gregorian Calendar。而作为中国人还有阴历,The Chinese Calendar。还有希伯来历等等。