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 if perl.pod, pod.lst, MANIFEST are consistent, and regenerated
36 # files are up to date, 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);
62 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
65 && GetOptions (verbose => \$Verbose,
67 showfiles => \$showfiles,
69 map {+"build-$_", \$build_these{$_}} @files, 'all');
70 if ($build_these{all}) {
73 while (my ($file, $want) = each %build_these) {
74 $Build{$file} = $Targets{$file} if $want;
80 sort { lc $a cmp lc $b }
82 my ($v, $d, $f) = File::Spec->splitpath($_);
84 @d = defined $d ? File::Spec->splitdir($d) : ();
86 File::Spec->catfile(@d ?
87 (@d == 1 && $d[0] eq '' ? () : @d)
95 # Don't copy these top level READMEs
103 print "I'm building $_\n" foreach keys %Build;
106 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
108 my ($delta_source, $delta_target);
113 # At least one upper case letter somewhere in the first group
114 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
118 my %flags = (header => 1);
119 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
120 $flags{aux} = 1 if $flags =~ tr/a//d;
121 die "$0: Unknown flag found in heading line: $_" if length $flags;
122 push @Master, [\%flags, $2];
124 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
126 my ($flags, $filename, $desc) = ($1, $2, $3);
128 my %flags = (indent => 0);
129 $flags{indent} = $1 if $flags =~ s/(\d+)//;
130 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
131 $flags{aux} = 1 if $flags =~ tr/a//d;
133 if ($flags =~ tr/D//d) {
134 $flags{manifest_omit} = 1;
135 $delta_source = "$filename.pod";
137 if ($flags =~ tr/d//d) {
138 $flags{perlpod_omit} = 1;
139 $delta_target = "$filename.pod";
141 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
143 if ($flags =~ tr/r//d) {
144 my $readme = $filename;
145 $readme =~ s/^perl//;
146 $Readmepods{$filename} = $Readmes{$readme} = $desc;
148 } elsif ($flags{aux}) {
149 $Aux{$filename} = $desc;
151 $Pods{$filename} = $desc;
153 die "$0: Unknown flag found in section line: $_" if length $flags;
154 push @Master, [\%flags, $filename, $desc];
158 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
161 if (defined $delta_source) {
162 if (defined $delta_target) {
163 # This way round so that keys can act as a MANIFEST skip list
164 # Targets will aways be in the pod directory. Currently we can only cope
165 # with sources being in the same directory.
166 $Copies{$delta_target} = $delta_source;
168 die "$0: delta source defined but not target";
170 } elsif (defined $delta_target) {
171 die "$0: delta target defined but not source";
178 my (%disk_pods, @disk_pods);
179 my (@manipods, %manipods);
180 my (@manireadmes, %manireadmes);
181 my (@perlpods, %perlpods);
184 # Convert these to a list of filenames.
185 foreach (keys %Pods, keys %Readmepods) {
186 $our_pods{"$_.pod"}++;
189 opendir my $dh, abs_from_top('pod/');
190 while (readdir $dh) {
191 next unless /\.pod\z/;
196 # Things we copy from won't be in perl.pod
197 # Things we copy to won't be in MANIFEST
199 my $filename = abs_from_top('MANIFEST');
200 open my $mani, '<', $filename or die "$0: opening $filename failed: $!";
202 if (m!^pod/([^.]+\.pod)\s+!i) {
204 } elsif (m!^README\.(\S+)\s+!i) {
206 push @manireadmes, "perl$1.pod";
209 close $mani or die $!;
210 @manipods{@manipods} = @manipods;
211 @manireadmes{@manireadmes} = @manireadmes;
213 $filename = abs_from_top('pod/perl.pod');
214 open my $perlpod, '<', $filename or die "$0: opening $filename failed: $!\n";
216 if (/^For ease of access, /../^\(If you're intending /) {
217 if (/^\s+(perl\S*)\s+\w/) {
218 push @perlpods, "$1.pod";
222 close $perlpod or die $!;
223 die "$0: could not find the pod listing of perl.pod\n"
225 @perlpods{@perlpods} = @perlpods;
228 foreach my $i (sort keys %disk_pods) {
229 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
230 unless $our_pods{$i};
231 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
232 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
233 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
234 if !$perlpods{$i} && !exists $Copies{$i};
237 ++$BuildFiles{$_} foreach values %Build;
239 foreach my $i (sort keys %our_pods) {
240 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
241 unless $disk_pods{$i} or $BuildFiles{$i};
243 foreach my $i (sort keys %manipods) {
244 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
245 unless $disk_pods{$i};
246 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
249 foreach my $i (sort keys %perlpods) {
250 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
251 unless $disk_pods{$i} or $BuildFiles{$i};
255 printf "1..%d\n", 1 + scalar keys %Build;
263 warn @inconsistent if @inconsistent;
267 # Find all the modules
270 find \&getpods => abs_from_top('lib/');
274 my $file = $File::Find::name;
275 return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
276 return if $file =~ m!(?:^|/)t/!;
277 return if $file =~ m!lib/Attribute/Handlers/demo/!;
278 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
279 return if $file =~ m!lib/Math/BigInt/t/!;
280 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
281 return if $file =~ m!XS/(?:APItest|Typemap)!;
283 return if $pod =~ s/pm$/pod/ && -e $pod;
284 unless (open (F, "< $_\0")) {
285 warn "$0: bogus <$file>: $!";
286 system "ls", "-l", $file;
290 while ($line = <F>) {
291 if ($line =~ /^=head1\s+NAME\b/) {
292 push @modpods, $file;
298 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
303 die "$0: no pods" unless @modpods;
308 $name =~ s/\.p(m|od)$//;
309 $name =~ s-.*?/lib/--;
311 next if $done{$name}++;
313 if ($name =~ /^[a-z]/) {
314 $Pragmata{$name} = $_;
316 $Modules{$name} = $_;
321 # OK. Now a lot of ancillary function definitions follow
322 # Main program returns at "Do stuff"
327 my $filename = shift;
329 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
331 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
332 # This file is autogenerated by buildtoc from all the other pods.
333 # Edit those files and run buildtoc --build-toc to effect changes.
337 perltoc - perl documentation table of contents
341 This page provides a brief table of contents for the rest of the Perl
342 documentation set. It is meant to be scanned quickly or grepped
343 through to locate the proper section you're looking for.
345 =head1 BASIC DOCUMENTATION
350 # All the things in the master list that happen to be pod filenames
351 foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
352 podset($_->[1], abs_from_top("pod/$_->[1].pod"));
356 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
360 =head1 PRAGMA DOCUMENTATION
364 foreach (sort keys %Pragmata) {
365 podset($_, $Pragmata{$_});
368 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
372 =head1 MODULE DOCUMENTATION
376 foreach (sort keys %Modules) {
377 podset($_, $Modules{$_});
383 =head1 AUXILIARY DOCUMENTATION
385 Here should be listed all the extra programs' documentation, but they
386 don't all have manual pages yet:
392 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
399 Larry Wall <F<larry\@wall.org>>, with the help of oodles
408 $OUT =~ s/\n\s+\n/\n\n/gs;
409 $OUT =~ s/\n{3,}/\n\n/g;
411 $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;
416 # Below are all the auxiliary routines for generating perltoc.pod
418 my ($inhead1, $inhead2, $initem);
421 my ($pod, $file) = @_;
425 open my $fh, '<', $file or die "Can't open file '$file' for $pod: $!";
429 if (s/^=head1 (NAME)\s*/=head2 /) {
431 $OUT .= "\n\n=head2 ";
433 # Remove svn keyword expansions from the Perl FAQ
434 s/ \(\$Revision: \d+ \$\)//g;
435 if ( /^\s*\Q$pod\E\b/ ) {
436 s/$pod\.pm/$pod/; # '.pm' in NAME !?
441 elsif (s/^=head1 (.*)/=item $1/) {
443 $OUT .= "=over 4\n\n" unless $inhead1;
447 elsif (s/^=head2 (.*)/=item $1/) {
449 $OUT .= "=over 4\n\n" unless $inhead2;
453 elsif (s/^=item ([^=].*)/$1/) {
454 next if $pod eq 'perldiag';
455 s/^\s*\*\s*$// && next;
460 next if $pod eq 'perlmodlib' && /^ftp:/;
461 $OUT .= ", " if $initem;
467 unhead1() if /^=cut\s*\n/;
477 $OUT .= "\n\n=back\n\n";
485 $OUT .= "\n\n=back\n\n";
497 # End of original buildtoc. From here on are routines to generate new sections
498 # for and inplace edit other files
500 sub generate_perlpod {
505 next if $flags->{aux};
506 next if $flags->{perlpod_omit};
510 push @output, "=head2 $_->[1]\n";
513 my $start = " " x (4 + $flags->{indent}) . $_->[1];
514 $maxlength = length $start if length ($start) > $maxlength;
515 push @output, [$start, $_->[2]];
520 die "$0: Illegal length " . scalar @$_;
523 # want at least 2 spaces padding
525 $maxlength = ($maxlength + 3) & ~3;
526 # sprintf gives $1.....$2 where ... are spaces:
527 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
532 sub generate_manifest {
533 # Annoyingly, unexpand doesn't consider it good form to replace a single
534 # space before a tab with a tab
535 # Annoyingly (2) it returns read only values.
536 my @temp = unexpand (map {sprintf "%-32s%s", @$_} @_);
537 map {s/ \t/\t\t/g; $_} @temp;
539 sub generate_manifest_pod {
540 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
541 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
543 sub generate_manifest_readme {
544 generate_manifest sort {$a->[0] cmp $b->[0]}
545 ["README.vms", "Notes about installing the VMS port"],
546 map {["README.$_", $Readmes{$_}]} keys %Readmes;
549 sub generate_roffitall {
550 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
552 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
554 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
556 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
560 sub generate_descrip_mms_1 {
561 local $Text::Wrap::columns = 150;
563 my @lines = map {"pod" . $count++ . " = $_"}
564 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
565 sort keys %Pods, keys %Readmepods);
566 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
569 sub generate_descrip_mms_2 {
571 [.lib.pods]$_.pod : [.pod]$_.pod
572 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
573 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
575 sort keys %Pods, keys %Readmepods;
578 sub generate_nmake_1 {
579 # XXX Fix this with File::Spec
580 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
582 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
585 # This doesn't have a trailing newline
586 sub generate_nmake_2 {
587 # Spot the special case
588 local $Text::Wrap::columns = 76;
589 my $line = wrap ("\t ", "\t ",
590 join " ", sort keys %Copies, keys %Generated,
591 map {"perl$_.pod"} keys %Readmes);
597 sub generate_pod_mak {
598 my $variable = shift;
600 my $line = join "\\\n", "\U$variable = ",
601 map {"\t$_.$variable\t"} sort keys %Pods;
603 $line =~ s/.*perltoc.html.*\n//m;
607 sub verify_contiguous {
608 my ($name, $content, $what) = @_;
609 my $sections = () = $content =~ m/\0+/g;
610 croak("$0: $name contains no $what") if $sections < 1;
611 croak("$0: $name contains discontiguous $what") if $sections > 1;
615 my ($name, $prev) = @_;
617 grep {! m!^pod/[^.]+\.pod.*!}
618 grep {! m!^README\.(\S+)! || $Ignore{$1}} split "\n", $prev;
620 # Dictionary order - fold and handle non-word chars as nothing
622 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
623 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
625 &generate_manifest_pod(),
626 &generate_manifest_readme()), '';
630 my ($name, $makefile) = @_;
631 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
632 verify_contiguous($name, $makefile, 'README copies');
633 # Now remove the other copies that follow
634 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
635 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
637 $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
638 {"$1\n" . &generate_nmake_2."\n\t$2"}se;
642 # shut up used only once warning
643 *do_dmake = *do_dmake = \&do_nmake;
646 my ($name, $pod) = @_;
648 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
649 (?:\s+[a-z]{4,}.*\n # fooo
650 |=head.*\n # =head foo
654 {$1 . join "", &generate_perlpod}mxe) {
655 die "$0: Failed to insert amendments in do_perlpod";
661 my ($name, $body) = @_;
662 foreach my $variable (qw(pod man html tex)) {
663 die "$0: could not find $variable in $name"
664 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
665 {"\n" . generate_pod_mak ($variable)}se;
671 my ($name, $makefile) = @_;
672 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
673 verify_contiguous($name, $makefile, 'pod assignments');
674 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
676 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
678 # Looking for the macro defining the current perldelta:
679 #PERLDELTA_CURRENT = [.pod]perl5139delta.pod
681 $makefile =~ s/\nPERLDELTA_CURRENT\s+=\s+\Q[.pod]perl\E\d+delta\.pod\n
683 verify_contiguous($name, $makefile, 'current perldelta macro');
684 $makefile =~ s/\0+/join "\n", '', "PERLDELTA_CURRENT = [.pod]$delta_target", ''/se;
686 # Looking for rules like this
687 # [.lib.pods]perl.pod : [.pod]perl.pod
688 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
689 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
691 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
692 [^\n]+\n # Another line
693 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
695 verify_contiguous($name, $makefile, 'copy rules');
696 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
702 my ($name, $makefile_SH) = @_;
704 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
705 {join ' ', $1, map "pod/$_",
706 sort keys %Copies, grep {!/perltoc/} keys %Generated
709 # pod/perl511delta.pod: pod/perldelta.pod
710 # cd pod && $(LNS) perldelta.pod perl511delta.pod
713 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
714 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
717 verify_contiguous($name, $makefile_SH, 'copy rules');
719 my @copy_rules = map "
720 pod/$_: pod/$Copies{$_}
721 \$(LNS) $Copies{$_} pod/$_
724 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
732 while (my ($target, $name) = each %Targets) {
733 print "Working on target $target\n" if $Verbose;
734 next unless $Build{$target};
737 print "Now processing $name\n" if $Verbose;
738 if ($target ne "toc") {
740 open THING, $name or die "Can't open $name: $!";
744 die "$0: $name contains NUL bytes" if $orig =~ /\0/;
749 &{"do_$target"}($target, $orig);
755 printf "ok %d # $name is up to date\n", $built + 1;
757 print "Was not modified\n";
761 printf "not ok %d # $name is up to date\n", $built + 1;
764 $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: $!";
768 open THING, ">$name" or die "$0: Can't open $name for writing: $!";
770 print THING $new or die "$0: print to $name failed: $!";
771 close THING or die "$0: close $name failed: $!";
773 chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
777 warn "$0: was not instructed to build anything\n" unless $built || $Test;