################################################################################
#
#  kPerl Core Library Group
#  Library to Serve as a Path Environment Interface
#  Functions for Path Analysis/Manipulation
#
#  copyright (c)
#    Karol Szafranski, 2005, 2008-2009
#    Fritz Lipmann Institute Jena, Genome Analysis Group, 2005, 2008
#    Karol Szafranski at UPenn Philadelphia, Center for Bioinformatics, 2004
#    Institute of Molecular Biotechnology Jena, Genome Analysis, 1998-2004
#  author
#    Karol Szafranski, karol.szafranski@leibniz-fli.de
#
################################################################################
#
#  DESCRIPTION
#
# - dependencies:
#   - Some aspects of the implementation are specific for UNIX/Linux systems.
#   - dependencies for external files and executables can be seen from the
#     definition of %CorePath below.
#
# - functions in this module are in close neighborhood to functions in
#   module MainLib::File. The separating criterion for the functions
#   inside here is: The code handles path information without any concern
#   about the physical file system - simply speaking: this module should
#   mostly work if the file system is down. But, there are some exceptions
#   to this rule:
#   &PathLinkResol  queries the file system for link properties of a path
#   &PathTempdir    queries the file system for the existence of some
#                   directories
#   &PathLogdir     queries the file system for the existence of some
#                   directories
#   &PathCwd        queries the file system for the current working directory
#                   via module Cwd.pm.
#   So, overall the discrimination between the two modules is quite diffuse.
#   In summary, one can say: Mostly, functions in this module do not modify
#   files nor do they query physical file system properties.
#
# - directory paths returned from functions usually don't contain a trailing
#   slash.
#
# - individual description of functions at the beginning of the code blocks
#
################################################################################
#
#  FUNCTIONS, DATA
#
#   @EXPORT
#
# - path defs for author's script and exec paths
#   $CorePath{call}{kExec*}
#   $CorePath{call}{PerlScript}
#   $CorePath{call}{GapScript}
#
# - path defs for external code
#   $CorePath{call}{blast}
#   $CorePath{call}{glimmerM}
#   $CorePath{call}{*}
#
# - path defs for Perl / shell / GAP4 script calls
#   $CorePath{call}{Gap*}
#   $CorePath{call}{*}
#
# - path defs for sequence pools, ReadWatch concept
#   $CorePath{GSCJ}
#   $CorePath{ReadWatch}
#
# - miscellaneous path defs
#   $CorePath{motif}
#   $CorePath{statist}
#
# - internet path defs
#   $CorePath{call}{*}
#   $CorePath{mail}{*}
#   $CorePath{www}
#
# - path functions - analyse/manipulate path strings
#   &PathSplit
#   &PathChgSuffix
#
# - path functions - determine path
#   &PathLogdir
#   &PathCwd
#
# - path functions - transform path
#   &PathExpand
#   &PathLinkResol
#
################################################################################

package MainLib::Path;

# includes
use strict; #use warnings;  # OK 20071122
use Cwd;
use MainLib::Misc qw(&MySub);

# symbol export
use Exporter qw(import);
our @EXPORT = qw (
  %CorePath
  &PathSplit &PathChgSuffix
  &PathLogdir &PathCwd
  &PathExpand &PathLinkResol
  );

# package-wide constants and variables
our %CorePath;


################################################################################
# path defs for author's script and exec paths
################################################################################

# author's source code location, used for path construction
$CorePath{call}{kCode}             = &PathExpand ('~szafrans/code');

# author's exec location
$CorePath{call}{architecture}      = ($ENV{MACHINE}=~m/linux/ or $ENV{MACHTYPE}=~m/linux/)? 'linux' : 'sparc';
$CorePath{call}{kExecBin}          = ($CorePath{call}{architecture} eq 'linux')? &PathExpand ('~szafrans/linux-exec') : &PathExpand ('~szafrans/exec');
$CorePath{call}{kExecSh}           = $CorePath{call}{kExecBin};

# Perl script location
# - 1st priority: list in $ENV{PERLPATH}
# - 2nd priority: list in @INC
$CorePath{call}{PerlScript}        = ( split(/:+/,$ENV{KPERLPATH}||$ENV{PERLPATH}||''), grep{ m/\bkPerl\b/ }@INC )[0]
                                     || &PathSplit(&Cwd::realpath($0))->{dir};
$CorePath{call}{PerlScript}        = &PathExpand ($CorePath{call}{PerlScript});

# GAP4 (Tcl & shell) script location
$CorePath{call}{GapScript}         = &PathExpand ($ENV{KGAPPATH}||$ENV{GAPSCRIPTPATH}||($CorePath{call}{kCode}.'/ScriptGAP4'));

# current program
$CorePath{call}{me}                = &PathExpand ($0);
$CorePath{call}{MeInst}            = &Cwd::realpath ($0);
$CorePath{call}{MeInstDir}         = $CorePath{call}{MeInst};
  $CorePath{call}{MeInstDir}       =~ s|(.*)/.+|$1|;
  $CorePath{call}{MeInstDir}     ||= '/';
$CorePath{call}{MeInstDoc}         = "$CorePath{call}{MeInstDir}/DocExample",
$CorePath{man}{Gap}                = 'manual_Gap.txt',
$CorePath{man}{Net}                = 'manual_Net.txt',
$CorePath{man}{SeqMotif}           = 'manual_SeqMotif.txt',
$CorePath{man}{SeqHandle}          = 'manual_SeqHandle.txt',


################################################################################
# path defs for external code
################################################################################
# some more defs in sections
#   path defs for Perl / shell / GAP4 script calls
#   internet path defs

# location of biosoftware binaries
$CorePath{call}{BioBin} = '/gen/fly/bin';

# BLAST
#
our $DirNcbiBlast = $CorePath{call}{BioBin};
our $DirWublast = '/usr/local/GDE/bin';
$CorePath{call}{blast} = {
  formatdb => "$DirNcbiBlast/formatdb",
  pressdb  => "$DirWublast/pressdb",
  setdb    => "$DirWublast/setdb",
  xdformat => "$DirWublast/xdformat",
  xdget    => "$DirWublast/xdget",
  SeqType2xdSuffix => {
    nucleotide => '-n',
    protein    => '-p',
    },
  # all program indices have to be written lower-case
  'NCBI 2.0' => {
    blastn       => "$DirNcbiBlast/blastall -p blastn",
    blastn2      => "$DirNcbiBlast/blastall -p blastn",
    blastp       => "$DirNcbiBlast/blastall -p blastp",
    blastp2      => "$DirNcbiBlast/blastall -p blastp",
    blastx       => "$DirNcbiBlast/blastall -p blastx",
    blastx2      => "$DirNcbiBlast/blastall -p blastx",
    tblastn      => "$DirNcbiBlast/blastall -p tblastn",
    tblastn2     => "$DirNcbiBlast/blastall -p tblastn",
    tblastx      => "$DirNcbiBlast/blastall -p tblastx",
    tblastx2     => "$DirNcbiBlast/blastall -p tblastx",
    },
  'WU 1.4' => {
    blastn       => "$DirWublast/blastn -compat1.4",
    blastn2      => "$DirWublast/blastn -compat1.4",
    'wu-blastn'  => "$DirWublast/blastn -compat1.4",
    blastp       => "$DirWublast/blastp -compat1.4",
    blastp2      => "$DirWublast/blastp -compat1.4",
    'wu-blastp'  => "$DirWublast/blastp -compat1.4",
    blastx       => "$DirWublast/blastx -compat1.4",
    blastx2      => "$DirWublast/blastx -compat1.4",
    'wu-blastx'  => "$DirWublast/blastx -compat1.4",
    tblastn      => "$DirWublast/tblastn -compat1.4",
    tblastn2     => "$DirWublast/tblastn -compat1.4",
    'wu-tblastn' => "$DirWublast/tblastn -compat1.4",
    tblastx      => "$DirWublast/tblastx -compat1.4",
    tblastx2     => "$DirWublast/tblastx -compat1.4",
    'wu-tblastx' => "$DirWublast/tblastx -compat1.4",
    },
  'WU 2.0' => {
    blastn       => "$DirWublast/blastn",
    blastn2      => "$DirWublast/blastn",
    'wu-blastn'  => "$DirWublast/blastn",
    blastp       => "$DirWublast/blastp",
    blastp2      => "$DirWublast/blastp",
    'wu-blastp'  => "$DirWublast/blastp",
    blastx       => "$DirWublast/blastx",
    blastx2      => "$DirWublast/blastx",
    'wu-blastx'  => "$DirWublast/blastx",
    tblastn      => "$DirWublast/tblastn",
    tblastn2     => "$DirWublast/tblastn",
    'wu-tblastn' => "$DirWublast/tblastn",
    tblastx      => "$DirWublast/tblastx",
    tblastx2     => "$DirWublast/tblastx",
    'wu-tblastx' => "$DirWublast/tblastx",
    },
  'WU 2.0 lic'   => {
    blastn       => "$DirWublast/blastn",
    blastn2      => "$DirWublast/blastn",
    'wu-blastn'  => "$DirWublast/blastn",
    blastp       => "$DirWublast/blastp",
    blastp2      => "$DirWublast/blastp",
    'wu-blastp'  => "$DirWublast/blastp",
    blastx       => "$DirWublast/blastx",
    blastx2      => "$DirWublast/blastx",
    'wu-blastx'  => "$DirWublast/blastx",
    tblastn      => "$DirWublast/tblastn",
    tblastn2     => "$DirWublast/tblastn",
    'wu-tblastn' => "$DirWublast/tblastn",
    tblastx      => "$DirWublast/tblastx",
    tblastx2     => "$DirWublast/tblastx",
    'wu-tblastx' => "$DirWublast/tblastx",
    },
  };

# glimmerM
$CorePath{glimmerM} = {
  home  => '/gen/fly/biosw/glimmerm',
  call  => 'glimmerm_SunOS',
  delta => 'orfs',
  };

# miscellaneous - bio software
$CorePath{call}{clustalw}          = '/usr/local/GDE/bin/clustalw';
$CorePath{call}{est2genome}        = 'est_genome';
$CorePath{call}{gap}               = 'gap4.5';
$CorePath{call}{Gap42}             = 'gap4.2';
$CorePath{call}{HmmAlign}          = '/usr/local/GDE/bin/hmmalign';
$CorePath{call}{HmmBuild}          = '/usr/local/GDE/bin/hmmbuild';
$CorePath{call}{HmmSearch}         = '/usr/local/GDE/bin/hmmsearch';
$CorePath{call}{makelogo}          = '/gen/fly/biosw/Delila/bin/makelogo';
$CorePath{call}{SampleName}        = 'getABISampleName';
$CorePath{call}{ReptMasker}        = 'RepeatMasker';
$CorePath{call}{tRNA}              = "$CorePath{call}{BioBin}/tRNAscan-SE";

# miscellaneous
$CorePath{call}{edit}              = $ENV{EDITOR} || 'nedit';
$CorePath{call}{grep}              = '/bin/grep';
$CorePath{call}{egrep}             = '/bin/egrep';
$CorePath{call}{gzip}              = '/usr/local/bin/gzip';
$CorePath{call}{gunzip}            = '/usr/local/bin/gunzip';
$CorePath{call}{ps}                = '/usr/bin/ps';
$CorePath{call}{rm}                = '/usr/bin/rm';
$CorePath{call}{ziptest}           = '/usr/local/bin/zip -T';


################################################################################
# path defs for Perl / shell / GAP4 script calls
################################################################################
# sorted thematically

# GAP4 script calls
#$CorePath{call}{GapAddBlast}       = $CorePath{call}{GapScript}   .'/'. 'GapAddBlast.sh';
#$CorePath{call}{GapAddRev}         = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -AddRev';
$CorePath{call}{GapAssembDirect}   = $CorePath{call}{GapScript}   .'/'. 'assembledirected.tcl';
$CorePath{call}{GapAssembNew}      = $CorePath{call}{GapScript}   .'/'. 'assemblenew.tcl';
$CorePath{call}{GapAssembPreass}   = $CorePath{call}{GapScript}   .'/'. 'assemblepreass.tcl';
$CorePath{call}{GapAssembShotgun}  = $CorePath{call}{GapScript}   .'/'. 'assembleshotgun.tcl';
$CorePath{call}{GapAssembTricky}   = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -AssembleTricky';
$CorePath{call}{GapContigIds}      = $CorePath{call}{GapScript}   .'/'. 'contigids.tcl';
$CorePath{call}{GapContigList}     = $CorePath{call}{GapScript}   .'/'. 'listcontigs.tcl';
$CorePath{call}{GapContigMv}       = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -ContigMv';
$CorePath{call}{GapContigMvSafe}   = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -ContigMv -solution=DelSrcSing,DelTgtSing,cancel';
$CorePath{call}{GapContigPlus}     = $CorePath{call}{GapScript}   .'/'. 'contigplus.tcl';
$CorePath{call}{GapContigReads}    = $CorePath{call}{GapScript}   .'/'. 'contigreads.tcl';
$CorePath{call}{GapContigRevcompl} = $CorePath{call}{GapScript}   .'/'. 'contigrevcompl.tcl';
$CorePath{call}{GapDbCopy}         = $CorePath{call}{GapScript}   .'/'. 'dbcopy.tcl';
$CorePath{call}{GapDbCopyPhys}     = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -cp -solution=overwrite';
$CorePath{call}{GapDbInfo}         = $CorePath{call}{GapScript}   .'/'. 'dbinfo.tcl';
$CorePath{call}{GapExec}           = $CorePath{call}{GapScript}   .'/'. 'execute.tcl';
$CorePath{call}{GapExportCons}     = $CorePath{call}{GapScript}   .'/'. 'exportconsensus.tcl -c 2';
$CorePath{call}{GapExportDirect}   = $CorePath{call}{GapScript}   .'/'. 'exportdirected.tcl';
$CorePath{call}{GapExportPreass}   = $CorePath{call}{GapScript}   .'/'. 'exportpreass.tcl';
$CorePath{call}{GapIndex}          = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -index';
$CorePath{call}{GapPairReport}     = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -PairReport';
$CorePath{call}{GapReadDel}        = $CorePath{call}{GapScript}   .'/'. 'readdisassemble.tcl -r';
$CorePath{call}{GapReadDisass}     = $CorePath{call}{GapScript}   .'/'. 'readdisassemble.tcl';
$CorePath{call}{GapReadList}       = $CorePath{call}{GapScript}   .'/'. 'listreads.tcl';
#$CorePath{call}{GapReadProvide}    = $CorePath{call}{PerlScript}  .'/'. 'GscjGap.pl -provide';
$CorePath{call}{GapReadRename}     = $CorePath{call}{GapScript}   .'/'. 'readrename.tcl';
$CorePath{call}{GapReadSingle}     = $CorePath{call}{GapScript}   .'/'. 'readsingles.tcl';
$CorePath{call}{GapScfs}           = $CorePath{call}{GapScript}   .'/'. 'listscfs.tcl';
$CorePath{call}{GapScf2}           = $CorePath{call}{GapScript}   .'/'. 'listscf2.tcl';
$CorePath{call}{GapSeq}            = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -seq';
$CorePath{call}{GapShowData}       = $CorePath{call}{GapScript}   .'/'. 'showdata.tcl';
$CorePath{call}{GapShowRelat}      = $CorePath{call}{GapScript}   .'/'. 'showrelationships.tcl';

# miscellaneous script calls
$CorePath{call}{AlnK}              = $CorePath{call}{PerlScript}  .'/'. 'Align.pl -script';
$CorePath{call}{AlnSimulPhyl}      = $CorePath{call}{PerlScript}  .'/'. 'Phylon.pl -AlnSimulPhyl';
$CorePath{call}{BlastWrap}         = $CorePath{call}{PerlScript}  .'/'. 'Blast.pl';
$CorePath{call}{BlastDB}           = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -BlastDB';
$CorePath{call}{Convert}           = $CorePath{call}{PerlScript}  .'/'. 'Convert.pl';
$CorePath{call}{DAQualAdjust}      = $CorePath{call}{PerlScript}  .'/'. 'Gap.pl -QualAdjust';
$CorePath{call}{DATagReduce}       = $CorePath{call}{PerlScript}  .'/'. 'GscjGap.pl -TagReduce';
$CorePath{call}{GffToExper}        = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -GffToExper';
$CorePath{call}{Misc}              = $CorePath{call}{PerlScript}  .'/'. 'Misc.pl';
$CorePath{call}{SeqBreakAssemb}    = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -BreakIntoAssembly';
$CorePath{call}{SeqCat}            = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -cat';
$CorePath{call}{SeqConcat}         = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -concat';
$CorePath{call}{SeqID}             = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -ListID';
$CorePath{call}{SeqMotif}          = $CorePath{call}{PerlScript}  .'/'. 'SeqMotif.pl -SearchMotif';
$CorePath{call}{SeqOrf}            = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -orf';
$CorePath{call}{SeqRandomize}      = $CorePath{call}{PerlScript}  .'/'. 'SeqMotif.pl -randomize';
$CorePath{call}{SeqStat}           = $CorePath{call}{PerlScript}  .'/'. 'SeqHandle.pl -stat';
$CorePath{call}{StatGaussMcarlo}   = $CorePath{call}{PerlScript}  .'/'. 'Statist.pl -GaussMcarlo';

# binaries from author's workshop
$CorePath{call}{LowCplx}           = $CorePath{call}{kExecBin}    .'/'. 'LowCplx';
$CorePath{call}{nblock_split}      = $CorePath{call}{kExecBin}    .'/'. 'nblock -f split';
$CorePath{call}{PWMscanNt}         = $CorePath{call}{kExecBin}    .'/'. 'PWMscanNt';
$CorePath{call}{PWMscanProt}       = $CorePath{call}{kExecBin}    .'/'. 'PWMscanProt';
$CorePath{call}{ReapEgn}           = '/gen/fly/biosw/reap/reap_egn.csh';
$CorePath{call}{RunPWMnt}          = $CorePath{call}{kExecBin}    .'/'. 'RunPWMnt';
$CorePath{call}{RunPWMprot}        = $CorePath{call}{kExecBin}    .'/'. 'RunPWMprot';


################################################################################
# path defs for sequence pools, ReadWatch concept
################################################################################

$CorePath{GSCJ} = {
  DictyBaylor   => '/gen/links/dicty/SeqBaylor/current.fa',
  DictyHome     => '/gen/links/dicty/DictyDB',
  DictyPre      => 'dicty.pre',
  DictyReads    => 'dicty.reads',
  DictyGenbank  => '/gen/links/dicty/SeqGenbank/current.fa',
  ExperHome     => '/gen/fox/pro',
  RawHome       => '/gen/bull/raw',
  RawReapSub    => '.read_names',
  RawReapList   => '.all_reads',
  };

$CorePath{ReadWatch} = {
  home          => '/gen/links/dicty/ReadWatch',
  ReadWatchXmpl => 'ReadWatch.rc',
  CloneLenRc    => 'CloneLen/CloneLen.rc',
  CloneLenXmpl  => 'CloneLen.rc',
  ClusterRc     => 'cluster/Cluster.rc',
  TgtSpecifRc   => 'TgtSpecif/TgtSpecif.rc',
  TgtSpecifXmpl => 'TgtSpecif.rc',
  #-----------------------------------------
  # Dictyostelium-specific parameters taking effect in GscjRead.pl, both as
  # defaults and constants. It would be useful to isolate these to an extern
  # (customisable) rc file.
  ExperGroup    => 'Dictyostelium',
  ExperEdited   => '/gen/links/dicty/edited',
  ExperIndex    => '/gen/links/dicty/DictyDB/IndexExper.tab',
  ExperTarget   => [ qw(AX4 PCR transpos Chr1 sdic2 Chr2 Chr3 IIA IIC III sdi45 sdic6 DY3142 DY3167 DY3307 DY3817 DY3850) ],
  GapHome       => '/gen/links/dicty/gap',
  GapMultiCopy  => '/gen/links/dicty/gap/MultiCopy',
  GapTranspos   => '/gen/links/dicty/gap/transpos',
  FoidExtra     => '/gen/links/dicty/DictyDB/contamextra.foid',
  FoidTranspos  => '/gen/links/dicty/DictyDB/contamtranspos.foid',
  FoidQual      => '/gen/links/dicty/DictyDB/contamqual.foid',
  };
$CorePath{ReadWatch}{ExperGroupDir} = $CorePath{GSCJ}{ExperHome} .'/'. $CorePath{ReadWatch}{ExperGroup};
$CorePath{ReadWatch}{RawGroupDir} = $CorePath{GSCJ}{RawHome} .'/'. $CorePath{ReadWatch}{ExperGroup};


################################################################################
# miscellaneous path defs
################################################################################


# motif resources
#
# DESCRIPTION
# - environment variable 'MOTIFPATH' contains the search paths
#   for motif library files. A motif library will be searched either there
#   or in the current working directory.
#
$CorePath{motif} = {
  lib      => [ split(/:+/,$ENV{MOTIFPATH}||'') ],
  restric  => 'restric.mtf',
  };
@{$CorePath{motif}{lib}} or
  $CorePath{motif}{lib} = [ "$CorePath{call}{PerlScript}/SeqLab/motif" ];


# statistics resources
#
$CorePath{statist}{GaussQuantPlot} = "$CorePath{call}{PerlScript}/Math/GaussQuant.tab";


################################################################################
# internet path defs
################################################################################

# internet transfer
$CorePath{call}{mailx}             = '/usr/bin/mailx';
$CorePath{mail}{AddrAdmin}         = 'szafrans@imb-jena.de';
$CorePath{mail}{AddrFromDft}       = $CorePath{mail}{AddrAdmin};
$CorePath{mail}{sFromDft}          = "kPerl admin <$CorePath{mail}{AddrFromDft}>";
$CorePath{mail}{AddrToDft}         = $CorePath{mail}{AddrAdmin};
$CorePath{mail}{sToDft}            = "kPerl admin <$CorePath{mail}{AddrToDft}>";


# NIH, NCBI
$CorePath{www}{NCBI} = {
  host     => 'www.ncbi.nlm.nih.gov',
  COG      => '/COG/old/xognitor.cgi',
  RetrvAsn => '/entrez/viewer.fcgi?cmd=&txt=1&save=0&cfm=&db=&view=asn&list_uids=',
  RetrvEst => '/entrez/viewer.fcgi?cmd=&txt=1&save=0&cfm=&view=est&list_uids=',
  RetrvFa  => '/entrez/viewer.fcgi?cmd=&txt=1&save=0&cfm=&db=&view=fasta&list_uids=',
  RetrvGb  => '/entrez/viewer.fcgi?cmd=&txt=1&save=0&cfm=&db=&view=genbank&list_uids=',
  RetrvGi  => '/entrez/viewer.fcgi?cmd=&txt=1&save=0&cfm=&db=&view=gi&list_uids=',
  };

# IMB Jena
$CorePath{www}{IMB} = {
  host       => 'genome.imb-jena.de',
  CgiEnviron => '/cgi-bin/dicty/environ.cgi',
  };


################################################################################
# path functions - analyse/manipulate path strings
################################################################################


# split file path to fields
#
# INTERFACE
# - argument 1: file path
# - return val: hash reference:
#               dir       path of directory without ending '/'
#                         - relative directory paths are not expanded
#                         - missing path is given by '.' (= cwd)
#                         - root path is given by '/'
#               dirstmt   path contains explicit directory statement:
#                         string of directory statement
#               name      full filename
#               nameroot  filename without suffix
#               suffix    filename suffix or suffices without leading '.'
#
sub PathSplit {
  my ($path, %opt) = @_;
  my ($pField);

  # split to fields
  $path =~ m|^(.*/)?(\.?[^.]+)(\..*)?$|;
  $pField = {
    dirstmt  => $1||'',
    name     => $2 . ($3||''),
    nameroot => $2,
    suffix   => (defined($3) and length($3)) ? substr($3,1) : '',
    };

  # refine directory information
  $$pField{dir} = $$pField{dirstmt}||'';
  $$pField{dir} =~ s|/*$||;
  unless (length ($$pField{dir})) {
    if ($path =~ m|/|) {
      $$pField{dir} = '/';
    } else {
      $$pField{dir} = '.';
    }
  }

  return $pField;
}


# change suffix of a filename
#
# INTERFACE
# - argument 1: given path string
#   argument 2: (new) path string suffix. May or may not include a preceding
#               '.'. An empty string or undef here causes removal of an existing
#               file suffix.
#
# - options:
#   -last       replace only last filename suffix segment if there're more
#               than one.
#   -old        regular expression to match old filename suffix
#
# - return:     new complete filename, path as in argument 1
#
sub PathChgSuffix {
  my ($sPath, $suffix, %opt) = @_;

  # work out suffix to use
  if ($suffix and $suffix !~ m/^_/) {
    $suffix =~ s/(^\.)?(\w*)$/\.$2/;
  }

  # remove current ending
  if (0) {
  } elsif ($opt{-old}) {
    my $sfxold = $opt{-old};
    if ($sPath=~m/$sfxold$/ and $`) { $sPath = $` }
  } elsif ($opt{-last}) {
    if ($sPath=~m/\.\w*$/ and $`) { $sPath = $` }
  } else {
    if ($sPath=~m/\.[\w\.]*$/ and $`) { $sPath = $` }
  }

  # append new suffix
  $sPath .= $suffix;

  return $sPath;
}


################################################################################
# path functions - determine path
################################################################################


# return path of LOG directory
#
# INTERFACE
# - return val: - LOG directory path, string without trailing slash
#               - undef if an error occurred
#
sub PathLogdir {
  my ($PathLogdir);

  # determine path, confirm existence
  $PathLogdir = (grep { $_ and -d $_ }
    $ENV{LOGPATH}, "$ENV{HOME}/log", '/tmp'
    )[0];
  unless ($PathLogdir and -d $PathLogdir) {
    print  STDERR "WARNING: unable to determine LOG directory\n";
    return undef;
  }
  unless (-w $PathLogdir) {
    print  STDERR "WARNING: no write permission on LOG directory\n";
    return undef;
  }

  # expand path
  $PathLogdir = &PathExpand ($PathLogdir);

  return $PathLogdir;
}


# return path of current working directory
#
# INTERFACE
# - return val: - CWD path, string without trailing slash
#               - undef if an error occurred
#
sub PathCwd {
  my ($PathCwd);

  # determine path, confirm existence
  $PathCwd = &cwd();
  unless (-d $PathCwd) { return undef }

  # prepare nice path
  $PathCwd = &PathExpand ($PathCwd);

  return $PathCwd;
}


################################################################################
# path functions - transform path
################################################################################


# expand path to absolute path
#
# INTERFACE
# - argument 1: path string
#
# - options:
#   -cwd        use this path instead of asking for cwd
#   -debug      [STD]
#   -pipe       expand path string "-" to stream symbol. Option argument states
#               pipe direction:
#               >  output => stdout
#               <  input => stdin
#
# - return val: - expand path string, directory string without trailing slash
#               - undef if an error occurs
#
# DESCRIPTION
# - success of this function does not depend on access to the file system,
#   with exception of the [~login] feature
# - what's performed:
#   - resolve relative path to absolute path
#   - resolve home directories
#   - don't follow links
#   - work out nice string for final path specification
#     single slashed delimiting subdirectories
#     no slash at the end of a directory path
#
sub PathExpand {
  my ($ArgPath,%opt) = @_;
  my $debug = $opt{-debug};
  my $sPath=$ArgPath;
  unless (length($sPath)) { return undef }

  # replace "-" by stream symbol
  if ($opt{-pipe} and $sPath eq '-') {
    return ($opt{-pipe} eq '<') ? 'stdin' : 'stdout';
  }

  # extend home directory
  $sPath =~ s<^~/><$ENV{HOME}/>;
  $sPath =~ s<^~(\w+)/><do{my $u=(getpwnam($1))[7]; $u?$u.'/':$&}>e;

  # extend current directory
  if ($sPath !~ m<^/>) {
    $sPath = ($opt{-cwd}||&PathCwd()) . "/$sPath";
  }

  # nice path string
  $sPath =~ s</+></>g;
  $sPath =~ s</$><>;

  # resolve relative paths
  while ($sPath =~ s</\.(/|$)></>g) { }
  while ($sPath =~ s</[^/]+/\.\.(/|$)></>g) { }

  $debug and printf STDERR "%s. expanded %s -> %s\n", &MySub,
    $ArgPath||"''", $sPath||"''";
  return $sPath;
}


# resolve symbolic link to physical path
#
# INTERFACE
# - argument 1: path string
#               may be a link or not
#
# - options:
#   -debug      [STD]
#
# - return val: - path string that surely represents a physical path
#                 exception: skip looped links
#               - undef if an error occurred
#
# DESCRIPTION
# - this function is strictly machine-dependent as it defines '/' as the
#   path subentry delimiter.
#
sub PathLinkResol {
  my ($ArgPath,%opt) = @_;
  my $debug = $opt{-debug};

  # grow path via adding split by split
  my @PathSplit = split (/\/+/, $ArgPath);
  my ($sPath,%PathOK,%PathLinkDone);
  while (defined (my $ItSplit = shift @PathSplit)) {
    $sPath .= (defined($sPath) ? '/':'') . $ItSplit;
    if (!length($sPath) or $PathOK{$sPath}) { next }
    $debug and printf STDERR "%s. current path %s\n", &MySub, $sPath;

    # found link
    if (-l $sPath) {

      # looped link - unable to resolve
      if ($PathLinkDone{$sPath}) {
        $debug and printf STDERR "%s. skipped looped link %s\n", &MySub, $sPath;
        $sPath =~ s<^(.+)/[^/]+$><$1>;
        next;
      }

      # resolve link
      $PathLinkDone{$sPath} = 1;
      my $PathLinkTgt = readlink ($sPath);
      $sPath = &PathExpand ($PathLinkTgt, -cwd=>&PathSplit($sPath)->{dir});

      # restart split/resolution
      unshift @PathSplit, split(/\/+/,$sPath);
      undef $sPath;
      next;
    }

    # path is physical until now
    $PathOK{$sPath} = 1;
  }

  return $sPath;
}


1;
