2013/06/26

print 出力をフィルタしてみる #perl

perl の print 関数の出力をフィルタしたい。

Benchmark::Confirm というモジュールを書いていたのですが、これは Benchmark のメソッドを一部上書いて欲しい機能を実装している。で、その出力をちょっとフィルタしたいという要件があり、とはいえ、元の Benchmark にある print の全てで渡す前に何かするというのはちょっと難しい。というかやってられない。

print 関数をオーバライドしちゃえば

やりたいことは、print される文字列の前に # を挿入して valid TAP な出力にしたい感じでした。

ばっくりと STDOUT 全部そうなって良さそうだったので、最初は print 関数をオーバーライドしてしまえばいいと思ったけど、それはダメでした。

$ perl -le '*CORE::GLOBAL::print = sub { CORE::print "aki" }; print "foo";'
foo

理由は print がサブルーチンプロトタイプで表現できない関数だから、という感じ。

$ perl -le 'my $which = (defined prototype("CORE::print")) ? "can override" : "impossible"; print $which;'
impossible

gfxさんのブログに説明があります

まあ、やりくちとしても強引過ぎる感じがしますね。

STDOUT キャプチャすればよさげ

いたって普通の結論ですが、今回はターゲットが STDOUT に print されるものだけだったので、STDOUT をキャプチャして、フィルタした結果を返せば実現できそう。 ふむふむ。*STDOUT をどっかむけて確保しといて、フィルタしつつあとでごにょごにょ出力、、Capture::Tiny 使ってテストでよくやるようなことをやればいいかなと思ったけど、今回は前述の通り、別モジュールのメソッドを上書いているのでちょっとやりにくい。コードブロックを囲むのが難しい。

というわけで CPAN を探してみると、その名もずばり IO::CaptureIO::Capture::Stdout がありました。

以下のような感じで start / stop の間の STDOUT をキャプチャしといて、あとで read してごにょごにょできる。

use IO::Capture::Stdout;

my $capture = IO::Capture::Stdout->new;

$capture->start;

print STDOUT "Foo\n";
print STDOUT "Bar\n";

$capture->stop;

while (my $line = $capture->read) {
    print "$line";
}

かくして Benchmark::Confirm 0.04 の TAP 出力は実現できました。おわり。

サイト内検索