4 use vars qw($masterpodfile %Build %Targets $Verbose $Quiet %Ignore
5 @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
6 %Copies %Generated $Test);
18 my $Top = File::Spec->catdir($FindBin::Bin, File::Spec->updir);
22 return File::Spec->catdir($Top, split /\//, $path) if $path =~ s!/\z!!;
23 return File::Spec->catfile($Top, split /\//, $path);
27 $masterpodfile = abs_from_top('pod.lst');
29 # Generate any/all of these files
30 # --verbose gives slightly more output
31 # --quiet suppresses routine warnings
32 # --build-all tries to build everything
33 # --build-foo updates foo as follows
34 # --showfiles shows the files to be changed
35 # --test exit early, exit if perl.pod, pod.lst, MANIFEST are
36 # consistent, die otherwise.
40 toc => 'pod/perltoc.pod',
41 manifest => 'MANIFEST',
42 perlpod => 'pod/perl.pod',
43 vms => 'vms/descrip_mms.template',
44 nmake => 'win32/Makefile',
45 dmake => 'win32/makefile.mk',
46 podmak => 'win32/pod.mak',
47 # plan9 => 'plan9/mkfile'),
48 unix => 'Makefile.SH',
52 foreach (values %Targets) {
53 $_ = abs_from_top($_);
57 my @files = keys %Targets;
58 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
61 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
64 && GetOptions (verbose => \$Verbose,
66 showfiles => \$showfiles,
68 map {+"build-$_", \$Build{$_}} @files, 'all');
69 # Set them all to true
70 @Build{@files} = @files if ($Build{all});
74 sort { lc $a cmp lc $b }
76 my ($v, $d, $f) = File::Spec->splitpath($_);
78 @d = defined $d ? File::Spec->splitdir($d) : ();
80 File::Spec->catfile(@d ?
81 (@d == 1 && $d[0] eq '' ? () : @d)
83 } @Targets{grep { $_ ne 'all' && $Build{$_} } keys %Build}),
89 # Don't copy these top level READMEs
97 print "I'm building $_\n" foreach grep {$Build{$_}} keys %Build;
100 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
102 my ($delta_source, $delta_target);
107 # At least one upper case letter somewhere in the first group
108 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
112 my %flags = (header => 1);
113 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
114 $flags{aux} = 1 if $flags =~ tr/a//d;
115 die "$0: Unknown flag found in heading line: $_" if length $flags;
116 push @Master, [\%flags, $2];
118 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
120 my ($flags, $filename, $desc) = ($1, $2, $3);
122 my %flags = (indent => 0);
123 $flags{indent} = $1 if $flags =~ s/(\d+)//;
124 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
125 $flags{aux} = 1 if $flags =~ tr/a//d;
127 if ($flags =~ tr/D//d) {
128 $flags{manifest_omit} = 1;
129 $delta_source = "$filename.pod";
131 if ($flags =~ tr/d//d) {
132 $flags{perlpod_omit} = 1;
133 $delta_target = "$filename.pod";
135 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
137 if ($flags =~ tr/r//d) {
138 my $readme = $filename;
139 $readme =~ s/^perl//;
140 $Readmepods{$filename} = $Readmes{$readme} = $desc;
142 } elsif ($flags{aux}) {
143 $Aux{$filename} = $desc;
145 $Pods{$filename} = $desc;
147 die "$0: Unknown flag found in section line: $_" if length $flags;
148 push @Master, [\%flags, $filename, $desc];
152 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
155 if (defined $delta_source) {
156 if (defined $delta_target) {
157 # This way round so that keys can act as a MANIFEST skip list
158 # Targets will aways be in the pod directory. Currently we can only cope
159 # with sources being in the same directory.
160 $Copies{$delta_target} = $delta_source;
162 die "$0: delta source defined but not target";
164 } elsif (defined $delta_target) {
165 die "$0: delta target defined but not target";
172 my (%disk_pods, @disk_pods);
173 my (@manipods, %manipods);
174 my (@manireadmes, %manireadmes);
175 my (@perlpods, %perlpods);
178 # Convert these to a list of filenames.
179 foreach (keys %Pods, keys %Readmepods) {
180 $our_pods{"$_.pod"}++;
183 opendir my $dh, abs_from_top('pod/');
184 while (readdir $dh) {
185 next unless /\.pod\z/;
190 # Things we copy from won't be in perl.pod
191 # Things we copy to won't be in MANIFEST
193 my $filename = abs_from_top('MANIFEST');
194 open my $mani, '<', $filename or die "$0: opening $filename failed: $!";
196 if (m!^pod/([^.]+\.pod)\s+!i) {
198 } elsif (m!^README\.(\S+)\s+!i) {
200 push @manireadmes, "perl$1.pod";
203 close $mani or die $!;
204 @manipods{@manipods} = @manipods;
205 @manireadmes{@manireadmes} = @manireadmes;
207 $filename = abs_from_top('pod/perl.pod');
208 open my $perlpod, '<', $filename or die "$0: opening $filename failed: $!\n";
210 if (/^For ease of access, /../^\(If you're intending /) {
211 if (/^\s+(perl\S*)\s+\w/) {
212 push @perlpods, "$1.pod";
216 close $perlpod or die $!;
217 die "$0: could not find the pod listing of perl.pod\n"
219 @perlpods{@perlpods} = @perlpods;
222 foreach my $i (sort keys %disk_pods) {
223 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
224 unless $our_pods{$i};
225 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
226 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
227 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
228 if !$perlpods{$i} && !exists $Copies{$i};
230 my @BuildTargets = grep {defined} @Targets{grep $_ ne 'all', keys %Build};
232 @BuildFiles{@BuildTargets} = @BuildTargets;
234 foreach my $i (sort keys %our_pods) {
235 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
236 unless $disk_pods{$i} or $BuildFiles{$i};
238 foreach my $i (sort keys %manipods) {
239 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
240 unless $disk_pods{$i};
241 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
244 foreach my $i (sort keys %perlpods) {
245 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
246 unless $disk_pods{$i} or $BuildFiles{$i};
258 warn @inconsistent if @inconsistent;
262 # Find all the modules
265 find \&getpods => abs_from_top('lib/');
269 my $file = $File::Find::name;
270 return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
271 return if $file =~ m!(?:^|/)t/!;
272 return if $file =~ m!lib/Attribute/Handlers/demo/!;
273 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
274 return if $file =~ m!lib/Math/BigInt/t/!;
275 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
276 return if $file =~ m!XS/(?:APItest|Typemap)!;
278 return if $pod =~ s/pm$/pod/ && -e $pod;
279 unless (open (F, "< $_\0")) {
280 warn "$0: bogus <$file>: $!";
281 system "ls", "-l", $file;
285 while ($line = <F>) {
286 if ($line =~ /^=head1\s+NAME\b/) {
287 push @modpods, $file;
293 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
298 die "$0: no pods" unless @modpods;
303 $name =~ s/\.p(m|od)$//;
304 $name =~ s-.*?/lib/--;
306 next if $done{$name}++;
308 if ($name =~ /^[a-z]/) {
309 $Pragmata{$name} = $_;
311 $Modules{$name} = $_;
316 # OK. Now a lot of ancillary function definitions follow
317 # Main program returns at "Do stuff"
322 my $filename = shift;
324 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
326 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
327 # This file is autogenerated by buildtoc from all the other pods.
328 # Edit those files and run buildtoc --build-toc to effect changes.
332 perltoc - perl documentation table of contents
336 This page provides a brief table of contents for the rest of the Perl
337 documentation set. It is meant to be scanned quickly or grepped
338 through to locate the proper section you're looking for.
340 =head1 BASIC DOCUMENTATION
345 # All the things in the master list that happen to be pod filenames
346 foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
347 podset($_->[1], abs_from_top("pod/$_->[1].pod"));
351 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
355 =head1 PRAGMA DOCUMENTATION
359 foreach (sort keys %Pragmata) {
360 podset($_, $Pragmata{$_});
363 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
367 =head1 MODULE DOCUMENTATION
371 foreach (sort keys %Modules) {
372 podset($_, $Modules{$_});
378 =head1 AUXILIARY DOCUMENTATION
380 Here should be listed all the extra programs' documentation, but they
381 don't all have manual pages yet:
387 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
394 Larry Wall <F<larry\@wall.org>>, with the help of oodles
403 $OUT =~ s/\n\s+\n/\n\n/gs;
404 $OUT =~ s/\n{3,}/\n\n/g;
406 $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;
408 open OUT, '>', $filename or die "$0: creating $filename failed: $!";
413 # Below are all the auxiliary routines for generating perltoc.pod
415 my ($inhead1, $inhead2, $initem);
418 my ($pod, $file) = @_;
422 open my $fh, '<', $file or die "Can't open file '$file' for $pod: $!";
426 if (s/^=head1 (NAME)\s*/=head2 /) {
428 $OUT .= "\n\n=head2 ";
430 # Remove svn keyword expansions from the Perl FAQ
431 s/ \(\$Revision: \d+ \$\)//g;
432 if ( /^\s*\Q$pod\E\b/ ) {
433 s/$pod\.pm/$pod/; # '.pm' in NAME !?
438 elsif (s/^=head1 (.*)/=item $1/) {
440 $OUT .= "=over 4\n\n" unless $inhead1;
444 elsif (s/^=head2 (.*)/=item $1/) {
446 $OUT .= "=over 4\n\n" unless $inhead2;
450 elsif (s/^=item ([^=].*)/$1/) {
451 next if $pod eq 'perldiag';
452 s/^\s*\*\s*$// && next;
457 next if $pod eq 'perlmodlib' && /^ftp:/;
458 $OUT .= ", " if $initem;
464 unhead1() if /^=cut\s*\n/;
474 $OUT .= "\n\n=back\n\n";
482 $OUT .= "\n\n=back\n\n";
494 # End of original buildtoc. From here on are routines to generate new sections
495 # for and inplace edit other files
497 sub generate_perlpod {
502 next if $flags->{aux};
503 next if $flags->{perlpod_omit};
507 push @output, "=head2 $_->[1]\n";
510 my $start = " " x (4 + $flags->{indent}) . $_->[1];
511 $maxlength = length $start if length ($start) > $maxlength;
512 push @output, [$start, $_->[2]];
517 die "$0: Illegal length " . scalar @$_;
520 # want at least 2 spaces padding
522 $maxlength = ($maxlength + 3) & ~3;
523 # sprintf gives $1.....$2 where ... are spaces:
524 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
529 sub generate_manifest {
530 # Annoyingly, unexpand doesn't consider it good form to replace a single
531 # space before a tab with a tab
532 # Annoyingly (2) it returns read only values.
533 my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
534 map {s/ \t/\t\t/g; $_} @temp;
536 sub generate_manifest_pod {
537 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
538 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
540 sub generate_manifest_readme {
541 generate_manifest sort {$a->[0] cmp $b->[0]}
542 ["README.vms", "Notes about installing the VMS port"],
543 map {["README.$_", $Readmes{$_}]} keys %Readmes;
546 sub generate_roffitall {
547 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
549 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
551 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
553 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
557 sub generate_descrip_mms_1 {
558 local $Text::Wrap::columns = 150;
560 my @lines = map {"pod" . $count++ . " = $_"}
561 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
562 sort keys %Pods, keys %Readmepods);
563 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
566 sub generate_descrip_mms_2 {
568 [.lib.pods]$_.pod : [.pod]$_.pod
569 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
570 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
572 sort keys %Pods, keys %Readmepods;
575 sub generate_descrip_mms_3 {
576 map qq{\t- If F\$Search("[.pod]$_").nes."" Then Delete/NoConfirm/Log [.pod]$_;*},
577 sort keys %Generated, keys %Copies;
580 sub generate_nmake_1 {
581 # XXX Fix this with File::Spec
582 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
584 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
587 # This doesn't have a trailing newline
588 sub generate_nmake_2 {
589 # Spot the special case
590 local $Text::Wrap::columns = 76;
591 my $line = wrap ("\t ", "\t ",
592 join " ", sort keys %Copies, keys %Generated,
593 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{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
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\d*[a-z]+\Q.pod").nes."" Then Delete/NoConfirm/Log [.pod]perl\E\d*[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/perl511delta.pod: pod/perldelta.pod
717 # cd pod && $(LNS) perldelta.pod perl511delta.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;
745 output_perltoc($name);
746 print "Finished\n" if $Verbose;
749 print "Now processing $name\n" if $Verbose;
750 open THING, $name or die "Can't open $name: $!";
753 my $orig = join '', @orig;
757 &{"do_$target"}($target, @orig);
759 my $new = join '', @new;
761 print "Was not modified\n" if $Verbose;
764 my $mode = (stat $name)[2] // die "$0: Can't stat $name: $!";
765 rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
766 open THING, ">$name" or die "$0: Can't open $name for writing: $!";
768 print THING $new or die "$0: print to $name failed: $!";
769 close THING or die "$0: close $name failed: $!";
770 chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
773 warn "$0: was not instructed to build anything\n" unless $built;