=head1 NAME

genhelp - Build lib/VCP/Help.pm by extracting POD from the listed files

=head1 SYNOPSYS

    genhelp bin/vcp lib/VCP.pm lib/VCP/Foo.pm ...

=head1

When bundling libraries and POD files with PAR <= 0.79, it is difficult
to find and parse the files to generate help with.  So we extract it
and build it in to a Perl module as a bunch of strings using this tool.

See Makefile.PL for how this tool is automated.

=cut

my $text;
    ## My::Pod::Usage writes to this instead of STDOUT.  It's a global
    ## to allow us to call pod2usage() and collect its output.

###############################################################################
package My::Pod::Text;

use Pod::Text;

@ISA = qw( Pod::Text );

sub parse_from_file {
    my $self = shift;
    $text = "";
    $self->SUPER::parse_from_file( @_ );
}

sub output {
    $_[1] =~ tr/\01/ /; ## cargo culted from Pod::Text::output().
    $text .= $_[1];
}

###############################################################################
package main;

use strict;
use lib 'lib';
use List::Util;
use Pod::Usage;
use VCP::PodDESCRIPTIONParser;
use VCP::PodOPTIONSParser;
use Text::Wrap qw( wrap );

## HACK: Pod::* are very single minded about only outputting to the
## console.  Subvert Pod::Usage.
@Pod::Usage::ISA = qw( My::Pod::Text );

use lib 'lib';

open OUTPUT, ">lib/VCP/Help.pm" or die "$!: lib/VCP/Help.pm";

warn "writing lib/VCP/Help.pm\n";

print OUTPUT <<PREAMBLE;
package VCP::Help;

\%topics = (
PREAMBLE

sub print_topic {
    my ( $topic, $text ) = @_;

    $topic = lc $topic;

    $text =~ s/^TOPIC/ TOPIC/mg
        and warn "TOPIC escaped in $topic\n";
        ## This should never happen, so it's ok if a wonky little leading
        ## space is added: graceful degredation.

    1 while chomp $text;

    print OUTPUT "#" x 72, "\n'$topic' => <<'TOPIC',\n$text\nTOPIC\n";
}

sub wrap_pod_paragraphs {
    local $Text::Wrap::columns = shift;
    return map /^\s/ || /==\z/
       ? "$_"
       : wrap(
           "",
           "",
           map split( /\n+/ ), $_
       ),
       @_;
}


sub pod_paragraphs_to_string {
    return join
       "\n",
       "",
       map( "$_\n", wrap_pod_paragraphs 60, @_ ),
           ## We wrap at 60 because paragraphs that pass through here
           ## are destined to be printed to a config file as inline
           ## commmentary and need to be narrow so they can be indented.
       "\n";

}


sub wrap_into_3_columns {
    my @topics = @_;

    ## Display 3 columns of topics
    push @topics, "" while @topics % 3;

    my $l = List::Util::max( map length, @topics );

    my $m = @topics / 3;

    return join "",
        map(
           sprintf(
              "    %-${l}s   %-${l}s   %s\n",
              @topics[ $_, $_+$m, $_+2*$m ]
           ),
           (0..$m - 1)
        );

}


sub class_hierarchy {
   my ( $class ) = @_;

   my @isa_q = ( $class );
       ## The queue of unvisited classes

   my %seen_classes;
       ## Classes to be skipped because they've been seen.

   my @classes;
   while ( @isa_q ) {
      my $class = shift @isa_q;
      next if $seen_classes{$class}++;

      push @classes, $class;

      push @isa_q, do {
         no strict "refs";
         @{"${class}::ISA"};
      };
   }

   return @classes;
}



###############################################################################

my @topics;
my %seen;
for my $fn ( @ARGV ) {
    my $topic = $fn;
    $topic =~ s{.*\b(bin|VCP)[\\/]}{}i;
    $topic =~ s{\..*}{};
    $topic =~ s{[\\/]}{::}g;

    warn( "Already emitted topic $topic from $seen{$topic}\n" ), next
        if $seen{$topic};
    $seen{$topic} = $fn;
    push @topics, $topic;

    ## Convert the whole POD in to a large help file for "vcp help"
    do {
        my $p = My::Pod::Text->new( width => 72 );
        $p->parse_from_file( $fn );
    };

    print_topic $topic, $text;

    ## Extract usage and config file docs for sources, filters and dests
    next unless $topic =~ /^(vcp$|source::|filter::|dest::)/i;

    pod2usage(
        -input   => $fn,
        -verbose => 0,
        -exitval => 'noexit',
    ) ;

    print_topic "$topic usage", $text;
    if ( $topic ne "vcp" ) {
        print_topic "$topic description",
            pod_paragraphs_to_string
                @{VCP::PodDESCRIPTIONParser->parse( $fn )};

        my $class = "VCP::$topic";
        eval "require $class" or die "$@: VCP::$class\n";
        my @classes = class_hierarchy $class;
        my $opts_hash = VCP::PodOPTIONSParser->parse( reverse @classes );
            ## reverse()d because we want parent classes to be scanned
            ## first so derived classes can replace options docs.

        for ( sort keys %$opts_hash ) {
            ( my $name = $_ ) =~ s/^--?//;
            print_topic(
                "$topic option $name",
                pod_paragraphs_to_string( @{$opts_hash->{$_}} )
            );
        };
    }
}

{
    @topics = sort @topics;

    print_topic "topics",
        join "",
            "vcp help topics:\n\n",
            wrap_into_3_columns( @topics );
}

{
    print_topic "", <<'TOPIC';
vcp - Version Copy, a tool for copying versions file repositories

help topics (use "vcp help <topic>" to see):

   vcp            General help for the vcp command

   source::cvs    Extracting from a cvs repository
   source::p4     Extracting from a p4 repository
   source::vss    Extracting from a VSS repository
   dest::cvs      Inserting in to a cvs repository
   dest::p4       Inserting in to a p4 repository

   newlines       Newline, ^Z and NULL issues
   process        How vcp works

   license        Copyright and license information
   topics         All available topics

The PAGER environment variable specifies pager program to use for
these help topics.
TOPIC
}

print OUTPUT <<POSTAMBLE;
);

sub get {
    shift;
    my ( \$topic ) = \@_;

    \$topic = '' unless defined \$topic;
    \$topic = lc \$topic;
    \$topic =~ s/^vcp:://;

    warn( "unkown help topic: '\$topic'\\n\\n" ), return
        unless \$topics{\$topic};

    return \$topics{\$topic};
}

sub print { CORE::print        shift->get( \@_ ); }
sub error { CORE::print STDERR shift->get( \@_ ); }

1;
POSTAMBLE