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 my $source = 'perldelta.pod';
124 my $filename = abs_from_top("pod/$source");
125 open my $fh, '<', $filename or my_die "Can't open $filename: $!";
127 my $contents = <$fh>;
129 $contents =~ /perldelta - what is new for perl v5\.(\d+)\.(\d+)\n/;
130 die "Can't extract version from $filename" unless @want;
131 $delta_target = "perl5$want[0]$want[1]delta.pod";
133 # This way round so that keys can act as a MANIFEST skip list
134 # Targets will always be in the pod directory. Currently we can only cope
135 # with sources being in the same directory.
136 $Copies{$delta_target} = $source;
141 open my $master, '<', $masterpodfile or my_die "Can't open $masterpodfile: $!";
143 foreach (<$master>) {
146 # At least one upper case letter somewhere in the first group
147 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
151 my %flags = (header => 1);
152 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
153 $flags{aux} = 1 if $flags =~ tr/a//d;
154 my_die "Unknown flag found in heading line: $_" if length $flags;
155 push @Master, [\%flags, $2];
157 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
159 my ($flags, $podname, $desc) = ($1, $2, $3);
160 my $filename = "${podname}.pod";
161 $filename = "pod/${filename}" if $filename !~ m{/};
163 my %flags = (indent => 0);
164 $flags{indent} = $1 if $flags =~ s/(\d+)//;
165 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
166 $flags{aux} = 1 if $flags =~ tr/a//d;
167 $flags{perlpod_omit} = "$podname.pod" eq $delta_target;
169 $Generated{"$podname.pod"}++ if $flags =~ tr/g//d;
171 if ($flags =~ tr/r//d) {
172 my $readme = $podname;
173 $readme =~ s/^perl//;
174 $Readmepods{$podname} = $Readmes{$readme} = $desc;
176 } elsif ($flags{aux}) {
177 $Aux{$podname} = $desc;
179 $Pods{$podname} = $desc;
181 my_die "Unknown flag found in section line: $_" if length $flags;
182 my $shortname = $podname =~ s{.*/}{}r;
183 push @Master, [\%flags, $podname, $filename, $desc, $shortname];
187 my_die "Malformed line: $_" if $1 =~ tr/A-Z//;
195 my (%disk_pods, @disk_pods);
196 my (@manipods, %manipods);
197 my (@manireadmes, %manireadmes);
198 my (@perlpods, %perlpods);
199 my (@cpanpods, %cpanpods, %cpanpods_short);
202 # Convert these to a list of filenames.
203 foreach (keys %Pods, keys %Readmepods) {
204 $our_pods{"$_.pod"}++;
207 opendir my $dh, abs_from_top('pod/');
208 while (defined ($_ = readdir $dh)) {
209 next unless /\.pod\z/;
214 # Things we copy from won't be in perl.pod
215 # Things we copy to won't be in MANIFEST
217 my $filename = abs_from_top('MANIFEST');
218 open my $mani, '<', $filename or my_die "opening $filename failed: $!";
222 if (m!^pod/([^.]+\.pod)!i) {
224 } elsif (m!^README\.(\S+)!i) {
226 push @manireadmes, "perl$1.pod";
227 } elsif (exists $our_pods{$_}) {
233 close $mani or my_die "close MANIFEST: $!\n";
234 @manipods{@manipods} = @manipods;
235 @manireadmes{@manireadmes} = @manireadmes;
236 @cpanpods{@cpanpods} = map { s/.*\///r } @cpanpods;
237 %cpanpods_short = reverse %cpanpods;
239 $filename = abs_from_top('pod/perl.pod');
240 open my $perlpod, '<', $filename or my_die "opening $filename failed: $!\n";
242 if (/^For ease of access, /../^\(If you're intending /) {
243 if (/^\s+(perl\S*)\s+\w/) {
244 push @perlpods, "$1.pod";
248 close $perlpod or my_die "close perlpod: $!\n";
249 my_die "could not find the pod listing of perl.pod\n"
251 @perlpods{@perlpods} = @perlpods;
254 foreach my $i (sort keys %disk_pods) {
255 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
256 unless $our_pods{$i};
257 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
258 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i} && !$cpanpods{$i};
259 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
260 if !$perlpods{$i} && !exists $Copies{$i} && !$cpanpods{$i};
263 foreach my $path (values %Build) {
264 (undef, undef, my $file) = File::Spec->splitpath($path);
268 foreach my $i (sort keys %our_pods) {
269 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
270 unless $disk_pods{$i} or $BuildFiles{$i};
272 foreach my $i (sort keys %manipods) {
273 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
274 unless $disk_pods{$i};
275 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
278 foreach my $i (sort keys %perlpods) {
279 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
280 unless $disk_pods{$i} or $BuildFiles{$i} or $cpanpods_short{$i};
284 printf "1..%d\n", 1 + scalar keys %Build;
292 warn @inconsistent if @inconsistent;
296 # Find all the modules
299 find \&getpods => abs_from_top('lib/');
303 my $file = $File::Find::name;
304 return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
305 return if $file =~ m!(?:^|/)t/!;
306 return if $file =~ m!lib/Attribute/Handlers/demo/!;
307 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
308 return if $file =~ m!lib/Math/BigInt/t/!;
309 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
310 return if $file =~ m!XS/(?:APItest|Typemap)!;
312 return if $pod =~ s/pm$/pod/ && -e $pod;
313 unless (open my $f, '<', $_) {
314 warn "$0: bogus <$file>: $!";
315 system "ls", "-l", $file;
319 while ($line = <$f>) {
320 if ($line =~ /^=head1\s+NAME\b/) {
321 push @modpods, $file;
325 warn "$0: NOTE: cannot find '=head1 NAME' in:\n $file\n" unless $Quiet;
330 my_die "Can't find any pods!\n" unless @modpods;
335 $name =~ s/\.p(m|od)$//;
336 $name =~ s-.*?/lib/--;
338 next if $done{$name}++;
340 if ($name =~ /^[a-z]/) {
341 $Pragmata{$name} = $_;
343 $Modules{$name} = $_;
348 # OK. Now a lot of ancillary function definitions follow
349 # Main program returns at "Do stuff"
354 my $filename = shift;
356 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
358 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
359 # This file is autogenerated by buildtoc from all the other pods.
360 # Edit those files and run buildtoc --build-toc to effect changes.
364 perltoc - perl documentation table of contents
368 This page provides a brief table of contents for the rest of the Perl
369 documentation set. It is meant to be scanned quickly or grepped
370 through to locate the proper section you're looking for.
372 =head1 BASIC DOCUMENTATION
377 # All the things in the master list that happen to be pod filenames
378 foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
379 podset($_->[1], abs_from_top($_->[2]));
383 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
387 =head1 PRAGMA DOCUMENTATION
391 foreach (sort keys %Pragmata) {
392 podset($_, $Pragmata{$_});
395 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
399 =head1 MODULE DOCUMENTATION
403 foreach (sort keys %Modules) {
404 podset($_, $Modules{$_});
410 =head1 AUXILIARY DOCUMENTATION
412 Here should be listed all the extra programs' documentation, but they
413 don't all have manual pages yet:
419 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
426 Larry Wall <F<larry\@wall.org>>, with the help of oodles
435 $OUT =~ s/\n\s+\n/\n\n/gs;
436 $OUT =~ s/\n{3,}/\n\n/g;
438 $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;
443 # Below are all the auxiliary routines for generating perltoc.pod
445 my ($inhead1, $inhead2, $initem);
448 my ($pod, $file) = @_;
452 open my $fh, '<', $file or my_die "Can't open file '$file' for $pod: $!";
456 if (s/^=head1 (NAME)\s*/=head2 /) {
458 $OUT .= "\n\n=head2 ";
460 # Remove svn keyword expansions from the Perl FAQ
461 s/ \(\$Revision: \d+ \$\)//g;
462 if ( /^\s*\Q$pod\E\b/ ) {
463 s/$pod\.pm/$pod/; # '.pm' in NAME !?
468 elsif (s/^=head1 (.*)/=item $1/) {
470 $OUT .= "=over 4\n\n" unless $inhead1;
474 elsif (s/^=head2 (.*)/=item $1/) {
476 $OUT .= "=over 4\n\n" unless $inhead2;
480 elsif (s/^=item ([^=].*)/$1/) {
481 next if $pod eq 'perldiag';
482 s/^\s*\*\s*$// && next;
487 next if $pod eq 'perlmodlib' && /^ftp:/;
488 $OUT .= ", " if $initem;
494 unhead1() if /^=cut\s*\n/;
504 $OUT .= "\n\n=back\n\n";
512 $OUT .= "\n\n=back\n\n";
524 # End of original buildtoc. From here on are routines to generate new sections
525 # for and inplace edit other files
527 sub generate_perlpod {
532 next if $flags->{aux};
533 next if $flags->{perlpod_omit};
537 push @output, "=head2 $_->[1]\n";
540 my $start = " " x (4 + $flags->{indent}) . $_->[4];
541 $maxlength = length $start if length ($start) > $maxlength;
542 push @output, [$start, $_->[3]];
547 my_die "Illegal length " . scalar @$_;
550 # want at least 2 spaces padding
552 $maxlength = ($maxlength + 3) & ~3;
553 # sprintf gives $1.....$2 where ... are spaces:
554 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
559 sub generate_manifest {
560 # Annoyingly, unexpand doesn't consider it good form to replace a single
561 # space before a tab with a tab
562 # Annoyingly (2) it returns read only values.
563 my @temp = unexpand (map {sprintf "%-32s%s", @$_} @_);
564 map {s/ \t/\t\t/g; $_} @temp;
566 sub generate_manifest_pod {
567 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
569 !$Copies{"$_.pod"} && !$Generated{"$_.pod"} && !-e "$_.pod"
572 sub generate_manifest_readme {
573 generate_manifest sort {$a->[0] cmp $b->[0]}
574 ["README.vms", "Notes about installing the VMS port"],
575 map {["README.$_", $Readmes{$_}]} keys %Readmes;
578 sub generate_roffitall {
579 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
581 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
583 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
585 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
589 sub generate_descrip_mms_1 {
590 local $Text::Wrap::columns = 150;
592 my @lines = map {"pod" . $count++ . " = $_"}
593 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
594 sort grep { $_ !~ m{/} } keys %Pods, keys %Readmepods);
595 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
598 sub generate_descrip_mms_2 {
600 [.lib.pods]$_.pod : [.pod]$_.pod
601 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
602 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
604 sort grep { $_ !~ m{/} } keys %Pods, keys %Readmepods;
607 sub generate_nmake_1 {
608 # XXX Fix this with File::Spec
609 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
611 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
614 # This doesn't have a trailing newline
615 sub generate_nmake_2 {
616 # Spot the special case
617 local $Text::Wrap::columns = 76;
618 my $line = wrap ("\t ", "\t ",
619 join " ", sort keys %Copies, keys %Generated,
620 map {"perl$_.pod"} keys %Readmes);
626 sub generate_pod_mak {
627 my $variable = shift;
629 my $line = join "\\\n", "\U$variable = ",
630 map {"\t$_.$variable\t"} sort grep { $_ !~ m{/} } keys %Pods;
632 $line =~ s/.*perltoc.html.*\n//m;
636 sub verify_contiguous {
637 my ($name, $content, $what) = @_;
638 my $sections = () = $content =~ m/\0+/g;
639 croak("$0: $name contains no $what") if $sections < 1;
640 croak("$0: $name contains discontiguous $what") if $sections > 1;
644 my ($name, $prev) = @_;
646 grep {! m!^pod/[^.]+\.pod.*!}
647 grep {! m!^README\.(\S+)! || $Ignore{$1}} split "\n", $prev;
649 # Dictionary order - fold and handle non-word chars as nothing
651 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
652 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
654 &generate_manifest_pod(),
655 &generate_manifest_readme()), '';
659 my ($name, $makefile) = @_;
660 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
661 verify_contiguous($name, $makefile, 'README copies');
662 # Now remove the other copies that follow
663 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
664 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
666 $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
667 {"$1\n" . &generate_nmake_2."\n\t$2"}se;
671 # shut up used only once warning
672 *do_dmake = *do_dmake = \&do_nmake;
675 my ($name, $pod) = @_;
677 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
678 (?:\s+[a-z]{4,}.*\n # fooo
679 |=head.*\n # =head foo
683 {$1 . join "", &generate_perlpod}mxe) {
684 my_die "Failed to insert amendments in do_perlpod";
690 my ($name, $body) = @_;
691 foreach my $variable (qw(pod man html tex)) {
692 my_die "could not find $variable in $name"
693 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
694 {"\n" . generate_pod_mak ($variable)}se;
700 my ($name, $makefile) = @_;
701 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
702 verify_contiguous($name, $makefile, 'pod assignments');
703 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
705 my_die "$name contains NUL bytes" if $makefile =~ /\0/;
707 # Looking for the macro defining the current perldelta:
708 #PERLDELTA_CURRENT = [.pod]perl5139delta.pod
710 $makefile =~ s/\nPERLDELTA_CURRENT\s+=\s+\Q[.pod]perl\E\d+delta\.pod\n
712 verify_contiguous($name, $makefile, 'current perldelta macro');
713 $makefile =~ s/\0+/join "\n", '', "PERLDELTA_CURRENT = [.pod]$delta_target", ''/se;
715 # Looking for rules like this
716 # [.lib.pods]perl.pod : [.pod]perl.pod
717 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
718 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
720 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
721 [^\n]+\n # Another line
722 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
724 verify_contiguous($name, $makefile, 'copy rules');
725 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
731 my ($name, $makefile_SH) = @_;
733 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
734 {join ' ', $1, map "pod/$_",
735 sort keys %Copies, grep {!/perltoc/} keys %Generated
738 # pod/perl511delta.pod: pod/perldelta.pod
739 # cd pod && $(LNS) perldelta.pod perl511delta.pod
742 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
743 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
746 verify_contiguous($name, $makefile_SH, 'copy rules');
748 my @copy_rules = map "
749 pod/$_: pod/$Copies{$_}
750 \$(LNS) $Copies{$_} pod/$_
753 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
761 while (my ($target, $name) = each %Targets) {
762 print "Working on target $target\n" if $Verbose;
763 next unless $Build{$target};
766 print "Now processing $name\n" if $Verbose;
767 if ($target ne "toc") {
769 open my $thing, '<', $name or my_die "Can't open $name: $!";
772 my_die "$name contains NUL bytes" if $orig =~ /\0/;
777 &{"do_$target"}($target, $orig);
783 printf "ok %d # $name is up to date\n", $built + 1;
785 print "Was not modified\n";
789 printf "not ok %d # $name is up to date\n", $built + 1;
792 $mode = (stat $name)[2] // my_die "Can't stat $name: $!";
793 rename $name, "$name.old" or my_die "Can't rename $name to $name.old: $!";
796 open my $thing, '>', $name or my_die "Can't open $name for writing: $!";
798 print $thing $new or my_die "print to $name failed: $!";
799 close $thing or my_die "close $name failed: $!";
801 chmod $mode & 0777, $name or my_die "can't chmod $mode $name: $!";
805 warn "$0: was not instructed to build anything\n" unless $built || $Test;