The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

IO::WithHeader - read/write header and body in a single file

SYNOPSIS

    use IO::WithHeader::MySubclass;
    
    $io = IO::WithHeader::MySubclass->new($path_or_filehandle);
    $io = IO::WithHeader::MySubclass->new(\%header);
    $io = IO::WithHeader::MySubclass->new(
        'path'   => '/path/to/a/file/which/might/not/exist/yet',
        'handle' => $fh,  # Mutually exclusive with path
        'mode'   => $mode,
        'header' => { 'title' => $title, ... },
        'body'   => $scalar_or_filehandle_to_copy_from,
    );
    
    $io->open($path, 'mode' => '>') or die;  # Open the body
    print $io "Something to put in the file's body\n";
    
    $path = $io->path;
    $io->path('/path/to/a/file');
    $io->open or die;
    while (<$io>) { ... }
    
    # Fetch and store 
    %header = %{ $io->header };
    $io->header(\%header);
    
    $body = $io->body;  # Read the entire body
    $io->body($body);   # Write the entire body

DESCRIPTION

IO::WithHeader and its subclasses allow you to read and write a file containing both a header and a body. The header and body may be changed without affecting the other.

IO::WithHeader itself doesn't provide code to actually read and write a file's header, since there are so many different varieties of headers. Instead, it must be subclassed to provide the desired functionality.

The IO::WithHeader distribution comes with two such subclasses, IO::WithHeader::YAML and IO::WithHeader::RFC822.

METHODS

new
    $subclass = 'IO::WithHeader::MySubclass';  # or whatever
    
    use IO::WithHeader::MySubclass;  # or whatever
    
    # Simplest constructor - must call $io->open(...) later
    $io = $subclass->new;
    
    # Concise forms
    $io = $subclass->new("$file");     # Default is read-only
    $io = $subclass->new("<$file");    # Read-only made explicit
    $io = $subclass->new(">$file");    # Read-write (empty header & body)
    $io = $subclass->new($file, 'mode' => '<');  # Or '>', '+<', 'r', etc.
    $io = $subclass->new(\*STDIN);
    $io = $subclass->new(\*STDOUT, 'mode' => '>');
    $io = $subclass->new($anything_that_isa_GLOB);
    
    # Full form (all arguments optional)
    $io = $subclass->new(
        'path'   => $file,        # File will be opened or created
           - or -
        'handle' => $fh,          # File handle (already open)
        'mode'   => '+>',         # Default is '<'
        'header' => \%hash,       # Default is {}
        'body'   => $scalar,      # Content to write to the new file
           - or -
        'body'   => $filehandle,  # Copy from a file handle to the new file
    );
    
    # Specify header and/or body
    $io = $subclass->new('header' => \%hash);     # Empty body
    $io = $subclass->new('body' => $scalar);      # Empty header
    $io = $subclass->new('body' => $filehandle);  # Empty header
    $io = $subclass->new(..., 'body' => $scalar, ...);
    $io = $subclass->new(..., 'body' => $filehandle, ...);

Instantiate an IO::WithHeader object (or, rather, an instance of a subclass of IO::WithHeader). An exception is thrown if anything goes wrong.

The new() method may be called in a concise form, in which the first argument is a file name, file handle, or hash reference and the (optional) remaining arguments are key-value pairs; or it may be called in a full form in which all (optional) arguments are specified as key-value pairs.

If a path is specified, the file at that path will be opened. If the file doesn't already exist, it will be created -- but only if you've specified a mode that permits writing; if you haven't, an exception will be thrown.

To use an already-open file handle, pass it to the constructor rather than the name of a file.

If neither a path nor a file handle is specified, you'll have to call the open() method yourself.

The following arguments may be specified in the constructor:

path

Path to a file to open (creating it, if possible, if write or append mode is specified).

mode

Read/write/append mode for the new file. This must be specified in one of the following forms:

<
>
+>
+<
r
r+
rw

Or any other standard form that I've forgotten about.

NOTE: Numeric modes and PerlIO layers are not yet implemented.

auto_save

If set to a true value, automatically save changes when closing the file.

If an odd number of arguments are given, the first argument is interpreted according to its type:

GLOB

File handle.

any scalar value

File path.

HASH

The header (to be written to the file). Don't use this unless you're opening the file for read (or append) access.

open
    $io = IO::WithHeader->new;
    $io->open("<$file") or die $!;
    $io->open($file, $mode) or die $!;

Open a file with the specified name and mode. You must use this method if the instance was created without a path element (and one has not been assigned using the path() method).

Upon failure, sets $! to a meaningful message and returns a false value.

The possible modes are as described for new.

The open() method may be called repeatedly on the same instance, without having to close it first.

close
    $io->close or die $!;

Close the filehandle.

    $header = $io->header;
    $io->header({...});
    
    $foo = $io->header('foo');
    $io->header('foo' => $foo);

Get or set the header, or a single element in the header. If setting all or part of the header, you must call save() for the change to be written to the file (or file handle).

The header's value must be a hash or a hash-based object:

    $io->header( [1, 2, 3, 4, 5] );   # ERROR
    $io->header( MyClass->new(...) ); # OK if hash-based
body
    $body = $io->body;
    @lines = $io->body;
    
    $io->body($body);
    $io->body(@lines);

Read or write the entire file body.

If called in list context, the lines of the file are returned as a list; this means that these are equivalent:

    @lines = <$io>;
    @lines = $io->getlines;
print
    print $io @one_or_more_scalar_values;
    $io->print(@one_or_more_scalar_values);
getline
    $line = $io->getline;
getlines
seek
    use Fcntl qw(SEEK_SET SEEK_CUR SEEK_END);  # Handy constants
    $io->seek($whence, $pos);
    
tell
seek
truncate
seek
seek
seek

SUBCLASSING

Generally speaking, the only methods your subclass needs to provide are _read_header and _write_header. For example, here's the complete source code of IO::WithHeader::YAML:

    package IO::WithHeader::YAML;
    
    use IO::WithHeader;
    use YAML qw();
    @IO::WithHeader::YAML::ISA = 'IO::WithHeader';
    
    sub _read_header {
        my ($fh) = @_;
        my $yaml = '';
        local $_;
        while (<$fh>) {
            last if /^\.\.\.$/;
            $yaml .= $_;
        }
        return YAML::Load($yaml);
    }
    
    sub _write_header {
        my ($fh, $header) = @_;
        my $yaml = YAML::Dump($header) . "...\n";
        print $fh $yaml;
    }
    
    1;

See IO::WithHeader::RFC822 for another example.

BUGS

Autoflush might not be working.

TO DO

Deal with PerlIO layers.

Implement permissions and numeric modes.

Allow for non-hash headers?

Implement auto-save.

SEE ALSO

IO::WithHeader::YAML, IO::WithHeader::RFC822

AUTHOR

Paul Hoffman (nkuitse AT cpan DOT org)

COPYRIGHT

Copyright 2004 Paul M. Hoffman.

This is free software, and is made available under the same terms as Perl itself.