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 $masterpodfile = abs_from_top('pod.lst');
30 # Generate any/all of these files
31 # --verbose gives slightly more output
32 # --quiet suppresses routine warnings
33 # --build-all tries to build everything
34 # --build-foo updates foo as follows
35 # --showfiles shows the files to be changed
36 # --test exit if perl.pod, pod.lst, MANIFEST are consistent, and regenerated
37 # files are up to date, die otherwise.
41 toc => 'pod/perltoc.pod',
42 manifest => 'MANIFEST',
43 perlpod => 'pod/perl.pod',
44 vms => 'vms/descrip_mms.template',
45 nmake => 'win32/Makefile',
46 dmake => 'win32/makefile.mk',
47 podmak => 'win32/pod.mak',
48 # plan9 => 'plan9/mkfile'),
49 unix => 'Makefile.SH',
53 foreach (values %Targets) {
54 $_ = abs_from_top($_);
58 my @files = keys %Targets;
59 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
63 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
66 && GetOptions (verbose => \$Verbose,
68 showfiles => \$showfiles,
70 map {+"build-$_", \$build_these{$_}} @files, 'all');
71 if ($build_these{all}) {
74 while (my ($file, $want) = each %build_these) {
75 $Build{$file} = $Targets{$file} if $want;
81 sort { lc $a cmp lc $b }
83 my ($v, $d, $f) = File::Spec->splitpath($_);
85 @d = defined $d ? File::Spec->splitdir($d) : ();
87 File::Spec->catfile(@d ?
88 (@d == 1 && $d[0] eq '' ? () : @d)
96 # Don't copy these top level READMEs
104 print "I'm building $_\n" foreach keys %Build;
107 open my $master, '<', $masterpodfile or die "$0: Can't open $masterpodfile: $!";
109 my ($delta_source, $delta_target);
111 foreach (<$master>) {
114 # At least one upper case letter somewhere in the first group
115 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
119 my %flags = (header => 1);
120 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
121 $flags{aux} = 1 if $flags =~ tr/a//d;
122 die "$0: Unknown flag found in heading line: $_" if length $flags;
123 push @Master, [\%flags, $2];
125 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
127 my ($flags, $filename, $desc) = ($1, $2, $3);
129 my %flags = (indent => 0);
130 $flags{indent} = $1 if $flags =~ s/(\d+)//;
131 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
132 $flags{aux} = 1 if $flags =~ tr/a//d;
134 if ($flags =~ tr/D//d) {
135 $flags{manifest_omit} = 1;
136 $delta_source = "$filename.pod";
138 if ($flags =~ tr/d//d) {
139 $flags{perlpod_omit} = 1;
140 $delta_target = "$filename.pod";
142 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
144 if ($flags =~ tr/r//d) {
145 my $readme = $filename;
146 $readme =~ s/^perl//;
147 $Readmepods{$filename} = $Readmes{$readme} = $desc;
149 } elsif ($flags{aux}) {
150 $Aux{$filename} = $desc;
152 $Pods{$filename} = $desc;
154 die "$0: Unknown flag found in section line: $_" if length $flags;
155 push @Master, [\%flags, $filename, $desc];
159 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
162 if (defined $delta_source) {
163 if (defined $delta_target) {
164 # This way round so that keys can act as a MANIFEST skip list
165 # Targets will aways be in the pod directory. Currently we can only cope
166 # with sources being in the same directory.
167 $Copies{$delta_target} = $delta_source;
169 die "$0: delta source defined but not target";
171 } elsif (defined $delta_target) {
172 die "$0: delta target defined but not source";
179 my (%disk_pods, @disk_pods);
180 my (@manipods, %manipods);
181 my (@manireadmes, %manireadmes);
182 my (@perlpods, %perlpods);
185 # Convert these to a list of filenames.
186 foreach (keys %Pods, keys %Readmepods) {
187 $our_pods{"$_.pod"}++;
190 opendir my $dh, abs_from_top('pod/');
191 while (readdir $dh) {
192 next unless /\.pod\z/;
197 # Things we copy from won't be in perl.pod
198 # Things we copy to won't be in MANIFEST
200 my $filename = abs_from_top('MANIFEST');
201 open my $mani, '<', $filename or die "$0: opening $filename failed: $!";
203 if (m!^pod/([^.]+\.pod)\s+!i) {
205 } elsif (m!^README\.(\S+)\s+!i) {
207 push @manireadmes, "perl$1.pod";
210 close $mani or die $!;
211 @manipods{@manipods} = @manipods;
212 @manireadmes{@manireadmes} = @manireadmes;
214 $filename = abs_from_top('pod/perl.pod');
215 open my $perlpod, '<', $filename or die "$0: opening $filename failed: $!\n";
217 if (/^For ease of access, /../^\(If you're intending /) {
218 if (/^\s+(perl\S*)\s+\w/) {
219 push @perlpods, "$1.pod";
223 close $perlpod or die $!;
224 die "$0: could not find the pod listing of perl.pod\n"
226 @perlpods{@perlpods} = @perlpods;
229 foreach my $i (sort keys %disk_pods) {
230 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
231 unless $our_pods{$i};
232 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
233 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
234 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
235 if !$perlpods{$i} && !exists $Copies{$i};
238 foreach my $path (values %Build) {
239 (undef, undef, my $file) = File::Spec->splitpath($path);
243 foreach my $i (sort keys %our_pods) {
244 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
245 unless $disk_pods{$i} or $BuildFiles{$i};
247 foreach my $i (sort keys %manipods) {
248 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
249 unless $disk_pods{$i};
250 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
253 foreach my $i (sort keys %perlpods) {
254 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
255 unless $disk_pods{$i} or $BuildFiles{$i};
259 printf "1..%d\n", 1 + scalar keys %Build;
267 warn @inconsistent if @inconsistent;
271 # Find all the modules
274 find \&getpods => abs_from_top('lib/');
278 my $file = $File::Find::name;
279 return if $file =~ qr!/lib/Pod/Functions.pm\z!; # Used only by pod itself
280 return if $file =~ m!(?:^|/)t/!;
281 return if $file =~ m!lib/Attribute/Handlers/demo/!;
282 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
283 return if $file =~ m!lib/Math/BigInt/t/!;
284 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
285 return if $file =~ m!XS/(?:APItest|Typemap)!;
287 return if $pod =~ s/pm$/pod/ && -e $pod;
288 unless (open my $f, '<', $_) {
289 warn "$0: bogus <$file>: $!";
290 system "ls", "-l", $file;
294 while ($line = <$f>) {
295 if ($line =~ /^=head1\s+NAME\b/) {
296 push @modpods, $file;
300 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
305 die "$0: no pods" unless @modpods;
310 $name =~ s/\.p(m|od)$//;
311 $name =~ s-.*?/lib/--;
313 next if $done{$name}++;
315 if ($name =~ /^[a-z]/) {
316 $Pragmata{$name} = $_;
318 $Modules{$name} = $_;
323 # OK. Now a lot of ancillary function definitions follow
324 # Main program returns at "Do stuff"
329 my $filename = shift;
331 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
333 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
334 # This file is autogenerated by buildtoc from all the other pods.
335 # Edit those files and run buildtoc --build-toc to effect changes.
339 perltoc - perl documentation table of contents
343 This page provides a brief table of contents for the rest of the Perl
344 documentation set. It is meant to be scanned quickly or grepped
345 through to locate the proper section you're looking for.
347 =head1 BASIC DOCUMENTATION
352 # All the things in the master list that happen to be pod filenames
353 foreach (grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master) {
354 podset($_->[1], abs_from_top("pod/$_->[1].pod"));
358 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
362 =head1 PRAGMA DOCUMENTATION
366 foreach (sort keys %Pragmata) {
367 podset($_, $Pragmata{$_});
370 ($_= <<"EOPOD2B") =~ s/^\t//gm and $OUT .= $_;
374 =head1 MODULE DOCUMENTATION
378 foreach (sort keys %Modules) {
379 podset($_, $Modules{$_});
385 =head1 AUXILIARY DOCUMENTATION
387 Here should be listed all the extra programs' documentation, but they
388 don't all have manual pages yet:
394 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
401 Larry Wall <F<larry\@wall.org>>, with the help of oodles
410 $OUT =~ s/\n\s+\n/\n\n/gs;
411 $OUT =~ s/\n{3,}/\n\n/g;
413 $OUT =~ s/([^\n]+)/wrap('', '', $1)/ge;
418 # Below are all the auxiliary routines for generating perltoc.pod
420 my ($inhead1, $inhead2, $initem);
423 my ($pod, $file) = @_;
427 open my $fh, '<', $file or die "Can't open file '$file' for $pod: $!";
431 if (s/^=head1 (NAME)\s*/=head2 /) {
433 $OUT .= "\n\n=head2 ";
435 # Remove svn keyword expansions from the Perl FAQ
436 s/ \(\$Revision: \d+ \$\)//g;
437 if ( /^\s*\Q$pod\E\b/ ) {
438 s/$pod\.pm/$pod/; # '.pm' in NAME !?
443 elsif (s/^=head1 (.*)/=item $1/) {
445 $OUT .= "=over 4\n\n" unless $inhead1;
449 elsif (s/^=head2 (.*)/=item $1/) {
451 $OUT .= "=over 4\n\n" unless $inhead2;
455 elsif (s/^=item ([^=].*)/$1/) {
456 next if $pod eq 'perldiag';
457 s/^\s*\*\s*$// && next;
462 next if $pod eq 'perlmodlib' && /^ftp:/;
463 $OUT .= ", " if $initem;
469 unhead1() if /^=cut\s*\n/;
479 $OUT .= "\n\n=back\n\n";
487 $OUT .= "\n\n=back\n\n";
499 # End of original buildtoc. From here on are routines to generate new sections
500 # for and inplace edit other files
502 sub generate_perlpod {
507 next if $flags->{aux};
508 next if $flags->{perlpod_omit};
512 push @output, "=head2 $_->[1]\n";
515 my $start = " " x (4 + $flags->{indent}) . $_->[1];
516 $maxlength = length $start if length ($start) > $maxlength;
517 push @output, [$start, $_->[2]];
522 die "$0: Illegal length " . scalar @$_;
525 # want at least 2 spaces padding
527 $maxlength = ($maxlength + 3) & ~3;
528 # sprintf gives $1.....$2 where ... are spaces:
529 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
534 sub generate_manifest {
535 # Annoyingly, unexpand doesn't consider it good form to replace a single
536 # space before a tab with a tab
537 # Annoyingly (2) it returns read only values.
538 my @temp = unexpand (map {sprintf "%-32s%s", @$_} @_);
539 map {s/ \t/\t\t/g; $_} @temp;
541 sub generate_manifest_pod {
542 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
543 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
545 sub generate_manifest_readme {
546 generate_manifest sort {$a->[0] cmp $b->[0]}
547 ["README.vms", "Notes about installing the VMS port"],
548 map {["README.$_", $Readmes{$_}]} keys %Readmes;
551 sub generate_roffitall {
552 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
554 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
556 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
558 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
562 sub generate_descrip_mms_1 {
563 local $Text::Wrap::columns = 150;
565 my @lines = map {"pod" . $count++ . " = $_"}
566 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
567 sort keys %Pods, keys %Readmepods);
568 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
571 sub generate_descrip_mms_2 {
573 [.lib.pods]$_.pod : [.pod]$_.pod
574 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
575 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
577 sort keys %Pods, keys %Readmepods;
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;
617 my ($name, $prev) = @_;
619 grep {! m!^pod/[^.]+\.pod.*!}
620 grep {! m!^README\.(\S+)! || $Ignore{$1}} split "\n", $prev;
622 # Dictionary order - fold and handle non-word chars as nothing
624 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
625 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
627 &generate_manifest_pod(),
628 &generate_manifest_readme()), '';
632 my ($name, $makefile) = @_;
633 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
634 verify_contiguous($name, $makefile, 'README copies');
635 # Now remove the other copies that follow
636 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
637 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
639 $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
640 {"$1\n" . &generate_nmake_2."\n\t$2"}se;
644 # shut up used only once warning
645 *do_dmake = *do_dmake = \&do_nmake;
648 my ($name, $pod) = @_;
650 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
651 (?:\s+[a-z]{4,}.*\n # fooo
652 |=head.*\n # =head foo
656 {$1 . join "", &generate_perlpod}mxe) {
657 die "$0: Failed to insert amendments in do_perlpod";
663 my ($name, $body) = @_;
664 foreach my $variable (qw(pod man html tex)) {
665 die "$0: could not find $variable in $name"
666 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
667 {"\n" . generate_pod_mak ($variable)}se;
673 my ($name, $makefile) = @_;
674 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
675 verify_contiguous($name, $makefile, 'pod assignments');
676 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
678 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
680 # Looking for the macro defining the current perldelta:
681 #PERLDELTA_CURRENT = [.pod]perl5139delta.pod
683 $makefile =~ s/\nPERLDELTA_CURRENT\s+=\s+\Q[.pod]perl\E\d+delta\.pod\n
685 verify_contiguous($name, $makefile, 'current perldelta macro');
686 $makefile =~ s/\0+/join "\n", '', "PERLDELTA_CURRENT = [.pod]$delta_target", ''/se;
688 # Looking for rules like this
689 # [.lib.pods]perl.pod : [.pod]perl.pod
690 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
691 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
693 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
694 [^\n]+\n # Another line
695 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
697 verify_contiguous($name, $makefile, 'copy rules');
698 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
704 my ($name, $makefile_SH) = @_;
706 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
707 {join ' ', $1, map "pod/$_",
708 sort keys %Copies, grep {!/perltoc/} keys %Generated
711 # pod/perl511delta.pod: pod/perldelta.pod
712 # cd pod && $(LNS) perldelta.pod perl511delta.pod
715 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
716 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
719 verify_contiguous($name, $makefile_SH, 'copy rules');
721 my @copy_rules = map "
722 pod/$_: pod/$Copies{$_}
723 \$(LNS) $Copies{$_} pod/$_
726 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
734 while (my ($target, $name) = each %Targets) {
735 print "Working on target $target\n" if $Verbose;
736 next unless $Build{$target};
739 print "Now processing $name\n" if $Verbose;
740 if ($target ne "toc") {
742 open my $thing, '<', $name or die "Can't open $name: $!";
745 die "$0: $name contains NUL bytes" if $orig =~ /\0/;
750 &{"do_$target"}($target, $orig);
756 printf "ok %d # $name is up to date\n", $built + 1;
758 print "Was not modified\n";
762 printf "not ok %d # $name is up to date\n", $built + 1;
765 $mode = (stat $name)[2] // die "$0: Can't stat $name: $!";
766 rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
769 open my $thing, '>', $name or die "$0: Can't open $name for writing: $!";
771 print $thing $new or die "$0: print to $name failed: $!";
772 close $thing or die "$0: close $name failed: $!";
774 chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
778 warn "$0: was not instructed to build anything\n" unless $built || $Test;