#!/usr/bin/perl

# -----------------------------------------------------------------------------
# msite.pl - static website generation
#
# Copyright MJ White <mark@celos.net>.  All rights reserved.
# 
# Msite is for generating static websites from simple template files; for
# handling site styles, navigation, news, and versioning painlessly and
# statically.  Features tend to be things I wanted at some point; the code and
# portability may both be broken.  Like most software people like me put
# on the web, it should probably be considered pre-release; but actually,
# I haven't added any features for ages.  There are a number of things I
# always planned to change, like the tree-traversal code, but didn't.  Let
# me know if you do anything interesting with it.
#
# Needless to say, there's no documentation, either. :-)
#
# $Id: msite.pl 125 2004-11-23 10:38:01Z mark $
# -----------------------------------------------------------------------------

use Getopt::Std;
use POSIX;
use File::Basename;
use File::stat;

# Blatte and Blatte::HTML are on CPAN.  They're supported by the .LATTE
# directives in msite, if you uncomment a bit more code further down.
# use Blatte;
# use Blatte::Builtins;
# use Blatte::HTML;

local %var;
local (@STRTAGS, @HTMLTAGS, @METATAGS, @PERPAGETAGS, %EXPHNDLR);
local ($dir, $base);

# -- tag classifications --

@STRTAGS{ qw(
    .NEWSDB.AUTHOR .NEWSDB.WHEN .NEWSDB.SUBJECT
    .AUTHOR .EMAIL .WHEN .NAV .TITLE .NEWSCOUNT .CVS
    .NAVSELECT .NAVUNSEL .NAVDB.TEXT .NAVDB.HREF .NAVSTYLE
    .SUBNAV.NAVSELECT .SUBNAV.NAVUNSEL 
    .SUBNAV.NAVDB.TEXT .SUBNAV.NAVDB.HREF .SUBNAV.NAVSTYLE
    .SITETITLE .TITLESEP
    .ORIGAUTHOR .ORIGEMAIL
    .PARENT
    .SUBNAVFILE
	.REALDIR
    .NAVSTRUCT.SUBNAVFILE .NAVSTRUCT.PARENT .NAVSTRUCT.LABEL .NAVSTRUCT.HREF
    .LATTE .PRE
) } = ( );

@HTMLTAGS{ qw(
    .NEWSHEAD .NEWSENTRY .NEWSFOOT .NEWSDB.ENTRY
    .STYLE .PAGE
    .NAVHEAD .NAVFOOT .NAVENTRY .NAVSEP .NAVACTIVE
    .SUBNAV.NAVHEAD .SUBNAV.NAVFOOT .SUBNAV.NAVENTRY 
    .SUBNAV.NAVSEP .SUBNAV.NAVACTIVE
    .PARENTON .PARENTOFF
) } = ( );

@METATAGS{ qw(
    .NEWSDB .NAVDB
    .SUBNAV .NAVSTRUCT
) } = ( );

# -- tags to blank at start of new page translation --

@PERPAGETAGS{ qw(
  .AUTHOR .EMAIL .WHEN .NAV .TITLE .NEWSCOUNT .ORIGAUTHOR .ORIGEMAIL
  .PAGE .PARENT .CVS .SUBNAV.NAVDB.TEXT .SUBNAV.NAVDB.HREF .SUBNAVFILE
  .REALDIR
  .LATTE .PRE
) } = ( );

# -- list tag syntaxes --

@LISTTAGS{ qw(
  .NEWSDB.AUTHOR .NEWSDB.WHEN .NEWSDB.SUBJECT .NEWSDB.ENTRY
  .NAVDB.TEXT .NAVDB.HREF
  .SUBNAV.NAVDB.TEXT .SUBNAV.NAVDB.HREF
  .NAVSTRUCT.SUBNAVFILE .NAVSTRUCT.PARENT .NAVSTRUCT.LABEL .NAVSTRUCT.HREF
) } = ( );

# These tags may exist multiple times, and the entries will be stored in order
# (primarily for news).  Other variables just have the values overwritten.
# Sort of like journalling variables instead of proper lists.  This method
# makes it easy to break the system... don't. :)

# -- substitution handlers --

%EXPHNDLR = (
      
'.STYLE$BODY$' => sub { 
  my $latte = 0;
  my $pre = 0;
  my $root = $var{'.PAGE'};

  if ($var{'.LATTE'} ne "") { $latte = 1; }
  if ($var{'.PRE'} ne "")   { $pre   = 1; }
 
#   if ($latte) {
#     my $perl = &Blatte::Parse('{ ' . $root . ' }');
#     my $val = eval $perl;
#     $root = "";
#     &Blatte::HTML::render($val, sub { $root .= shift; });
#   }

  if ($pre) {
      $root = parse_pre($root);
  }
  
  $var{'.PAGE'} = $root;
  return sub_root('.PAGE'); 
},

'.STYLE$AUTHOR$' => sub { return $var{'.AUTHOR'}; },
'.STYLE$EMAIL$' => sub { return $var{'.EMAIL'}; },
'.STYLE$TITLE$' => sub { return $var{'.TITLE'}; },
'.STYLE$CVS$' => sub { return $var{'.CVS'}; },

'.STYLE$WHEN$' => sub { 
    my $b;
    if ($var{'.WHEN'} ne "") {
      return $var{'.WHEN'}; 
    } elsif ($var{'.CVS'} ne "") {
      $b = $var{'.CVS'};
      if ($b =~ /\s(\d+)\/(\d+)\/(\d+)\s/) {
        return strftime("%d %b %Y", 0, 0, 0, $3, $2 - 1, $1 - 1900);
      } else {
        return "sometime";
      }
    }
  },

'.STYLE$PARENTLINK$' => sub { 
  if ($var{'.PARENT'} ne "") {
    return sub_root('.PARENTON'); 
  } else {
    return sub_root('.PARENTOFF');
  }
},

'.PARENTON$PARENT$' => sub { return $var{'.PARENT'}; },

'.STYLE$COMPTITLE$' => sub {
  my ($out, $sep);
  $out = $var{'.SITETITLE'};
  if ( $var{'.TITLESEP'} ) {
    $sep = $var{'.TITLESEP'};
  } else {
    $sep = ":";
  }
  if ( $var{'.TITLE'} ne "" ) {
    $out .= " $sep " . $var{'.TITLE'};
  }
  return $out;
},

'.STYLE$DATESTAMP$' => sub {
  my $out = '';
  my $orig = $var{'.ORIGAUTHOR'};
  my $orige = $var{'.ORIGEMAIL'};
  my $author = $var{'.AUTHOR'};
  my $email = $var{'.EMAIL'};

  if ($orig ne "") {
    $out .= "Page by: ";
    if ($orige ne "") {
      $out .= "<a href=\"mailto:$orige\">$orig</a>.\n";
    } else {
      $out .= "$orig.\n";
    }
    $out .= "&nbsp; ";
  }
  $out .= "Last updated: ";
  if ($email ne "") {
    $out .= "<a href=\"mailto:$email\">$author</a> ";
  } else {
    $out .= "$author ";
  }
  $out .= $EXPHNDLR{'.STYLE$WHEN$'}->();
  return $out;
},

# -- NAV stuff

'.STYLE$NAVBAR$' => sub {
  my $out = sub_root('.NAVHEAD');
  my $val;

  my $first = 1;

  foreach $val (0 .. (scalar @{$var{'.NAVDB.TEXT'}}) - 1) {
    if ($first == 0 and $var{'.NAVSTYLE'} =~ /strip/) {
        $out .= sub_root('.NAVSEP');
    }
    $first = 0;
    $var{'.NAVTEXT.TMP'} = ${$var{'.NAVDB.TEXT'}}[$val];
    $var{'.NAVHREF.TMP'} = ${$var{'.NAVDB.HREF'}}[$val];
    if ( $var{'.NAVSTYLE'} =~ /strip/ 
      and $var{'.NAV'} eq $var{'.NAVTEXT.TMP'}) {
      $out .= sub_root('.NAVACTIVE');
    } else {
      $out .= sub_root('.NAVENTRY');
    }
  }

  $out .= sub_root('.NAVFOOT');

  return $out;
},

'.NAVACTIVE$IMAGE$' => sub {
  if ( $var{'.NAV'} eq $var{'.NAVTEXT.TMP'} ) {
    return $var{'.NAVSELECT'};
  } else {
    return $var{'.NAVUNSEL'};
  }
},

'.NAVACTIVE$TEXT$' => sub { return $var{'.NAVTEXT.TMP'}; },
'.NAVACTIVE$HREF$' => sub { return $var{'.NAVHREF.TMP'}; },

'.NAVENTRY$IMAGE$' => sub {
  if ( $var{'.NAV'} eq $var{'.NAVTEXT.TMP'} ) {
    return $var{'.NAVSELECT'};
  } else {
    return $var{'.NAVUNSEL'};
  }
},

'.NAVENTRY$TEXT$' => sub { return $var{'.NAVTEXT.TMP'}; },
'.NAVENTRY$HREF$' => sub { return $var{'.NAVHREF.TMP'}; },

# -- SUBNAV stuff

'.STYLE$SUBNAVBAR$' => sub {

  if ($var{'.SUBNAVFILE'} ne "") {

    # first we need to evaluate the subnavbar file
    parse_file("meta/" . $var{'.SUBNAVFILE'});
    # print_vars();
   
    my $thisgrp = $var{'.SUBNAVFILE'};
    my $out = sub_root('.SUBNAV.NAVHEAD');
    my $val;
    my $first = 1;

    # find all parents
    local (@parents, @names, @refs);
    sub traverse {
      my $thisgrp = shift;
      my $val;
      foreach $val (0 .. scalar @{$var{'.NAVSTRUCT.SUBNAVFILE'}} - 1) {
        if (${$var{'.NAVSTRUCT.SUBNAVFILE'}}[$val] eq $thisgrp) {
          push (@parents, ${$var{'.NAVSTRUCT.PARENT'}}[$val]);
          push (@names, ${$var{'.NAVSTRUCT.LABEL'}}[$val]);
          push (@refs, ${$var{'.NAVSTRUCT.HREF'}}[$val]);
          traverse(${$var{'.NAVSTRUCT.PARENT'}}[$val]);
        }
      }
    }
    traverse $thisgrp;
    
    # now print them
    foreach $val (1 .. scalar @parents - 1) { 
      $var{'.SUBNAV.NAVTEXT.TMP'} = pop @names;
      $var{'.SUBNAV.NAVHREF.TMP'} = pop @refs;
      if ( $var{'.SUBNAV.NAVSTYLE'} =~ /strip/ 
        and "$dir/$base" =~ /(\.\/)?$var{'.SUBNAV.NAVHREF.TMP'}/) {
        $out .= sub_root('.SUBNAV.NAVACTIVE');
      } else {
        $out .= sub_root('.SUBNAV.NAVENTRY');
      }
      if ($var{'.SUBNAV.NAVSTYLE'} =~ /strip/) {
          $out .= sub_root('.SUBNAV.NAVTRAIL');
      }
    }

    foreach $val (0 .. (scalar @{$var{'.SUBNAV.NAVDB.TEXT'}}) - 1) {
      if ($first == 0 and $var{'.SUBNAV.NAVSTYLE'} =~ /strip/) {
          $out .= sub_root('.SUBNAV.NAVSEP');
      }
      $first = 0;
      $var{'.SUBNAV.NAVTEXT.TMP'} = ${$var{'.SUBNAV.NAVDB.TEXT'}}[$val];
      $var{'.SUBNAV.NAVHREF.TMP'} = ${$var{'.SUBNAV.NAVDB.HREF'}}[$val];
      # print "$dir/$base : ".$var{'.SUBNAV.NAVHREF.TMP'}."\n";
      if ( $var{'.SUBNAV.NAVSTYLE'} =~ /strip/ 
        and "$dir/$base" =~ /(\.\/)?$var{'.SUBNAV.NAVHREF.TMP'}/) {
        $out .= sub_root('.SUBNAV.NAVACTIVE');
      } else {
        $out .= sub_root('.SUBNAV.NAVENTRY');
      }
    }

    $out .= sub_root('.SUBNAV.NAVFOOT');

    return $out;
  } else {
    return "";
  }
},

'.SUBNAV.NAVACTIVE$IMAGE$' => sub {
  if ("$dir/$base" =~ /(\.\/)?$var{'.SUBNAV.NAVHREF.TMP'}/) {
    return $var{'.SUBNAV.NAVSELECT'};
  } else {
    return $var{'.SUBNAV.NAVUNSEL'};
  }
},

'.SUBNAV.NAVACTIVE$TEXT$' => sub { return $var{'.SUBNAV.NAVTEXT.TMP'}; },
'.SUBNAV.NAVACTIVE$HREF$' => sub { return $var{'.SUBNAV.NAVHREF.TMP'}; },

'.SUBNAV.NAVENTRY$IMAGE$' => sub {
  if ("$dir/$base" =~ /(\.\/)?$var{'.SUBNAV.NAVHREF.TMP'}/) {
    return $var{'.SUBNAV.NAVSELECT'};
  } else {
    return $var{'.SUBNAV.NAVUNSEL'};
  }
},

'.SUBNAV.NAVENTRY$TEXT$' => sub { return $var{'.SUBNAV.NAVTEXT.TMP'}; },
'.SUBNAV.NAVENTRY$HREF$' => sub { return $var{'.SUBNAV.NAVHREF.TMP'}; },

# -- NEWS stuff

'.PAGE$NEWS$' => sub {

  my $out = sub_root('.NEWSHEAD');
  my ($val, $max);

  $max = (scalar @{$var{'.NEWSDB.ENTRY'}}) - 1;
  if ($var{'.NEWSCOUNT'} !~ /all/) {
    if ($max > $var{'.NEWSCOUNT'} - 1) {
      $max = $var{'.NEWSCOUNT'} - 1;
    }
  }
  foreach $val (0 .. $max) {
    $var{'.NEWSAUTHOR.TMP'} = ${$var{'.NEWSDB.AUTHOR'}}[$val];
    $var{'.NEWSWHEN.TMP'} = ${$var{'.NEWSDB.WHEN'}}[$val];
    $var{'.NEWSSUBJECT.TMP'} = ${$var{'.NEWSDB.SUBJECT'}}[$val];
    $var{'.NEWSENTRY.TMP'} = ${$var{'.NEWSDB.ENTRY'}}[$val];
    $out .= sub_root('.NEWSENTRY');
  }

  $out .= sub_root('.NEWSFOOT');

  return $out;
},

'.NEWSENTRY$SUBJECT$' => sub { return $var{'.NEWSSUBJECT.TMP'}; },
'.NEWSENTRY$AUTHOR$' => sub { return $var{'.NEWSAUTHOR.TMP'}; },
'.NEWSENTRY$WHEN$' => sub { return $var{'.NEWSWHEN.TMP'}; },
'.NEWSENTRY$ENTRY$' => sub { return sub_root('.NEWSENTRY.TMP'); }

);

# ----------------------------------------------------------------------
# Below here are the actual parsers, and the tree traversal code.  These
# are unlikely to need modifying if you're just adding new substitution
# keys and variables.
# ----------------------------------------------------------------------

# -- outer grammar (dot-syntax) parser --

sub blank_pagekeys {
  my $i;
  for $i (keys %PERPAGETAGS) {
    if (exists $LISTTAGS{$i}) {
      $var{$i} = [];
    } else {
      $var{$i} = "";
    }
  }
}

sub setup_var {
  my $k;
  foreach $k(keys %LISTTAGS) {
    $var{$k} = [];
  }
}

sub parse_file {
  my $filename = shift;
  local *FH;
  open(FH, $filename) or die "Erp: Didn't open $filename with $!\n";

  my $meta = "";
  my ($tag, $data, $l);

  my $valid = 0;
  LINE_LOOP:
  while (<FH>) {
    s/\r*\n+$//;            # handle DOS format files, too

    next LINE_LOOP if (/^\s*#/);
    
    if ($valid == 0 and not /^\s*$/) {  # first line with something on
      if (/^[^\.#]/) {
        return undef;
      }
      $valid = 1;
    }

    if (/^(\.[A-Z]+)/) {    # dot command

      # print $meta . $1 . "\n";
      if ( exists $STRTAGS{$meta . $1} ) {
        # print "String tag\n";
        $tag = $meta . $1; $data = "";
        if ($_ =~ /^(\.[A-Z]+)\s+(\S.*)$/) {
          $tag = $meta . $1;  $data = $2;
        }

        if ( exists $LISTTAGS{$tag} ) {
          # print "Listable tag\n";
          push @{$var{$tag}}, $data;
        }
        else {
          $var{$tag} = $data;
        }
      }

      elsif ( exists $HTMLTAGS{$meta . $1} ) { 
        # print "HTML tag\n";
        $tag = $meta . $1;
        $data = "";
HTML_READ:
        while ($l = <FH>) {
          next HTML_READ if ($l =~ /^\#/);
          last HTML_READ if ($l =~ /^\.END/);
          $data .= $l;
        }
        if ( exists $LISTTAGS{$tag} ) {
          push @{$var{$tag}}, $data;
        }
        else {
          $var{$tag} = $data;
        }
      }

      elsif ( exists $METATAGS{$1} ) {
        # print "Meta tag $1\n";
        $meta .= $1
      }

      elsif ( $1 eq ".END" ) {  # must be end of META
        # print "END tag\n";
        if ($meta eq "") {
          print "Parser: found .END when not in a meta environment\n";
        }
        $meta =~ s/^([^\.]*)\.[A-Z]+$/$1/;  # remove last term
      }
      
      else {
        # print "Implicit string tag\n";
        $tag = $meta . $1; $data = "";
        if ($_ =~ /^(\.[A-Z]+)\s+(\S.*)$/) {
          $tag = $meta . $1;  $data = $2;
        }
        $var{$tag} = $data;
      }

    }
  }

  close(FH) or die "Eek: Couldn't close $filename with $!\n";
}

sub print_vars {
  my ($k, $val);
  foreach $k (keys %var) {
    if (exists $LISTTAGS{$k}) {
      print "$k:\n";
      foreach $val (@{$var{$k}}) {
        print "  -> $val\n";
      }
    } else {
      print "$k => $var{$k}\n";
    }

  }
}

# -- inner grammar (dollar-substitution) parser --

sub sub_root {
  my $root = shift;
  my $output = '';
  my ($substr, $substitute, $line);

  foreach $line (split("\n",$var{$root})) {
    while ($line =~ /\$(\w+)\$/g) {
      $substr = $1;
      if ($substr eq "R") { # root handler
		if ($var{'.REALDIR'} ne "") {
          $substitute = inv_dir($var{'.REALDIR'});
        } else {
          $substitute = inv_dir($dir);
        }
      } else {
        if (exists $EXPHNDLR{"$root\$$substr\$"}) {           # known handler
          $substitute = $EXPHNDLR{"$root\$$substr\$"}->();
        } else {                                              # default
          $substitute = sub_root(".$substr");
        }
      }
      $line =~ s/\$$substr\$/$substitute/;
    }
    $output .= $line . "\n";
  }
  return $output;
}

# -- preformated text parser --
# The syntax is lifted almost verbatim from Latte here, although the substance
# of the language is almost totally unrelated.  I used "goto".  Muahahaha....

sub parse_pre {

  my $text = shift;
  my ($line, $break, $tag, $ptext, $inside, $guts, $last, $popd);
  my @tagstack;
  my @nobreaktags = qw/p li ul ol dl h1 h2 h3 h4 h5 h6 dh dd 
                      table th td tr/;

  # add \p tags
  foreach $line (split /\n/,$text) {
    if ($line =~ /^\s*$/) {
      $break = 1; goto LINE;
    }
    if ($break) {
      for $tag (@nobreaktags) {
        if ($line =~ /^\s*(({?\\$tag)|})/i) {
          $break = 0; goto LINE;
        }
      }
      # ok, we can break it
      $line =~ s/^(\s*)(\S)/$1\\p $2/;
    }
    $break = 0;
    LINE: $ptext .= $line . "\n";
  }

  # obscure escaped Latte characters
  $ptext =~ s/\\{/\\leftbrace /g;
  $ptext =~ s/\\}/\\rightbrace /g;
  $ptext =~ s/\\\\/\\backslash /g;
 
  # handle escaped HTML metacharacters
  $ptext =~ s/\\\&/\&amp;/g;
  $ptext =~ s/\\</\&lt;/g;
  $ptext =~ s/\\>/\&gt;/g;
  $ptext =~ s/\\\"/\&quot;/g;
    
  # process pairs
  while ($ptext =~ 
    /(({\\[\w\d]+((\s+\\[\w\d]+\=[^"\s]+)|(\s+\\[\w\d]+\="[^"]*"))*\s*}?)|})/sg ) {
    $inside = $1;
    if ($inside =~ 
      /{\\(([\w\d]+)((\s+\\[\w\d]+\=[^"\s]+)|(\s+\\[\w\d]+\="[^"]*"))*\s*}?)/s ) {
      $guts = $1; $tag = $2;
      $guts =~ s/\\([\w\d]+)\=([^\"\s]+)/\1\="\2"/sg;
      $guts =~ s/\\([\w\d]+)\=("[^\"]*")/\1\=\2/sg;
      $guts =~ s/\="T"//g;
      $guts =~ s/\s*}?$//s;
      substr($ptext, pos($ptext)-length($inside),length($inside))
        = "<$guts>";
      push @tagstack, $tag;
    }
    if ($inside =~ /}/) {
      $popd = pop @tagstack;
      substr($ptext, pos($ptext)-1, 1) = "</$popd>";
    }
  }
  
  # process singles
  $ptext =~ s/\\leftbrace /{/g;
  $ptext =~ s/\\rightbrace /}/g;
  $ptext =~ s/\\backslash /<backslash>/g;
  $ptext =~ s/\\([\w\d]+)\s+/\<\1\>/g;
  $ptext =~ s/<backslash>/\\/g;
    
  return $ptext;
  
}

# -- directory inversion --
# XXX which could do with a slight rethink to remove redundant ../back terms in
# paths expansions; this would require a grammar change, too.  Probably I
# should just use the relevant perl module, instead.

sub inv_dir {
	my $dir = shift;
	my $invdir = '';
	while ($dir =~ /([^\s\/]+)/g) {
	  if ($1 eq '.') {
		$invdir .= './';
	  } elsif ($1 eq '..') {
		die "Ack: Cannot invert .. in directory $dir\n";
	  } else {
		$invdir .= '../';
	  }
	}
	$invdir =~ s/\/$//;
	return $invdir;
}


# -- main code --

sub build {

  my ($pagestyles, $navbars, $newsstyles, $newsdbs);
  my (@navfiles, %forcelist, $nfn, $line);
  my (@dirfiles, $targetname, $sourcename, $parseregex);
  my ($skipcount, @allfiles, @alltargetfiles, $sites, $targets);
  my ($opbase, $opname);
  local (*DIR, *NAVFILE, *OUTFILE);

  parse_file("meta/pagestyle.html");
  parse_file("meta/navbar.html");
  parse_file("meta/newsstyle.html");
  parse_file("meta/newsdb.html");
  parse_file("meta/structure.nav");
  
  # regex for matching $base when parsing needed
  $parseregex = "(\.latte)|(\.html)|(\.php)|(\.epl)";
  
  $pagestyles = stat("meta/pagestyle.html")->mtime;
  $navbars = stat("meta/navbar.html")->mtime;
  $newsstyles = stat("meta/newsstyle.html")->mtime;
  $newsdbs = stat("meta/newsdb.html")->mtime;

  # -- guess which pages have updated subnav information --
  opendir(DIR, "meta") or die "Gak: Nav bars: $!\n";
  @navfiles = readdir(DIR);
  closedir(DIR);

  foreach $nfn (@navfiles) {
    if ($nfn =~ /.nav$/) {
      open(NAVFILE,"meta/$nfn") or die "Thrppt: Nav file: $!\n";
      foreach $line (<NAVFILE>) {
        if ($line =~ /.*\.HREF (.*)/) {      # broken, I know; should use parser
          if (-e "target/$1" and 
            stat("target/$1")->mtime < stat("meta/$nfn")->mtime) {
            $forcelist{$1} = 1;
            # print "Forcing: $1\n";
          }
        }
      }
      close(NAVFILE);
    }
  }

  # XXX The next section could do with a rewrite really; the logic is rather
  # convoluted, and it depends on the unix 'find' command.

  # -- directory structure duplication --
  @dirfiles = split ("\n",`find site -type d`);
  foreach $sourcename (@dirfiles) {
    $targetname = $sourcename;
    $targetname =~ s/^site/target/;
    unless ($sourcename =~ /CVS/ or $sourcename =~ /\.svn/) {
      unless (-d $targetname) {
        system("umask 022; mkdir $targetname");
      }
    }
  }

  print "Updating target...\n";
  umask 022;

  # -- tree traversal --
  $skipcount = 0;
  @allfiles = split ("\n",`find site -type f -print`);
  @alltargetfiles = split ("\n",`find target -type f -print`);
  foreach $sourcename (@allfiles) {

    $sourcename =~ s/^site\/?//;    # strip 'site' part
    $base = basename($sourcename);
    $dir = dirname($sourcename);

    $sites = stat("site/$sourcename")->mtime;

    $opname = $sourcename;
    $opname =~ s/\.latte$/\.html/;
    
    if (-e "target/$opname") {
      $targets = stat("target/$opname")->mtime;
    } else {
      $targets = 0;
    }

    unless (($base =~ /^\.[^.]/ and not $base =~ /^\.htaccess/) or $dir =~ /CVS/ or $dir =~ /\.svn/) {

      if ($base =~ $parseregex) {

        if ($targets < $sites or $targets < $pagestyles or $targets < $navbars
          or $targets < $newsdbs or $targets < $newsstyles 
          or $forcelist{"$dir/$base"}
          or $forcelist{$base} or $opt_a) {

          blank_pagekeys();
          $rval = parse_file("site/$sourcename");
            
          if (not defined $rval) {    # we should copy this page, instead

            if ($targets < $sites) {
              print "- copying $sourcename\n";
              unless (-d "target/$sourcename") {
                system("rm -f target/$sourcename");
                system("cp -p site/$sourcename target/$sourcename");
                system("chmod a+r target/$sourcename");
              }
            } else {
              $skipcount += 1;
            }

          } else {

            # IF really need to update news, OR here for some other reason
            if ($var{".PAGE"} =~ /\$NEWS\$/ or
              $targets < $sites or $targets < $pagestyles or $targets < $navbars
              or $forcelist{"$dir/$base"} == 1 
              or $forcelist{$base} == 1 or $opt_a) {

              $opbase = $base;
              if ($opbase =~ /\.latte$/) {
                $opbase =~ s/\.latte$/\.html/;
                $var{'.LATTE'} = "yes";
              }

              $page = sub_root('.STYLE');
              
              print "- translating $dir/$base\n";
              open(OUTFILE, ">target/$dir/$opbase") 
                or die "Urk: Couldn't open output file\n";
              print OUTFILE $page;
            } else {
              $skipcount += 1;
            }

          }

        } else {
          $skipcount += 1;
        }
      } else {
        if ($targets < $sites) {
          print "- copying $sourcename\n";
          unless (-d "target/$sourcename") {
            system("rm -f target/$sourcename");
            system("cp -p site/$sourcename target/$sourcename");
            system("chmod a+r target/$sourcename");
          }
        } else {
          $skipcount += 1;
        }
      }

    } # not a dotfile or CVS dir
  }

  # -- delete target files no longer in site tree --
  foreach $sourcename (@alltargetfiles) {
    $sourcename =~ s/^target\/?//;    # strip 'site' part
    unless (-e "site/$sourcename") {
      system("rm -f target/$sourcename");
      print "- deleting $sourcename\n";
    }
  }

  print "Skipped $skipcount files not newer.\n";
}


# -- and finally, do something... --

# debugging - display state
# print_vars;

getopts("ah");

if ($opt_h) {
  print "msite.pl, MJ White 2001\n",
        "  -h      print this message\n",
        "  -a      process all files, regardless\n";
  exit;
}

build;


#  vim:tw=79:sw=2:sts=2:et

