NAME

Tie::Layers - read and write files pipelined through a stack of subroutine layers

SYNOPSIS

#####
# Subroutines
#
use Tie::Layers qw(is_handle);

$yes = is_handle( $file_handle );

require Tie::Layers;

#####
# Using support methods and file handle with
# the file subroutines such as open(), readline()
# print(), close()
#
tie *LAYERS_FILEHANDLE, 'Tie::Layers', @options
$layers = tied \*LAYERS_FILEHANDLE; 

#####
# Using support methods only, no file subroutines
# 
$layers = Tie::Layers->TIEHANDLE(@options);

$data = $layers->fin($filename, @options);

$data = $layers->fout($filename, $data, @options);

$yes = $layers->is_handle( $file_handle );

If a subroutine or method will process a list of options, @options, that subroutine will also process an array reference, \@options, [@options], or hash reference, \%options, {@options}.

DESCRIPTION

The Tie::Layers program module contains the tie file handle Tie::Layers package. The Tie::Layers package provides the ability to insert a stack of subroutines between file subroutines print and realine and the underlying $file. The syntax of the subroutines of each layer of the readline stack and the print stack must comply to the the requirements described herein below. This is necessary so that the Tie::Layers READLINE and PRINT subroutines know how to transfer the output from one layer to the input of another layer. The stacks are setup by supplying options with a reference to the subroutine for each layer in the print stack and the readline stack. The Tie::Layers are line orientated and do not support any character file subrouintes. The getc, read, and write file subroutines are supported by the Tie::Layers package. The seek routines are line oriented in that the seek and tell subroutine positions are the line in the underlying file and not the character position in the file.

options

The Tie::Layers program module subroutines/methods and the file subroutines using a filehandle that was created with a tie to 'Tie::Layers, use the following options:

option            default            description
----------------------------------------------------------------------
binary       =>   0,                 apply binmode to the $file
warn         =>   1,                 issue a warn for events
print_layers =>   [],                stack of print subroutines 
print_record =>   undef,             print to $file
read_layers  =>   [],                stack of readline subroutines
read_record  =>   \&layer_readline,  read a line from $file

readline stack

The stack for readline is setup with the read_layers and read_record options. Say the layers are numbered so that layer 0 reads a line from the underlying $file, and the line data is processing layer_1 to layer_2 and so forth to the last layer_n. The reference to the subroutine for each layer would be as follows:

read_record  => \&realine_0    #layer 0

read_layers => 
    [ \&read_layer_routine_1,  # layer 1

      # ....
                 
      \&read_layer_routine_n,  # layer n
    ];

The synopsis for the read_record and read_layers subroutine references are as follow:

$line = read_record($self);  # layer 0
$lineref = read_layer_routines($self, $lineref); # layers 1 - n

If the read_record option does not exist, the Tie::Layers methods will supply a default read_record.

Events are passed from the layer routines by as follows:

$self->{current_event} = $event;

The $lineref may be either a scalar text or any valid Perl reference. When the layer $lineref are references, the

@array = readline(LAYER)  # or
@array = <LAYER>

will return an @array of references; otherwise, they behave as usual and return an @array of text scalar lines. The added feature of allowing returns of an array of references gives layered Tie::Layers the capability to decode database files such as comma delimited variable files.

The stack for print is setup with the print_layers and print_record options. Say the layers are numbered so that layer 0 prints a line from the underlying $file, and the line data is processing from top layer_n down to layer_2 and layer_1. The reference to the subroutine for each layer would be as follows:

print_record  => \&print_0    #layer 0

print_layers => 
    [ \&print_layer_routine_1,  # layer 1

      # ....
                 
      \&print_layer_routine_n,  # layer n
    ];

If the print_record option does not exist, the Tie::Layers methods will use print to print to the underlying file.

The synopsis for the print_record and print_layers subroutine references are as follow:

$success = print_record($self, $line);  # layer 0
$lineref = print_layer_subroutine($self, $lineref); # layers 1 - n

Events are passed from the layer routines by as follows:

$self->{current_event} = $event;

The $lineref may be either a scalar text or any valid Perl reference.

open

open  (LAYERS_FILEHANDLE, $mode, $filename, @options)
open  (LAYERS_FILEHANDLE, "$mode$filename", @options)

When using a LAYERS_FILEHANDLE tied to the Tie::Layers package methods with a c<open> subroutine, the open subroutine will process Tie::Layers package @options and the $filename may be either a file name or a file handle to the underlying file.

fin

$data = $layers->fin($filename, @options);

The fin method slurps in the entire $filename using the readline stack. The $layers-{event}> returns the events from each layer in the readline stack.

fout

$success = $layers->fout($filename, $data, @options);

The fout method

is_handle

$yes = is_handle( $file_handle );

The is_handle subroutine determines whether or not $file_handle is a file handle.

DEMONSTRATION

#########
# perl Layers.d
###

~~~~~~ Demonstration overview ~~~~~

The results from executing the Perl Code follow on the next lines as comments. For example,

2 + 2
# 4

~~~~~~ The demonstration follows ~~~~~

    use File::Package;

    my $uut = 'Tie::Layers'; # Unit Under Test
    my $fp = 'File::Package';
    my $loaded;

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #
    # LAYER 2:  ENCODE/DECODE FIELDS
    #
    #~~~~~~   

    #####
    # 
    # Encodes a field a field_name, field_value pairs
    # into a scalar encoded_field. Also get a snap shot 
    # of the options.  
    #
    #
    sub encode_field
    {
        my ($self,$record) = @_;
        unless ($record) {
            $self->{current_event} = "No input\n" ;
            return undef;
        }

        return undef unless( $record);
        my @fields = @$record;

        ######
        # Record that called a stub layer
        #
        my $encoded_fields = "layer 2: encode_field\n";

        ######
        # Process the data and record it
        #
        my( $name, $data );
        for( my $i=0; $i < @fields; $i += 2) {
            ($name, $data) = ($fields[$i], $fields[$i+1]);   
            $encoded_fields .= "$name: $data\n";
        }

        #####
        # Get a snap-short of the options
        #
        my $options = $self->{options};
        foreach my $key (sort keys %$options ) {
            next if $key =~ /(print_record|print_layers|read_record|read_layers)/;
            $encoded_fields .= "option $key: $options->{$key}\n";
        }
        \$encoded_fields;
    }

    #####
    # 
    # Encodes a field a field_name, field_value pairs
    # into a scalar encoded_field. Also get a snap shot 
    # of the options.  
    #
    #
    sub decode_field
    {
        my ($self,$record) = @_;
        unless ($record) {
            $self->{current_event} = "No input\n" ;
            return undef;
        }
        $record  = "layer 2: decode_field\n" . $record;
        my @fields = split /\s*[:\n]\s*/,$record;
        return \@fields;
    }

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #
    # LAYER 1:  ENCODE/DECODE RECORD
    #
    #~~~~~~   

    #########
    # This function un escapes the record separator
    #
    sub decode_record
    {
        my ($self,$record) = @_;
        unless ($record) {
            $self->{current_event} = "No input\n" ;
            return undef;
        }
        #######
        # Unless in strict mode, change CR and LF
        # to end of line string for current operating system
        #
        unless( $self->{options}->{binary} ) {
            $$record =~ s/\015\012|\012\015/\012/g;  # replace LFCR or CRLF with a LF
            $$record =~ s/\012|\015/\n/g;   # replace CR or LF with logical \n 
        }

        "layer 1: decode_record\n" . $$record;
     } 

    #############
    # encode the record
    #
    sub encode_record
    {
        my ($self, $record) = @_;
        unless ($record) {
            $self->{current_event} = "No input\n" ;
            return undef;
        }
        my $output = "layer 1: encode_record\n" . $$record;   
        \$output;
    } 

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #
    # LAYER 0:  READ-WRITE FILE RECORD
    #
    #~~~~~~   

    #########
    # This function gets the next record from
    # the file and unescapes the record separator
    #
    sub read_record
    {
       my ($self) = @_;

       local($/);
       $/ = "\n\~-\~\n";

       my ($fh) = $self->{FH};
       $! = 0;
       my $record = <$fh>;
       unless($record) {
           $self->{current_event} = $!;
           return undef;
       }
       $record = substr($record, 0, length($record) - 4);
       $record = "layer 0: get_record\n" . $record;
       return $record;
    } 

    #######
    # append a record to the file and adding the
    # record separator
    #
    sub print_record
    {
        my ($self, $record) = @_;
        my ($fh) = $self->{FH};
        $record .= "\n" unless substr($record, -1, 1) eq "\n";
        $! = 0;
        my $success = print $fh "layer 0: put_record\n$record\~-\~\n";
        $self->{current_event} = $! unless($success);
        $success;
    }
    my (@records, $record);   # force context

##################
# Load UUT
# 

my $errors = $fp->load_package($uut)
$errors

# ''
#
    my $version = $Tie::Layers::VERSION;
    $version = '' unless $version;

##################
# Tie::Layers Version 0.03 loaded
# 

$fp->is_package_loaded($uut)

# 1
#
    tie *LAYERS, 'Tie::Layers', 
        print_record => \&print_record, # layer 0
        print_layers => [
           \&encode_record, # layer 1
           \&encode_field,  # layer 2
        ],
        read_record => \&read_record, # layer 0
        read_layers => [
           \&decode_record,  # layer 1
           \&decode_field,    # layer 2
        ];

    my $layers = tied *LAYERS;
    unlink 'layers1.txt';

##################
# open( *LAYERS,'>layers1.txt')
# 

open( \*LAYERS,'>layers1.txt')

# 1
#

##################
# print LAYERS [qw(field1 value1 field2 value2)]
# 

(print LAYERS [qw(field1 value1 field2 value2)])

# '1'
#

##################
# print LAYERS [qw(field3 value3)]
# 

(print LAYERS [qw(field3 value3)])

# '1'
#

##################
# print LAYERS [qw(field4 value4 field5 value5 field6 value6)]
# 

(print LAYERS [qw(field4 value4 field5 value5 field6 value6)])

# '1'
#

##################
# print close(LAYERS)
# 

close(LAYERS)

# 1
#
    local(*FIN);
    tie *FIN, 'Tie::Layers', 
        binary => 1,
        read_layers => [
            sub 
            {
                my ($self,$record) = @_;
                unless ($record) {
                   $self->{current_event} = "No input\n" ;
                   return undef;
                }
                #######
                # Unless in strict mode, change CR and LF
                # to end of line string for current operating system
                #
                $$record =~ s/\015\012|\012\015/\012/g;  # replace LFCR or CRLF with a LF
                $$record =~ s/\012|\015/\n/g;   # replace CR or LF with logical \n 
                $$record;
            }
        ];
    my $slurp = tied *FIN;

##################
# Verify file layers1.txt content
# 

$slurp->fin('layers1.txt')

# 'layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field1: value1
#field2: value2
#option binary: 0
#option warn: 1
#~-~
#layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field3: value3
#option binary: 0
#option warn: 1
#~-~
#layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field4: value4
#field5: value5
#field6: value6
#option binary: 0
#option warn: 1
#~-~
#'
#

##################
# open( *LAYERS,'<layers1.txt')
# 

open( \*LAYERS,'<layers1.txt')

# 1
#

##################
# readline record 1
# 

$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field1',
#          'value1',
#          'field2',
#          'value2',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# readline record 2
# 

$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field3',
#          'value3',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# readline record 3
# 

$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field4',
#          'value4',
#          'field5',
#          'value5',
#          'field6',
#          'value6',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# seek(LAYERS,0,0)
# 

seek(LAYERS,0,0)
$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field1',
#          'value1',
#          'field2',
#          'value2',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# seek(LAYERS,2,0)
# 

seek(LAYERS,2,0)
$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field4',
#          'value4',
#          'field5',
#          'value5',
#          'field6',
#          'value6',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# seek(LAYERS,-1,1)
# 

seek(LAYERS,-1,1)
$record = <LAYERS>

# [
#          'layer 2',
#          'decode_field',
#          'layer 1',
#          'decode_record',
#          'layer 0',
#          'get_record',
#          'layer 0',
#          'put_record',
#          'layer 1',
#          'encode_record',
#          'layer 2',
#          'encode_field',
#          'field3',
#          'value3',
#          'option binary',
#          '0',
#          'option warn',
#          '1'
#        ]
#

##################
# readline close(LAYERS)
# 

close(LAYERS)

# 1
#

##################
# Verify fout content
# 

$slurp->fout('layers1.txt', $test_data1);
$slurp->fin('layers1.txt')

# 'layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field1: value1
#field2: value2
#option binary: 0
#option warn: 1
#~-~
#layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field3: value3
#option binary: 0
#option warn: 1
#~-~
#layer 0: put_record
#layer 1: encode_record
#layer 2: encode_field
#field4: value4
#field5: value5
#field6: value6
#option binary: 0
#option warn: 1
#~-~
#'
#

QUALITY ASSURANCE

Running the test script Layers.t verifies the requirements for this module. The tmake.pl cover script for Test::STDmaker automatically generated the Layers.t test script, Layers.d demo script, and t::Tie::Layers Software Test Description (STD) program module POD, from the t::Tie::Layers program module contents. The tmake.pl cover script automatically ran the Layers.d demo script and inserted the results into the 'DEMONSTRATION' section above. The t::Tie::Layers program module is in the distribution file Tie-Layers-$VERSION.tar.gz.

NOTES

Author

The holder of the copyright and maintainer is

< support@SoftwareDiamonds.com >

Copyrighted (c) 2002 Software Diamonds

All Rights Reserved

Binding Requirements Notice

Binding requirements are indexed with the

pharse 'shall[dd]' where dd is an unique number for each header section. This conforms to standard federal government practices, 490A 3.2.3.6. In accordance with the License for 'Tie::Gzip', Software Diamonds is not liable for meeting any requirement, binding or otherwise.

License

Software Diamonds permits the redistribution and use in source and binary forms, with or without modification, provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

SOFTWARE DIAMONDS, http://www.softwarediamonds.com, PROVIDES THIS SOFTWARE 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTWARE DIAMONDS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING USE OF THIS SOFTWARE, EVEN IF ADVISED OF NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE POSSIBILITY OF SUCH DAMAGE.

SEE ALSO

Docs::Site_SVD::Tie_Layers
Test::STDmaker