On Module::New
昨日のSoozy Con4で発表したスライド。Text::Hatenaで加工していたのでここに(ほとんど)そのまま貼り付けておきます。
2008/01/31追記:
最新版はこちらから。近日中にアップの予定はありますが、まだCPANにはあがっていません。
http://svn.coderepos.org/share/lang/perl/Module-New/trunk
Soozy Conf #4
Jan. 27, 2008
Kenichi Ishigaki (charsbar)
Module::Newとは?
こんなの。
11:44 <t********> o 11:45 <t********> なんか妙なのが。 11:45 <t********> pmsetup みたいなやつっぽい 11:45 <m*******> 巨大だなぁw
pmsetupというより、Module::Starterを拡張したもの。
Module::Newの使い方
> module_new dist Dist-Name > cd Dist-Name/trunk > module_new file Dist::Name::Plugin > module_new file t/dist_test.t --edit --type=Test > module_new manifest --force
実際には module_new というコマンドを new という名前にリネームして使っている。
Module::Newを使うと
- モジュールのひな形をつくれる
- テストやスクリプトのひな形もつくれる
この辺はあたりまえ。
- モジュールをあとから追加できる
- 下の方のディレクトリにいても大丈夫
- エディタを起動してくれる
- MANIFESTもアップデートしてくれる
- テストを実行できたりもする(まだ公開してない)
大事なのはアフターサービス。
Module::Newの実装(1)
コマンドラインから与えた引数に対して、こんな感じのレシピを実行している。
package Module::New::Recipe::Dist; use strict; use warnings; use Module::New::Command; available_options qw( make=s shipit test|t=s@ edit|e ); flow { set_distname; create_distdir; create_maketool; create_general_files; create_tests; shipit( optional => 1 ); create_files(qw( MainModule )); create_manifest; edit_mainfile( optional => 1 ); }; 1;
Module::Newの実装(2)
複数の引数を与えてループさせるレシピはこんな感じ。
package Module::New::Recipe::File; use strict; use warnings; use Module::New::Command; available_options qw( edit|e type|t=s ); flow { guess_root; loop { set_file; create_files('{ANY_TYPE}'); edit_mainfile( optional => 1 ); }; create_manifest; }; 1;
Module::Newの実装(3)
レシピを表現するのに使うコマンドの例。
sub create_maketool (;$) { my $type = shift; _register { my $self = shift; $type ||= $self->config('make') || 'MakeMaker'; $type = 'ModuleInstall' if $type eq 'MI'; $type = 'ModuleBuild' if $type eq 'MB'; $type = 'MakeMaker' if $type eq 'EUMM'; $self->context->add_files( $type ); }}
Module::Newの実装(4)
コマンドはグローバルなキューに登録して、最後にまとめて実行。
sub _register (&) { my $caller = caller(1); { no strict 'refs'; push @{ ${"$caller\::queue"} ||= [] }, shift; } } sub flow (&) { my $flow = shift; my $caller = caller; _register { my ($self, @args) = @_; { no strict 'refs'; my $queue = []; local ${"$caller\::queue"} = $queue; $flow->(); # let internal functions into the local queue foreach my $func ( @{ $queue } ) { $func->( $self, @args ); } } }}
Module::Newを拡張するときは(1)
まずは自前の名前空間を用意する。
package MyNew; use strict; use warnings; use base qw( Module::New ); 1;
Module::Newを拡張するときは(2)
その下に追加したいコマンドやレシピを用意して
package MyNew::Command::MyCommand; use strict; use warnings; use Exporter::Lite; use Module::New::Command::Util; our @EXPORT = qw( some_command ); sub some_command () { _register{ .... }} 1;
Module::Newを拡張するときは(3)
package MyNew::Recipe::MyRecipe; use strict; use warnings; use Module::New::Command; use MyNew::Command::MyCommand; flow { some_command; }; 1;
Module::Newを拡張するときは(4)
最後に起動スクリプトで自前の名前空間からdispatchすればOK。
#!perl use strict; use warnings; use MyNew; MyNew->dispatch;
自分で上書きしない限り、Module::Newのレシピもそのまま使える。
Module::Newがこんな風になっている理由(1)
アフターサービス重要というのももちろんありますが。
- Pluggableにすればするほど定型のPODを書くのがだるくなる
- DSLっぽくしてしまえばPODを書かなくてもすむ
というのが最大の理由。
Module::Newがこんな風になっている理由(2)
PODを書かずにすませる方法はいくつかある。
- メソッド名をアンダースコアではじめる
- Pod::Coverageが無視する予約語を使う
- メソッドを動的に生成する
ただし、やみくもに省略するのはよくない。
- 一般的にPODが充実していないモジュールは評価されない
DBICのスキーマみたいに__PACKAGE__を使いまくる手もある。
でも、JDBIのスキーマと比べてどっちがいいか、という個人の趣味。
Module::Newがこんな風になっている理由(3)
ちなみにPODというのは
なので、常人の三倍のスピードを誇るニュータイプが相手なら気にしなくてもいいです。
- 脚なんて飾りです。偉い人にはそれがわからんのです
Module::Newの問題点
いろいろあるけど
- DSLっぽくつくるのであれば、@EXPORT(_OK)の処理はきちんとするべし
- たいてい use base が使えなくなるので、Exporter(::Lite) などでフォローできるようにした方がいい
Module::Newの今後
- スライドドリブン開発
- POD
- テスト
- 依存まわりとか更新履歴とか