The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package File::PathInfo;
use Cwd;
use Carp;
use strict;
use warnings;
require Exporter;
use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS $VERSION $DEBUG);
@ISA = qw(Exporter);
@EXPORT_OK = qw(abs_path_n);
%EXPORT_TAGS = (
	all => \@EXPORT_OK,
);
$VERSION = sprintf "%d.%02d", q$Revision: 1.27 $ =~ /(\d+)/g;

$DEBUG =0;

sub DEBUG : lvalue { $File::PathInfo::DEBUG }
$file::PathInfo::RESOLVE_SYMLINKS=1; 
sub RESOLVE_SYMLINKS : lvalue { $File::PathInfo::RESOLVE_SYMLINKS }
$File::PathInfo::TIME_FORMAT = 'yyyy/mm/dd hh::mm'; 
sub TIME_FORMAT : lvalue { $File::PathInfo::TIME_FORMAT }

___make_get_premethod( '_stat' => qw(is_binary is_dir is_text is_file filesize size ctime 
   atime ctime_pretty atime_pretty mtime_pretty filesize_pretty mtime ino rdev gid uid 
   dev blocks blksize mode nlink));

___make_get_premethod( _abs => qw(abs_path filename abs_loc ext filename_only));


sub new {
	my ($class, $self) = (shift, shift);
	$self ||= {};		
	
	my $arg;
	unless( ref $self ){
		print STDERR "arg is not a ref, treating as arg\n" if $DEBUG; # assume to be path argument
		$arg = $self;
		$self = {};	
	}	
	bless $self, $class;			

	if ($arg){ $self->set($arg) or Carp::cluck("failed set() $arg") }	
		
	$self;	
}


sub set {
	my $self= shift;
	$self->{_data} = undef;	
   my $arg = shift;
	$self->{_data}->{_argument} = $arg;	
   
	unless( $self->_abs){
      Carp::cluck("set() '$arg' is not on disk.");
      $self->{_data}->{exists} = 0 ;
      return 0;
   }  
   $self->{_data}->{exists} = 1 ;
	$self->abs_path;
}

sub _argument {
	my $self = shift;
	$self->{_data}->{_argument} or confess("you must call set() before any other methods");
	return $self->{_data}->{_argument};
}


sub _abs {
	my $self = shift;	

#	croak($self->errstr) if $self->errstr;	

	unless( defined $self->{_data}->{_abs} ){

		my $_abs = {
         abs_loc => undef,
         filename => undef,
         abs_path => undef,
         filename_only => undef,
         ext => undef,       
      };	
	   $self->{_data}->{_abs} = $_abs;
      
		my $abs_path;		
		my $argument = $self->_argument;

		
		
		# IS ARGUMENT ABS PATH ?
		if ( $argument =~/^\// ) {			

			if (RESOLVE_SYMLINKS){		
				$abs_path = Cwd::abs_path($argument);
			}

			else {
				$abs_path = abs_path_n($argument);
			}
				
			unless($abs_path){ 
				print STDERR "argument : '$argument', cant resolve with Cwd::abs_path\n" if $DEBUG;
				 return ;
			}	
		}



		# IS ARG REL TO CWD ?
		# if starts with dot.. resolve to cwd
		elsif ( $argument =~/^\.\// ){
			unless( $abs_path = Cwd::abs_path(cwd().'/'.$argument) ){
					print STDERR "argument: '$argument', "
					."cant resolve as path rel to current working dir with Cwd abs_path\n" if $DEBUG;
					return 0 ;
			}	
		}


		# IS ARG REL TO DOC ROOT ?
		else {
			### assume to be rel path then	
			unless( $self->DOCUMENT_ROOT ){
				print STDERR "argument: '$argument'- DOCUMENT_ROOT "
				."is not set, needed for an argument starting with a dot\n" if $DEBUG
				and return 0;
			}	
	
			unless( $abs_path = Cwd::abs_path($self->DOCUMENT_ROOT .'/'.$argument) ){
            print STDERR 
               "argument: '$argument' cant resolve as relative to DOCUMENT ROOT either\n" 
               if $DEBUG;
            return 0 ;
			}	
	
		}




		# set main vars
	
		$_abs->{abs_path} = $abs_path or return 0; 

	   unless (defined $self->{check_exist}){
         $self->{check_exist} = 1;
      } 
		if ($self->{check_exist}){
			unless( -e $_abs->{abs_path} ){ 
				print STDERR "'$$_abs{abs_path}' is not on disk\n" if $DEBUG;
				#$self->_error( $_abs->{abs_path} ." is not on disk.");
            ### $abs_path 
            ### is explicitely !-e on disk            
            return 0; 
			}					
		}

		$abs_path=~/^(\/.+)\/([^\/]+)$/ 
			or die("problem matching abs loc and filename in [$abs_path], ".
			"argument was [$argument] - maybe you are trying to use a path like /etc,"
			."bad juju."); # should not happen
		$_abs->{abs_loc} = $1;
		$_abs->{filename} = $2;
		if ($_abs->{filename}=~/^(.+)\.(\w{1,4})$/){
			$_abs->{filename_only} =$1;
			$_abs->{ext} = $2;
		}
		else { #may be a dir
			$_abs->{filename_only} = $_abs->{filename};	
		}
		
		$self->{_data}->{_abs} = $_abs;	
	}
	
	$self->{_data}->{_abs};
}


sub _rel {
	my $self = shift;

	croak($self->errstr) if $self->errstr;	

	unless( defined $self->{_data}->{_rel}){
		my $_rel = {
         rel_path => undef,
         rel_loc => undef,         
      };
	   $self->{_data}->{_rel} = $_rel;
      $self->DOCUMENT_ROOT or warn('cant use rel methods because DOCUMENT ROOT is not set')
			and return $_rel;
      
		my $doc_root = $self->DOCUMENT_ROOT;
		my $abs_path = $self->abs_path or return $_rel;

		if ($doc_root eq $abs_path){
			$_rel->{rel_path} = '';
			$_rel->{rel_loc} = '';			
		}

		else {
         
         unless( $self->is_in_DOCUMENT_ROOT ){ 
				warn("cant use rel methods because this file [$abs_path] is "
				."NOT WITHIN DOCUMENT ROOT:".$self->DOCUMENT_ROOT) if $DEBUG;
				return $_rel;
			}	
         
			my $rel_path = $abs_path; #  by now if it was the same as document root, should have been detected
			$rel_path=~s/^$doc_root\/// or croak("abs path [$abs_path] is NOT within DOCUMENT ROOT [$doc_root]");
	
			$_rel->{rel_path} = $rel_path;

			if ($rel_path=~/^(.+)\/([^\/]+)$/){
				my $rel_loc = $1;
				my $filename = $2;

				$filename eq $self->filename or 
					die("filename from abs path not same as filename from init rel regex, why??");
		
				$_rel->{rel_loc} = $1;	
			}
			else {
				$_rel->{rel_loc} = ''; # file is in topmost dir in doc root	
			}
		}

		$self->{_data}->{_rel} = $_rel;	
	}
	
	return $self->{_data}->{_rel};
}

___make_get_premethod( _rel => qw(rel_path rel_loc) );

sub is_topmost {
	my $self = shift;
	defined $self->DOCUMENT_ROOT or return 0;
	$self->abs_loc eq $self->DOCUMENT_ROOT or return 0;
	return 1;
}

sub is_DOCUMENT_ROOT {
	my $self = shift;	
	defined $self->DOCUMENT_ROOT or return 0;	
	$self->abs_path eq $self->DOCUMENT_ROOT or return 0;
	return 1;
}
sub is_in_DOCUMENT_ROOT {
	my $self = shift;
   $self->exists or return;
	my $abs_path = $self->abs_path;
	my $document_root = $self->DOCUMENT_ROOT;

	$abs_path=~/^$document_root\// or return 0; # the trailing slash is imperative

	return 1;
}

sub DOCUMENT_ROOT_set {
   my ($self,$abs)=@_;
   defined $abs or confess("missing argument");
   -d $abs or warn("[$abs] not a dir");
   
   $self->{_data}->{DOCUMENT_ROOT} = $abs;
   return 1;
}




sub DOCUMENT_ROOT {
	my $self = shift;	

	croak($self->errstr) if $self->errstr;

	
	unless ( defined $self->{_data}->{DOCUMENT_ROOT}){	
	
		my $abs_document_root;

		if( $self->{DOCUMENT_ROOT} ){
			$abs_document_root = Cwd::abs_path(	$self->{DOCUMENT_ROOT} ) or 
				$self->_error(" DOCUMENT_ROOT [$$self{DOCUMENT_ROOT}] does not resolve to disk") and return;
		}	

		elsif ( $ENV{DOCUMENT_ROOT} ){
			$abs_document_root = Cwd::abs_path(	$ENV{DOCUMENT_ROOT} ) or 
				$self->_error(" ENV DOCUMENT_ROOT [$ENV{DOCUMENT_ROOT}] does not resolve to disk") and return;		
		}
		
		$self->{_data}->{DOCUMENT_ROOT} = $abs_document_root;
	}	
	return $self->{_data}->{DOCUMENT_ROOT};
}


# init stat
sub _stat {
	my $self = shift;
   unless( $self->exists ){
		Carp::cluck('File::PathInfo : no file is set(). Use set().');
		return {};
	}	
	croak($self->errstr) if $self->errstr;

	unless( defined $self->{_data}->{_stat}){	

	
		my @stat =  stat $self->abs_path or die("$! - cant stat ".$self->abs_path);

		my $data = {
			is_file				=> -f _ ? 1 : 0,
			is_dir				=> -d _ ? 1 : 0,
			is_binary			=> -B _ ? 1 : 0,
			is_text				=> -T _ ? 1 : 0,		
         is_topmost			=> $self->is_topmost,
         is_document_root	=> $self->DOCUMENT_ROOT ? $self->is_DOCUMENT_ROOT : undef,
         is_in_document_root =>  $self->DOCUMENT_ROOT ? $self->is_in_DOCUMENT_ROOT : undef,		
		};
		
		my @keys = qw(dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks);
		#map { $data->{ shift @keys } = $_ } @stat; 
		for (@stat) {
		 	my $v= $_;
		 	my $key = shift @keys;		
			$data->{$key} = $v;		
		}
		
		$data->{ filesize_pretty }	= ( sprintf "%d",($data->{size} / 1024 )).'k';

      require Time::Format;      
      for my $v (qw(ctime atime mtime)){
         $data->{$v.'_pretty'} = Time::Format::time_format($self->_time_format, $data->{$v} );
      }
         
		$data->{ filesize }		= $data->{size};
	
		$self->{_data}->{_stat} = $data;		
	}

	return $self->{_data}->{_stat};	
}

sub _time_format {
   my $self = shift;
   $self->{time_format} ||= 'yyyy/mm/dd hh:mm';
   return $self->{time_format};
}


# this is to replace 
# all these :
# sub is_binary {
#	my $self = shift;
#	return $self->_stat->{is_binary};
# }
sub ___make_get_premethod {
   my $method_data = shift;   
   no strict 'refs';
   for my $method_name ( @_ ){
      *{"File\:\:PathInfo\:\:$method_name"} = sub { return $_[0]->$method_data->{$method_name} };
   }
   return;
}


sub get_datahash {
	my $data = {};	
   for my $method ( qw(_abs _rel _stat) ){      
      KEY: while( my ($k,$v) = each %{$_[0]->$method} ){
         defined $v or next KEY;
         $data->{$k} =$v;
      }
   }
	$data;	
}

sub _error { $_[0]->{_data}->{_errors}.="File::Info, $_[1]\n" }
sub errstr {
	my $self = shift;
   ($self->{_data}->{_errors} = $_[0]) if $_[0];
	$self->{_data}->{_errors}
}

sub exists {
   my $self = shift;
   defined $self->{_data}->{exists} or confess('must call set() first');      
   $self->{_data}->{exists};
}


# NON OO

sub abs_path_n {
	my $absPath = shift;
	return $absPath if $absPath =~ m{^/$};
   my @elems = split m{/}, $absPath;
   my $ptr = 1;
   while($ptr <= $#elems)
    {
        if($elems[$ptr] eq q{})
        {
            splice @elems, $ptr, 1;
        }
        elsif($elems[$ptr] eq q{.})
        {
            splice @elems, $ptr, 1;
        }
        elsif($elems[$ptr] eq q{..})
        {
            if($ptr < 2)
            {
                splice @elems, $ptr, 1;
            }
            else
            {
                $ptr--;
                splice @elems, $ptr, 2;
            }
        }
        else
        {
            $ptr++;
        }
    }
    return $#elems ? join q{/}, @elems : q{/};

	# by JohnGG 
	# http://perlmonks.org/?node_id=603442	
}




1;

# see lib/File/PathInfo.pod