4 use vars qw($masterpodfile %Build %Targets $Verbose $Quiet $Up %Ignore
5 @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
17 $Up = File::Spec->updir;
18 $masterpodfile = File::Spec->catfile($Up, "pod.lst");
20 # Generate any/all of these files
21 # --verbose gives slightly more output
22 # --quiet suppresses routine warnings
23 # --build-all tries to build everything
24 # --build-foo updates foo as follows
25 # --showfiles shows the files to be changed
30 manifest => File::Spec->catdir($Up, "MANIFEST"),
31 perlpod => "perl.pod",
32 vms => File::Spec->catfile($Up, "vms", "descrip_mms.template"),
33 nmake => File::Spec->catfile($Up, "win32", "Makefile"),
34 dmake => File::Spec->catfile($Up, "win32", "makefile.mk"),
35 podmak => File::Spec->catfile($Up, "win32", "pod.mak"),
36 # plan9 => File::Spec->catdir($Up, "plan9", "mkfile"),
37 unix => File::Spec->catfile($Up, "Makefile.SH"),
42 my @files = keys %Targets;
43 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
46 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
49 && GetOptions (verbose => \$Verbose,
51 showfiles => \$showfiles,
52 map {+"build-$_", \$Build{$_}} @files, 'all');
53 # Set them all to true
54 @Build{@files} = @files if ($Build{all});
58 sort { lc $a cmp lc $b }
60 my ($v, $d, $f) = File::Spec->splitpath($_);
62 @d = defined $d ? File::Spec->splitdir($d) : ();
64 File::Spec->catfile(@d ?
65 (@d == 1 && $d[0] eq '' ? () : @d)
67 } @Targets{grep { $_ ne 'all' && $Build{$_} } keys %Build}),
73 # Don't copy these top level READMEs
81 print "I'm building $_\n" foreach grep {$Build{$_}} keys %Build;
84 chdir $FindBin::Bin or die "$0: Can't chdir $FindBin::Bin: $!";
86 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
88 my ($delta_source, $delta_target);
93 # At least one upper case letter somewhere in the first group
94 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
98 my %flags = (header => 1);
99 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
100 $flags{aux} = 1 if $flags =~ tr/a//d;
101 die "$0: Unknown flag found in heading line: $_" if length $flags;
102 push @Master, [\%flags, $2];
104 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
106 my ($flags, $filename, $desc) = ($1, $2, $3);
108 my %flags = (indent => 0);
109 $flags{indent} = $1 if $flags =~ s/(\d+)//;
110 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
111 $flags{aux} = 1 if $flags =~ tr/a//d;
113 if ($flags =~ tr/D//d) {
114 $flags{perlpod_omit} = 1;
115 $delta_source = "$filename.pod";
117 if ($flags =~ tr/d//d) {
118 $flags{manifest_omit} = 1;
119 $delta_target = "$filename.pod";
121 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
123 if ($flags =~ tr/r//d) {
124 my $readme = $filename;
125 $readme =~ s/^perl//;
126 $Readmepods{$filename} = $Readmes{$readme} = $desc;
128 } elsif ($flags{aux}) {
129 $Aux{$filename} = $desc;
131 $Pods{$filename} = $desc;
133 die "$0: Unknown flag found in section line: $_" if length $flags;
134 push @Master, [\%flags, $filename, $desc];
138 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
141 if (defined $delta_source) {
142 if (defined $delta_target) {
143 # This way round so that keys can act as a MANIFEST skip list
144 # Targets will aways be in the pod directory. Currently we can only cope
145 # with sources being in the same directory.
146 $Copies{$delta_target} = $delta_source;
148 die "$0: delta source defined but not target";
150 } elsif (defined $delta_target) {
151 die "$0: delta target defined but not target";
158 my (%disk_pods, @disk_pods);
159 my (@manipods, %manipods);
160 my (@manireadmes, %manireadmes);
161 my (@perlpods, %perlpods);
165 # Convert these to a list of filenames.
166 foreach (keys %Pods, keys %Readmepods) {
167 $our_pods{"$_.pod"}++;
170 # None of these filenames will be boolean false
171 @disk_pods = glob("*.pod");
172 @disk_pods{@disk_pods} = @disk_pods;
174 # Things we copy from won't be in perl.pod
175 # Things we copy to won't be in MANIFEST
176 @sources{values %Copies} = ();
178 open(MANI, "../MANIFEST") || die "$0: opening ../MANIFEST failed: $!";
180 if (m!^pod/([^.]+\.pod)\s+!i) {
182 } elsif (m!^README\.(\S+)\s+!i) {
184 push @manireadmes, "perl$1.pod";
188 @manipods{@manipods} = @manipods;
189 @manireadmes{@manireadmes} = @manireadmes;
191 open(PERLPOD, "perl.pod") || die "$0: opening perl.pod failed: $!\n";
193 if (/^For ease of access, /../^\(If you're intending /) {
194 if (/^\s+(perl\S*)\s+\w/) {
195 push @perlpods, "$1.pod";
200 die "$0: could not find the pod listing of perl.pod\n"
202 @perlpods{@perlpods} = @perlpods;
204 foreach my $i (sort keys %disk_pods) {
205 warn "$0: $i exists but is unknown by buildtoc\n"
206 unless $our_pods{$i};
207 warn "$0: $i exists but is unknown by ../MANIFEST\n"
208 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
209 warn "$0: $i exists but is unknown by perl.pod\n"
210 if !$perlpods{$i} && !exists $sources{$i};
212 my @BuildTargets = grep {defined} @Targets{grep $_ ne 'all', keys %Build};
214 @BuildFiles{@BuildTargets} = @BuildTargets;
216 foreach my $i (sort keys %our_pods) {
217 warn "$0: $i is known by buildtoc but does not exist\n"
218 unless $disk_pods{$i} or $BuildFiles{$i};
220 foreach my $i (sort keys %manipods) {
221 warn "$0: $i is known by ../MANIFEST but does not exist\n"
222 unless $disk_pods{$i};
223 warn "$0: $i is known by ../MANIFEST but is marked as generated\n"
226 foreach my $i (sort keys %perlpods) {
227 warn "$0: $i is known by perl.pod but does not exist\n"
228 unless $disk_pods{$i} or $BuildFiles{$i};
232 # Find all the modules
235 find \&getpods => qw(../lib ../ext);
239 my $file = $File::Find::name;
240 return if $file eq '../lib/Pod/Functions.pm'; # Used only by pod itself
241 return if $file =~ m!(?:^|/)t/!;
242 return if $file =~ m!lib/Attribute/Handlers/demo/!;
243 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
244 return if $file =~ m!lib/Math/BigInt/t/!;
245 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
246 return if $file =~ m!XS/(?:APItest|Typemap)!;
248 return if $pod =~ s/pm$/pod/ && -e $pod;
249 die "$0: tut $File::Find::name" if $file =~ /TUT/;
250 unless (open (F, "< $_\0")) {
251 warn "$0: bogus <$file>: $!";
252 system "ls", "-l", $file;
256 while ($line = <F>) {
257 if ($line =~ /^=head1\s+NAME\b/) {
258 push @modpods, $file;
259 #warn "GOOD $file\n";
265 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
270 die "$0: no pods" unless @modpods;
274 #($name) = /(\w+)\.p(m|od)$/;
275 my $name = path2modname($_);
276 if ($name =~ /^[a-z]/) {
277 $Pragmata{$name} = $_;
279 if ($done{$name}++) {
280 # warn "already did $_\n";
283 $Modules{$name} = $_;
288 # OK. Now a lot of ancillary function definitions follow
289 # Main program returns at "Do stuff"
303 open(OUT, ">perltoc.pod") || die "$0: creating perltoc.pod failed: $!";
307 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
309 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
310 # This file is autogenerated by buildtoc from all the other pods.
311 # Edit those files and run buildtoc --build-toc to effect changes.
315 perltoc - perl documentation table of contents
319 This page provides a brief table of contents for the rest of the Perl
320 documentation set. It is meant to be scanned quickly or grepped
321 through to locate the proper section you're looking for.
323 =head1 BASIC DOCUMENTATION
328 # All the things in the master list that happen to be pod filenames
329 podset(map {"$_->[1].pod"} grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master);
332 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
336 =head1 PRAGMA DOCUMENTATION
340 podset(sort values %Pragmata);
342 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
346 =head1 MODULE DOCUMENTATION
350 podset( @Modules{ sort keys %Modules } );
355 =head1 AUXILIARY DOCUMENTATION
357 Here should be listed all the extra programs' documentation, but they
358 don't all have manual pages yet:
364 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
371 Larry Wall <F<larry\@wall.org>>, with the help of oodles
379 output "\n"; # flush $LINE
383 # Below are all the auxiliary routines for generating perltoc.pod
385 my ($inhead1, $inhead2, $initem);
391 return unless scalar(@ARGV);
395 if (s/^=head1 (NAME)\s*/=head2 /) {
396 $pod = path2modname($ARGV);
398 output "\n \n\n=head2 ";
400 # Remove svn keyword expansions from the Perl FAQ
401 s/ \(\$Revision: \d+ \$\)//g;
402 if ( /^\s*$pod\b/ ) {
403 s/$pod\.pm/$pod/; # '.pm' in NAME !?
411 if (s/^=head1 (.*)/=item $1/) {
413 output "=over 4\n\n" unless $inhead1;
415 output $_; nl(); next;
417 if (s/^=head2 (.*)/=item $1/) {
419 output "=over 4\n\n" unless $inhead2;
421 output $_; nl(); next;
423 if (s/^=item ([^=].*)/$1/) {
424 next if $pod eq 'perldiag';
425 s/^\s*\*\s*$// && next;
430 next if $pod eq 'perlmodlib' && /^ftp:/;
431 ##print "=over 4\n\n" unless $initem;
432 output ", " if $initem;
438 if (s/^=cut\s*\n//) {
448 output "\n\n=back\n\n";
456 output "\n\n=back\n\n";
464 ##print "\n\n=back\n\n";
473 my $NEWLINE = 0; # how many newlines have we seen recently
474 my $LINE; # what remains to be printed
477 for (split /(\n)/, shift) {
480 print OUT wrap('', '', $LINE);
483 if (($NEWLINE) < 2) {
488 elsif (/\S/ && length) {
495 # End of original buildtoc. From here on are routines to generate new sections
496 # for and inplace edit other files
498 sub generate_perlpod {
503 next if $flags->{aux};
504 next if $flags->{perlpod_omit};
508 push @output, "=head2 $_->[1]\n";
511 my $start = " " x (4 + $flags->{indent}) . $_->[1];
512 $maxlength = length $start if length ($start) > $maxlength;
513 push @output, [$start, $_->[2]];
518 die "$0: Illegal length " . scalar @$_;
521 # want at least 2 spaces padding
523 $maxlength = ($maxlength + 3) & ~3;
524 # sprintf gives $1.....$2 where ... are spaces:
525 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
530 sub generate_manifest {
531 # Annyoingly unexpand doesn't consider it good form to replace a single
532 # space before a tab with a tab
533 # Annoyingly (2) it returns read only values.
534 my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
535 map {s/ \t/\t\t/g; $_} @temp;
537 sub generate_manifest_pod {
538 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
539 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
541 sub generate_manifest_readme {
542 generate_manifest sort {$a->[0] cmp $b->[0]}
543 ["README.vms", "Notes about installing the VMS port"],
544 map {["README.$_", $Readmes{$_}]} keys %Readmes;
547 sub generate_roffitall {
548 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
550 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
552 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
554 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
558 sub generate_descrip_mms_1 {
559 local $Text::Wrap::columns = 150;
561 my @lines = map {"pod" . $count++ . " = $_"}
562 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
563 sort keys %Pods, keys %Readmepods);
564 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
567 sub generate_descrip_mms_2 {
569 [.lib.pods]$_.pod : [.pod]$_.pod
570 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
571 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
573 sort keys %Pods, keys %Readmepods;
576 sub generate_descrip_mms_3 {
577 map qq{\t- If F\$Search("[.pod]$_").nes."" Then Delete/NoConfirm/Log [.pod]$_;*},
578 sort keys %Generated, keys %Copies;
581 sub generate_nmake_1 {
582 # XXX Fix this with File::Spec
583 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
585 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
588 # This doesn't have a trailing newline
589 sub generate_nmake_2 {
590 # Spot the special case
591 local $Text::Wrap::columns = 76;
592 my $line = wrap ("\t ", "\t ",
593 join " ", sort keys %Copies, keys %Generated,
594 map {"perl$_.pod"} keys %Readmes);
599 sub generate_pod_mak {
600 my $variable = shift;
602 my $line = join "\\\n", "\U$variable = ",
603 map {"\t$_.$variable\t"} sort keys %Pods;
605 $line =~ s/.*perltoc.html.*\n//m;
609 sub verify_contiguous {
610 my ($name, $content, $what) = @_;
611 my $sections = () = $content =~ m/\0+/g;
612 croak("$0: $name contains no $what") if $sections < 1;
613 croak("$0: $name contains discontiguous $what") if $sections > 1;
619 grep {! m!^pod/[^.]+\.pod.*\n!}
620 grep {! m!^README\.(\S+)! || $Ignore{$1}} @_;
621 # Dictionary order - fold and handle non-word chars as nothing
623 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
624 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
626 &generate_manifest_pod(),
627 &generate_manifest_readme();
632 my $makefile = join '', @_;
633 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
634 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
635 verify_contiguous($name, $makefile, 'README copies');
636 # Now remove the other copies that follow
637 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
638 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
640 $makefile =~ s{(del /f [^\n]+podchecker[^\n]+).*?(pod2html)}
641 {"$1\n" . &generate_nmake_2."\n\t $2"}se;
645 # shut up used only once warning
646 *do_dmake = *do_dmake = \&do_nmake;
650 my $pod = join '', @_;
652 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
653 (?:\s+[a-z]{4,}.*\n # fooo
654 |=head.*\n # =head foo
658 {$1 . join "", &generate_perlpod}mxe) {
659 die "$0: Failed to insert amendments in do_perlpod";
666 my $body = join '', @_;
667 foreach my $variable (qw(pod man html tex)) {
668 die "$0: could not find $variable in $name"
669 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
670 {"\n" . generate_pod_mak ($variable)}se;
677 my $makefile = join '', @_;
678 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
679 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
680 verify_contiguous($name, $makefile, 'pod assignments');
681 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
683 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
685 # Looking for rules like this
686 # [.lib.pods]perl.pod : [.pod]perl.pod
687 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
688 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
690 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
691 [^\n]+\n # Another line
692 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
694 verify_contiguous($name, $makefile, 'copy rules');
695 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
697 # Looking for rules like this:
698 # - If F$Search("[.pod]perldelta.pod").nes."" Then Delete/NoConfirm/Log [.pod]perldelta.pod;*
699 $makefile =~ s!(?:\t- If F\$Search\("\[\.pod\]perl[a-z]+\Q.pod").nes."" Then Delete/NoConfirm/Log [.pod]perl\E[a-z]+\.pod;\*\n)+!\0!sg;
700 verify_contiguous($name, $makefile, 'delete rules');
701 $makefile =~ s/\0+/join "\n", &generate_descrip_mms_3, ''/se;
708 my $makefile_SH = join '', @_;
709 die "$0: $name contains NUL bytes" if $makefile_SH =~ /\0/;
711 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
712 {join ' ', $1, map "pod/$_",
713 sort keys %Copies, grep {!/perltoc/} keys %Generated
716 # pod/perldelta.pod: pod/perl511delta.pod
717 # cd pod && $(LNS) perl511delta.pod perldelta.pod
720 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
721 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
724 verify_contiguous($name, $makefile_SH, 'copy rules');
726 my @copy_rules = map "
727 pod/$_: pod/$Copies{$_}
728 \$(LNS) $Copies{$_} pod/$_
731 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
739 while (my ($target, $name) = each %Targets) {
740 print "Working on target $target\n" if $Verbose;
741 next unless $Build{$target};
743 if ($target eq "toc") {
744 print "Now processing $name\n" if $Verbose;
746 print "Finished\n" if $Verbose;
749 print "Now processing $name\n" if $Verbose;
750 open THING, $name or die "Can't open $name: $!";
752 my $orig = join '', @orig;
756 &{"do_$target"}($target, @orig);
758 my $new = join '', @new;
760 print "Was not modified\n" if $Verbose;
763 rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
764 open THING, ">$name" or die "$0: Can't open $name for writing: $!";
765 print THING $new or die "$0: print to $name failed: $!";
766 close THING or die "$0: close $name failed: $!";
769 warn "$0: was not instructed to build anything\n" unless $built;