Path: usenet.cis.ufl.edu!usenet.eel.ufl.edu!hookup!news.kei.com!simtel!news.sprintlink.net!psgrain!nntp.teleport.com!usenet
From: Bennett Todd <[email protected]>
Newsgroups: comp.lang.perl.announce,comp.lang.perl.misc,comp.lang.perl
Subject: Libbent: File.pm and Error.pm: OO File-I/O with error checking
Followup-To: comp.lang.perl.misc
Date: 15 Jul 1995 12:53:40 GMT
Organization: Mordor International BBS - Jersey City, NJ
Lines: 263
Approved: [email protected] (comp.lang.perl.announce)
Message-ID: <[email protected]>
NNTP-Posting-Host: linda.teleport.com
X-Disclaimer: The "Approved" header verifies header information for article transmission and does not imply approval of content.
Xref: usenet.cis.ufl.edu comp.lang.perl.announce:75 comp.lang.perl.misc:1772 comp.lang.perl:54391

I've recently started hacking up a utility library. My starting point is the
same place I started when I did my C utility library libbent, lo these many
years ago; I noticed some idioms that were getting tiresome, I was doing them
so often. It's gotten complete enough to start doing some useful things.

       $src = Libbent::File::new "<src";
       $dst = Libbent::File::new ">dst";
       while ($_ = $src->Read) {
               $dst->Print($_);
       }
       # The above loop could be replaced by:
       #       $dst->Print($src->Read);
       # using the chomp-a-whole-file-in-array-context trick

The above style of I/O, using this library, checks for errors on every
operation. There's also a debugging flag that traces the method invocations; I
find this a useful way to get a quick feel for what a large program is doing.

-Bennett
[email protected]

#!/bin/sh
# To unpack this Shell Archive, save this into a file, edit it deleting everything
# before the above ``#!/bin/sh'' line, and run the command ``sh filename''.
test -d 'Libbent' || (
       echo x - 'Libbent'/
       mkdir 'Libbent'
)
cd 'Libbent'
echo x - 'Libbent/File.pm'
sed 's/^        //' <<'EOF' >'File.pm'
       package Libbent::File; # Version 1.0
       require Exporter;
       @ISA = qw(Exporter);
       use Libbent::Error qw(Error);

       =head1 NAME

       File --- vaguely OO File I/O

       =head1 SYNOPSIS

           use Libbent::File;
           $file = Libbent::File::new(">filename");
           $line = $file->Read();
           @wholefile = $file->Read();
           $block = $file->Read($blocksize);
           $file-Print($some_output);

       =head1 DESCRIPTION

       This package is designed to address various wishes I have for I/O.

       It provides interface routines that don't need to explicitly be checked for
       errors; if an error occurs they will exit with a suitable message. On those
       occasions where you want to catch an error and deal with it explicitly (rare,
       in my experience) you can just replace a call like (e.g.)

               $file = Libbent::File::new(">filename");

       with

               $file = eval { Libbent::File::new(">filename"); };

       and then check $file to see if it's undef; if it is, then the message that
       Open would have died with will be found in $@.

       It performs another task: handles, as returned by File::new, are simple
       scalars, and so can be localized with my(), and otherwise treated like normal
       objects.

       =head1 FUNCTIONS

       =over 4

       =cut

       =item new filename

       C<new> creates new File handles. It takes file and/or command arguments in the
       same syntax as Perl's open(), with leading "<" for file read, ">" for file
       write, "|" for pipe, and so on. It returns the handle.

       =cut

       sub new {
               my($arg) = @_; my($self) = {}; &debug($self, @_);
               ($self->{'name'} = $arg) =~ s/^[<>]//;
               my($seq) = '000000';
               $seq++ while exists $handle{$arg . $seq};
               $self->{'handle'} = $arg . $seq;
               $handle{$self->{'handle'}}++;
               open $self->{'handle'}, $arg or Error 'open', $self->{'name'};
               bless $self;
       }

       =item Read [n]

       C<Read> is the input method. If no count is specified it behaves like Perl's
       C<<>> input operator, including sensitivity to scalar or array context. If
       C<n> is specified, then it uses perl's C<read> procedure, returning the
       bufferfull of data.

       =cut

       sub Read {
               my($self, $n) = @_; &debug(@_);
               my($handle) = $self->{'handle'};
               if (defined($n)) {
                       my($buff);
                       defined(read($handle, $buff, $n)) or
                               Error 'read', $self->{'name'};
                       return $buff;
               }
               if (wantarray) {
                       my(@ret);
                       defined(@ret = <$handle>) or
                               Error 'read', $self->{'name'};
                       return(@ret);
               } else {
                       my($ret);
                       defined($ret = <$handle>) or
                               Error 'read', $self->{'name'};
                       return($ret);
               }
       }


       =item Print stuff

       C<Print> behaves exactly like perl's C<print>, except of course for trapping
       errors.

       =cut

       sub Print {
               my($self, @args) = @_; &debug(@_);
               my($handle) = $self->{'handle'};
               print $handle @args or Error 'print', $self->{'name'};
       }

       =item DESTROY

       The DESTROY procedure doesn't need to be explicitly called; Perl handles that
       for you when the last reference to the file handle is lost. The DESTROY method
       closes the filehandle and cleans up internal data structures. The implication
       of this is that C<close> can be forced by C<undef>-ing your reference to the
       handle.

       =cut

       sub DESTROY {
               my($self) = @_; &debug(@_);
               close $self->{'handle'};
               delete $handle_exists{$self->{'handle'}};
       }

       =item Debug [n]

       C<Debug> turns debugging on or off, either class-wide for for specific
       handles. Specifically:

               Libbent::File->Debug 1;         # Turns on debugging class-wide
               Libbent::File->Debug 0;         # Turns it back off, class-wide
               $file->Debug 1;                 # Overrides the class default

       =cut

       $debug = 0;
       $debug_file = 'STDERR';

       sub Debug {
               my($what, $value) = @_;
               if (defined($value)) {
                       if (ref $what) {
                               $what->{'debug'} = $value;
                       } else {
                               $debug = $value;
                       }
               } else {
                       if (ref $what and exists $what->{'debug'}) {
                               return $what->{'debug'};
                       } else {
                               return $debug;
                       }
               }
       }

       sub debug {
               my($self, @args) = @_;

               return unless $self->{'debug'} or $debug;
               my($subr) = (caller(1))[3];
               $subr =~ s/^.*:://;
               my(@toprint) = grep {defined $_} $subr, $self->{'name'}, @args;
               print $debug_file join(' ', @toprint), "\n";
       }

       =back

       =head1 SEE ALSO

       Libbent::Error, which this class uses

       =head1 AUTHOR

       Bennett Todd <[email protected]>, with guidance and advice from Jeff Moore
       <[email protected]>

       =cut
EOF
echo x - 'Libbent/Error.pm'
sed 's/^        //' <<'EOF' >'Error.pm'
       package Libbent::Error; # Version 1.0
       require Exporter;
       @ISA = qw(Exporter);
       @EXPORT_OK = qw(Error);
       use File::Basename;

       =head1 NAME

       Error -- die with suitable error message

       =head1 SYNOPSIS

           use Libbent::Error qw(Error);
           try something or Error "what I tried";

       =cut

       sub Error {
               die join(': ',
                       basename($main::0),
                       join(' ', grep { defined $_ } @_),
                       $!
                   ) . "\n";
       }
EOF
echo x - 'Libbent/cat'
sed 's/^        //' <<'EOF' >'cat'
       #!/usr/local/bin/perl5 -w # Version 1.0
       BEGIN { unshift @INC, $ENV{'HOME'} . '/lib/perl'; }
       use File::Basename;
       use Getopt::Std;
       use Libbent::File;

       $progname = basename($0);
       $syntax = "syntax: $progname [-d] [file ...]\n";
       $opt_d = 0;
       getopts 'd' or die $syntax;
       Libbent::File->Debug(1) if $opt_d;
       unshift @ARGV, '&STDIN' unless @ARGV;

       $out = Libbent::File::new '>&STDOUT';

       foreach (@ARGV) {
               $out->Print(Libbent::File::new("<$_")->Read());
       }
EOF
chmod a+x 'cat'
cd ..
exit 0