fa26d664fb7ad6f68c8842617810224169e364ef
[p5sagit/p5-mst-13.2.git] / pod / buildtoc
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw($masterpodfile %Build %Targets $Verbose $Up %Ignore
5             @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules);
6 use File::Spec;
7 use File::Find;
8 use FindBin;
9 use Text::Tabs;
10 use Text::Wrap;
11 use Getopt::Long;
12
13 no locale;
14
15 $Up = File::Spec->updir;
16 $masterpodfile = File::Spec->catdir($Up, "pod.lst");
17
18 # Generate any/all of these files
19 # --verbose gives slightly more output
20 # --build-all tries to build everything
21 # --build-foo updates foo as follows
22 # --showfiles shows the files to be changed
23
24 %Targets
25   = (
26      toc => "perltoc.pod",
27      manifest => File::Spec->catdir($Up, "MANIFEST"),
28      perlpod => "perl.pod",
29      vms => File::Spec->catdir($Up, "vms", "descrip_mms.template"),
30      nmake => File::Spec->catdir($Up, "win32", "Makefile"),
31      dmake => File::Spec->catdir($Up, "win32", "makefile.mk"),
32      podmak => File::Spec->catdir($Up, "win32", "pod.mak"),
33      # plan9 =>  File::Spec->catdir($Up, "plan9", "mkfile"),
34     );
35
36 {
37   my @files = keys %Targets;
38   my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
39   my $showfiles;
40   die <<__USAGE__
41 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
42 __USAGE__
43   unless @ARGV
44         && GetOptions (verbose => \$Verbose,
45                        showfiles => \$showfiles,
46                        map {+"build-$_", \$Build{$_}} @files, 'all');
47   # Set them all to true
48   @Build{@files} = @files if ($Build{all});
49   if ($showfiles) {
50       print
51           join(" ",
52                sort { lc $a cmp lc $b }
53                map {
54                    my ($v, $d, $f) = File::Spec->splitpath($_);
55                    my @d;
56                    @d = defined $d ? File::Spec->splitdir($d) : ();
57                    shift @d if @d;
58                    File::Spec->catfile(@d ?
59                                        (@d == 1 && $d[0] eq '' ? () : @d)
60                                        : "pod", $f);
61                } @Targets{grep { $_ ne 'all' && $Build{$_} } keys %Build}),
62           "\n";
63       exit(0);
64   }
65 }
66
67 # Don't copy these top level READMEs
68 %Ignore
69   = (
70      Y2K => 1,
71      micro => 1,
72 #     vms => 1,
73      );
74
75 if ($Verbose) {
76   print "I'm building $_\n" foreach grep {$Build{$_}} keys %Build;
77 }
78
79 chdir $FindBin::Bin or die "$0: Can't chdir $FindBin::Bin: $!";
80
81 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
82
83 foreach (<MASTER>) {
84   next if /^\#/;
85
86   # At least one upper case letter somewhere in the first group
87   if (/^(\S+)\s(.*)/ && $1 =~ tr/A-Z//) {
88     # it's a heading
89     my $flags = $1;
90     my %flags = (header => 1);
91     $flags{toc_omit} = 1 if $flags =~ tr/O//d;
92     $flags{include} = 1 if $flags =~ tr/I//d;
93     $flags{aux} = 1 if $flags =~ tr/A//d;
94     die "$0: Unknown flag found in heading line: $_" if length $flags;
95     push @Master, [\%flags, $2];
96
97   } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
98     # it's a section
99     my ($flags, $filename, $desc) = ($1, $2, $3);
100
101     my %flags = (indent => 0);
102     $flags{indent} = $1 if $flags =~ s/(\d+)//;
103     $flags{toc_omit} = 1 if $flags =~ tr/o//d;
104     $flags{aux} = 1 if $flags =~ tr/a//d;
105     if ($flags =~ tr/r//d) {
106       my $readme = $filename;
107       $readme =~ s/^perl//;
108       $Readmepods{$filename} = $Readmes{$readme} = $desc;
109       $flags{readme} = 1;
110     } elsif ($flags{aux}) {
111       $Aux{$filename} = $desc;
112     } else {
113       $Pods{$filename} = $desc;
114     }
115     die "$0: Unknown flag found in section line: $_" if length $flags;
116     push @Master, [\%flags, $filename, $desc];
117   } elsif (/^$/) {
118     push @Master, undef;
119   } else {
120     die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
121   }
122 }
123
124 close MASTER;
125
126 # Sanity cross check
127 {
128   my (%disk_pods, @disk_pods);
129   my (@manipods, %manipods);
130   my (@manireadmes, %manireadmes);
131   my (@perlpods, %perlpods);
132   my (%our_pods);
133
134   # Convert these to a list of filenames.
135   foreach (keys %Pods, keys %Readmepods) {
136     $our_pods{"$_.pod"}++;
137   }
138
139   # None of these filenames will be boolean false
140   @disk_pods = glob("*.pod");
141   @disk_pods{@disk_pods} = @disk_pods;
142
143   open(MANI, "../MANIFEST") || die "$0: opening ../MANIFEST failed: $!";
144   while (<MANI>) {
145     if (m!^pod/([^.]+\.pod)\s+!i) {
146       push @manipods, $1;
147     } elsif (m!^README\.(\S+)\s+!i) {
148       next if $Ignore{$1};
149       push @manireadmes, "perl$1.pod";
150     }
151   }
152   close(MANI);
153   @manipods{@manipods} = @manipods;
154   @manireadmes{@manireadmes} = @manireadmes;
155
156   open(PERLPOD, "perl.pod") || die "$0: opening perl.pod failed: $!\n";
157   while (<PERLPOD>) {
158     if (/^For ease of access, /../^\(If you're intending /) {
159       if (/^\s+(perl\S*)\s+\w/) {
160         push @perlpods, "$1.pod";
161       }
162     }
163   }
164   close(PERLPOD);
165   die "$0: could not find the pod listing of perl.pod\n"
166     unless @perlpods;
167   @perlpods{@perlpods} = @perlpods;
168
169   foreach my $i (sort keys %disk_pods) {
170     warn "$0: $i exists but is unknown by buildtoc\n"
171       unless $our_pods{$i};
172     warn "$0: $i exists but is unknown by ../MANIFEST\n"
173       if !$manipods{$i} && !$manireadmes{$i};
174     warn "$0: $i exists but is unknown by perl.pod\n"
175         unless $perlpods{$i};
176   }
177   foreach my $i (sort keys %our_pods) {
178     warn "$0: $i is known by buildtoc but does not exist\n"
179       unless $disk_pods{$i};
180   }
181   foreach my $i (sort keys %manipods) {
182     warn "$0: $i is known by ../MANIFEST but does not exist\n"
183       unless $disk_pods{$i};
184   }
185   foreach my $i (sort keys %perlpods) {
186     warn "$0: $i is known by perl.pod but does not exist\n"
187       unless $disk_pods{$i};
188   }
189 }
190
191 # Find all the mdoules
192 {
193   my @modpods;
194   find \&getpods => qw(../lib ../ext);
195
196   sub getpods {
197     if (/\.p(od|m)$/) {
198       my $file = $File::Find::name;
199       return if $file eq '../lib/Pod/Functions.pm'; # Used only by pod itself
200       return if $file =~ m!(?:^|/)t/!;
201       return if $file =~ m!lib/Attribute/Handlers/demo/!;
202       return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
203       return if $file =~ m!lib/Math/BigInt/t/!;
204       return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
205       return if $file =~ m!XS/(?:APItest|Typemap)!;
206       die "$0: tut $File::Find::name" if $file =~ /TUT/;
207       unless (open (F, "< $_\0")) {
208         warn "$0: bogus <$file>: $!";
209         system "ls", "-l", $file;
210       }
211       else {
212         my $line;
213         while ($line = <F>) {
214           if ($line =~ /^=head1\s+NAME\b/) {
215             push @modpods, $file;
216             #warn "GOOD $file\n";
217             return;
218           }
219         }
220         warn "$0: $file: cannot find =head1 NAME\n";
221       }
222     }
223   }
224
225   die "$0: no pods" unless @modpods;
226
227   my %done;
228   for (@modpods) {
229     #($name) = /(\w+)\.p(m|od)$/;
230     my $name = path2modname($_);
231     if ($name =~ /^[a-z]/) {
232       $Pragmata{$name} = $_;
233     } else {
234       if ($done{$name}++) {
235         # warn "already did $_\n";
236         next;
237       }
238       $Modules{$name} = $_;
239     }
240   }
241 }
242
243 # OK. Now a lot of ancillay function definitions follow
244 # Main program returns at "Do stuff"
245
246 sub path2modname {
247     local $_ = shift;
248     s/\.p(m|od)$//;
249     s-.*?/(lib|ext)/--;
250     s-/-::-g;
251     s/(\w+)::\1/$1/;
252     return $_;
253 }
254
255 sub output ($);
256
257 sub output_perltoc {
258   open(OUT, ">perltoc.pod") || die "$0: creating perltoc.pod failed: $!";
259
260   $/ = '';
261
262   ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
263
264         # !!!!!!!   DO NOT EDIT THIS FILE   !!!!!!!
265         # This file is autogenerated by buildtoc from all the other pods.
266         # Edit those files and run buildtoc --build-toc to effect changes.
267
268         =head1 NAME
269
270         perltoc - perl documentation table of contents
271
272         =head1 DESCRIPTION
273
274         This page provides a brief table of contents for the rest of the Perl
275         documentation set.  It is meant to be scanned quickly or grepped
276         through to locate the proper section you're looking for.
277
278         =head1 BASIC DOCUMENTATION
279
280 EOPOD2B
281 #' make emacs happy
282
283   # All the things in the master list that happen to be pod filenames
284   podset(map {"$_->[1].pod"} grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master);
285
286
287   ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
288
289
290
291         =head1 PRAGMA DOCUMENTATION
292
293 EOPOD2B
294
295   podset(sort values %Pragmata);
296
297   ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
298
299
300
301         =head1 MODULE DOCUMENTATION
302
303 EOPOD2B
304
305   podset( @Modules{ sort keys %Modules } );
306
307   $_= <<"EOPOD2B";
308
309
310         =head1 AUXILIARY DOCUMENTATION
311
312         Here should be listed all the extra programs' documentation, but they
313         don't all have manual pages yet:
314
315         =over 4
316
317 EOPOD2B
318
319   $_ .=  join "\n", map {"\t=item $_\n"} sort keys %Aux;
320   $_ .= <<"EOPOD2B" ;
321
322         =back
323
324         =head1 AUTHOR
325
326         Larry Wall <F<larry\@wall.org>>, with the help of oodles
327         of other folks.
328
329
330 EOPOD2B
331
332   s/^\t//gm;
333   output $_;
334   output "\n";                    # flush $LINE
335 }
336
337 # Below are all the auxiliary routines for generating perltoc.pod
338
339 my ($inhead1, $inhead2, $initem);
340
341 sub podset {
342     local @ARGV = @_;
343     my $pod;
344
345     while(<>) {
346         if (s/^=head1 (NAME)\s*/=head2 /) {
347             $pod = path2modname($ARGV);
348             unhead1();
349             output "\n \n\n=head2 ";
350             $_ = <>;
351             if ( /^\s*$pod\b/ ) {
352                 s/$pod\.pm/$pod/;       # '.pm' in NAME !?
353                 output $_;
354             } else {
355                 s/^/$pod, /;
356                 output $_;
357             }
358             next;
359         }
360         if (s/^=head1 (.*)/=item $1/) {
361             unhead2();
362             output "=over 4\n\n" unless $inhead1;
363             $inhead1 = 1;
364             output $_; nl(); next;
365         }
366         if (s/^=head2 (.*)/=item $1/) {
367             unitem();
368             output "=over 4\n\n" unless $inhead2;
369             $inhead2 = 1;
370             output $_; nl(); next;
371         }
372         if (s/^=item ([^=].*)/$1/) {
373             next if $pod eq 'perldiag';
374             s/^\s*\*\s*$// && next;
375             s/^\s*\*\s*//;
376             s/\n/ /g;
377             s/\s+$//;
378             next if /^[\d.]+$/;
379             next if $pod eq 'perlmodlib' && /^ftp:/;
380             ##print "=over 4\n\n" unless $initem;
381             output ", " if $initem;
382             $initem = 1;
383             s/\.$//;
384             s/^-X\b/-I<X>/;
385             output $_; next;
386         }
387         if (s/^=cut\s*\n//) {
388             unhead1();
389             next;
390         }
391     }
392 }
393
394 sub unhead1 {
395     unhead2();
396     if ($inhead1) {
397         output "\n\n=back\n\n";
398     }
399     $inhead1 = 0;
400 }
401
402 sub unhead2 {
403     unitem();
404     if ($inhead2) {
405         output "\n\n=back\n\n";
406     }
407     $inhead2 = 0;
408 }
409
410 sub unitem {
411     if ($initem) {
412         output "\n\n";
413         ##print "\n\n=back\n\n";
414     }
415     $initem = 0;
416 }
417
418 sub nl {
419     output "\n";
420 }
421
422 my $NEWLINE = 0;        # how many newlines have we seen recently
423 my $LINE;               # what remains to be printed
424
425 sub output ($) {
426     for (split /(\n)/, shift) {
427         if ($_ eq "\n") {
428             if ($LINE) {
429                 print OUT wrap('', '', $LINE);
430                 $LINE = '';
431             }
432             if (($NEWLINE) < 2) {
433                 print OUT;
434                 $NEWLINE++;
435             }
436         }
437         elsif (/\S/ && length) {
438             $LINE .= $_;
439             $NEWLINE = 0;
440         }
441     }
442 }
443
444 # End of original buildtoc. From here on are routines to generate new sections
445 # for and inplace edit other files
446
447 sub generate_perlpod {
448   my @output;
449   my $maxlength = 0;
450   foreach (@Master) {
451     my $flags = $_->[0];
452     next if $flags->{aux};
453
454     if (@$_ == 2) {
455       # Heading
456       push @output, "=head2 $_->[1]\n";
457     } elsif (@$_ == 3) {
458       # Section
459       my $start = " " x (4 + $flags->{indent}) . $_->[1];
460       $maxlength = length $start if length ($start) > $maxlength;
461       push @output, [$start, $_->[2]];
462     } elsif (@$_ == 0) {
463       # blank line
464       push @output, "\n";
465     } else {
466       die "$0: Illegal length " . scalar @$_;
467     }
468   }
469   # want at least 2 spaces padding
470   $maxlength += 2;
471   $maxlength = ($maxlength + 3) & ~3;
472   # sprintf gives $1.....$2 where ... are spaces:
473   return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
474                    @output);
475 }
476
477
478 sub generate_manifest {
479   # Annyoingly unexpand doesn't consider it good form to replace a single
480   # space before a tab with a tab
481   # Annoyingly (2) it returns read only values.
482   my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
483   map {s/ \t/\t\t/g; $_} @temp;
484 }
485 sub generate_manifest_pod {
486   generate_manifest map {["pod/$_.pod", $Pods{$_}]} sort keys %Pods;
487 }
488 sub generate_manifest_readme {
489   generate_manifest map {["README.$_", $Readmes{$_}]} sort keys %Readmes;
490 }
491
492 sub generate_roffitall {
493   (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
494    "\t\t\\",
495    map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
496    "\t\t\\",
497    map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
498    "\t\t\\",
499    map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
500   )
501 }
502
503 sub generate_descrip_mms_1 {
504   local $Text::Wrap::columns = 150;
505   my $count = 0;
506   my @lines = map {"pod" . $count++ . " = $_"}
507     split /\n/, wrap('', '', join " ", map "[.lib.pod]$_.pod",
508                      sort keys %Pods, keys %Readmepods);
509   @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
510 }
511
512 sub generate_descrip_mms_2 {
513   map {sprintf <<'SNIP', $_, $_ eq 'perlvms' ? 'vms' : 'pod', $_}
514 [.lib.pod]%s.pod : [.%s]%s.pod
515         @ If F$Search("[.lib]pod.dir").eqs."" Then Create/Directory [.lib.pod]
516         Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pod]
517 SNIP
518    sort keys %Pods, keys %Readmepods;
519 }
520
521 sub generate_nmake_1 {
522   map {sprintf "\tcopy ..\\README.%-8s .\\perl$_.pod\n", $_}
523     sort keys %Readmes;
524 }
525
526 # This doesn't have a trailing newline
527 sub generate_nmake_2 {
528   # Spot the special case
529   local $Text::Wrap::columns = 76;
530   my $line = wrap ("\t    ", "\t    ",
531                    join " ", sort map {"perl$_.pod"} "vms", keys %Readmes);
532   $line =~ s/$/ \\/mg;
533   $line;
534 }
535
536 sub generate_pod_mak {
537   my $variable = shift;
538   my @lines;
539   my $line = join "\\\n", "\U$variable = ",
540     map {"\t$_.$variable\t"} sort keys %Pods;
541   # Special case
542   $line =~ s/.*perltoc.html.*\n//m;
543   $line;
544 }
545
546 sub do_manifest {
547   my $name = shift;
548   my @manifest =
549     grep {! m!^pod/[^.]+\.pod.*\n!}
550       grep {! m!^README\.(\S+)! || $Ignore{$1}} @_;
551   # Dictionary order - fold and handle non-word chars as nothing
552   map  { $_->[0] }
553   sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
554   map  { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
555     @manifest,
556       &generate_manifest_pod(),
557         &generate_manifest_readme();
558 }
559
560 sub do_nmake {
561   my $name = shift;
562   my $makefile = join '', @_;
563   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
564   $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
565   my $sections = () = $makefile =~ m/\0+/g;
566   die "$0: $name contains no README copies" if $sections < 1;
567   die "$0: $name contains discontiguous README copies" if $sections > 1;
568   $makefile =~ s/\0+/join "", &generate_nmake_1/se;
569
570   $makefile =~ s{(cd \$\(PODDIR\) && del /f [^\n]+).*?(pod2html)}
571     {"$1\n" . &generate_nmake_2."\n\t    $2"}se;
572   $makefile;
573 }
574
575 # shut up used only once warning
576 *do_dmake = *do_dmake = \&do_nmake;
577
578 sub do_perlpod {
579   my $name = shift;
580   my $pod = join '', @_;
581
582   unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
583                     (?:\s+[a-z]{4,}.*\n #   fooo
584                     |=head.*\n          # =head foo
585                     |\s*\n              # blank line
586                    )+
587                   }
588           {$1 . join "", &generate_perlpod}mxe) {
589     die "$0: Failed to insert ammendments in do_perlpod";
590   }
591   $pod;
592 }
593
594 sub do_podmak {
595   my $name = shift;
596   my $body = join '', @_;
597   foreach my $variable qw(pod man html tex) {
598     die "$0: could not find $variable in $name"
599       unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
600         {"\n" . generate_pod_mak ($variable)}se;
601   }
602   $body;
603 }
604
605 sub do_vms {
606   my $name = shift;
607   my $makefile = join '', @_;
608   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
609   $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
610   my $sections = () = $makefile =~ m/\0+/g;
611   die "$0: $name contains no pod assignments" if $sections < 1;
612   die "$0: $name contains $sections discontigous pod assignments"
613     if $sections > 1;
614   $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
615
616   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
617
618 # Looking for rules like this
619 # [.lib.pod]perl.pod : [.pod]perl.pod
620 #       @ If F$Search("[.lib]pod.dir").eqs."" Then Create/Directory [.lib.pod]
621 #       Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pod]
622
623   $makefile =~ s/\n\Q[.lib.pod]\Eperl[^\n\.]*\.pod[^\n]+\n
624                  [^\n]+\n       # Another line
625                  [^\n]+\Q[.lib.pod]\E\n         # ends [.lib.pod]
626                     /\0/gsx;
627   $sections = () = $makefile =~ m/\0+/g;
628   die "$0: $name contains no copy rules" if $sections < 1;
629   die "$0: $name contains $sections discontigous copy rules"
630     if $sections > 1;
631   $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
632   $makefile;
633 }
634
635 # Do stuff
636
637 my $built;
638 while (my ($target, $name) = each %Targets) {
639   next unless $Build{$target};
640   $built++;
641   if ($target eq "toc") {
642     &output_perltoc;
643     next;
644   }
645   print "Now processing $name\n" if $Verbose;
646   open THING, $name or die "Can't open $name: $!";
647   my @orig = <THING>;
648   my $orig = join '', @orig;
649   close THING;
650   my @new = do {
651     no strict 'refs';
652     &{"do_$target"}($target, @orig);
653   };
654   my $new = join '', @new;
655   if ($new eq $orig) {
656     print "Was not modified\n" if $Verbose;
657     next;
658   }
659   rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
660   open THING, ">$name" or die "$0: Can't open $name for writing: $!";
661   print THING $new or die "$0: print to $name failed: $!";
662   close THING or die die "$0: close $name failed: $!";
663 }
664
665 warn "$0: was not instructed to build anything\n" unless $built;