package File::Spec::Link; use strict; use warnings; require File::Spec; push our @ISA, qw(File::Spec); our $VERSION = 0.080; # 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 <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 is an extension to C, adding methods for resolving symbolic links; it was created to implement C. =over =item C<< linked($link) >> Returns the filename linked to by C<$link>: by Cing 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. Returns C 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. =item C<< full_resolve($file) >> Returns the filename of C<$file> with all links in the path resolved. This sub tries to use C 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 and is independent of the rest of C. =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 as C, and ignoring C 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 from empty list =item C<< splitlast($path) >> Get component from C<$path> (using C) and returns remaining path and compenent, as strings. [Not used] =back =head2 EXPORT None - all subs are methods for C. =head1 SEE ALSO File::Spec(3) File::Copy::Link(3) =head1 AUTHOR Robin Barker, =head1 COPYRIGHT AND LICENSE Copyright 2003, 2005, 2006, 2007, 2011, 2014, 2024 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$