totofugaのブログ

ネットワークとかc言語、perlの話。

Module::Starterから自分用のテンプレートを生成する

Module::Starter

http://search.cpan.org/~xsawyerx/Module-Starter-1.60/lib/Module/Starter.pm

Module::Starterをcpanからインストールするとmodule-starterコマンドが使用できるようになり、

module-starter --modules=Hoge::Fuga --distro=hoge-fuga

とかすると、

hoge-fuga/:
Changes  MANIFEST  Makefile.PL  README  lib  t

hoge-fuga/lib:
Hoge

hoge-fuga/lib/Hoge:
Fuga.pm

hoge-fuga/t:
00.load.t  perlcritic.t  pod-coverage.t  pod.t

のようにモジュール用のテンプレートファイルが作成されます。 便利なのですが、現在のプロジェクトに合わせるのには自分用にいろいろカスタマイズが必要だったので その方法を調べてみました。

module-starterの設定

module-sterterのconfigファイルは ~/.module-starter/confg ファイルで行います。

root@localhost$ cat ~/.module-starter/config 
author: totofuga
email: test@test
plugins: Module::Starter::Simple

author には作成者名 email にはメールアドレスになります。

module-starterコマンド

f:id:totofuga:20130909151147g:plain

module-starterの呼び出すと上記のように Module::Starter::Appのrunが呼び出されます。 Module::Starter::Appのrunはmodule-starterのconfigファイルのplugins項目に書かれているクラスを生成して

の各メソッドを順に呼び出します。

先ほど設定したplugins項目のModule::Starter::Simpleはモジュール呼び出しのベースとなるクラスで、 上の関数がすべて定義されたクラスなのです。

module-starterをテンプレートから読み込むようにする

plugins項目に複数のクラスを書くとそれらを継承したクラスが呼ばれるようになるのを利用して Module::Starter::Simpleを拡張します。

まずはModule::Starter::Plugin::Templateクラスを追加してみます。

http://search.cpan.org/dist/Module-Starter/lib/Module/Starter/Plugin/Template.pm

cpanからModule::Starter::Plugin::Templateを追加してconfigファイルのplugins項目を

plugins: Module::Starter::Simple, Module::Starter::Plugin::Template

のように変更します。 左が継承元、右が継承先になります。

Module::Starter::Plugin::Templateはcreate_distroをオーバーライドして、 pmやtファイルの生成時にrenderを呼び出すように変更するテンプレートメソッドのクラスになっていてそのままでは使用できません。

Module::Starter::Plugin::Templateを使用するためには

  • renderer (出力エンジンを返す)
  • render (出力を行う)
  • templates (使用するテンプレートを返す)

の三つを実装する必要があります。

f:id:totofuga:20130909154721g:plain

templatesを実装するモジュール

Module::Starter::Plugin::DirStoreはtemplatesを実装してありディレクトリからテンプレートを取得するモジュールです。 今回はこれを使用してみます。

http://search.cpan.org/~rjbs/Module-Starter-Plugin-SimpleStore-0.144/lib/Module/Starter/Plugin/DirStore.pm

cpanからインストール後、configファイルのplugins項目に追加して、 さらにtemplate_dir項目を追加することそのディレクトリからテンプレートファイルを読み込むようになります。

plugins: Module::Starter::Simple, Module::Starter::Plugin::Template, Module::Starter::PluginDirStore
template_dir: /root/.module-starter/templates

注意: この時template_dirを~/ではじめるとエラーになるので絶対パスで指定します。

rendererとrenderを実装するモジュール

Module::Starter::Plugin::TT2はTemplateToolKitを使用してrendererとrenderを実装するモジュールです。 今回はこれを使用してみます。

http://search.cpan.org/~rjbs/Module-Starter-Plugin-TT2-0.125/lib/Module/Starter/Plugin/TT2.pm

cpanからインストール後、configファイルのplugins項目に追加します。 さらにそのままだとwarningが出るのでtemplate_parmsもundefで設定しておきます。(template_parmsはTemplate->newに渡す引数でevalされて使用されます。)

plugins: Module::Starter::Simple, Module::Starter::Plugin::Template, Module::Starter::Plugin::DirStore, Module::Starter::Plugin::TT2
template_dir: /root/.module-starter/templates
template_parms: undef

f:id:totofuga:20130909210118g:plain

テンプレートファイルを作成する

作成可能な主なファイルは以下です。

  • MANIFEST(files)
  • Makefile.PL(main_module, main_pm_file)
  • README(build_instructions)
  • Module.pm(module, rtname)
  • .tで終わるテストファイル(modules)

カッコで指定した値はファイル内で使用できる変数です。 ファイルを作成しないと空ファイルとして作成され、 .tで終わるファイルを作成するとt/下のフォルダにそのファイルが作成されます。

今回は Module.pm を定義してみましょう。

~/.module-starter/templates/にModule.pmを作成して以下の内容を書き込みます。

# this is a module.pm
# module: [% module %]
# rtname: [% rtname %]
# author: [% self.author %]
# hoge:   [% self.hoge %]

先ほど書いたようにModule.pmはmoduel, rtnameという変数が使用できます。 その他にself.で始めるとconfigファイルに書かれた値を参照できます。 hogeを参照しているので、configファイルに

author: test_user
hoge: fuga

を追加しておきます。

そして

module-starter --distro=hoge-fuga --module=Hoge::Fuga

を実行するとhoge-fuga/lib/Hoge/Fuga.pmに

# this is a module.pm
# module: Hoge::Fuga
# rtname: hoge-fuga
# author: test_user
# hoge:   fuga

と展開され自分用のテンプレートが作成できるようになります。

参考にさせてもらったサイト

外部プログラム実行時にエラー出力と終了ステータスも取得する

外部プログラムを実行した時に出力される値には

があります。

http://codepad.org/duNbERYM

use strict;
use warnings;
 
print "std out print";
warn "std err print\n";
 
exit(99);

のようなテストプログラムのすべての値を取得したいとします。

標準出力の取得

perlには外部プログラムを実行する便利な構文としてバッククォートがあるので

my $std_output = `output.pl`;

のように使用すれば標準出力はすぐに取得することができます。

標準エラー出力の取得

先ほどのプログラムでは標準エラー出力が親プロセスと同じになってしまうため 画面にそのまま表示されてしまいます。

そこで標準エラー出力をキャプチャしてみました。

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw(say);

my $std_err;
my $std_out;
{
    open my $err_handle, '>', \$std_err or die "open error";
    local *STDERR = *$err_handle;

    $std_out = `./print.pl`;
}

say "std-out: $std_out";
say "std-err: $std_err";

しかし結果は、

std err print
std-out: std out print
Use of uninitialized value $std_err in concatenation (.) or string at /root/abc/test.pl line 16.
std-err: 

どうやらSTDERRは子に反映されないようです。

open3を使用する

調べてみるとCPANIPC::Open3と言うものがあり、 標準出力と標準エラー出力を分けてとれるようになるみたいです。

ということで簡単に書いてみたのですが、

#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Open3;
use File::Spec;
use feature qw(say);
use Symbol;

my ($stdout_handle, $stderr_handle) = (gensym, gensym);
my $pid = open3(undef, $stdout_handle, $stderr_handle, './print.pl') or die "error $?";

waitpid($pid, 1);

my $std_out = <$stdout_handle>;
my $std_err = <$stderr_handle>;

say "std-out: $std_out";
say "std-err: $std_err";

しかし上記のコードだと http://d.hatena.ne.jp/kazuhooku/20100813/1281690025で kazuhookuさんが言っているように、デッドロックを起こしてしまいます。

修正してみると

#!/usr/bin/env perl
use strict;
use warnings;
use IPC::Open3;
use File::Spec;
use feature qw(say);
use Symbol;
use IO::Select;

my ($stdout_handle, $stderr_handle) = (gensym, gensym);
my $pid = open3(undef, $stdout_handle, $stderr_handle, './print.pl') or die "error $?";

my $print_selector = IO::Select->new($stdout_handle, $stderr_handle);

my $std_out;
my $std_err;
while ( my @redy = $print_selector->can_read ) {
    foreach my $handle ( @redy ) {
        if ($handle == $stdout_handle ) {
            $std_out = <$stdout_handle>;
        } else {
            $std_err = <$stderr_handle>;
        }

        $print_selector->remove($handle) if eof($handle);
    }
}

waitpid($pid, 0);
my $exit_code = $? >> 8;

say "std-out:   $std_out";
say "std-err:   $std_err";
say "exit_code: $exit_code";

結果

std-out:   std out print
std-err:   std err print
exit_code: 99

これで、デッドロックされずにちゃんと取得できるようになります。

参考にさせてもらったサイト

Perlクックブック(Volume2)のレシピ16-9にこの辺の話が詳しく載っています。