package File::Spec::Link;
use strict;
use warnings;
use File::Spec ();
use base q(File::Spec);
our $VERSION = 0.073;
# over-ridden class method - just a debugging wrapper
#
sub canonpath {
my($spec, $path) = @_;
return $spec->SUPER::canonpath($path) if $path;
require Carp;
Carp::cluck( "canonpath: ",
defined $path ? "empty path" : "path undefined"
);
return $path;
}
sub catdir { my $spec = shift; return @_ ? $spec->SUPER::catdir(@_) : $spec->curdir }
# new class methods - implemented via objects
#
sub linked {
my $self = shift -> new(@_);
return unless $self -> follow;
return $self -> path;
}
sub resolve {
my $self = shift -> new(@_);
return unless $self -> resolved;
return $self -> path;
}
sub resolve_all {
my $self = shift -> new(@_);
return unless $self -> resolvedir;
return $self -> path;
}
sub relative_to_file {
my($spec, $path) = splice @_, 0, 2;
my $self = $spec -> new(@_);
return unless $self -> relative($path);
return $self -> path;
}
sub chopfile {
my $self = shift -> new(@_);
return $self -> path if length($self -> chop);
return
}
# other new class methods - implemented via Cwd
#
sub full_resolve {
my($spec, $file) = @_;
my $path = $spec->resolve_path($file);
return defined $path ? $path : $spec->resolve_all($file);
}
sub resolve_path {
my($spec, $file) = @_;
my $path = do {
local $SIG{__WARN__} = sub {
if ($_[0] =~ /^opendir\b/ and
$_[0] =~ /\bNot\s+a\s+directory\b/ and
$Cwd::VERSION < 2.18 and
not -d $file)
{
warn <<WARN;
Cwd::abs_path() only works on directories, not: $file
Use Cwd v2.18 or later
WARN
}
else {
warn $_[0]
}
};
eval { require Cwd } && Cwd::abs_path($file)
};
return unless $path;
return $spec->file_name_is_absolute($file)
? $path : $spec->abs2rel($path);
}
# old class method - not needed
#
sub splitlast {
my $self = shift -> new(@_);
my $last_path = $self -> chop;
return ($self -> path, $last_path);
}
# object methods:
# constructor methods new
# access methods path, canonical, vol, dir
# updating methods add, pop, push, split, chop
# relative, follow, resolved, resolvedir
sub new {
my $self = bless { }, shift;
$self -> split(shift) if @_;
return $self;
}
sub path {
my $self = shift;
return $self -> catpath( $self->vol, $self->dir, q{} );
}
sub canonical { my $self = shift; return $self -> canonpath( $self -> path ); }
sub vol { my $vol = shift->{vol}; return defined $vol ? $vol : q{} }
sub dir { my $self = shift; return $self -> catdir( $self -> dirs ); }
sub dirs { my $dirs = shift->{dirs}; return $dirs ? @{$dirs} : () }
sub add {
my($self, $file) = @_;
if( $file eq $self -> curdir ) { }
elsif( $file eq $self -> updir ) { $self -> pop }
else { $self -> push($file); }
return;
}
sub pop {
my $self = shift;
my @dirs = $self -> dirs;
if( not @dirs or $dirs[-1] eq $self -> updir ) {
push @{$self->{dirs}}, $self -> updir;
}
elsif( length $dirs[-1] and $dirs[-1] ne $self -> curdir) {
CORE::pop @{$self->{dirs}}
}
else {
require Carp;
Carp::cluck( "Can't go up from ",
length $dirs[-1] ? $dirs[-1]: "empty dir"
);
}
return;
}
sub push {
my $self = shift;
my $file = shift;
CORE::push @{$self->{dirs}}, $file if length $file;
return;
}
sub split {
my($self, $path) = @_;
my($vol, $dir, $file) = $self->splitpath($path, 1);
$self->{vol} = $vol;
$self->{dirs} = [ $self->splitdir($dir) ];
$self->push($file);
return;
}
sub chop {
my $self = shift;
my $dirs = $self->{dirs};
my $file = '';
while( @$dirs ) {
last if @$dirs == 1 and not length $dirs->[0]; # path = '/'
last if length($file = CORE::pop @$dirs);
}
return $file;
}
sub follow {
my $self = shift;
my $path = $self -> path;
my $link = readlink $self->path;
return $self->relative($link) if defined $link;
require Carp;
Carp::confess(
"Can't readlink ", $self->path,
" : ",
(-l $self->path ? "but it is" : "not"),
" a link"
);
}
sub relative {
my($self, $path) = @_;
unless( $self->file_name_is_absolute($path) ) {
return unless length($self->chop);
$path = $self->catdir($self->path, $path);
}
# what we want to do here is just set $self->{path}
# to be read by $self->path; but would need to
# unset $self->{path} whenever it becomes invalid
$self->split($path);
return 1;
}
sub resolved {
my $self = shift;
my $seen = @_ ? shift : {};
while( -l $self->path ) {
return if $seen->{$self->canonical}++;
return unless $self->follow;
}
return 1;
}
sub resolvedir {
my $self = shift;
my $seen = @_ ? shift : {};
my @path;
while( 1 ) {
return unless $self->resolved($seen);
my $last = $self->chop;
last unless length $last;
unshift @path, $last;
}
$self->add($_) for @path;
return 1;
}
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
File::Spec::Link - Perl extension for reading and resolving symbolic links
=head1 SYNOPSIS
use File::Spec::Link;
my $file = File::Spec::Link->linked($link);
my $file = File::Spec::Link->resolve($link);
my $dirname = File::Spec::Link->chopfile($file);
my $newname = File::Spec::Link->relative_to_file($path, $link);
my $realname = File::Spec::Link->full_resolve($file);
my $realname = File::Spec::Link->resolve_path($file);
my $realname = File::Spec::Link->resolve_all($file);
=head1 DESCRIPTION
C<File::Spec::Link> is an extension to C<File::Spec>, adding methods for
resolving symbolic links; it was created to implement C<File::Copy::Link>.
=over
=item C<< linked($link) >>
Returns the filename linked to by C<$link>: by C<readlink>ing C<$link>,
and resolving that path relative to the directory of C<$link>.
=item C<< resolve($link) >>
Returns the non-link ultimately linked to by C<$link>, by repeatedly
calling C<linked>. Returns C<undef> if the link can not be resolved.
=item C<< chopfile($file) >>
Returns the directory of C<$file>, by splitting the path of C<$file>
and returning (the volumne and) directory parts.
=item C<< relative_to_file($path, $file) >>
Returns the path of C<$path> relative to the directory of file
C<$file>. If C<$path> is absolute, just returns C<$path>.
=item C<< resolve_all($file) >>
Returns the filename of C<$file> with all links in the path resolved,
wihout using C<Cwd>.
=item C<< full_resolve($file) >>
Returns the filename of C<$file> with all links in the path resolved.
This sub tries to use C<Cwd::abs_path> via C<< ->resolve_path >>.
=item C<< resolve_path($file) >>
Returns the filename of C<$file> with all links in the path resolved.
This sub uses C<Cwd::abs_path> and is independent of the rest of
C<File::Spec::Link>.
=back
=head2 Object methods
=over 4
=item C<< new([$path]) >>
create new path object: stores path as a list
=item C<< path >>
returns path as a string, using catpath
=item C<< canonical >>
returns canonical path, using canonpath
=item C<< vol >>
returns volume element of path, see File::Spec->splitpath
=item C<< dir >>
returns directory element of path, as a string, see File::Spec->splitpath
=item C<< dirs >>
return list of directory components in path, see File::Spec->splitdir
=item C<< pop >>
remove last component of the path
=item C<< push($file) >>
add a file component to the path, ignoring empty strings
=item C<< add($file) >>
add a component to the path:
treating C<updir> as C<pop>,
and ignoring C<curdir> and empty strings
=item C<< split($path) >>
populate a path object, using splitpath
=item C<< chop >>
remove and return a file component from path,
an empty string returns means this was root dir.
=item C<< relative($path) >>
replace the path object with the supplied path,
where the new path is relative to the path object
=item C<< follow >>
follow the link, where the path object is a link
=item C<< resolved >>
resolve the path object, by repeatedly following links
=item C<< resolvedir >>
resolve the links at all component levels within the path object
=back
=head2 Other class methods
=over 4
=item C<< canonpath($path) >>
Wrapper round File::Spec::canonpath, fatal if empty input
=item C<< catdir(@dirs) >>
Wrapper round File::Spec::catdir, returns C<curdir> from empty list
=item C<< splitlast($path) >>
Get component from C<$path> (using C<chop>)
and returns remaining path and compenent, as strings.
[Not used]
=back
=head2 EXPORT
None - all subs are methods for C<File::Spec::Link>.
=head1 SEE ALSO
File::Spec(3) File::Copy::Link(3)
=head1 AUTHOR
Robin Barker, <RMBarker@cpan.org>
=head1 COPYRIGHT AND LICENSE
Copyright 2003, 2005, 2006, 2007, 2011, 2014 by Robin Barker
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
$Id: Link.pm 342 2014-06-23 18:30:53Z robin $