4 use vars qw($masterpodfile %Build %Targets $Verbose $Quiet %Ignore
5 @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
6 %Copies %Generated $Test);
19 my $Top = File::Spec->catdir($FindBin::Bin, File::Spec->updir);
23 return File::Spec->catdir($Top, split /\//, $path) if $path =~ s!/\z!!;
24 return File::Spec->catfile($Top, split /\//, $path);
28 # make it clearer when we haven't run to completion, as we can be quite
29 # noisy when things are working ok
32 print STDERR "$0: ", @_;
33 print STDERR "\n" unless $_[-1] =~ /\n\z/;
34 print STDERR "ABORTED\n";
39 $masterpodfile = abs_from_top('pod.lst');
41 # Generate any/all of these files
42 # --verbose gives slightly more output
43 # --quiet suppresses routine warnings
44 # --build-all tries to build everything
45 # --build-foo updates foo as follows
46 # --showfiles shows the files to be changed
47 # --test exit if perl.pod, pod.lst, MANIFEST are consistent, and regenerated
48 # files are up to date, die otherwise.
52 toc => 'pod/perltoc.pod',
53 manifest => 'MANIFEST',
54 perlpod => 'pod/perl.pod',
55 vms => 'vms/descrip_mms.template',
56 nmake => 'win32/Makefile',
57 dmake => 'win32/makefile.mk',
58 podmak => 'win32/pod.mak',
59 # plan9 => 'plan9/mkfile'),
60 unix => 'Makefile.SH',
64 foreach (values %Targets) {
65 $_ = abs_from_top($_);
68 # process command-line switches
71 my @files = keys %Targets;
72 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
76 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
79 && GetOptions (verbose => \$Verbose,
81 showfiles => \$showfiles,
83 map {+"build-$_", \$build_these{$_}} @files, 'all');
84 if ($build_these{all}) {
87 while (my ($file, $want) = each %build_these) {
88 $Build{$file} = $Targets{$file} if $want;
94 sort { lc $a cmp lc $b }
96 my ($v, $d, $f) = File::Spec->splitpath($_);
98 @d = defined $d ? File::Spec->splitdir($d) : ();
100 File::Spec->catfile(@d ?
101 (@d == 1 && $d[0] eq '' ? () : @d)
109 # Don't copy these top level READMEs
117 print "I will be building $_\n" foreach keys %Build;
123 open my $master, '<', $masterpodfile or my_die "Can't open $masterpodfile: $!";
125 my ($delta_source, $delta_target);
127 foreach (<$master>) {
130 # At least one upper case letter somewhere in the first group
131 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
135 my %flags = (header => 1);
136 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
137 $flags{aux} = 1 if $flags =~ tr/a//d;
138 my_die "Unknown flag found in heading line: $_" if length $flags;
139 push @Master, [\%flags, $2];
141 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
143 my ($flags, $filename, $desc) = ($1, $2, $3);
145 my %flags = (indent => 0);
146 $flags{indent} = $1 if $flags =~ s/(\d+)//;
147 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
148 $flags{aux} = 1 if $flags =~ tr/a//d;
150 if ($flags =~ tr/D//d) {
151 $flags{manifest_omit} = 1;
152 $delta_source = "$filename.pod";
154 if ($flags =~ tr/d//d) {
155 $flags{perlpod_omit} = 1;
156 $delta_target = "$filename.pod";
158 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
160 if ($flags =~ tr/r//d) {
161 my $readme = $filename;
162 $readme =~ s/^perl//;
163 $Readmepods{$filename} = $Readmes{$readme} = $desc;
165 } elsif ($flags{aux}) {
166 $Aux{$filename} = $desc;
168 $Pods{$filename} = $desc;
170 my_die "Unknown flag found in section line: $_" if length $flags;
171 push @Master, [\%flags, $filename, $desc];
175 my_die "Malformed line: $_" if $1 =~ tr/A-Z//;
178 if (defined $delta_source) {
179 if (defined $delta_target) {
180 # This way round so that keys can act as a MANIFEST skip list
181 # Targets will aways be in the pod directory. Currently we can only cope
182 # with sources being in the same directory.
183 $Copies{$delta_target} = $delta_source;
185 my_die "delta source defined but not target";
187 } elsif (defined $delta_target) {
188 my_die "delta target defined but not source";
195 my (%disk_pods, @disk_pods);
196 my (@manipods, %manipods);
197 my (@manireadmes, %manireadmes);
198 my (@perlpods, %perlpods);
201 # Convert these to a list of filenames.
202 foreach (keys %Pods, keys %Readmepods) {
203 $our_pods{"$_.pod"}++;
206 opendir my $dh, abs_from_top('pod/');
207 while (defined ($_ = readdir $dh)) {
208 next unless /\.pod\z/;
213 # Things we copy from won't be in perl.pod
214 # Things we copy to won't be in MANIFEST
216 my $filename = abs_from_top('MANIFEST');
217 open my $mani, '<', $filename or my_die "opening $filename failed: $!";
219 if (m!^pod/([^.]+\.pod)\s+!i) {
221 } elsif (m!^README\.(\S+)\s+!i) {
223 push @manireadmes, "perl$1.pod";
226 close $mani or my_die "close MANIFEST: $!\n";
227 @manipods{@manipods} = @manipods;
228 @manireadmes{@manireadmes} = @manireadmes;
230 $filename = abs_from_top('pod/perl.pod');
231 open my $perlpod, '<', $filename or my_die "opening $filename failed: $!\n";
233 if (/^For ease of access, /../^\(If you're intending /) {
234 if (/^\s+(perl\S*)\s+\w/) {
235 push @perlpods, "$1.pod";
239 close $perlpod or my_die "close perlpod: $!\n";
240 my_die "could not find the pod listing of perl.pod\n"
242 @perlpods{@perlpods} = @perlpods;
245 foreach my $i (sort keys %disk_pods) {
246 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
247 unless $our_pods{$i};
248 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
249 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
250 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
251 if !$perlpods{$i} && !exists $Copies{$i};
254 foreach my $path (values %Build) {
255 (undef, undef, my $file) = File::Spec->splitpath($path);
259 foreach my $i (sort keys %our_pods) {
260 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
261 unless $disk_pods{$i} or $BuildFiles{$i};
263 foreach my $i (sort keys %manipods) {
264 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
265 unless $disk_pods{$i};
266 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
269 foreach my $i (sort keys %perlpods) {
270 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
271 unless $disk_pods{$i} or $BuildFiles{$i};
275 printf "1..%d\n", 1 + scalar keys %Build;
283 warn @inconsistent if @inconsistent;
287 # Find all the modules
290 find \&getpods => abs_from_top('lib/');
294 my $file = $File::Find::name;
295 return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
296 return if $file =~ m!(?:^|/)t/!;
297 return if $file =~ m!lib/Attribute/Handlers/demo/!;
298 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
299 return if $file =~ m!lib/Math/BigInt/t/!;
300 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
301 return if $file =~ m!XS/(?:APItest|Typemap)!;
303 return if $pod =~ s/pm$/pod/ && -e $pod;
304 unless (open my $f, '<', $_) {
305 warn "$0: bogus <$file>: $!";
306 system "ls", "-l", $file;
310 while ($line = <$f>) {
311 if ($line =~ /^=head1\s+NAME\b/) {
312 push @modpods, $file;
316 warn "$0: NOTE: cannot find '=head1 NAME' in:\n $file\n" unless $Quiet;
321 my_die "Can't find any pods!\n" unless @modpods;
326 $name =~ s/\.p(m|od)$//;
327 $name =~ s-.*?/lib/--;
329 next if $done{$name}++;
331 if ($name =~ /^[a-z]/) {
332 $Pragmata{$name} = $_;
334 $Modules{$name} = $_;
339 # OK. Now a lot of ancillary function definitions follow
340 # Main program returns at "Do stuff"
345 my $filename = shift;
347 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
349 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
350 # This file is autogenerated by buildtoc from all the other pods.
351 # Edit those files and run buildtoc --build-toc to effect changes.
355 perltoc - perl documentation table of contents
359 This page provides a brief table of contents for the rest of the Perl
360 documentation set. It is meant to be scanned quickly or grepped
361 through to locate the proper section you're looking for.
363 =head1 BASIC DOCUMENTATION
368 # All the things in the master list that happen to be pod filenames
369 foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
370 podset($_->[1], abs_from_top("pod/$_->[1].pod"));
374 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
378 =head1 PRAGMA DOCUMENTATION
382 foreach (sort keys %Pragmata) {
383 podset($_, $Pragmata{$_});
386 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
390 =head1 MODULE DOCUMENTATION
394 foreach (sort keys %Modules) {
395 podset($_, $Modules{$_});
401 =head1 AUXILIARY DOCUMENTATION
403 Here should be listed all the extra programs' documentation, but they
404 don't all have manual pages yet:
410 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
417 Larry Wall <F<larry\@wall.org>>, with the help of oodles
426 $OUT =~ s/\n\s+\n/\n\n/gs;
427 $OUT =~ s/\n{3,}/\n\n/g;
429 $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;
434 # Below are all the auxiliary routines for generating perltoc.pod
436 my ($inhead1, $inhead2, $initem);
439 my ($pod, $file) = @_;
443 open my $fh, '<', $file or my_die "Can't open file '$file' for $pod: $!";
447 if (s/^=head1 (NAME)\s*/=head2 /) {
449 $OUT .= "\n\n=head2 ";
451 # Remove svn keyword expansions from the Perl FAQ
452 s/ \(\$Revision: \d+ \$\)//g;
453 if ( /^\s*\Q$pod\E\b/ ) {
454 s/$pod\.pm/$pod/; # '.pm' in NAME !?
459 elsif (s/^=head1 (.*)/=item $1/) {
461 $OUT .= "=over 4\n\n" unless $inhead1;
465 elsif (s/^=head2 (.*)/=item $1/) {
467 $OUT .= "=over 4\n\n" unless $inhead2;
471 elsif (s/^=item ([^=].*)/$1/) {
472 next if $pod eq 'perldiag';
473 s/^\s*\*\s*$// && next;
478 next if $pod eq 'perlmodlib' && /^ftp:/;
479 $OUT .= ", " if $initem;
485 unhead1() if /^=cut\s*\n/;
495 $OUT .= "\n\n=back\n\n";
503 $OUT .= "\n\n=back\n\n";
515 # End of original buildtoc. From here on are routines to generate new sections
516 # for and inplace edit other files
518 sub generate_perlpod {
523 next if $flags->{aux};
524 next if $flags->{perlpod_omit};
528 push @output, "=head2 $_->[1]\n";
531 my $start = " " x (4 + $flags->{indent}) . $_->[1];
532 $maxlength = length $start if length ($start) > $maxlength;
533 push @output, [$start, $_->[2]];
538 my_die "Illegal length " . scalar @$_;
541 # want at least 2 spaces padding
543 $maxlength = ($maxlength + 3) & ~3;
544 # sprintf gives $1.....$2 where ... are spaces:
545 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
550 sub generate_manifest {
551 # Annoyingly, unexpand doesn't consider it good form to replace a single
552 # space before a tab with a tab
553 # Annoyingly (2) it returns read only values.
554 my @temp = unexpand (map {sprintf "%-32s%s", @$_} @_);
555 map {s/ \t/\t\t/g; $_} @temp;
557 sub generate_manifest_pod {
558 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
559 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
561 sub generate_manifest_readme {
562 generate_manifest sort {$a->[0] cmp $b->[0]}
563 ["README.vms", "Notes about installing the VMS port"],
564 map {["README.$_", $Readmes{$_}]} keys %Readmes;
567 sub generate_roffitall {
568 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
570 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
572 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
574 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
578 sub generate_descrip_mms_1 {
579 local $Text::Wrap::columns = 150;
581 my @lines = map {"pod" . $count++ . " = $_"}
582 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
583 sort keys %Pods, keys %Readmepods);
584 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
587 sub generate_descrip_mms_2 {
589 [.lib.pods]$_.pod : [.pod]$_.pod
590 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
591 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
593 sort keys %Pods, keys %Readmepods;
596 sub generate_nmake_1 {
597 # XXX Fix this with File::Spec
598 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
600 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
603 # This doesn't have a trailing newline
604 sub generate_nmake_2 {
605 # Spot the special case
606 local $Text::Wrap::columns = 76;
607 my $line = wrap ("\t ", "\t ",
608 join " ", sort keys %Copies, keys %Generated,
609 map {"perl$_.pod"} keys %Readmes);
615 sub generate_pod_mak {
616 my $variable = shift;
618 my $line = join "\\\n", "\U$variable = ",
619 map {"\t$_.$variable\t"} sort keys %Pods;
621 $line =~ s/.*perltoc.html.*\n//m;
625 sub verify_contiguous {
626 my ($name, $content, $what) = @_;
627 my $sections = () = $content =~ m/\0+/g;
628 croak("$0: $name contains no $what") if $sections < 1;
629 croak("$0: $name contains discontiguous $what") if $sections > 1;
633 my ($name, $prev) = @_;
635 grep {! m!^pod/[^.]+\.pod.*!}
636 grep {! m!^README\.(\S+)! || $Ignore{$1}} split "\n", $prev;
638 # Dictionary order - fold and handle non-word chars as nothing
640 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
641 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
643 &generate_manifest_pod(),
644 &generate_manifest_readme()), '';
648 my ($name, $makefile) = @_;
649 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
650 verify_contiguous($name, $makefile, 'README copies');
651 # Now remove the other copies that follow
652 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
653 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
655 $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
656 {"$1\n" . &generate_nmake_2."\n\t$2"}se;
660 # shut up used only once warning
661 *do_dmake = *do_dmake = \&do_nmake;
664 my ($name, $pod) = @_;
666 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
667 (?:\s+[a-z]{4,}.*\n # fooo
668 |=head.*\n # =head foo
672 {$1 . join "", &generate_perlpod}mxe) {
673 my_die "Failed to insert amendments in do_perlpod";
679 my ($name, $body) = @_;
680 foreach my $variable (qw(pod man html tex)) {
681 my_die "could not find $variable in $name"
682 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
683 {"\n" . generate_pod_mak ($variable)}se;
689 my ($name, $makefile) = @_;
690 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
691 verify_contiguous($name, $makefile, 'pod assignments');
692 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
694 my_die "$name contains NUL bytes" if $makefile =~ /\0/;
696 # Looking for the macro defining the current perldelta:
697 #PERLDELTA_CURRENT = [.pod]perl5139delta.pod
699 $makefile =~ s/\nPERLDELTA_CURRENT\s+=\s+\Q[.pod]perl\E\d+delta\.pod\n
701 verify_contiguous($name, $makefile, 'current perldelta macro');
702 $makefile =~ s/\0+/join "\n", '', "PERLDELTA_CURRENT = [.pod]$delta_target", ''/se;
704 # Looking for rules like this
705 # [.lib.pods]perl.pod : [.pod]perl.pod
706 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
707 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
709 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
710 [^\n]+\n # Another line
711 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
713 verify_contiguous($name, $makefile, 'copy rules');
714 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
720 my ($name, $makefile_SH) = @_;
722 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
723 {join ' ', $1, map "pod/$_",
724 sort keys %Copies, grep {!/perltoc/} keys %Generated
727 # pod/perl511delta.pod: pod/perldelta.pod
728 # cd pod && $(LNS) perldelta.pod perl511delta.pod
731 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
732 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
735 verify_contiguous($name, $makefile_SH, 'copy rules');
737 my @copy_rules = map "
738 pod/$_: pod/$Copies{$_}
739 \$(LNS) $Copies{$_} pod/$_
742 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
750 while (my ($target, $name) = each %Targets) {
751 print "Working on target $target\n" if $Verbose;
752 next unless $Build{$target};
755 print "Now processing $name\n" if $Verbose;
756 if ($target ne "toc") {
758 open my $thing, '<', $name or my_die "Can't open $name: $!";
761 my_die "$name contains NUL bytes" if $orig =~ /\0/;
766 &{"do_$target"}($target, $orig);
772 printf "ok %d # $name is up to date\n", $built + 1;
774 print "Was not modified\n";
778 printf "not ok %d # $name is up to date\n", $built + 1;
781 $mode = (stat $name)[2] // my_die "Can't stat $name: $!";
782 rename $name, "$name.old" or my_die "Can't rename $name to $name.old: $!";
785 open my $thing, '>', $name or my_die "Can't open $name for writing: $!";
787 print $thing $new or my_die "print to $name failed: $!";
788 close $thing or my_die "close $name failed: $!";
790 chmod $mode & 0777, $name or my_die "can't chmod $mode $name: $!";
794 warn "$0: was not instructed to build anything\n" unless $built || $Test;