package Archive::Builder::Section;
# A section is a tree of Archive::Builder::File's
use 5.005;
use strict;
use Scalar::Util ('refaddr');
use Params::Util ('_INSTANCE');
use Archive::Builder ();
use vars qw{$VERSION %_PARENT};
BEGIN {
$VERSION = '1.16';
%_PARENT = ();
}
#####################################################################
# Main interface methods
# A Section's only creation property is its name
sub new {
my $class = shift;
my $name = Archive::Builder->_check( 'name', $_[0] ) ? shift
: return $class->_error( 'Invalid section name format' );
# Create the object
bless {
name => $name,
path => $name,
zfiles => {},
}, $class;
}
# Get the name
sub name { $_[0]->{name} }
# Get or set the path
sub path {
my $self = shift;
return $self->{path} unless @_;
# Set the path
my $path = Archive::Builder->_relative_path($_[0]) ? shift : return undef;
$self->{path} = $path;
1;
}
# Test generate and cache all files
sub test {
my $self = shift;
# Generate each file
foreach my $File ( $self->file_list ) {
unless ( defined $File->contents ) {
return $self->_error( "Generation failed for file '" . $File->path
. "' in section '$self->{name}': "
. $File->errstr );
}
}
1;
}
# Save the entire section
sub save {
my $self = shift;
my $base = shift || '.';
# Can we write to the base location
unless ( File::Flat->canWrite( $base ) ) {
return $self->_error( "Insufficient permissions to write below $base" );
}
# Save each of the files
foreach my $File ( $self->file_list ) {
my $filename = File::Spec->catfile( $base, $File->path );
unless ( $File->save( $filename ) ) {
return $self->_error( "Failed to save file '$filename' in Section '$self->{name}'" );
}
}
1;
}
# Get the parent for the Section, if one exists
sub Builder {
$_PARENT{ refaddr $_[0] };
}
# Delete this from from its parent, and remove all our children
sub delete {
my $self = shift;
if ( $self->Builder ) {
# Remove from our parent
$self->Builder->remove_section( $self->path );
}
# Remove all our children
foreach ( $self->file_list ) {
delete $Archive::Builder::File::_PARENT{ refaddr $_ };
}
$self->{zfiles} = {};
1;
}
# If any files have been generated, flush the content cache
# so they will be generated again.
# Just pass the call down to the files.
sub reset {
foreach ( $_[0]->file_list ) {
$_->reset;
}
1;
}
# Get an Archive for just this section
sub archive {
Archive::Builder::Archive->new( $_[1], $_[0] );
}
# Get the archive content hash
sub _archive_content {
my $self = shift;
# Add from each of the Files
my %tree = ();
foreach my $File ( $self->file_list ) {
my $contents = $File->contents or return undef;
$tree{$File->path} = $contents;
}
\%tree;
}
# Get the archive mode hash
sub _archive_mode {
my $self = shift;
# Add for each file that needs an executable bit
my %tree = ();
foreach my $File ( $self->file_list ) {
$tree{$File->path} = $File->{executable} ? 0755 : 0644;
}
\%tree;
}
#####################################################################
# Working with files
# Add a new file and return it
sub new_file {
my $self = shift;
# Create the File
my $File = Archive::Builder::File->new( @_ )
or return undef;
# Add the file
$self->add_file( $File ) ? $File : undef;
}
# Add a new file
sub add_file {
my $self = shift;
my $File = _INSTANCE(shift, 'Archive::Builder::File' )
or return $self->_error( 'Did not pass a File as argument' );
# Does the file clash with an existing one
unless ( $self->_no_path_clashes( $File->path ) ) {
return $self->_error( "Bad file path: " . $self->errstr );
}
# Add the File
$self->{zfiles}->{$File->path} = $File;
# Add its parent reference
$Archive::Builder::File::_PARENT{ refaddr $File } = $self;
1;
}
# Get a copy of the hash of files
sub files { %{ $_[0]->{zfiles} } ? { %{ $_[0]->{zfiles} } } : 0 }
# Return the files as a List, sorted by file name
sub file_list {
my $files = $_[0]->{zfiles};
map { $files->{$_} } sort keys %$files;
}
# Get a single file by name
sub file { defined $_[1] ? $_[0]->{zfiles}->{$_[1]} : undef }
# Remove a single file by name
sub remove_file {
my $self = shift;
my $name = defined $_[0] ? shift : return undef;
my $File = $self->{zfiles}->{$name} or return undef;
# Delete from our files
delete $self->{zfiles}->{$name};
# Remove the parent link
delete $Archive::Builder::File::_PARENT{ refaddr $File };
1;
}
# Get a count of the number of files
sub file_count { scalar keys %{ $_[0]->{zfiles} } }
# Does a path clash with an existing path.
# A clash occurs if two paths are exactly the same,
# or a situation will occur where a file and directory
# of the same will would exist, which will fail on writing out
# to disk.
sub _no_path_clashes {
my $self = shift;
my $path = shift;
# Iterate over the file paths
foreach ( sort keys %{ $self->{zfiles} } ) {
# Are they the same.
if ( $path eq $_ ) {
return $self->_error( "The file '$path' already exists" );
}
# Does our file already exist as a directory
### THIS DOES NOT SUPPORT VMS...
### I can't decifer File::Spec::VMS well enough
my $directory_seperator = {
MacOS => ':',
Win32 => '\\',
dos => '\\'
}->{$0} || '/';
if ( $_ =~ m!^$path$directory_seperator! ) {
return $self->_error( "The file '$path' would clash with a directory of the same name" );
}
# Would the creation of our file involve a directory
# that already exists as a file
if ( $path =~ m!$_$directory_seperator! ) {
return $self->_error( "The file '$path' would create a directory that clash with an existing file '$_'" );
}
}
1;
}
#####################################################################
# Utility methods
# Pass through error
sub errstr { Archive::Builder->errstr }
sub _error { shift; Archive::Builder->_error(@_) }
sub _clear { Archive::Builder->_clear }
1;
__END__
=pod
The documentation for this class is part of L<Archive::Builder>.
=cut