#!/usr/local/bin/perl
#
# $Id: build-web.pl,v 1.4 2003/09/14 20:52:05 bc Exp $
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
# 
# The contents of this file are subject to the Mozilla Public License Version 
# 1.1 (the "License"); you may not use this file except in compliance with 
# the License. You may obtain a copy of the License at 
# http://www.mozilla.org/MPL/
# 
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
# 
# The Original Code is Netscape code.
#
# The Initial Developer of the Original Code is
# Netscape Corporation.
# Portions created by the Initial Developer are Copyright (C) 2001
# the Initial Developer. All Rights Reserved.
#
# Contributor(s): Bob Clary <bclary@netscape.com>
# Contributor(s): Bob Clary <http://bclary.com>
# 
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
# 
# ***** END LICENSE BLOCK *****
# usage 
# perl build-web.pl --help to get a help page
#

use File::Copy;
use File::Basename;
use Cwd;
use Pod::Usage;
use Getopt::Long;

# Global Variables

# set $DEBUG to 1 to turn on verbose debugging messages
# set $DEBUG to 0 to turn off verbose debugging messages
my $DEBUG = 0;
# @DEBUGSTACK is used by pushDEBUG and popDEBUG to set local
# debugging values.
my @DEBUGSTACK = (); 

if ($ENV{DEBUG})
{
  $DEBUG=$ENV{DEBUG};
}

# Source Files/Directories
my %SRCEPHYS;
$SRCEPHYS{dirlist} = ();
$SRCEPHYS{filelist} = ();

# Destination Files/Directories
my %DESTPHYS;
$DESTPHYS{dirlist} = ();
$DESTPHYS{filelist} = ();

my $TEMPLATE_CONTENTS_MENU = '';
my $TEMPLATE_CONTENTS_NOMENU = '';
local %WEBDIRMAP;
my %FILEDATA = ();

my $HTMLFILEREGEXP = '\.html?$';
my $TEXTFILEREGEXP = '(\.xml|\.xul|\.xsl|\.htaccess|\.txt|\.css|\.js|\.map|\.psp|\.php|\.asp|\.pl)$';
my $BINARYFILEREGEXP = '(\.gif|\.jpg|\.jpeg|\.zip|\.tar|\.gz|\.png)$';

my $HELP         = '';
my $MAKEPROG     = 'make';
my $SRCEPHYSDIR  = 'srce';
my $COPY         = 'off';
my $SSIPROCESS   = 'off';
my $CLEAN        = 'off';
my $DIRMAP       = '';
my $SRCEWEBDIR   = '';
my $DESTPHYSDIR  = '';

GetOptions('help'          => \$HELP,
           'make=s'        => \$MAKEPROG, 
           'srcephysdir=s' => \$SRCEPHYSDIR,
           'copy=s'        => \$COPY,
           'ssiprocess=s'  => \$SSIPROCESS, 
           'cleandest=s'   => \$CLEAN, 
           'dirmap=s'      => \$DIRMAP,
           'srcewebdir=s'  => \$SRCEWEBDIR,
           'destphysdir=s' => \$DESTPHYSDIR);

pod2usage(-verbose => 2) if $HELP;

my $usagemsg = '';

if (!$MAKEPROG || !$SRCEPHYSDIR)
{
  $usagemsg .= "--make and --srcephysdir must be specified\n";
}

if ($SSIPROCESS eq 'on')
{
  ;
}
elsif ($SSIPROCESS eq 'off')
{
  $SSIPROCESS = '';
}
else
{
  $usagemsg .= "--ssiprocess must be either on or off\n";
}

if ($CLEAN eq 'on')
{
  ;
}
elsif ($CLEAN eq 'off')
{
  $CLEAN = '';
}
else
{
  $usagemsg .= "--clean must be either on or off\n";
}

if ($COPY eq  'on')
{
  if ($COPY && (!$SRCEWEBDIR || !$DIRMAP || !$DESTPHYSDIR) )
  {
    $usagemsg .= "--copy requires --dirmap --srcewebdir --destphysdir\n";
  }
}
elsif ($COPY eq 'off')
{
  $COPY = '';
  if ($SSIPROCESS eq 'on' || $CLEAN eq 'on' || $DIRMAP || $SRCEWEBDIR || $DESTPHYSDIR)
  {
    $usagemsg .= "--ssiprocess=on, --clean=on, --dirmap, --srcewebdir, --destphysdir require --copy\n";
  }
}
else
{
  $usagemsg .= "--copy must be either on or off\n";
}

if ($usagemsg)
{
  $usagemsg .= "\nTry build-web.pl --help for more help\n";

  pod2usage(-message => $usagemsg, -exitstatus => 1, -verbose => 1);
}

# remove trailing / from source, destination directory names
if (substr($SRCEPHYSDIR, length($SRCEPHYSDIR)-1, 1) eq '/')
{
  chop $SRCEPHYSDIR;
}

if (substr($DESTPHYSDIR, length($DESTPHYSDIR)-1, 1) eq '/')
{
  chop $DESTPHYSDIR;
}

die "source dir $SRCEPHYSDIR is not a directory" if (! -d $SRCEPHYSDIR);
die "dest dir $DESTPHYSDIR exists and is not a directory" if (-e $DESTPHYSDIR && ! -d $DESTPHYSDIR);

# global timestamp set by the templates and include files
my $TIMESTAMP = getRealTimeStamp("$SRCEPHYSDIR/timestamp");
my $HTMLTIMESTAMPMENU = getRealTimeStamp("$SRCEPHYSDIR/template.html");
my $HTMLTIMESTAMPNOMENU = getRealTimeStamp("$SRCEPHYSDIR/template-nomenu.html");

build_srce_filelist($SRCEPHYSDIR);

debug_msg("\n-- SRCEPHYS --");
dump_loc(\%SRCEPHYS);

if ($COPY)
{
  loadWebDirMap();

  getTemplates();

  createDestDirectories();

  # remove entries in destination that are not in source
  if ($CLEAN)
  {
    build_dest_filelist($DESTPHYSDIR);

    debug_msg("\n-- DESTPHYS --");
    dump_loc(\%DESTPHYS);

    clean_dest('filelist');
    clean_dest('dirlist');
  }

  copySrceToDest('filelist');
}

if ($COPY)
{
  processMakefilePost($DESTPHYSDIR);
}
else
{
  processMakefilePost($SRCEPHYSDIR);
}

exit(0);

#
# End program
#

sub build_srce_filelist
{
  # Load the directory and file names from the Source
  # that are to be copied to Destination
  my $currpath = shift;

  _build_srce_filelist($currpath);

  @{$SRCEPHYS{filelist}}     = sort @{$SRCEPHYS{filelist}};
  @{$SRCEPHYS{dirlist}}      = sort @{$SRCEPHYS{dirlist}};
}

sub _build_srce_filelist
{
  my $currpath = shift;

  debug_msg("_build_srce_filelist(\"$currpath\")");

  my $makefile = "$currpath/Makefile.pre";
  if (-f $makefile)
  {
    $makefile =~ s/^$currpath\///;
    #pushDEBUG();
    debug_msg("_build_srce_filelist: Make path=$currpath file=$makefile");
    #popDEBUG();
    system("$MAKEPROG -C $currpath -f Makefile.pre > $currpath/$makefile.log 2>&1") == 0 or die "make $currpath/$makefile failed\n";
  }

  my $entry;

  # Load Directory Names
  if (!opendir(DIRH, $currpath))
  {
    warn "unable to open directory $currpath, $!\n";
    return;
  }
  my @tempdirlist = grep -d, map "$currpath/$_", readdir DIRH;
  closedir DIRH;

  my @currdirlist = ();

  foreach $entry (@tempdirlist)
  {
    if ($entry =~ /(\.\.?|CVS|_vti_[^\/]*)$/) 
    {
      ; # Skip ., .., any CVS or FrontPage directories
    }
    else
    {
      push @currdirlist, $entry;
    }
  }

  # Load File Names
  if (!opendir(DIRH, $currpath))
  {
    warn "unable to open directory $currpath, $!\n";
    return;
  }
  my @currfilelist    = grep -f, map "$currpath/$_", readdir DIRH;
  closedir DIRH;

  my $file;
  my $realtimestamp;
  my $othertimestamp;

  foreach $entry (@currfilelist)
  {
     $realtimestamp = getRealTimeStamp($entry);

     if ($entry =~ /NOWRAP$/)
     {
       my $file;
       open(NOWRAP, $entry) || die "Unable to open $entry, $!\n";
       while ($file = <NOWRAP>)
       {
         chomp $file;
         $file =~ s/\r//g;
         next if ($file eq '');
         $file = $currpath . '/' . $file;

         $othertimestamp = getRealTimeStamp($file);
         if ($othertimestamp > $realtimestamp)
         {
           setEffectiveTimeStamp($file, $othertimestamp);
         }
         $FILEDATA{$file}{NOWRAP} = 1;
         debug_msg("NOWRAPFILES [$file]");
       }
       close NOWRAP;
     }
     elsif ($entry =~ /NOMENU$/)
     {
       my $file;
       open(NOMENU, $entry) || die "Unable to open $entry, $!\n";
       while ($file = <NOMENU>)
       {
         chomp $file;
         $file =~ s/\r//g;
         next if ($file eq '');
         $file = $currpath . '/' . $file;

         $othertimestamp = getRealTimeStamp($file);
         if ($othertimestamp > $realtimestamp)
         {
           setEffectiveTimeStamp($file, $othertimestamp);
         }
         $FILEDATA{$file}{NOMENU} = 1;
         debug_msg("NOMENUFILES [$file]");
       }
       close NOMENU;
     }
     elsif ($SSIPROCESS && $entry =~ /\-incl.html?$/)
     {
       ;
     }
     elsif ($entry =~ /^$SRCEPHYSDIR\/(template.html|template-nomenu.html)$/)
     {
       #print "template file: $entry\n";
     }
     elsif ($entry =~ /Makefile\.(pre|post)$/)
     {
       ;
     }
     elsif ($entry =~ /(\.cvsignore|\~|\.[bB][aA][kK]|\.swp|timestamp|Makefile\.post\.log|Makefile\.pre\.log)$/)
     {
       ; # Skip .cvsignore, and backup files
     }
     else
     {
       push @{$SRCEPHYS{filelist}}, $entry;
     }
  }

  # Load the subdirectories
  foreach $entry (@currdirlist)
  {
    _build_srce_filelist($entry);
  }
  
  push @{$SRCEPHYS{dirlist}}, @currdirlist;
}

sub build_dest_filelist
{
  # Load the directory and file names from the Destination
  my $currpath = shift;

  _build_dest_filelist($currpath);

  if (defined(@{$DESTPHYS{filelist}}))
  {
    @{$DESTPHYS{filelist}}     = sort @{$DESTPHYS{filelist}};
  }
  if (defined(@{$DESTPHYS{dirlist}}))
  {
    @{$DESTPHYS{dirlist}}      = sort @{$DESTPHYS{dirlist}};
  }
}

sub _build_dest_filelist
{
  my $currpath = shift;

  debug_msg("_build_dest_filelist(\"$currpath\")");

  my $entry;

  # Load Directory Names
  if (!opendir(DIRH, $currpath))
  {
    warn "unable to open directory $currpath, $!\n";
    return;
  }
  my @tempdirlist = grep -d, map "$currpath/$_", readdir DIRH;
  closedir DIRH;

  my @currdirlist = ();

  foreach $entry (@tempdirlist)
  {
    if ($entry =~ /\.\.?$/) 
    {
      ; # Skip ., ..
    }
    else
    {
      push @currdirlist, $entry;
    }
  }

  # Load File Names
  if (!opendir(DIRH, $currpath))
  {
    warn "unable to open directory $currpath, $!\n";
    return;
  }
  my @currfilelist    = grep -f, map "$currpath/$_", readdir DIRH;
  closedir DIRH;

  foreach $entry (@currfilelist)
  {
     push @{$DESTPHYS{filelist}}, $entry;
  }

  # Load Subdirectories
  foreach $entry (@currdirlist)
  {
    _build_dest_filelist($entry);
  }
  
  push @{$DESTPHYS{dirlist}}, @currdirlist;
}

sub clean_dest
{
  # Remove entries from Destination that are not in the Source
  my $arrayname = shift;

  debug_msg("clean_dest(\"$arrayname\")");

  my $isrce;
  my $idest;
  my $srcefile;
  my $destfile;
  my $tempfile;

  $isrce = 0;
  $srcefile = $SRCEPHYS{$arrayname}[$isrce];

  foreach $destfile (@{$DESTPHYS{$arrayname}})
  {

    debug_msg("clean_dest: dest $destfile");
    debug_msg("clean_dest: srce $srcefile");

    $tempfile = $srcefile;
    $tempfile =~ s/^$SRCEPHYSDIR/$DESTPHYSDIR/o;

    while ($tempfile lt $destfile && $isrce <= $#{$SRCEPHYS{$arrayname}})
    {
      $srcefile = $SRCEPHYS{$arrayname}[$isrce++];
      $tempfile = $srcefile;
      $tempfile =~ s/^$SRCEPHYSDIR/$DESTPHYSDIR/o;
    }

    if ($tempfile gt $destfile)
    {
      if (-d $destfile)
      {
        debug_msg("clean_dest(\"$arrayname\"): rmdir $destfile");
        rmdir $destfile;
      }
      else
      {
        debug_msg("clean_dest(\"$arrayname\"): unlink $destfile");
        unlink $destfile;
      }
    }
  }
}

sub readFileIntoString 
{
  # read the contents of a file and return as a string
  my $file = $_[0];
  my $text = \$_[1];

  debug_msg("readFileIntoString(\"$file\", \"...\")");

  open(FILE, "<$file") || die "readFileIntoString: Can't open file: $file from " . cwd() . " Error: $!\n";

  $$text = join("", <FILE>);
  
  #XXXbc: Hack problem in Cygwin with embedded \r
  #$$text =~ s/\r//g;

  debug_msg("readFileIntoString(\"$file\", $$text)");

  close(FILE);
}

sub writeFileFromString 
{
  # write a file from a string
  my $file = $_[0];
  my $text = \$_[1];

  debug_msg("writeFileFromString(\"$file\", $$text)");

  #my $dirname = dirname($file);
  #if (! -d $dirname)
  #{
  #  createPath($dirname);
  #}

  open(FILE, ">$file") || die "writeFileFromString: Can't open file: $file from " . cwd() . ", Error: $!\n";
  print FILE $$text;
  close(FILE);
}

sub mapWebDirectories
{
  # remap webdirectorys
  my $str = \$_[0];
  
  debug_msg("ENTER mapWebDirectories($$str)");

  my $olddir;
  my $newdir;

  foreach $olddir (keys %WEBDIRMAP)
  {
     $newdir = $WEBDIRMAP{$olddir};
     debug_msg("map OLD $olddir to $newdir");
     $$str =~ s/$olddir/$newdir/sg;
  }
  debug_msg("EXIT mapWebDirectories($$str)");
}

sub createDestDirectories
{
  return if (!$COPY);

  # create destination directories to match the source directories
  my $srceentry;
  my $destentry;
  my $srcetimestamp;
  my $desttimestamp;

  debug_msg("createDestDirectories");

  # Create Destination Directory
  $srcetimestamp = getRealTimeStamp($SRCEPHYSDIR);

  if (! -d $DESTPHYSDIR)
  {
    debug_msg("createDestDirectories: $DESTPHYSDIR");
    mkdir $DESTPHYSDIR, 0777 || die "error mkdir $DESTPHYSDIR : $!\n";
  }
  utime $srcetimestamp, $srcetimestamp, ($DESTPHYSDIR);

  # Create Destination Subdirectories
  foreach $srceentry (@{$SRCEPHYS{dirlist}})
  {
    $srcetimestamp = getRealTimeStamp($srceentry);
  
    $destentry = $srceentry;
    $destentry =~ s/^$SRCEPHYSDIR/$DESTPHYSDIR/o;
  
    debug_msg("srceentry=$srceentry");
    debug_msg("destentry=$destentry");

    if (! -d $destentry)
    {
      debug_msg("create: $destentry");
      mkdir $destentry, 0777 || die "error mkdir $destentry : $!\n";
    }
    else
    {
      debug_msg("$destentry Exists");
    }

    utime $srcetimestamp, $srcetimestamp, ($destentry);
  }
}

sub processMakefilePost
{
  # perform Makefile.post processing
  my $srceentry;
  my $destentry;

  debug_msg("performMakefilePost");

  # Destination Subdirectories
  foreach $srceentry (@{$SRCEPHYS{dirlist}})
  {
    $destentry = $srceentry;
    if ($COPY)
    {
      $destentry =~ s/^$SRCEPHYSDIR/$DESTPHYSDIR/o;
    }
  
    debug_msg("srceentry=$srceentry");
    debug_msg("destentry=$destentry");

    if (-f "$srceentry/Makefile.post")
    {
      debug_msg("performMakefilePost directory $destentry");
      if ($COPY)
      {
        copy("$srceentry/Makefile.post", "$destentry/Makefile.post");
      }
      system("$MAKEPROG -C $destentry -f Makefile.post > $destentry/Makefile.post.log 2>&1") == 0 or die "make $destentry/Makefile.post failed\n";
      if ($COPY)
      {
        # remove the Makefile from the destination
        copy("$destentry/Makefile.post.log", "$srceentry/Makefile.post.log");
        unlink "$destentry/Makefile.post";
        unlink "$destentry/Makefile.post.log";
      }
    }
  }
}

sub copySrceToDest
{
  # Copy Files from Source Directories to Destination Directories
  my $arrayname = shift;

  #pushDEBUG();
  debug_msg("copySrceToDest(\"$arrayname\")");
  #popDEBUG();

  my $srceentry;
  my $srcetimestamp;
  my $realtimestamp;

  my $destentry;
  my $desttimestamp;

  my $filecontents = '';
  my $docopy;
  my $filetype;
  
  my $thissrcedirname = '';
  my $lastsrcedirname = '';
  my $thisdestdirname = '';
  my $lastdestdirname = '';


  foreach $srceentry (@{$SRCEPHYS{$arrayname}})
  {
    $docopy = 0;
  
    $destentry = $srceentry;
    $destentry =~ s/^$SRCEPHYSDIR/$DESTPHYSDIR/o;
  
    $thissrcedirname = dirname($srceentry);
    $thisdestdirname = dirname($destentry);

    $effectivetimestamp = getEffectiveTimeStamp($srceentry);
    $realtimestamp = getRealTimeStamp($srceentry);
    $desttimestamp = getRealTimeStamp($destentry);
  
    #print "$srceentry: TimeStamps: Effective $effectivetimestamp, Real $realtimestamp, Dest $desttimestamp\n";

    if ($thissrcedirname ne $lastsrcedirname)
    {
      if (! -d $thisdestdirname)
      {
        #pushDEBUG();
        debug_msg("copySrceToDest: creating directory $thisdestdirname");
        #popDEBUG();
        createPath($thisdestdirname);
      }
    }
    
    if ($srceentry =~ /$HTMLFILEREGEXP/oi)
    {
      $filetype = 'H';
      if ($effectivetimestamp > $desttimestamp)
      {
        # copy srce if dest / srce modification times do not agree
        # note files that not not exist will return timestamp -1
        $docopy = 1; 
      }
      elsif (defined($FILEDATA{$srceentry}{NOWRAP}))
      {
      }
      elsif (defined($FILEDATA{$srceentry}{NOMENU}))
      {
        if ($HTMLTIMESTAMPNOMENU > $desttimestamp)
        {
          # copy srce if the NOMENU template changed 
          $docopy = 1; 
        }
      }
      else
      {
        if ($HTMLTIMESTAMPMENU > $desttimestamp)
        {
          # copy srce if the MENU template changed 
          $docopy = 1; 
        }
      }
    }
    else
    {
      if ($srceentry =~ /$TEXTFILEREGEXP/oi)
      {
         $filetype = 'T';
      }
      elsif ($srceentry =~ /$BINARYFILEREGEXP/oi)
      {
         $filetype = 'B';
      }
      else
      {
         $filetype = 'B';
      }
      if ($effectivetimestamp > $desttimestamp)
      {
        # copy srce if dest / srce modification times do not agree
        # note files that not not exist will return timestamp -1
        $docopy = 1; 
      }
    }

    #pushDEBUG();
    debug_msg("copySrceToDest: $srceentry $filetype docopy=$docopy Timestamp Real $realtimestamp, Effective $effectivetimestamp Dest $desttimestamp");
    #popDEBUG();

    if ($docopy && $COPY)
    {
      #pushDEBUG();
      debug_msg("copySrceToDest: COPY $filetype $srceentry Timestamp Real $realtimestamp, Effective $effectivetimestamp Dest $desttimestamp");
      #popDEBUG();
        
      if ($filetype eq 'H')
      {
        readFileIntoString($srceentry, $filecontents);
        if ($SSIPROCESS eq 'on')
        {
          processSSI($srceentry, $filecontents);
        }
        applyHtmlTemplates($srceentry, $filecontents, $realtimestamp);
        mapWebDirectories($filecontents);
        writeFileFromString($destentry, $filecontents);
      }
      elsif ($filetype eq 'T')
      {
        readFileIntoString($srceentry, $filecontents);
        mapWebDirectories($filecontents);
        writeFileFromString($destentry, $filecontents);
      }
      elsif ($filetype eq 'B')
      {
        copy($srceentry, $destentry); # copy binary files
      }

      utime $realtimestamp, $realtimestamp, ($destentry);
    }

    $lastsrcedirname = $thissrcedirname;
    $lastdestdirname = $thisdestdirname;
  }
}
  
sub loadWebDirMap
{
  debug_msg("loadWebDirMap");

  return if (!$COPY);
  require $DIRMAP || die "failed to load $DIRMAP\n";
  if ($DEBUG)
  {
    my $key;
    foreach $key (keys %WEBDIRMAP)
    {
      print "map $key to $WEBDIRMAP{$key}\n";
    }
  }
}

sub processSSI
{
  my $filepath = $_[0];
  my $filecontents = \$_[1];

  debug_msg("ENTER processSSI(\"$filepath\", $$filecontents)");

  my $inclregexp = "<!--[ ]*#include[^>]*-->";
  my $currpath = dirname($filepath);
  my $includefile;
  my $includefilecontents;

  my (@directives) = ($$filecontents =~ /($inclregexp)/gi);

  foreach $directive (@directives)
  {
    debug_msg("$directive");

    if ($directive =~ /file=/i)
    {
       ($includefile) = ($directive =~ /file=\"([^\"]*?)\"/);
       if ($includefile =~ /^\//)
       {
         debug_msg("absolute file path"); # already absolute,... 
       }
       else
       {
         # prepend the current directory to make relative into absolute path
         debug_msg("relative file path");
         $includefile = $currpath . '/' . $includefile;
       }

       readFileIntoString($includefile, $includefilecontents);
       #debug_msg("\nBEGIN INCLUDE $includefile:\n\n$includefilecontents\nEND INCLUDE\n");
       
       $$filecontents =~ s/$directive/$includefilecontents/;
    }
    elsif ($directive =~ /virtual=/i)
    {
       ($includefile) = ($directive =~ /virtual=\"([^\"]*?)\"/);
       if ($includefile =~ /^\//)
       {
         # convert absolute virtual path to absolute physical path 
         die "absolute virtual path $includefile in file $filepath does not begin with $SRCEWEBDIR" if (!($includefile =~ /$SRCEWEBDIR/o));
         debug_msg("absolute virtual path");
         debug_msg("includefile before=$includefile");
         $includefile =~ s/^$SRCEWEBDIR/$SRCEPHYSDIR/o;
         debug_msg("includefile after=$includefile");
       }
       else
       {
         # prepend the current directory to make relative into absolute path
         debug_msg("relative virtual path");
         debug_msg("includefile before=$includefile");
         $includefile = $currpath . '/' . $includefile;
         debug_msg("includefile after=$includefile");
       }

       readFileIntoString($includefile, $includefilecontents);
       #debug_msg("\nBEGIN INCLUDE $includefile:\n\n$includefilecontents\nEND INCLUDE\n");
       $$filecontents =~ s/$directive/$includefilecontents/;
    }
    else
    {
      die "unknown SSI $directive in file $filepath\n";
    }
  }

  mapWebDirectories($$filecontents);
  debug_msg("EXIT processSSI(\"$filepath\", $$filecontents)");

}

sub applyHtmlTemplates
{
  my $filepath = $_[0];
  my $filecontents = \$_[1];
  my $filetimestamp = $_[2];
  my $template;

  debug_msg("ENTER applyHtmlTemplates(\"$filepath\", $$filecontents)");

  if ($FILEDATA{$filepath}{NOWRAP})
  {
    #pushDEBUG();
    debug_msg("applyHTMLTemplates: NOWRAP $filepath");
    #popDEBUG();
    debug_msg("applyHtmlTemplates NOWRAP $filepath");
    return;
  }

  if ($$filecontents =~ /<FRAMESET/i)
  {
    # since the directory mapping is now separate from
    # applying the html templates, do not apply templates
    # at all to framesets.
    #
    # XXXbc: this may break for pages with JS that write 
    # frameset tags out to other pages.
    #pushDEBUG();
    debug_msg("applyHTMLTemplates: FRAMESET $filepath");
    #popDEBUG();
    return;
  }

  my $filename = $filepath;
  $filename =~ s/^$SRCEPHYSDIR//o;

  if ($FILEDATA{$filepath}{NOMENU})
  {
    debug_msg("applyHtmlTemplates NOMENU $filepath");
    $template = $TEMPLATE_CONTENTS_NOMENU;
  }
  else
  {
    debug_msg("applyHtmlTemplates MENU $filepath");
    $template = $TEMPLATE_CONTENTS_MENU;
  }

  my @months = ("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
  my ($sec, $min, $hour, $mday, $mon, $year) = localtime($filetimestamp);

  $year += 1900;
  $mon = $months[$mon];

  my $last_updated = sprintf("%s %2d,  %04d", $mon, $mday, $year);

  debug_msg("$filepath last_updated=$last_updated");

  if (!$template)
  {
    $$filecontents =~ s/%%LAST_UPDATED%%/$last_updated/g;
    $$filecontents =~ s/%%FILENAME%%/$filename/g;
    return;
  }

  my $doctype      = '';
  my $head         = '';
  my $title        = '';

  $$filecontents =~ s|(<!DOCTYPE[^>]*>)||i;

  $doctype = $1;
  if (!$doctype)
  {
    $doctype = '';
  }

  $$filecontents =~ s|</?HTML[^>]*>||gomi;

  # $head now contains the contents of the HEAD tag
  # not including the containing HEAD tags. This will
  # allow the template to contain information in it's HEAD
  # and have it added to the output documents.
  $head = "";

  if ($$filecontents =~ s|(<HEAD>)(.*)(</HEAD>)||si)
  {
    $head .= $2;
  }

  # if there's still a <title> tag, that means the document didn't
  # have a proper <head> section so we'll give it one.
  if ($$filecontents =~ s|(<TITLE>[^<]*</TITLE>)||i)
  {
    $head .= $1;
  }

  #extract the title
  ($title) = $head =~ m|<TITLE>([^<]*)</TITLE>|i;
  if (!$title)
  {
    $title = '';
  }

  my ($bodytag) = ($$filecontents =~ m|(<BODY[^>]*>)|i);
 
  # if the page is a frameset with a noframes and a \
  # body inside of the noframes we don't want to treat it as
  # if it has a body, so remove it.

  if ($bodytag)
  {
    debug_msg("applyHtmlTemplates: name=$filename, replace <body> with $bodytag");

    # remove body tag 
    $$filecontents =~ s|</?BODY[^>]*>||gi;

    my ($templatebodyattr) = ($template =~ m|<BODY ([^>]*)>|i);
    my ($localbodyattr) = ($bodytag =~ m|<body ([^>]*)>|i);

    if (!$templatebodyattr)
    {
      $templatebodyattr = '';
    }

    if (!$localbodyattr)
    {
      $localbodyattr = '';
    }
       
    debug_msg("applyHtmlTemplates: templateattr=$templatebodyattr, localbodyattr=$localbodyattr\n");

    $template =~ s|<body[^>]*>|<body $templatebodyattr $localbodyattr>|i;
  }
  else
  {
    # no body tag in input file, remove from template
    debug_msg("applyHtmlTemplates: no body tag in input, remove from template\n");
    $template =~ s|</?BODY[^>]*>||goi;
  }
        
  $template =~ s/%%BODY%%/$$filecontents/;
  $$filecontents = $template;
  $template = '';

  $$filecontents =~ s/%%DOCTYPE%%/$doctype/;
  $$filecontents =~ s/%%HEAD%%/$head/;
  $$filecontents =~ s/%%TITLE%%/$title/g;
  $$filecontents =~ s/%%LAST_UPDATED%%/$last_updated/g;
  $$filecontents =~ s/%%FILENAME%%/$filename/g;

  debug_msg("EXIT applyHtmlTemplates(\"$filepath\", $$filecontents)");
}

sub getTemplates
{
  debug_msg("getTemplates");

  if (-f "$SRCEPHYSDIR/template.html")
  {
    readFileIntoString("$SRCEPHYSDIR/template.html", $TEMPLATE_CONTENTS_MENU);
    if ($SSIPROCESS eq 'on')
    {
      processSSI("$SRCEPHYSDIR/template.html", $TEMPLATE_CONTENTS_MENU);
    }
    mapWebDirectories($TEMPLATE_CONTENTS_MENU);
  }

  if (-f "$SRCEPHYSDIR/template-nomenu.html")
  {
    readFileIntoString("$SRCEPHYSDIR/template-nomenu.html", $TEMPLATE_CONTENTS_NOMENU);
    if ($SSIPROCESS eq 'on')
    {
      processSSI("$SRCEPHYSDIR/template-nomenu.html", $TEMPLATE_CONTENTS_NOMENU);
    }
    mapWebDirectories($TEMPLATE_CONTENTS_NOMENU);
  }
}

sub createPath
{
  my $dirname  = shift;
  my @pathlist = split '/', $dirname;
  my $currdir  = '';
  my $subdir;

  foreach $subdir (@pathlist)
  {
    $currdir = $currdir . $subdir . '/';

    if (! -d $currdir)
    {
      mkdir $currdir, 0777 or die "unable to create $currdir from " . cwd() . ": $!\n";
    }
  }
}

#
# time stamp stuff
#
sub getRealTimeStamp
{
  my $file = shift;
  my $timestamp = 0;

  $timestamp = $FILEDATA{$file}{realtimestamp};
  if (!$timestamp)
  {
    if (-e $file)
    {
      $timestamp = (stat $file)[9];
    }
    else
    {
      $timestamp = -1;
    }
    $FILEDATA{$file}{effectivetimestamp} = $FILEDATA{$file}{realtimestamp} = $timestamp;
  }

  return $timestamp;
}

sub getEffectiveTimeStamp
{
  my $file = shift;
  my $timestamp = 0;

  $timestamp = $FILEDATA{$file}{effectivetimestamp};
  if (!$timestamp)
  {
    if (-e $file)
    {
      $timestamp = (stat $file)[9];
    }
    else
    {
      $timestamp = -1;
    }
    $FILEDATA{$file}{effectivetimestamp} = $FILEDATA{$file}{realtimestamp} = $timestamp;
  }

  return $timestamp;
}

sub setEffectiveTimeStamp
{
  my $file = shift;
  my $timestamp = shift;
  my $effectivetimestamp = getEffectiveTimeStamp($file);

  #pushDEBUG();
  debug_msg("setEffectiveTimeStamp: file=$file, timestamp=$timestamp, effectivetimestamp=$effectivetimestamp"); 
  #popDEBUG();

  if ($timestamp > $effectivetimestamp)
  {
    $effectivetimestamp = $FILEDATA{$file}{effectivetimestamp} = $timestamp;
  }

  return $effectivetimestamp;
}

#
# debugging stuff
#
sub debug_msg 
{
  # print a debug message to STDOUT

  print "DEBUG: $_[0]\n" if ($DEBUG);
}

sub pushDEBUG
{
  push @DEBUGSTACK, $DEBUG;
  $DEBUG = 1;
}

sub popDEBUG
{
  $DEBUG = pop @DEBUGSTACK;
}

sub dump_loc
{
  if ($DEBUG)
  {
    my $locref = shift;
  
    print "\n== dirlist ==\n";
    print join "\n", @{$$locref{dirlist}};
    print "\n== filelist ==\n";
    print join "\n", @{$$locref{filelist}};
  }
}

__END__

=head1 build-web.pl

Build Web Sites 

=head1 SYNOPSIS

build-web.pl [options]

=head1 OPTIONS

=over 8

=item B<--help>

Display this help page.

=item B<--make>=I<makeprog>

Specify make program to be used. 

Default value is I<make>.

=item B<--srcephysdir>=I<srcedir>

Specify the path to the physical directory containing the raw html
to be processed.

Default value is I<srce>.

=item B<--copy>=I<[on|off]>

Write the results to the destination physical directory. 
If B<--copy>=I<off> is specified then only the pre and post
makefile processing is performed.

B<--copy>=I<on> requires that the B<--srcewebdir>, B<--destphysdir> and B<--dirmap>
options be specified.

=item B<--srcewebdir>=I<srcewebdir>

Assume the raw html contained in the srcephysdir is written
as if the root web directory is I<srcewebdir>. 

Requires B<--copy>=I<on>

=item B<--destphysdir>=I<destphysdir>

Write the results of processing the web site to the 
physical directory I<destphysdir>.

Requires B<--copy>=I<on>

=item B<--ssiprocess>=I<[on|off]>

Replace the results of Server Side includes (virtual and file only)
with the contents of the included files. Default value is I<off>.

Requires B<--copy>=I<on>

=item B<--dirmap>=I<dirmap.pl>

Replace occurences of directories using the map specified in the
I<dirmap.pl> script.

Requires B<--copy>=I<on>

=item B<--clean>=I<[on|off]>

Remove files from the destphysdir if they do not exist in the 
srcephysdir. Default value is I<off>.

Requires B<--copy>=I<on>

=back

=head1 DESCRIPTION

If B<--copy>=I<on>, build-web.pl will build a Web Site in a given Physical 
Destination directory from a Web Site contained in a given Source directory 
by copying the files from the Physical Source directory tree to the Physical
Destination directory tree while remapping directory paths as specified in 
the "DIRMAP.pl" perl program, optionally processing Server Side includes, 
and optionally applying HTML Templates to HTML files.

The Source Web Site exists in the physical directory SRCEPHYSDIR and 
is assumed to be written as it were located on a Web Server in the 
SRCEWEBDIR subdirectory of the Server's root directory.

This program essentially copies a Physical Source Directory and all subdirectories and 
files to a Destination Directory while performing the following tasks. 

If Makefiles exist in the Source Directories, they are 'executed' using the 
make program specified in B<--make>=I<makeprog>. Makefiles with the name "Makefile.pre" 
will be executed in the Physical Source directory tree before any other processing 
and can produce new files which will be copied to the Physical Destiantion directory tree.
Makefiles with the name "Makefile.post" will be executed in the Destination
directory tree and can be used to post process output files generate during 
the build... For example, zip files of the final versions of files in the
DEST directory tree can be created using post Makefiles. In the case where
B<--copy>=I<off>, the Physical Source and Destination directories are the
same and post Makefiles are executed in the Physical Source directory.

If the Destination Directories do not exist, they are created.

Source Files are not copied if their modification times are not different 
from the modification times of the existing Destination files. This rule is
broken when the template files, include files or Makefiles are modified. A 
Global timestamp is maintained on the last time the build was run. If any 
of the templates, include files, Makefiles are modified after the last build,
all files are rebuilt.

The following Source Directories are not copied: CVS, _vti_*

The following Source Files are not copied:

Makefile.pre, Makefile.post, Makefile.log, NOWRAP, NOMENU,
*-incl.html, template.html, template-nomenu.html, .cvsignore,
*~, *.bak, *.swp

If the B<--clean>=I<on> option is specified, files which exist in the 
Destination Directories but not in the Source Directories are deleted 
from the Destination Directories.

Binary Files are copied as is.

Text Files (not HTML files, i.e. *.htm or *.html) are copied with 
directory remapping as specified in "DIRMAP.pl".

The DIRMAP.pl program specifies the remapping of referenced directories 
in all Text files.  

These include *.js, *.css, *.htm, *.html, *.psp, *.php, *.asp, *.pl files.  
DIRMAP.pl, which can be named anything, consists of a single Hash which 
specifies the old=>new directory remapping as in:
   
   $WEBDIRMAP{'/fromdir/'} = '/todir/';

   Note that WEBDIRMAP must contain at least a mapping for the 
   SRCEWEBDIR to a new Web Server directory.

HTML Files are copied with directory remapping, then Server Side includes, 
then HTML Template wrapping.

Server Side includes in HTML Files are replaced by the contents of the 
included files. The included files must reside in the SRCEPHYDIR tree and 
are not copied to the Destination Directories. The Server Side includes 
may be either file or virtual, but typically virtual is the most useful.

HTML Templates are specified in special files in the SRCEPHYSDIR directory 

  template.html - full template including navigation
  template-nomenu.html - template wihthout full navigation

  By default, all HTML files will be wrapped in the template.html template. 
  This may be overridden in each directory by listing the filename on 
  distinct lines in either the NOMENU or NOWRAP text files in each directory. 

  Any file listed in the NOMENU file will be wrapped with the 
  template-nomenu.html file, while any file listed in the NOWRAP file will 
  not be wrapped in a template at all. However, all HTML Files will have 
  their references to directories remapped according to the DIRMAP.pl program.

  Each Template file uses special strings which are replaced by the 
  corresponding parts of the HTML file being wrapped:

     %%DOCTYPE%%
     %%HEAD%%
     %%TITLE%%
     %%LAST_UPDATED%%
     %%BODY%%
     %%FILENAME%%

=cut


# eof: build-web.pl
