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
まあ、やりくちとしても強引過ぎる感じがしますね。
STDOUT キャプチャすればよさげ
いたって普通の結論ですが、今回はターゲットが STDOUT に print されるものだけだったので、STDOUT をキャプチャして、フィルタした結果を返せば実現できそう。
ふむふむ。*STDOUT
をどっかむけて確保しといて、フィルタしつつあとでごにょごにょ出力、、Capture::Tiny 使ってテストでよくやるようなことをやればいいかなと思ったけど、今回は前述の通り、別モジュールのメソッドを上書いているのでちょっとやりにくい。コードブロックを囲むのが難しい。
というわけで CPAN を探してみると、その名もずばり IO::Capture の IO::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 出力は実現できました。おわり。