#!/usr/bin/perl -w

use strict;
use vars qw($masterpodfile %Build %Targets $Verbose $Quiet %Ignore
	    @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
	    %Copies %Generated $Test);
use File::Spec;
use File::Find;
use FindBin;
use Text::Tabs;
use Text::Wrap;
use Getopt::Long;
use Carp;

no locale;
require 5.010;

{
  my $Top = File::Spec->catdir($FindBin::Bin, File::Spec->updir);

  sub abs_from_top {
    my $path = shift;
    return File::Spec->catdir($Top, split /\//, $path) if $path =~ s!/\z!!;
    return File::Spec->catfile($Top, split /\//, $path);
  }
}

$masterpodfile = abs_from_top('pod.lst');

# Generate any/all of these files
# --verbose gives slightly more output
# --quiet suppresses routine warnings
# --build-all tries to build everything
# --build-foo updates foo as follows
# --showfiles shows the files to be changed
# --test exit if perl.pod, pod.lst, MANIFEST are consistent, and regenerated
#   files are up to date, die otherwise.

%Targets
  = (
     toc => 'pod/perltoc.pod',
     manifest => 'MANIFEST',
     perlpod => 'pod/perl.pod',
     vms => 'vms/descrip_mms.template',
     nmake => 'win32/Makefile',
     dmake => 'win32/makefile.mk',
     podmak => 'win32/pod.mak',
     # plan9 =>  'plan9/mkfile'),
     unix => 'Makefile.SH',
     # TODO: add roffitall
    );

foreach (values %Targets) {
  $_ = abs_from_top($_);
}

{
  my @files = keys %Targets;
  my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
  my $showfiles;
  my %build_these;
  die <<__USAGE__
$0: Usage: $0 [--verbose] [--showfiles] $filesopts
__USAGE__
  unless @ARGV
	&& GetOptions (verbose => \$Verbose,
		       quiet => \$Quiet,
		       showfiles => \$showfiles,
		       test => \$Test,
		       map {+"build-$_", \$build_these{$_}} @files, 'all');
  if ($build_these{all}) {
    %Build = %Targets;
  } else {
    while (my ($file, $want) = each %build_these) {
      $Build{$file} = $Targets{$file} if $want;
    }
  }
  if ($showfiles) {
      print
	  join(" ",
	       sort { lc $a cmp lc $b }
	       map {
		   my ($v, $d, $f) = File::Spec->splitpath($_);
		   my @d;
		   @d = defined $d ? File::Spec->splitdir($d) : ();
		   shift @d if @d;
		   File::Spec->catfile(@d ?
				       (@d == 1 && $d[0] eq '' ? () : @d)
				       : "pod", $f);
	       } @Targets{@files}),
          "\n";
      exit(0);
  }
}

# Don't copy these top level READMEs
%Ignore
  = (
     micro => 1,
#     vms => 1,
     );

if ($Verbose) {
  print "I'm building $_\n" foreach keys %Build;
}

open my $master, '<', $masterpodfile or die "$0: Can't open $masterpodfile: $!";

my ($delta_source, $delta_target);

foreach (<$master>) {
  next if /^\#/;

  # At least one upper case letter somewhere in the first group
  if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
    # it's a heading
    my $flags = $1;
    $flags =~ tr/h//d;
    my %flags = (header => 1);
    $flags{toc_omit} = 1 if $flags =~ tr/o//d;
    $flags{aux} = 1 if $flags =~ tr/a//d;
    die "$0: Unknown flag found in heading line: $_" if length $flags;
    push @Master, [\%flags, $2];

  } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
    # it's a section
    my ($flags, $filename, $desc) = ($1, $2, $3);

    my %flags = (indent => 0);
    $flags{indent} = $1 if $flags =~ s/(\d+)//;
    $flags{toc_omit} = 1 if $flags =~ tr/o//d; 
    $flags{aux} = 1 if $flags =~ tr/a//d;

    if ($flags =~ tr/D//d) {
      $flags{manifest_omit} = 1;
      $delta_source = "$filename.pod";
    }
    if ($flags =~ tr/d//d) {
      $flags{perlpod_omit} = 1;
      $delta_target = "$filename.pod";
    }
    $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;

    if ($flags =~ tr/r//d) {
      my $readme = $filename;
      $readme =~ s/^perl//;
      $Readmepods{$filename} = $Readmes{$readme} = $desc;
      $flags{readme} = 1;
    } elsif ($flags{aux}) {
      $Aux{$filename} = $desc;
    } else {
      $Pods{$filename} = $desc;
    }
    die "$0: Unknown flag found in section line: $_" if length $flags;
    push @Master, [\%flags, $filename, $desc];
  } elsif (/^$/) {
    push @Master, undef;
  } else {
    die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
  }
}
if (defined $delta_source) {
  if (defined $delta_target) {
    # This way round so that keys can act as a MANIFEST skip list
    # Targets will aways be in the pod directory. Currently we can only cope
    # with sources being in the same directory.
    $Copies{$delta_target} = $delta_source;
  } else {
    die "$0: delta source defined but not target";
  }
} elsif (defined $delta_target) {
  die "$0: delta target defined but not source";
}

close $master;

# Sanity cross check
{
  my (%disk_pods, @disk_pods);
  my (@manipods, %manipods);
  my (@manireadmes, %manireadmes);
  my (@perlpods, %perlpods);
  my (%our_pods);

  # Convert these to a list of filenames.
  foreach (keys %Pods, keys %Readmepods) {
    $our_pods{"$_.pod"}++;
  }

  opendir my $dh, abs_from_top('pod/');
  while (readdir $dh) {
    next unless /\.pod\z/;
    push @disk_pods, $_;
    ++$disk_pods{$_};
  }

  # Things we copy from won't be in perl.pod
  # Things we copy to won't be in MANIFEST

  my $filename = abs_from_top('MANIFEST');
  open my $mani, '<', $filename or die "$0: opening $filename failed: $!";
  while (<$mani>) {
    if (m!^pod/([^.]+\.pod)\s+!i) {
      push @manipods, $1;
    } elsif (m!^README\.(\S+)\s+!i) {
      next if $Ignore{$1};
      push @manireadmes, "perl$1.pod";
    }
  }
  close $mani or die $!;
  @manipods{@manipods} = @manipods;
  @manireadmes{@manireadmes} = @manireadmes;

  $filename = abs_from_top('pod/perl.pod');
  open my $perlpod, '<', $filename or die "$0: opening $filename failed: $!\n";
  while (<$perlpod>) {
    if (/^For ease of access, /../^\(If you're intending /) {
      if (/^\s+(perl\S*)\s+\w/) {
	push @perlpods, "$1.pod";
      }
    }
  }
  close $perlpod or die $!;
  die "$0: could not find the pod listing of perl.pod\n"
    unless @perlpods;
  @perlpods{@perlpods} = @perlpods;

  my @inconsistent;
  foreach my $i (sort keys %disk_pods) {
    push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
      unless $our_pods{$i};
    push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
      if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
    push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
	if !$perlpods{$i} && !exists $Copies{$i};
  }
  my %BuildFiles;
  foreach my $path (values %Build) {
    (undef, undef, my $file) = File::Spec->splitpath($path);
    ++$BuildFiles{$file}
  }

  foreach my $i (sort keys %our_pods) {
    push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
      unless $disk_pods{$i} or $BuildFiles{$i};
  }
  foreach my $i (sort keys %manipods) {
    push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
      unless $disk_pods{$i};
    push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
      if $Generated{$i};
  }
  foreach my $i (sort keys %perlpods) {
    push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
      unless $disk_pods{$i} or $BuildFiles{$i};
  }
  if ($Test) {
    delete $Build{toc};
    printf "1..%d\n", 1 + scalar keys %Build;
    if (@inconsistent) {
      print "not ok 1\n";
      die @inconsistent
    }
    print "ok 1\n";
  }
  else {
    warn @inconsistent if @inconsistent;
  }
}

# Find all the modules
if ($Build{toc}) {
  my @modpods;
  find \&getpods => abs_from_top('lib/');

  sub getpods {
    if (/\.p(od|m)$/) {
      my $file = $File::Find::name;
      return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
      return if $file =~ m!(?:^|/)t/!;
      return if $file =~ m!lib/Attribute/Handlers/demo/!;
      return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
      return if $file =~ m!lib/Math/BigInt/t/!;
      return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
      return if $file =~ m!XS/(?:APItest|Typemap)!;
      my $pod = $file;
      return if $pod =~ s/pm$/pod/ && -e $pod;
      unless (open my $f, '<', $_) {
	warn "$0: bogus <$file>: $!";
	system "ls", "-l", $file;
      }
      else {
	my $line;
	while ($line = <$f>) {
	  if ($line =~ /^=head1\s+NAME\b/) {
	    push @modpods, $file;
	    return;
	  }
	}
	warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
      }
    }
  }

  die "$0: no pods" unless @modpods;

  my %done;
  for (@modpods) {
    my $name = $_;
    $name =~ s/\.p(m|od)$//;
    $name =~ s-.*?/lib/--;
    $name =~ s-/-::-g;
    next if $done{$name}++;

    if ($name =~ /^[a-z]/) {
      $Pragmata{$name} = $_;
    } else {
      $Modules{$name} = $_;
    }
  }
}

# OK. Now a lot of ancillary function definitions follow
# Main program returns at "Do stuff"

my $OUT;

sub do_toc {
  my $filename = shift;

  ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;

	# !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
	# This file is autogenerated by buildtoc from all the other pods.
	# Edit those files and run buildtoc --build-toc to effect changes.

	=head1 NAME

	perltoc - perl documentation table of contents

	=head1 DESCRIPTION

	This page provides a brief table of contents for the rest of the Perl
	documentation set.  It is meant to be scanned quickly or grepped
	through to locate the proper section you're looking for.

	=head1 BASIC DOCUMENTATION

EOPOD2B
#' make emacs happy

  # All the things in the master list that happen to be pod filenames
  foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
    podset($_->[1], abs_from_top("pod/$_->[1].pod"));
  }


  ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;



	=head1 PRAGMA DOCUMENTATION

EOPOD2B

  foreach (sort keys %Pragmata) {
    podset($_, $Pragmata{$_});
  }

  ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;



	=head1 MODULE DOCUMENTATION

EOPOD2B

  foreach (sort keys %Modules) {
    podset($_, $Modules{$_});
  }

  $_= <<"EOPOD2B";


	=head1 AUXILIARY DOCUMENTATION

	Here should be listed all the extra programs' documentation, but they
	don't all have manual pages yet:

	=over 4

EOPOD2B

  $_ .=  join "\n", map {"\t=item $_\n"} sort keys %Aux;
  $_ .= <<"EOPOD2B" ;

	=back

	=head1 AUTHOR

	Larry Wall <F<larry\@wall.org>>, with the help of oodles
	of other folks.


EOPOD2B

  s/^\t//gm;
  $OUT .= "$_\n";

  $OUT =~ s/\n\s+\n/\n\n/gs;
  $OUT =~ s/\n{3,}/\n\n/g;

  $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;

  return $OUT;
}

# Below are all the auxiliary routines for generating perltoc.pod

my ($inhead1, $inhead2, $initem);

sub podset {
    my ($pod, $file) = @_;

    local $/ = '';

    open my $fh, '<', $file or die "Can't open file '$file' for $pod: $!";

    while(<$fh>) {
	tr/\015//d;
	if (s/^=head1 (NAME)\s*/=head2 /) {
	    unhead1();
	    $OUT .= "\n\n=head2 ";
	    $_ = <$fh>;
	    # Remove svn keyword expansions from the Perl FAQ
	    s/ \(\$Revision: \d+ \$\)//g;
	    if ( /^\s*\Q$pod\E\b/ ) {
		s/$pod\.pm/$pod/;       # '.pm' in NAME !?
	    } else {
		s/^/$pod, /;
	    }
	}
	elsif (s/^=head1 (.*)/=item $1/) {
	    unhead2();
	    $OUT .= "=over 4\n\n" unless $inhead1;
	    $inhead1 = 1;
	    $_ .= "\n";
	}
	elsif (s/^=head2 (.*)/=item $1/) {
	    unitem();
	    $OUT .= "=over 4\n\n" unless $inhead2;
	    $inhead2 = 1;
	    $_ .= "\n";
	}
	elsif (s/^=item ([^=].*)/$1/) {
	    next if $pod eq 'perldiag';
	    s/^\s*\*\s*$// && next;
	    s/^\s*\*\s*//;
	    s/\n/ /g;
	    s/\s+$//;
	    next if /^[\d.]+$/;
	    next if $pod eq 'perlmodlib' && /^ftp:/;
	    $OUT .= ", " if $initem;
	    $initem = 1;
	    s/\.$//;
	    s/^-X\b/-I<X>/;
	}
	else {
	    unhead1() if /^=cut\s*\n/;
	    next;
	}
	$OUT .= $_;
    }
}

sub unhead1 {
    unhead2();
    if ($inhead1) {
	$OUT .= "\n\n=back\n\n";
    }
    $inhead1 = 0;
}

sub unhead2 {
    unitem();
    if ($inhead2) {
	$OUT .= "\n\n=back\n\n";
    }
    $inhead2 = 0;
}

sub unitem {
    if ($initem) {
	$OUT .= "\n\n";
    }
    $initem = 0;
}

# End of original buildtoc. From here on are routines to generate new sections
# for and inplace edit other files

sub generate_perlpod {
  my @output;
  my $maxlength = 0;
  foreach (@Master) {
    my $flags = $_->[0];
    next if $flags->{aux};
    next if $flags->{perlpod_omit};

    if (@$_ == 2) {
      # Heading
      push @output, "=head2 $_->[1]\n";
    } elsif (@$_ == 3) {
      # Section
      my $start = " " x (4 + $flags->{indent}) . $_->[1];
      $maxlength = length $start if length ($start) > $maxlength;
      push @output, [$start, $_->[2]];
    } elsif (@$_ == 0) {
      # blank line
      push @output, "\n";
    } else {
      die "$0: Illegal length " . scalar @$_;
    }
  }
  # want at least 2 spaces padding
  $maxlength += 2;
  $maxlength = ($maxlength + 3) & ~3;
  # sprintf gives $1.....$2 where ... are spaces:
  return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
		   @output);
}


sub generate_manifest {
  # Annoyingly, unexpand doesn't consider it good form to replace a single
  # space before a tab with a tab
  # Annoyingly (2) it returns read only values.
  my @temp = unexpand (map {sprintf "%-32s%s", @$_} @_);
  map {s/ \t/\t\t/g; $_} @temp;
}
sub generate_manifest_pod {
  generate_manifest map {["pod/$_.pod", $Pods{$_}]}
    sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
}
sub generate_manifest_readme {
  generate_manifest sort {$a->[0] cmp $b->[0]}
    ["README.vms", "Notes about installing the VMS port"],
      map {["README.$_", $Readmes{$_}]} keys %Readmes;
}

sub generate_roffitall {
  (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
   "\t\t\\",
   map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
   "\t\t\\",
   map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
   "\t\t\\",
   map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
  )
}

sub generate_descrip_mms_1 {
  local $Text::Wrap::columns = 150;
  my $count = 0;
  my @lines = map {"pod" . $count++ . " = $_"}
    split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
		     sort keys %Pods, keys %Readmepods);
  @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
}

sub generate_descrip_mms_2 {
  map {<<"SNIP"}
[.lib.pods]$_.pod : [.pod]$_.pod
	\@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
	Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
SNIP
   sort keys %Pods, keys %Readmepods;
}

sub generate_nmake_1 {
  # XXX Fix this with File::Spec
  (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
    sort keys %Readmes),
      (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
}

# This doesn't have a trailing newline
sub generate_nmake_2 {
  # Spot the special case
  local $Text::Wrap::columns = 76;
  my $line = wrap ("\t    ", "\t    ",
		   join " ", sort keys %Copies, keys %Generated,
				  map {"perl$_.pod"} keys %Readmes);
  $line =~ s/$/ \\/mg;
  $line =~ s/ \\$//;
  $line;
}

sub generate_pod_mak {
  my $variable = shift;
  my @lines;
  my $line = join "\\\n", "\U$variable = ",
    map {"\t$_.$variable\t"} sort keys %Pods;
  # Special case
  $line =~ s/.*perltoc.html.*\n//m;
  $line;
}

sub verify_contiguous {
  my ($name, $content, $what) = @_;
  my $sections = () = $content =~ m/\0+/g;
  croak("$0: $name contains no $what") if $sections < 1;
  croak("$0: $name contains discontiguous $what") if $sections > 1;
}

sub do_manifest {
  my ($name, $prev) = @_;
  my @manifest =
    grep {! m!^pod/[^.]+\.pod.*!}
      grep {! m!^README\.(\S+)! || $Ignore{$1}} split "\n", $prev;
  join "\n", (
	      # Dictionary order - fold and handle non-word chars as nothing
	      map  { $_->[0] }
	      sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
	      map  { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
	      @manifest,
	      &generate_manifest_pod(),
	      &generate_manifest_readme()), '';
}

sub do_nmake {
  my ($name, $makefile) = @_;
  $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
  verify_contiguous($name, $makefile, 'README copies');
  # Now remove the other copies that follow
  1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
  $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;

  $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
    {"$1\n" . &generate_nmake_2."\n\t$2"}se;
  $makefile;
}

# shut up used only once warning
*do_dmake = *do_dmake = \&do_nmake;

sub do_perlpod {
  my ($name, $pod) = @_;

  unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
		    (?:\s+[a-z]{4,}.*\n	#   fooo
		    |=head.*\n		# =head foo
		    |\s*\n		# blank line
		   )+
		  }
	  {$1 . join "", &generate_perlpod}mxe) {
    die "$0: Failed to insert amendments in do_perlpod";
  }
  $pod;
}

sub do_podmak {
  my ($name, $body) = @_;
  foreach my $variable (qw(pod man html tex)) {
    die "$0: could not find $variable in $name"
      unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
	{"\n" . generate_pod_mak ($variable)}se;
  }
  $body;
}

sub do_vms {
  my ($name, $makefile) = @_;
  $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
  verify_contiguous($name, $makefile, 'pod assignments');
  $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;

  die "$0: $name contains NUL bytes" if $makefile =~ /\0/;

# Looking for the macro defining the current perldelta:
#PERLDELTA_CURRENT = [.pod]perl5139delta.pod

  $makefile =~ s/\nPERLDELTA_CURRENT\s+=\s+\Q[.pod]perl\E\d+delta\.pod\n
		    /\0/sx;
  verify_contiguous($name, $makefile, 'current perldelta macro');
  $makefile =~ s/\0+/join "\n", '', "PERLDELTA_CURRENT = [.pod]$delta_target", ''/se;

# Looking for rules like this
# [.lib.pods]perl.pod : [.pod]perl.pod
#	@ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
#	Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]

  $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
		 [^\n]+\n	# Another line
		 [^\n]+\Q[.lib.pods]\E\n		# ends [.lib.pods]
		    /\0/gsx;
  verify_contiguous($name, $makefile, 'copy rules');
  $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;

  $makefile;
}

sub do_unix {
  my ($name, $makefile_SH) = @_;

  $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
		   {join ' ', $1, map "pod/$_",
			sort keys %Copies, grep {!/perltoc/} keys %Generated
		    }mge;

# pod/perl511delta.pod: pod/perldelta.pod
# 	cd pod && $(LNS) perldelta.pod perl511delta.pod

  $makefile_SH =~ s!(
pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
	\$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
)+!\0!gm;

  verify_contiguous($name, $makefile_SH, 'copy rules');

  my @copy_rules = map "
pod/$_: pod/$Copies{$_}
	\$(LNS) $Copies{$_} pod/$_
", keys %Copies;

  $makefile_SH =~ s/\0+/join '', @copy_rules/se;
  $makefile_SH;

}

# Do stuff

my $built;
while (my ($target, $name) = each %Targets) {
  print "Working on target $target\n" if $Verbose;
  next unless $Build{$target};
  $built++;
  my ($orig, $mode);
  print "Now processing $name\n" if $Verbose;
  if ($target ne "toc") {
    local $/;
    open my $thing, '<', $name or die "Can't open $name: $!";
    binmode $thing;
    $orig = <$thing>;
    die "$0: $name contains NUL bytes" if $orig =~ /\0/;
  }

  my $new = do {
    no strict 'refs';
    &{"do_$target"}($target, $orig);
  };

  if (defined $orig) {
    if ($new eq $orig) {
      if ($Test) {
	printf "ok %d # $name is up to date\n", $built + 1;
      } elsif ($Verbose) {
	print "Was not modified\n";
      }
      next;
    } elsif ($Test) {
      printf "not ok %d # $name is up to date\n", $built + 1;
      next;
    }
    $mode = (stat $name)[2] // die "$0: Can't stat $name: $!";
    rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
  }

  open my $thing, '>', $name or die "$0: Can't open $name for writing: $!";
  binmode $thing;
  print $thing $new or die "$0: print to $name failed: $!";
  close $thing or die "$0: close $name failed: $!";
  if (defined $mode) {
    chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
  }
}

warn "$0: was not instructed to build anything\n" unless $built || $Test;