##   ____ __  __       _        
##  / ___|  \/  | __ _| | _____ 
##  \___ \ |\/| |/ _` | |/ / _ \
##   ___) ||  | | (_| |   <  __/
##  |____/_|  |_|\__,_|_|\_\___|
##                             
##  SMake -- Makefile generator
##
##  SMake is a powerful mechanism to generate standard Makefiles out
##  of skeleton Makefiles which only provide the essential parts.
##  The missing stuff gets automatically filled in by shared include
##  files. A great scheme to create a huge Makefile hierarchy and to
##  keep it consistent for the time of development.  The trick is
##  that it merges the skeleton and the templates in a
##  priority-driven way. The idea is taken from X Consortiums Imake,
##  but the goal here is not inherited system independency, the goal
##  is consistency and power without the need of manually maintaining
##  a Makefile hierarchy. 
##
##  Copyright (C) 1994-1997 Ralf S. Engelschall, <rse@engelschall.com>
##
##  This program is free software; it may be redistributed and/or
##  modified only under the terms of the GNU General Public License,
##  which may be found in the SMake source distribution.  Look at the
##  file COPYING. 
##  
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
##  General Public License for more details.
## 
##  smake_file.pl -- file I/O stuff
##

package file;


#   Use this function to convert a relative path to a reverse path.
#   i.e.   ./xx/abc -> ../../
#
#   SYNOPSIS: $newfilename = &file'RevPath($filename)
#
sub RevPath {
    my ($path) = @_;
    my ($cwd, $dir);

    if ($path =~ m|^/.*|) {
        $revpath = `pwd`;
    }
    else {
        push(@cwd, split(/\//, &CanonFilename(`pwd`)));
        $revpath = '';
        foreach $name (split(/\//, &CanonFilename($path))) {
            if ($name eq "..") {
                $revpath =  pop(@cwd) . ' ' . $revpath;
            }
            elsif ($name eq ".") {
                $revpath = '.' . ' ' . $revpath;
            }
            else {
                $revpath = '..' . ' ' . $revpath;
            }
        }
        $revpath =~ s|^[ ]*(.*[^ ]+)[ ]*$|\1|;
        $revpath =~ s| +|/|g;
    }
    return ($revpath);
}


#   Use this function to convert a filename to absolute path.
#
#   SYNOPSIS: $newfilename = &file'AbsFilename($filename)
#
sub AbsFilename {
    my ($filename) = @_;
    my ($cwd, $dir);

    if ($filename !~ m|^/.*|) {
        if (-d "$filename") {
            $cwd = `pwd`;
            chdir($filename);
            $filename = `pwd`;
            chdir($cwd);
        }
        else {
            $dir = $filename;
            if ($dir =~ m|^[^/]+$|) {
                $filename = "`pwd`/$filename";
            }
            else {
                $dir =~ s|^(.*)/[^/]+$|\1|;
                $cwd = `pwd`;
                chdir($dir);
                $filename = "`pwd`/$filename";
                chdir($cwd);
            }
        }
    }
    return ($filename);
}


#   Use this function to canonicalize a filename.
#
#   SYNOPSIS: $newfilename = &file'CanonFilename($filename)
#
sub CanonFilename {
    local ($filename) = @_;

    $filename =~ s|\n$||;                      # remove newline terminator

    $filename =~ s|/$||;                       # strip useless slash at end

    $filename =~ s|/\./|/|g;                   # canonicalize /ab/./xy/ -> ab/xy
    $filename =~ s|^\./||;                     # canonicalize ./xy -> xy
    $filename =~ s|/\.[/]*$||g;                # canonicalize /ab/./ -> ab

    $filename =~ s|//|/|g;                     # strip unneeded slahes even
    $filename =~ s|//|/|g;                     # strip unneeded slahes odd

    if ($filename !~ m|/\.\./\.\./|) {
        $filename =~ s|/([^/]+)/\.\./|/|g;     # canonicalize /ab/../xy/ -> /xy/
    }

    if ($filename !~ m|^\.\./\.\./|) {
        $filename =~ s|^([^/]+)/\.\./||g;      # canonicalize ab/../xy/ -> xy/
    }

    if ($filename !~ m|/\.\./\.\.[/]*$|) {
        $filename =~ s|/([^/]+)/\.\.[/]*$||g;  # canonicalize /ab/../xy -> /xy
    }

    return ($filename);
}


#   xfts -- eXtended FileTraverSe 
#   This is a modified version of the dodir() function found
#   in ``The Camel Book'' on page 56/57. This version collects
#   all files in a list instead of printing them directly. 
#   It is also sorts each subtree and has the startup bug fixed.
#   It also does a chdir() to the PWD which was active when called
#   and handles the "//<n>" notation and it canonicalises
#   the initial dir name.
#
#   <path>      only the contents of <path>, no subdir recusion
#   <path>/     only the contents of <path>, no subdir recusion
#   <path>//0   same as <path> or <path>/
#   <path>//<N> only the contents of <path> and the contents of
#               all subdirs <N> subdir-steps deep.
#   <path>//    all under path (recursive)
#
#   This functions uses two optimization tricks:
#   1. dirs without real subdirs are scanned very quick due to
#      comparison with its nlink attribute.
#   2. we traverse via chdir to each subtree relatively to avoid
#      full pathname expansion by the UNIX system.
#
#   SYNOPSIS: @filelist = &file'xfts($dirpath);
#
sub xfts {
    my ($dir, $nlink, $depth) = @_;
    my ($dev, $ino, $mode, $subcount);
    my (@flist, $name);
    my ($pwd);

    #   initialize at startup
    if ($nlink == 0) {
        #   handle //<N> notation
        chop($dir) if ($dir =~ /\n$/);        # remove newline terminator
        if ($dir =~ m|(.*)//(\d*)$|) {
            ($dir, $depth) = ($1, $2 eq '' ? -1 : $2); 
        }
        else {
            $depth = 0;
        }

        #   canonicalise remaining dirname
        $dir =~ s|/$||g;                      # strip useless slash at end
        $dir =~ s|//|/|g;                     # strip unneeded slahes even
        $dir =~ s|//|/|g;                     # strip unneeded slahes odd
        $dir =~ s|/\./|/|g;                   # canonicalize /ab/./xy/ -> ab/xy
        $dir =~ s|/([^/]+)/\.\./|/|g;         # canonicalize /ab/../xy/ -> /xy/

        #   determine current working directory
        #   taken from pwd.pl:initpwd()
        #   it is not needed to set it here for this process,
        #   but if xfts() is called again, a correct $ENV{'PWD'}
        #   will speed up a little bit.
        if ($ENV{'PWD'}) {
            local($dd,$di) = stat('.');
            local($pd,$pi) = stat($ENV{'PWD'});
            if ($di != $pi || $dd != $pd) {
                chop($ENV{'PWD'} = `pwd`);
            }
        }
        else {
            chop($ENV{'PWD'} = `pwd`);
        }

        #   remember the CWD
        $pwd = $ENV{'PWD'};
        #   change to the root-dir which should be examined
        #   and initialize the nlink variable
        chdir($dir) || die "$dir: Can't chdir to this directory\n";
        ($dev, $ino, $mode, $nlink) = stat('.');
    }

    #   read all filenames in sorted order
    opendir(DIRFP, '.') || die("$dir: $!\n");
    local(@filenames) = sort(readdir(DIRFP));
    closedir(DIRFP);

    #   step through the files... 
    @flist = ();
    if ($nlink == 2) {
        #   optimization: a directory with no real subdirs has only
        #   two directories (nlinks == 2): dot and dotdot!
        for (@filenames) {
            next if $_ eq '.';    # skip dot
            next if $_ eq '..';   # skip dotdot
            push(@flist, "$dir/$_");
        }
    }
    else {
        #   there are real subdirs...
        $subcount = $nlink - 2;
        for (@filenames) {
            next if $_ eq '.';    # skip dot
            next if $_ eq '..';   # skip dotdot
            $name = "$dir/$_";
            push(@flist, $name);

            next if $subcount == 0;   # no more subdirs

            ($dev, $ino, $mode, $nlink) = lstat($_);
            next unless -d _;     # use _ to use Perls internal knowledge

            if ($depth > 0 || $depth == -1) {
                #   change to directory and recurse into it
                chdir($_) || die "$_: Can't chdir to this directory\n";
            push(@flist, &xfts($name, $nlink, $depth - 1)) if ($depth >= 0);
            push(@flist, &xfts($name, $nlink, -1))         if ($depth == -1);
                chdir('..');
            }

            --$subcount;
        }
    }

    #    change back to the original CWD which was
    #    active when this function was called by the user
    #    this is not executed if this is a recursive call!
    chdir($pwd) if ($pwd ne '');
    
    #    return the file list
    return(@flist);
}
   
package main;
1;

#EOF#
