#!/usr/bin/perl -w
# preautoconf - generate lists of sources for doxygen to extract docs from
#
# Copyright (C) 2005,2007,2013 Olly Betts
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

use strict;

my @makefile_am;
# version.h is generated by configure, and isn't listed in BUILT_SOURCES.
my %built_sources = qw(include/xapian/version.h 1);

if ($0 =~ m!(.*)/!) {
    $1 eq '.' or chdir $1 or die $!;
}

my %s;
for (parse_makefile_am("")) {
    $s{$_}++;
}
my @apidoc_src = ();
my @sourcedoc_src = ();
for (sort keys %s) {
    s!^\./!!;
    next if m!^tests/! || $_ eq 'include/xapian/errordispatch.h';
    my $src = $built_sources{$_} ? '$(top_builddir)' : '$T';
    $src .= "/$_";
    if (m!^include/!) {
	push @apidoc_src, $src;
    } else {
	push @sourcedoc_src, $src;
    }
}

my @dir_contents = ();
my %desc = ();
for (map {m!^(.*?)[^/]*$!; $1} @makefile_am) {
    my $dir = $_;
    my $dir_contents = "${dir}dir_contents";
    push @dir_contents, $dir_contents;
    if (! -f $dir_contents) {
	print STDERR "$0: Warning: `$dir_contents' not found\n";
	next;
    }

    open DIR_CONTENTS, "<$dir_contents" or die "$dir_contents: $!\n";
    local $/ = undef;
    my $xml = <DIR_CONTENTS>;
    close DIR_CONTENTS;

    my ($xmldir) = ($xml =~ m!<Directory>\s*(\S+)\s*</Directory>!);
    if (!defined $xmldir) {
	die "$dir_contents: No valid <Directory> tag found\n";
    }
    if ($xmldir eq "ROOT") {
	$xmldir = "";
    }
    $dir =~ s!/$!!;
    if ($dir ne $xmldir) {
	die "$dir_contents: File is in `$dir', but <Directory> says `$xmldir'\n";
    }
    my ($desc) = ($xml =~ m!<Description>\s*(.*?)\s*</Description>!s);
    if (!defined $desc) {
	die "$dir_contents: No valid <Description> tag found\n";
    }
}

open MAKEFRAG, ">docs/docsource.tmp" or die $!;
print MAKEFRAG "APIDOC_SRC=", join("\\\n\t", "", @apidoc_src), "\n";
print MAKEFRAG "SOURCEDOC_SRC=", join("\\\n\t", "", @sourcedoc_src), "\n";
print MAKEFRAG "DIR_CONTENTS_FILES=", join("\\\n\t\$T/", "", @dir_contents), "\n";
close MAKEFRAG or do { unlink "docs/docsource.tmp"; die $! };
rename "docs/docsource.tmp", "docs/docsource.mk" or do { unlink "docs/docsource.tmp"; die $! };

open MAKEFRAG, ">docsource.tmp" or die $!;
print MAKEFRAG "docsource.mk: preautoreconf ", join(" ", @makefile_am, @dir_contents), "\n";
print MAKEFRAG "\tcd \$(top_srcdir) && ./preautoreconf\n";
# Add dummy rules so that "make" works even if a Makefile.am or dir_contents
# file is removed.
for (@makefile_am, @dir_contents) {
    print MAKEFRAG "\n$_:\n";
}
close MAKEFRAG or do { unlink "docsource.tmp"; die $! };
rename "docsource.tmp", "docsource.mk" or do { unlink "docsource.tmp"; die $! };

exit 0;

sub parse_makefile_am {
    my $dir = shift;
    my %v;
    my @l;
    my $makefile_am = $dir . "Makefile.am";
    my @pending = ();
    open M, "<$makefile_am" or die "$makefile_am: $!\n";
    push @makefile_am, $makefile_am;
    while (<M>) {
pending:
	chomp;
	while (s/\\$/ /) {
	    if (@pending) {
		$_ .= shift @pending;
	    } else {
		$_ .= <M>;
	    }
	    chomp;
	}
	if (/^\s*(\w+)\s*[+:]?=\s*(.*?)\s*(?:#.*)?$/) {
	    my $var = $1;
	    if (exists $v{$var}) {
		$v{$var} .= " $2";
	    } else {
		$v{$var} = $2;
		if ($var =~ /_(?:SOURCE|HEADER)S$/) {
		    push @l, $var;
		}
	    }
	} elsif (/^\s*include\s+(\S+)/ && $1 ne 'docsource.mk') {
	    # automake looks for a nested included file starting from the
	    # directory which the original file was in, so the behaviour
	    # here is correct.
	    my $inc = $dir . $1;
	    open INC, "<$inc" or die "$inc: $!\n";
	    push @makefile_am, $inc;
	    unshift @pending, <INC>;
	    close INC;
	}
	if (@pending) {
	    $_ = shift @pending;
	    goto pending;
	}
    }
    close M;

    if (exists $v{BUILT_SOURCES}) {
	for (map {/^$/ ? () : $dir.$_} split /\s+/,
		expand('BUILT_SOURCES', \%v)) {
	    $built_sources{$_}++;
	}
    }

    my @src;
    for (@l) {
	push @src, map {/^$/ ? () : $dir.$_} split /\s+/, expand($_, \%v);
    }

    my $subdirs = $v{DIST_SUBDIRS} || $v{SUBDIRS} || '';
    for (split /\s+/, $subdirs) {
	next if $_ eq ".";
	push @src, parse_makefile_am("$dir$_/");
    }
    return @src;
}

sub expand {
   my ($var, $from, $to, $vref);
   if (scalar @_ == 2) {
      ($var, $vref) = @_;
   } else {
      ($var, $from, $to, $vref) = @_;
   }

   my $val = $$vref{$var};

   # Deal with $(FOO:.bar=.c) pattern substitutions.
   if (defined $from) {
       my $x = $val;
       $val =~ s/\Q$from\E(?:\s|$)/$to/g;
   }

   if (!defined $val) {
       print STDERR "$0: Unknown variable: \$($var)\n";
       return "";
   }
   $val =~ s/\$\((\w+)(?::([^=)]+)=([^=)]+))?\)/expand($1, $2, $3, $vref)/eg;
   return $val;
}
