d66b712f256ceb569c1e9cbb17b0b1cfdc5cf0bc
[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         tr/\015//d;
347         if (s/^=head1 (NAME)\s*/=head2 /) {
348             $pod = path2modname($ARGV);
349             unhead1();
350             output "\n \n\n=head2 ";
351             $_ = <>;
352             if ( /^\s*$pod\b/ ) {
353                 s/$pod\.pm/$pod/;       # '.pm' in NAME !?
354                 output $_;
355             } else {
356                 s/^/$pod, /;
357                 output $_;
358             }
359             next;
360         }
361         if (s/^=head1 (.*)/=item $1/) {
362             unhead2();
363             output "=over 4\n\n" unless $inhead1;
364             $inhead1 = 1;
365             output $_; nl(); next;
366         }
367         if (s/^=head2 (.*)/=item $1/) {
368             unitem();
369             output "=over 4\n\n" unless $inhead2;
370             $inhead2 = 1;
371             output $_; nl(); next;
372         }
373         if (s/^=item ([^=].*)/$1/) {
374             next if $pod eq 'perldiag';
375             s/^\s*\*\s*$// && next;
376             s/^\s*\*\s*//;
377             s/\n/ /g;
378             s/\s+$//;
379             next if /^[\d.]+$/;
380             next if $pod eq 'perlmodlib' && /^ftp:/;
381             ##print "=over 4\n\n" unless $initem;
382             output ", " if $initem;
383             $initem = 1;
384             s/\.$//;
385             s/^-X\b/-I<X>/;
386             output $_; next;
387         }
388         if (s/^=cut\s*\n//) {
389             unhead1();
390             next;
391         }
392     }
393 }
394
395 sub unhead1 {
396     unhead2();
397     if ($inhead1) {
398         output "\n\n=back\n\n";
399     }
400     $inhead1 = 0;
401 }
402
403 sub unhead2 {
404     unitem();
405     if ($inhead2) {
406         output "\n\n=back\n\n";
407     }
408     $inhead2 = 0;
409 }
410
411 sub unitem {
412     if ($initem) {
413         output "\n\n";
414         ##print "\n\n=back\n\n";
415     }
416     $initem = 0;
417 }
418
419 sub nl {
420     output "\n";
421 }
422
423 my $NEWLINE = 0;        # how many newlines have we seen recently
424 my $LINE;               # what remains to be printed
425
426 sub output ($) {
427     for (split /(\n)/, shift) {
428         if ($_ eq "\n") {
429             if ($LINE) {
430                 print OUT wrap('', '', $LINE);
431                 $LINE = '';
432             }
433             if (($NEWLINE) < 2) {
434                 print OUT;
435                 $NEWLINE++;
436             }
437         }
438         elsif (/\S/ && length) {
439             $LINE .= $_;
440             $NEWLINE = 0;
441         }
442     }
443 }
444
445 # End of original buildtoc. From here on are routines to generate new sections
446 # for and inplace edit other files
447
448 sub generate_perlpod {
449   my @output;
450   my $maxlength = 0;
451   foreach (@Master) {
452     my $flags = $_->[0];
453     next if $flags->{aux};
454
455     if (@$_ == 2) {
456       # Heading
457       push @output, "=head2 $_->[1]\n";
458     } elsif (@$_ == 3) {
459       # Section
460       my $start = " " x (4 + $flags->{indent}) . $_->[1];
461       $maxlength = length $start if length ($start) > $maxlength;
462       push @output, [$start, $_->[2]];
463     } elsif (@$_ == 0) {
464       # blank line
465       push @output, "\n";
466     } else {
467       die "$0: Illegal length " . scalar @$_;
468     }
469   }
470   # want at least 2 spaces padding
471   $maxlength += 2;
472   $maxlength = ($maxlength + 3) & ~3;
473   # sprintf gives $1.....$2 where ... are spaces:
474   return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
475                    @output);
476 }
477
478
479 sub generate_manifest {
480   # Annyoingly unexpand doesn't consider it good form to replace a single
481   # space before a tab with a tab
482   # Annoyingly (2) it returns read only values.
483   my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
484   map {s/ \t/\t\t/g; $_} @temp;
485 }
486 sub generate_manifest_pod {
487   generate_manifest map {["pod/$_.pod", $Pods{$_}]} sort keys %Pods;
488 }
489 sub generate_manifest_readme {
490   generate_manifest map {["README.$_", $Readmes{$_}]} sort keys %Readmes;
491 }
492
493 sub generate_roffitall {
494   (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
495    "\t\t\\",
496    map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
497    "\t\t\\",
498    map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
499    "\t\t\\",
500    map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
501   )
502 }
503
504 sub generate_descrip_mms_1 {
505   local $Text::Wrap::columns = 150;
506   my $count = 0;
507   my @lines = map {"pod" . $count++ . " = $_"}
508     split /\n/, wrap('', '', join " ", map "[.lib.pod]$_.pod",
509                      sort keys %Pods, keys %Readmepods);
510   @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
511 }
512
513 sub generate_descrip_mms_2 {
514   map {sprintf <<'SNIP', $_, $_ eq 'perlvms' ? 'vms' : 'pod', $_}
515 [.lib.pod]%s.pod : [.%s]%s.pod
516         @ If F$Search("[.lib]pod.dir").eqs."" Then Create/Directory [.lib.pod]
517         Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pod]
518 SNIP
519    sort keys %Pods, keys %Readmepods;
520 }
521
522 sub generate_nmake_1 {
523   map {sprintf "\tcopy ..\\README.%-8s .\\perl$_.pod\n", $_}
524     sort keys %Readmes;
525 }
526
527 # This doesn't have a trailing newline
528 sub generate_nmake_2 {
529   # Spot the special case
530   local $Text::Wrap::columns = 76;
531   my $line = wrap ("\t    ", "\t    ",
532                    join " ", sort map {"perl$_.pod"} "vms", keys %Readmes);
533   $line =~ s/$/ \\/mg;
534   $line;
535 }
536
537 sub generate_pod_mak {
538   my $variable = shift;
539   my @lines;
540   my $line = join "\\\n", "\U$variable = ",
541     map {"\t$_.$variable\t"} sort keys %Pods;
542   # Special case
543   $line =~ s/.*perltoc.html.*\n//m;
544   $line;
545 }
546
547 sub do_manifest {
548   my $name = shift;
549   my @manifest =
550     grep {! m!^pod/[^.]+\.pod.*\n!}
551       grep {! m!^README\.(\S+)! || $Ignore{$1}} @_;
552   # Dictionary order - fold and handle non-word chars as nothing
553   map  { $_->[0] }
554   sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
555   map  { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
556     @manifest,
557       &generate_manifest_pod(),
558         &generate_manifest_readme();
559 }
560
561 sub do_nmake {
562   my $name = shift;
563   my $makefile = join '', @_;
564   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
565   $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
566   my $sections = () = $makefile =~ m/\0+/g;
567   die "$0: $name contains no README copies" if $sections < 1;
568   die "$0: $name contains discontiguous README copies" if $sections > 1;
569   $makefile =~ s/\0+/join "", &generate_nmake_1/se;
570
571   $makefile =~ s{(cd \$\(PODDIR\) && del /f [^\n]+).*?(pod2html)}
572     {"$1\n" . &generate_nmake_2."\n\t    $2"}se;
573   $makefile;
574 }
575
576 # shut up used only once warning
577 *do_dmake = *do_dmake = \&do_nmake;
578
579 sub do_perlpod {
580   my $name = shift;
581   my $pod = join '', @_;
582
583   unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
584                     (?:\s+[a-z]{4,}.*\n #   fooo
585                     |=head.*\n          # =head foo
586                     |\s*\n              # blank line
587                    )+
588                   }
589           {$1 . join "", &generate_perlpod}mxe) {
590     die "$0: Failed to insert ammendments in do_perlpod";
591   }
592   $pod;
593 }
594
595 sub do_podmak {
596   my $name = shift;
597   my $body = join '', @_;
598   foreach my $variable qw(pod man html tex) {
599     die "$0: could not find $variable in $name"
600       unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
601         {"\n" . generate_pod_mak ($variable)}se;
602   }
603   $body;
604 }
605
606 sub do_vms {
607   my $name = shift;
608   my $makefile = join '', @_;
609   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
610   $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
611   my $sections = () = $makefile =~ m/\0+/g;
612   die "$0: $name contains no pod assignments" if $sections < 1;
613   die "$0: $name contains $sections discontigous pod assignments"
614     if $sections > 1;
615   $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
616
617   die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
618
619 # Looking for rules like this
620 # [.lib.pod]perl.pod : [.pod]perl.pod
621 #       @ If F$Search("[.lib]pod.dir").eqs."" Then Create/Directory [.lib.pod]
622 #       Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pod]
623
624   $makefile =~ s/\n\Q[.lib.pod]\Eperl[^\n\.]*\.pod[^\n]+\n
625                  [^\n]+\n       # Another line
626                  [^\n]+\Q[.lib.pod]\E\n         # ends [.lib.pod]
627                     /\0/gsx;
628   $sections = () = $makefile =~ m/\0+/g;
629   die "$0: $name contains no copy rules" if $sections < 1;
630   die "$0: $name contains $sections discontigous copy rules"
631     if $sections > 1;
632   $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
633   $makefile;
634 }
635
636 # Do stuff
637
638 my $built;
639 while (my ($target, $name) = each %Targets) {
640   next unless $Build{$target};
641   $built++;
642   if ($target eq "toc") {
643     &output_perltoc;
644     next;
645   }
646   print "Now processing $name\n" if $Verbose;
647   open THING, $name or die "Can't open $name: $!";
648   my @orig = <THING>;
649   my $orig = join '', @orig;
650   close THING;
651   my @new = do {
652     no strict 'refs';
653     &{"do_$target"}($target, @orig);
654   };
655   my $new = join '', @new;
656   if ($new eq $orig) {
657     print "Was not modified\n" if $Verbose;
658     next;
659   }
660   rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
661   open THING, ">$name" or die "$0: Can't open $name for writing: $!";
662   print THING $new or die "$0: print to $name failed: $!";
663   close THING or die die "$0: close $name failed: $!";
664 }
665
666 warn "$0: was not instructed to build anything\n" unless $built;