http://www.perlmonks.org?node_id=27423


in reply to Filehandle Filter

I like this, a lot. It could be a CPAN module if someone (push me and I'll do it) provided a mechanism where you could specify the output. Here's how I got around the tie-untie reference problem:
package Filter::Handle::Tie; use vars qw/@ISA/; use Tie::Handle; @ISA = qw/Tie::Handle/; sub TIEHANDLE { my $class = shift; my $fh = shift; bless { fh => $fh }, $class; } sub PRINT { my $self = shift; my $fh = *{ $self->{fh} }; my($file, $line) = (caller)[1,2]; print $fh sprintf "%s:%d - %s\n", $file, $line, join ' ', @_; }
This avoids the deep recursion by not printing to a tied filehandle -- it's copied by dereference before printing to it.

Maybe users could pass in a subref with stuff they want to print instead. Interesting idea.

Replies are listed 'Best First'.
RE (2): Filehandle Filter
by tilly (Archbishop) on Aug 11, 2000 at 05:23 UTC
    Well since you beat me to the original, here is an implementation that by default does what btrott's original display did, but can do anything you want. For giggles and grins I have implemented new and PRINTF using two different idioms:
    package Filter::Handle; use strict; use Carp; sub PRINT { my $self = shift; my $fh = $self->{fh}; print $fh $self->{disp}->(@_); } sub PRINTF { my $self = shift; @_ = ($self, sprintf(shift, @_)); goto &PRINT; } sub TIEHANDLE { my $class = shift; my $fh = shift or croak("Need a filehandle to tie to."); my $disp = shift || sub { my ($file, $line) = (caller(1))[1,2]; sprintf("%s:%d - %s\n", $file, $line, "@_"); }; return bless ({fh => $fh, disp => $disp}, $class); } # An OO interface for free! :-) *new = *TIEHANDLE; *print = *PRINT; *printf = *PRINTF; 1;
    (And yes, chip this is for you because of RE: RE: Shot myself in the foot with a pos. Who said that goto was useless? :-)

    EDIT
    Chip made 2 very good style suggestions on the anon sub, and I am glad to have made both of them.

    EDIT 2
    In addition to the suggestions chip made, I had removed my other return. At the time I wondered if I should, and tye's misgivings confirmed my misgivings. It belongs, it really does.

    Note that this could be more compact still. For instance the anon sub could be made even shorter by removing the temporary variable. But that would not optimize my ability to understand my code. :-)

    EDIT 3
    OK, I got tired of the temporary variable in PRINTF that was clearly not needed. I have not (yet) convinced myself that it would be a good thing to drop the two in the anon sub. I also added a print and printf functions as syntactic sugar to make the OO interface a bit nicer. Oh, and I added a comment. :-)

    For those who do not understand tie, this package has two distinct interfaces. The OO one works like this:

    my $out = new Filter::Handle(\*STDOUT); $out->print("Hello world\n"); $out->printf("%s %s\n", "Hello", "world");
    The tied interface like this:
    tie (*OUT, 'Filter::Handle', \*STDOUT); select(OUT); print "Hello world\n"; printf ("%s %s\n", "Hello", "world");
    The apparently shocking similarity in the implementations drives home the fact that tie is nothing more than syntactic sugar to allow you to think about an object which happens to provide the right methods as a native Perl datatype. :-)

    BTW my absolutely favorite part of this code is the DESTROY method. Stuff like that is what makes Perl great! :-)

      I love it! :-)

      My favorite bit is *new = *TIEHANDLE. That's just inspired.

      I have to deduct a style point for unnecessary use of the return operator, and the explicit call of join when "@_" would be both shorter and clearer (IMO).

      But to repeat: This is a neat hack. Thanks.

          -- Chip Salzenberg, Free-Floating Agent of Chaos

        Agreed on both points, though I had to ask what you meant partially b/c I often use "" to offset snippets of code. I will make both changes. :-)

        I disagree strongly about avoiding return. That practice was desirable on old versions of Perl for speed. However, its explicit use makes the code easier to read, especially since you can't declare whether your sub is supposed to return a value or if it just happens to return a value because of the last statement executed.

                - tye (but my friends call me "Tye")
RE: RE: Filehandle Filter
by btrott (Parson) on Aug 11, 2000 at 05:06 UTC
    Ooh. Thanks so much for that trick. Very nice.

    How's this for the output functionality?

    package Filter::Handle; use strict; use vars qw/@ISA/; use Tie::Handle; @ISA = qw/Tie::Handle/; sub TIEHANDLE { my $class = shift; bless { @_ }, $class; } sub new { my $class = shift; my $fh = shift; tie *{$fh}, __PACKAGE__, fh => $fh, @_; bless { fh => $fh }, $class; } sub DESTROY { my $self = shift; my $fh = $self->{fh}; { local $^W = 0; untie *{$fh} } } sub PRINT { my $self = shift; my $fh = *{ $self->{fh} }; die "No output handler installed" unless defined $self->{output}; print $fh $self->{output}->(@_); } sub CLOSE { }
    Now to be used like this:
    my $f = Filter::Handle->new(\*STDOUT, output => sub { my($file, $line) = (caller(1))[1,2]; return sprintf "%s:%d - %s\n", $file, $line, join ' ', @_; } ); print "Foo";
    I think this might be nice to put on CPAN. I've just sent in my registration for a user ID. So... thanks very much for that idea!
      Okay, I got rid of the hash and used blessed typeglobs, just because I'm that kinda guy and I want the approval of heroes like chip and merlyn and Damian.
      package Filter::Handle; use strict; use vars qw/@ISA/; use Tie::Handle; @ISA = qw/Tie::Handle/; sub TIEHANDLE { my $class = shift; my $fh = shift; bless $fh, $class; } sub new { my $class = shift; my $fh = shift; my $sub = shift; *fh = $sub; tie *{$fh}, __PACKAGE__, $fh; bless $fh, $class; } sub DESTROY { my $self = shift; my $fh = *{ $self }; { local $^W = 0; untie *{$fh} } } sub PRINT { my $self = shift; my $fh = *$self; my $code = *fh{CODE}; die "No output handler installed" unless defined $code; print $fh $code->(@_); } sub CLOSE { }
      You can pass an anonymous sub or a sub ref to the constructor:
      #!/usr/bin/perl -w use strict; use Filter::Handle; local *OUTPUT; open(OUTPUT, ">handle.txt") || die "Can't open: $!"; my $fh = \*OUTPUT; my $f = Filter::Handle->new($fh, sub { my($file, $line) = (caller(1))[1,2]; return sprintf "%s:%d - %s\n", $file, $line, @_; }); print OUTPUT "Foo"; print OUTPUT "Bar";
      Yeah, that's pretty evil. But hey, why not?