#! /usr/bin/perl

##  Generate linkage code and makefiles for storage and overview methods.
##
##  Goes through all subdirectories of the current directory and finds
##  directories that history methods or overview methods.  Builds
##  hismethods.[ch] as well as makefile stubs.

require 5.003;

use strict;
use vars qw(@HISTORY);

# History API functions.
@HISTORY = qw(
    open close sync lookup check write replace expire walk remember ctl
);

# Used to make heredocs more readable.
sub unquote { my ($string) = @_; $string =~ s/^:( {0,7}|\t)//gm; $string }

# Parse a hismethod.config file for a history method, putting information
# about that history method into the given hash ref.
sub parse_config {
    my ($dir, $file, $config) = @_;
    local $_;
    $$config{sources} ||= [];
    $$config{extra} ||= [];
    $$config{programs} ||= [];
    $$config{makefiles} ||= [];
    open(CONFIG, "$dir/$file") or die "Can't open $dir/$file: $!\n";
    while (<CONFIG>) {
        s/^\s+//;
        s/\s+$//;
        if (/^name\s*=\s*(\S+)$/) {
            my $method = $1;
            die "$dir/$file: $method has already been defined\n"
              if (defined $$config{method}{$method});
            $$config{method}{$method} = $dir;
        } elsif (/^number\s*=\s*(\d+)$/) {
            my $number = $1;
            if (defined $$config{number}{$number}) {
                die "$dir/$file: method number $number was already "
                  . "allocated in $$config{number}{$number}\n";
            }
            $$config{number}{$dir} = $number;
        } elsif (/^sources\s*=\s*(.*)/) {
            my $sources = $1;
            my @sources = split(' ', $sources);
            push(@{ $$config{sources} }, map { "$dir/$_" } @sources);
        } elsif (/^extra-sources\s*=\s*(.*)/) {
            my $extra = $1;
            my @extra = split(' ', $extra);
            push(@{ $$config{extra} }, map { "$dir/$_" } @extra);
        } elsif (/^programs\s*=\s*(.*)/) {
            my $programs = $1;
            my @programs = split(' ', $programs);
            push(@{ $$config{programs} }, map { "$dir/$_" } @programs);
        } else {
            warn "$dir/$file: ignoring unknown line: $_\n";
        }
    }

    # If there is a makefile fragment in the directory, note it.
    if (-f "$dir/hismethod.mk") {
        push(@{ $$config{makefiles} }, "$dir/hismethod.mk");
    }
}

# Write out include directives for a list of files.
sub write_includes {
    my ($fh, $config) = @_;
    for my $method (sort keys %{ $$config{method} }) {
        my $path = $$config{method}{$method};
        print $fh qq(#include "$path/$method.h"\n);
    }
}

# Write out the method struct.
sub write_methods {
    my ($fh, $config, $prefix, @funcs) = @_;
    my $notfirst;
    for my $method (sort keys %{ $$config{method} }) {
        print $fh "\n},\n" if $notfirst;
        print $fh qq({\n    "$method");
        print $fh ', ', $prefix, '_', uc($method) if $prefix;
        for (@funcs) {
            print $fh ",\n    ${method}_$_";
        }
        $notfirst++;
    }
    print $fh "\n}\n};\n\n";
}

# Write out hismethods.c and hismethods.h for the interface to the history
# methods.
sub write_history {
    my $history = shift;
    open(DEF, '> hismethods.c.new')
      or die "Can't create hismethods.c.new: $!\n";
    print DEF unquote(<<'EOE');
:       /* This file is automatically generated by buildconfig. */
:
:       #include "hisinterface.h"
:       #include "hismethods.h"
EOE
    write_includes(\*DEF, $history);
    print DEF "\nHIS_METHOD his_methods[",
      scalar(keys %{ $$history{method} }), "] = {\n";
    write_methods(\*DEF, $history, undef, @HISTORY);
    close DEF;
    rename('hismethods.c.new', 'hismethods.c');

    open(H, '> hismethods.h.new') or die "Can't open hismethods.h.new: $!\n";
    print H unquote(<<'EOE');
:       /* This file is automatically generated by buildconfig */
:
:       #ifndef HISMETHODS_H
:       #define HISMETHODS_H 1
:
:       #include "hisinterface.h"
:
EOE
    print H '#define NUM_HIS_METHODS ',
      scalar(keys %{ $$history{method} }), "\n";
    print H unquote(<<'EOE');
:
:       extern HIS_METHOD his_methods[NUM_HIS_METHODS];
:
:       #endif /* HISMETHODS_H */
EOE
    close H;
    rename('hismethods.h.new', 'hismethods.h');
}

# Return a string setting a makefile variable.  Tab over the = properly and
# wrap to fit our coding standards.
sub makefile_var {
    my ($variable, @values) = @_;
    my $output;
    $output = sprintf("%-15s =", $variable);
    my $column = 17;
    for (@values) {
        if ($column > 17 && 77 - $column < length($_)) {
            $output .= " \\\n" . ' ' x 17;
            $column = 17;
        }
        $output .= " $_";
        $column += 1 + length($_);
    }
    $output .= "\n";
    return $output;
}

# Write out the makefile fragment for history methods.
sub write_makefile {
    my ($dirs_unused, $sources, $extra, $programs, $makefiles) = @_;
    open(MAKE, '> Make.methods.new')
      or die "Can't create Make.methods.new: $!\n";
    print MAKE "# This file is automatically generated by buildconfig\n\n";
    print MAKE makefile_var('METHOD_SOURCES', @$sources);
    print MAKE makefile_var('EXTRA_SOURCES', @$extra);
    print MAKE makefile_var('PROGRAMS', @$programs);
    for (@$makefiles) {
        print MAKE "\n\n##  Included from $_\n\n";
        open(FRAG, $_) or die "Can't open $_: $!\n";
        print MAKE <FRAG>;
        close FRAG;
    }
    rename('Make.methods.new', 'Make.methods');
}

my %history;
if (!-d 'hisv6') {
    if (-d 'history/cnfs') {
        chdir 'history' or die "Can't chdir to history: $!\n";
    } else {
        die "Can't find history directory (looking for history/hisv6)\n";
    }
}
opendir(D, ".") or die "Can't open current directory: $!\n";
my @dirs = sort readdir D;
for my $dir (@dirs) {
    if (-e "$dir/hismethod.config") {
        parse_config($dir, 'hismethod.config', \%history);
    }
}
write_history(\%history);
@dirs = sort values %{ $history{method} };
my @sources = sort @{ $history{sources} };
my @extra = sort @{ $history{extra} };
my @programs = sort @{ $history{programs} };
my @makefiles = sort @{ $history{makefiles} };
write_makefile(\@dirs, \@sources, \@extra, \@programs, \@makefiles);
