File Coverage

blib/lib/File/SAUCE.pm
Criterion Covered Total %
statement 150 187 80.2
branch 46 64 71.8
condition 6 6 100.0
subroutine 26 27 96.3
pod 15 15 100.0
total 243 299 81.2


line stmt bran cond sub pod time code
1             package File::SAUCE;
2              
3             =head1 NAME
4              
5             File::SAUCE - A library to manipulate SAUCE metadata
6              
7             =head1 SYNOPSIS
8              
9             use File::SAUCE;
10              
11             # Read the data...
12             # file, handle or string
13             my $sauce = File::SAUCE->new( file => 'myansi.ans' );
14              
15             # Does the file have a SAUCE record?
16             print $sauce->has_sauce ? "has SAUCE" : "does not have SAUCE";
17              
18             # Print the metadata...
19             $sauce->print;
20              
21             # Get a value...
22             my $title = $sauce->title;
23              
24             # Set a value...
25             $sauce->title( 'ANSi is 1337' );
26              
27             # Get the SAUCE record as a string...
28             my $output = $sauce->as_string;
29              
30             # Write the data...
31             # file, handle or string
32             $sauce->write( file => 'myansi.ans' );
33              
34             # Clear the in-memory data...
35             $sauce->clear;
36              
37             # Read the data...
38             # file, handle or string
39             $sauce->read( file => 'myansi.ans' );
40              
41             # Remove the data...
42             # file, handle or string
43             $sauce->remove( file => 'myansi.ans' );
44              
45             =head1 DESCRIPTION
46              
47             SAUCE stands for Standard Architecture for Universal Comment Extentions. It is used as metadata
48             to describe the file to which it is associated. It's most common use has been with the ANSI and
49             ASCII "art scene."
50              
51             A file containing a SAUCE record looks like this:
52              
53             +----------------+
54             | FILE Data |
55             +----------------+
56             | EOF Marker |
57             +----------------+
58             | SAUCE Comments |
59             +----------------+
60             | SAUCE Record |
61             +----------------+
62              
63             The SAUCE Comments block holds up to 255 comment lines, each 64 characters wide. It looks like this:
64              
65             +----------------+------+------+---------+-------------+
66             | Field | Size | Type | Default | set / get |
67             +----------------+------+------+---------+-------------+
68             | ID | 5 | Char | COMNT | commment_id |
69             +----------------+------+------+---------+-------------+
70             | Comment Line 1 | 64 | Char | | comments* |
71             +----------------+------+------+---------+-------------+
72             | ... |
73             +----------------+------+------+---------+-------------+
74             | Comment Line X | 64 | Char | | comments |
75             +----------------+------+------+---------+-------------+
76              
77             * Comments are stored as an array ref
78              
79             And lastly, the SAUCE Record. It is exactly 128 bytes long:
80              
81             +----------------+------+------+---------+-------------+
82             | Field | Size | Type | Default | set / get |
83             +----------------+------+------+---------+-------------+
84             | ID | 5 | Char | SAUCE | sauce_id |
85             +----------------+------+------+---------+-------------+
86             | SAUCE Version | 2 | Char | 00 | version |
87             +----------------+------+------+---------+-------------+
88             | Title | 35 | Char | | title |
89             +----------------+------+------+---------+-------------+
90             | Author | 20 | Char | | author |
91             +----------------+------+------+---------+-------------+
92             | Group | 20 | Char | | group |
93             +----------------+------+------+---------+-------------+
94             | Date | 8 | Char | | date |
95             +----------------+------+------+---------+-------------+
96             | FileSize | 4 | Long | | filesize |
97             +----------------+------+------+---------+-------------+
98             | DataType | 1 | Byte | | datatype_id |
99             +----------------+------+------+---------+-------------+
100             | FileType | 1 | Byte | | filetype_id |
101             +----------------+------+------+---------+-------------+
102             | TInfo1 | 2 | Word | | tinfo1 |
103             +----------------+------+------+---------+-------------+
104             | TInfo2 | 2 | Word | | tinfo2 |
105             +----------------+------+------+---------+-------------+
106             | TInfo3 | 2 | Word | | tinfo3 |
107             +----------------+------+------+---------+-------------+
108             | TInfo4 | 2 | Word | | tinfo4 |
109             +----------------+------+------+---------+-------------+
110             | Comments | 1 | Byte | | comments |
111             +----------------+------+------+---------+-------------+
112             | Flags | 1 | Byte | | flags_id |
113             +----------------+------+------+---------+-------------+
114             | Filler | 22 | Byte | | filler |
115             +----------------+------+------+---------+-------------+
116              
117             For more information see ACiD.org's SAUCE page at http://www.acid.org/info/sauce/sauce.htm
118              
119             =head1 WARNING
120              
121             From the SAUCE documenation:
122              
123             SAUCE was initially created for supporting only the ANSi
124             & RIP screens. Since both ANSi and RIP are in effect
125             text-based and have no other form of control but the
126             End-Of-File marker, SAUCE should never interfere with the
127             workings of a program using either ANSi or RIP. If it does,
128             the program is not functionning the way it should. This is
129             NOT true for the other types of files however. Adding SAUCE
130             to some of the other filetypes supported in the SAUCE
131             specifications may have serious consequences on the proper
132             functionning of programs using those files, In the worst
133             case, they'll simply refuse the file, stating it is invalid.
134              
135             The author(s) of this software take no resposibility for loss of data!
136              
137             =head1 INSTALLATION
138              
139             perl Makefile.PL
140             make
141             make test
142             make install
143              
144             =cut
145              
146 12     12   473186 use strict;
  12         29  
  12         608  
147 12     12   68 use warnings;
  12         24  
  12         824  
148 12     12   73 use Carp;
  12         38  
  12         1354  
149 12     12   20268 use FileHandle;
  12         236010  
  12         83  
150 12     12   20865 use IO::String;
  12         48821  
  12         468  
151 12     12   28850 use Time::Piece;
  12         186022  
  12         95  
152              
153 12     12   1458 use base qw( Class::Accessor );
  12         30  
  12         15859  
154              
155             our $VERSION = '0.25';
156              
157             # some SAUCE constants
158 12     12   37271 use constant SAUCE_ID => 'SAUCE';
  12         30  
  12         968  
159 12     12   74 use constant SAUCE_VERSION => '00';
  12         32  
  12         804  
160 12     12   271 use constant SAUCE_FILLER => ' ' x 22;
  12         25  
  12         900  
161 12     12   65 use constant COMNT_ID => 'COMNT';
  12         18  
  12         51660  
162              
163             # vars for use with pack() and unpack()
164             my $sauce_template = 'A5 A2 A35 A20 A20 A8 V C C v v v v C C A22';
165             my @sauce_fields
166             = qw( sauce_id version title author group date filesize datatype_id filetype_id tinfo1 tinfo2 tinfo3 tinfo4 comments flags_id filler );
167             my $comnt_template = 'A5 A64';
168             my @comnt_fields = qw( comment_id comments );
169             my $date_format = '%Y%m%d';
170              
171             __PACKAGE__->mk_accessors( @sauce_fields, $comnt_fields[ 0 ], 'has_sauce' );
172              
173             # define datatypes and filetypes as per SAUCE specs
174             my @datatypes
175             = qw(None Character Graphics Vector Sound BinaryText XBin Archive Executable);
176             my $filetypes = {
177             None => {
178             filetypes => [ qw( Undefined ) ],
179             flags => { 0 => 'None' }
180             },
181             Character => {
182             filetypes =>
183             [ qw( ASCII ANSi ANSiMation RIP PCBoard Avatar HTML Source ) ],
184             flags => { 0 => 'None', 1 => 'iCE Color' },
185             tinfo => [
186             ( { tinfo1 => 'Width', tinfo2 => 'Height' } ) x 3,
187             { tinfo1 => 'Width', tinfo2 => 'Height', tinfo3 => 'Colors' },
188             ( { tinfo1 => 'Width', tinfo2 => 'Height' } ) x 2
189             ]
190             },
191             Graphics => {
192             filetypes => [
193             qw( GIF PCX LBM/IFF TGA FLI FLC BMP GL DL WPG PNG JPG MPG AVI )
194             ],
195             flags => { 0 => 'None' },
196             tinfo => [
197             ( { tinfo1 => 'Width',
198             tinfo2 => 'Height',
199             tinfo3 => 'Bits Per Pixel'
200             }
201             ) x 14
202             ]
203             },
204             Vector => {
205             filetypes => [ qw( DXF DWG WPG 3DS ) ],
206             flags => { 0 => 'None' }
207             },
208             Sound => {
209             filetypes => [
210             qw( MOD 669 STM S3M MTM FAR ULT AMF DMF OKT ROL CMF MIDI SADT VOC WAV SMP8 SMP8S SMP16 SMP16S PATCH8 PATCH16 XM HSC IT )
211             ],
212             flags => { 0 => 'None' },
213             tinfo => [ ( {} ) x 16, ( { tinfo1 => 'Sampling Rate' } ) x 4 ]
214             },
215             BinaryText => {
216             filetypes => [ qw( Undefined ) ],
217             flags => { 0 => 'None', 1 => 'iCE Color' }
218             },
219             XBin => {
220             filetypes => [ qw( Undefined ) ],
221             flags => { 0 => 'None' },
222             tinfo => [ { tinfo1 => 'Width', tinfo2 => 'Height' }, ]
223             },
224             Archive => {
225             filetypes => [ qw( ZIP ARJ LZH ARC TAR ZOO RAR UC2 PAK SQZ ) ],
226             flags => { 0 => 'None' }
227             },
228             Executable => {
229             filetypes => [ qw( Undefined ) ],
230             flags => { 0 => 'None' }
231             }
232             };
233              
234             =head1 PUBLIC METHODS
235              
236             =head2 new( [ %OPTIONS ] )
237              
238             Creates a new File::SAUCE object. All arguments are optional. You can pass one
239             of two groups of options (as a hash). If you wish to read a SAUCE record from
240             a source, you can pass a file, handle or string.
241              
242             my $sauce = File::SAUCE->new( file => 'filename.ext' );
243             my $sauce = File::SAUCE->new( handle => \*FILEHANDLE );
244             my $sauce = File::SAUCE->new( string => $string );
245              
246             If you want to create a new record with certain metadata values, just pass them
247             in as a hash.
248              
249             my $sauce = File::SAUCE->new(
250             author => 'Me',
251             title => 'My Work',
252             group => 'My Group'
253             );
254              
255             =cut
256              
257             sub new {
258 45     45 1 11894 my $class = shift;
259 45         115 my $self = {};
260 45         231 my %options = @_;
261              
262 45         119 bless $self, $class;
263              
264 45         890 $self->clear;
265              
266 45 100 100     1001 if ( exists $options{ file }
      100        
267             or exists $options{ string }
268             or exists $options{ handle } )
269             {
270 31         95 $self->read( @_ );
271             }
272             else {
273 14         71 $self->set( $_ => $options{ $_ } ) for keys %options;
274 14 100       144 $self->date( $options{ date } ) if exists $options{ date };
275             }
276              
277 45         396 return $self;
278             }
279              
280             =head2 clear( )
281              
282             Resets the in-memory SAUCE data to the default information.
283              
284             =cut
285              
286             sub clear {
287 122     122 1 542 my $self = shift;
288 122         511 my $date = localtime;
289              
290             # Set empty/default SAUCE and COMMENT values
291 122         11685 $self->set( $_ => '' ) for @sauce_fields[ 2 .. 4 ];
292 122         4196 $self->set( $_ => 0 ) for @sauce_fields[ 6 .. 13, 14 ];
293 122         6343 $self->sauce_id( SAUCE_ID );
294 122         1506 $self->version( SAUCE_VERSION );
295 122         1345 $self->filler( SAUCE_FILLER );
296 122         1134 $self->comment_id( COMNT_ID );
297 122         1641 $self->date( $date );
298 122         5795 $self->comments( [] );
299 122         2400 $self->has_sauce( undef );
300             }
301              
302             =head2 read( %OPTIONS )
303              
304             Tries to read a SAUCE record from a source. Uses the same options as C.
305              
306             =cut
307              
308             sub read {
309 78     78 1 1675041 my $self = shift;
310 78         300 my %options = @_;
311 78         979 my $file = $self->_create_io_object( \%options, '<' );
312              
313 76         239 $self->clear;
314              
315 76         1803 my $buffer;
316             my %info;
317              
318 76 100       903 if ( ( $file->stat )[ 7 ] < 128 ) {
319 18         1042 $self->has_sauce( 0 );
320 18         456 return;
321             }
322              
323 58         1749 $file->seek( -128, 2 );
324 58         956 $file->read( $buffer, 128 );
325              
326 58 100       1462 if ( substr( $buffer, 0, 5 ) ne SAUCE_ID ) {
327 21         69 $self->has_sauce( 0 );
328 21         349 return;
329             }
330              
331 37         666 @info{ @sauce_fields } = unpack( $sauce_template, $buffer );
332              
333             # because trailing spaces are stripped....
334 37         274 $info{ filler } = SAUCE_FILLER;
335              
336             # Do we have any comments?
337 37         67 my $comments = $info{ comments };
338 37         151 delete $info{ comments };
339              
340 37         247 $self->set( $_ => $info{ $_ } ) for keys %info;
341 37         3474 $self->has_sauce( 1 );
342              
343 37 100       702 if ( $comments > 0 ) {
344 15         66 $file->seek( -128 - 5 - $comments * 64, 2 );
345 15         163 $file->read( $buffer, 5 + $comments * 64 );
346              
347 15 100       242 if ( substr( $buffer, 0, 5 ) eq COMNT_ID ) {
348 14         81 my $template = $comnt_template
349             . ( split( / /, $comnt_template ) )[ 1 ] x ( $comments - 1 );
350 14         75 my ( $id, @comments ) = unpack( $template, $buffer );
351 14         110 $self->comment_id( $id );
352 14         143 $self->comments( \@comments );
353             }
354             }
355             }
356              
357             =head2 write( %OPTIONS )
358              
359             Writes the in-memory SAUCE data to a destination. Uses the same options as
360             C. It calls C before writing the data.
361              
362             =cut
363              
364             sub write {
365 6     6 1 1411 my $self = shift;
366              
367 6         29 $self->remove( @_ );
368              
369 6         18 my %options = @_;
370 6         18 my $file = $self->_create_io_object( \%options, '>>' );
371              
372 6         37 $file->seek( 0, 2 );
373 6         91 $file->print( $self->as_string );
374              
375 6 100       339 return ${ $file->string_ref } if ref $file eq 'IO::String';
  2         10  
376             }
377              
378             =head2 remove( %OPTIONS )
379              
380             Removes any SAUCE data from the destination. This module enforces spoon
381             (ftp://ftp.artpacks.acid.org/pub/artpacks/programs/dos/editors/spn2d161.zip)
382             compatibility. The following calculation is used to determine how big the file
383             should be after truncation:
384              
385             if( Filesize on disk - Filesize in SAUCE rec - Size of SAUCE rec ( w/ comments ) == 0 or 1 ) {
386             truncate to Filesize in SAUCE rec
387             }
388             else {
389             truncate to Filesize on disk - Size of SAUCE rec - 1 (EOF char)
390             }
391              
392             =cut
393              
394             sub remove {
395 23     23 1 16426 my $self = shift;
396 23         172 my $sauce = File::SAUCE->new( @_ );
397 23         76 my $has_sauce = $sauce->has_sauce;
398 23         219 my %options = @_;
399              
400 23 100       239 unless ( $has_sauce ) {
401 12 100       612 return $options{ string } if exists $options{ string };
402 8         56 return;
403             }
404              
405 11         36 my $file = $self->_create_io_object( \%options, '>>' );
406              
407             # remove SAUCE
408 11         39 my $sizeondisk = ( $file->stat )[ 7 ];
409 11         184 my $sizeinrec = $sauce->filesize;
410 11         94 my $comments = scalar @{ $sauce->comments };
  11         33  
411 11 100       103 my $saucesize = 128 + ( $comments ? 5 + $comments * 64 : 0 );
412 11         30 my $size = $sizeondisk - $sizeinrec - $saucesize;
413              
414             # for spoon compatibility
415             # Size on disk - size in record - SAUCE size (w/ comments) == 0 or 1 --> use size in record
416 11 100       58 if ( $size =~ /^0|1$/ ) {
417 7 100       41 $file->truncate( $sizeinrec ) or carp "$!";
418             }
419              
420             # figure it out on our own -- spoon just balks
421             else {
422 4 100       38 $file->truncate( $sizeondisk - $saucesize - 1 ) or carp "$!";
423             }
424              
425 11 100       1324 return ${ $file->string_ref } if ref $file eq 'IO::String';
  3         12  
426             }
427              
428             =head2 as_string( )
429              
430             Returns the SAUCE record (including EOF char and comments) as a string.
431              
432             =cut
433              
434             sub as_string {
435 9     9 1 758 my $self = shift;
436              
437             # Fix values incase they've been changed
438 9         27 $self->sauce_id( SAUCE_ID );
439 9         93 $self->version( SAUCE_VERSION );
440 9         84 $self->filler( SAUCE_FILLER );
441 9         81 $self->comment_id( COMNT_ID );
442              
443             # EOF marker...
444 9         74 my $output = chr( 26 );
445              
446             # comments...
447 9         14 my $comments = scalar @{ $self->comments };
  9         212  
448 9 100       97 if ( $comments ) {
449 4         41 $output .= pack(
450             $comnt_template
451             . (
452             ( split( / /, $comnt_template ) )[ 1 ] x ( $comments - 1 )
453             ),
454             $self->comment_id,
455 4         35 @{ $self->comments }
456             );
457             }
458              
459             # SAUCE...
460 9         122 my @template = split( / /, $sauce_template );
461 9         36 for ( 0 .. $#sauce_fields ) {
462 144         176 my $field = $sauce_fields[ $_ ];
463 144 100       654 my $value
464             = ( $field ne 'comments' ) ? $self->get( $field ) : $comments;
465 144         995 $output .= pack( $template[ $_ ], $value );
466             }
467              
468 9         61 return $output;
469             }
470              
471             =head2 print( )
472              
473             View the SAUCE structure (including comments) in a "pretty" format.
474              
475             =cut
476              
477             sub print {
478 0     0 1 0 my $self = shift;
479 0         0 my $width = 10;
480 0         0 my $label = '%' . $width . 's:';
481 0         0 my $has_sauce = $self->has_sauce;
482 0         0 my $output;
483              
484 0 0       0 if ( $has_sauce == 0 ) {
485 0         0 print "The file last read did not contain a SAUCE record\n";
486 0         0 return;
487             }
488              
489 0         0 for ( @sauce_fields ) {
490 0 0       0 if ( /^(datatype|filetype|flags)_id$/ ) {
    0          
    0          
    0          
491 0         0 $output = sprintf( "$label %s", ucfirst( $1 ), $self->get( $_ ) );
492 0         0 my $value = $self->$1;
493 0         0 print $output;
494 0 0       0 print ' (' . $value . ')' if $value;
495 0         0 print "\n";
496             }
497             elsif ( /^tinfo\d$/ ) {
498 0         0 $output = sprintf( "$label %s", ucfirst( $_ ), $self->get( $_ ) );
499 0         0 my $name = $_ . '_name';
500 0         0 my $value = $self->$name;
501 0         0 print $output;
502 0 0       0 print ' (' . $value . ')' if $value;
503 0         0 print "\n";
504             }
505             elsif ( $_ eq 'date' ) {
506 0         0 $output
507             = sprintf( "$label %s\n", 'Date', $self->date->mdy( '/' ) );
508 0         0 print $output;
509             }
510             elsif ( $_ eq 'comments' ) {
511 0         0 $output = sprintf( "$label %s\n",
512 0         0 'Comments', scalar @{ $self->comments } );
513 0         0 print $output;
514             }
515             else {
516 0         0 $output
517             = sprintf( "$label %s\n", ucfirst( $_ ), $self->get( $_ ) );
518 0         0 print $output;
519             }
520             }
521              
522 0         0 my @comments = @{ $self->comments };
  0         0  
523              
524 0 0       0 return unless @comments;
525              
526 0         0 $output = sprintf( "$label %s\n", 'Comment_id', $self->comment_id );
527 0         0 $output .= sprintf( $label, 'Comments' );
528              
529 0         0 print $output;
530              
531 0         0 for ( 0 .. $#comments ) {
532 0 0       0 $output = sprintf(
533             $_ == 0 ? " %s\n" : ( ' ' x ( $width + 1 ) ) . " %s\n",
534             $comments[ $_ ]
535             );
536 0         0 print $output;
537             }
538             }
539              
540             =head2 datatype( )
541              
542             Return the string version of the file's datatype. Use datatype_id to get the integer version.
543              
544             =cut
545              
546             sub datatype {
547              
548             # Return the datatype name
549 105     105 1 29606 return $datatypes[ $_[ 0 ]->datatype_id ];
550             }
551              
552             =head2 filetype( )
553              
554             Return the string version of the file's filetype. Use filetype_id to get the integer version.
555              
556             =cut
557              
558             sub filetype {
559              
560             # Return the filetype name
561             return $filetypes->{ $_[ 0 ]->datatype }->{ filetypes }
562 15     15 1 23254 ->[ $_[ 0 ]->filetype_id ];
563             }
564              
565             =head2 flags( )
566              
567             Return the string version of the file's flags. Use flags_id to get the integer version.
568              
569             =cut
570              
571             sub flags {
572              
573             # Return an english description of the flags
574             return $filetypes->{ $_[ 0 ]->datatype }->{ flags }
575 15     15 1 69927 ->{ $_[ 0 ]->flags_id };
576             }
577              
578             =head2 tinfo1_name( )
579              
580             Return an english description of what this info value represents (returns undef if there isn't one)
581              
582             =cut
583              
584             sub tinfo1_name {
585              
586             # Return an english description of info flag (1) or blank if there is none
587             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
588 15     15 1 22338 ->[ $_[ 0 ]->filetype_id ]->{ tinfo1 };
589             }
590              
591             =head2 tinfo2_name( )
592              
593             Return an english description of what this info value represents (returns undef if there isn't one)
594              
595             =cut
596              
597             sub tinfo2_name {
598              
599             # Return an english description of info flag (2) or blank if there is none
600             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
601 15     15 1 21867 ->[ $_[ 0 ]->filetype_id ]->{ tinfo2 };
602             }
603              
604             =head2 tinfo3_name( )
605              
606             Return an english description of what this info value represents (returns undef if there isn't one)
607              
608             =cut
609              
610             sub tinfo3_name {
611              
612             # Return an english description of info flag (3) or blank if there is none
613             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
614 15     15 1 19699 ->[ $_[ 0 ]->filetype_id ]->{ tinfo3 };
615             }
616              
617             =head2 tinfo4_name( )
618              
619             Return an english description of what this info value represents (returns undef if there isn't one)
620              
621             =cut
622              
623             sub tinfo4_name {
624              
625             # Return an english description of info flag (4) or blank if there is none
626             return $filetypes->{ $_[ 0 ]->datatype }->{ tinfo }
627 15     15 1 19063 ->[ $_[ 0 ]->filetype_id ]->{ tinfo4 };
628             }
629              
630             =head2 date( [ $date ] )
631              
632             This is an overloaded date accessor. It accepts two types of dates as inputs:
633             a Time::Piece object or a string in the format of 'YYYYMMDD'. It always
634             returns a Time::Piece object.
635              
636             =cut
637              
638             sub date {
639 158     158 1 90198 my $self = shift;
640 158         466 my $date = shift;
641              
642 158 100       4112313 if ( $date ) {
643 126 100       9591 $self->set( 'date', $date->strftime( $date_format ) )
644             if ref( $date ) eq 'Time::Piece';
645 126 100       5483 $self->set( 'date', $date ) if $date =~ /^\d{8}$/;
646             }
647              
648 158         5477 return Time::Piece->strptime( $self->get( 'date' ), $date_format );
649             }
650              
651             =head1 PRIVATE METHODS
652              
653             =head2 _create_io_object( { OPTIONS }, MODE )
654              
655             Generates an IO object. Uses FileHandle or IO::String.
656              
657             =cut
658              
659             sub _create_io_object {
660 95     95   740 my $self = shift;
661 95         125 my %options = %{ $_[ 0 ] };
  95         314  
662 95         223 my $perms = $_[ 1 ];
663              
664 95         118 my $file;
665              
666             # use appropriate IO object for what we get in
667 95 100       366 if ( exists $options{ file } ) {
    100          
    100          
668 36 100       336 $file = FileHandle->new( $options{ file }, $perms ) or croak "$!";
669             }
670             elsif ( exists $options{ string } ) {
671 28         215 $file = IO::String->new( $options{ string } );
672             }
673             elsif ( exists $options{ handle } ) {
674 30         59 $file = $options{ handle };
675             }
676             else {
677 1         153 croak(
678             "No valid read type. Must be one of 'file', 'string' or 'handle'."
679             );
680             }
681              
682 93         5600 binmode $file;
683 93         368 return $file;
684             }
685              
686             =head1 AUTHOR
687              
688             Brian Cassidy Ebricas@cpan.orgE
689              
690             =head1 COPYRIGHT AND LICENSE
691              
692             Copyright 2003-2009 by Brian Cassidy
693              
694             This library is free software; you can redistribute it and/or modify
695             it under the same terms as Perl itself.
696              
697             =cut
698              
699             1;