4 use vars qw($masterpodfile %Build %Targets $Verbose $Quiet $Up %Ignore
5 @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
17 $Up = File::Spec->updir;
18 $masterpodfile = File::Spec->catfile($Up, "pod.lst");
20 # Generate any/all of these files
21 # --verbose gives slightly more output
22 # --quiet suppresses routine warnings
23 # --build-all tries to build everything
24 # --build-foo updates foo as follows
25 # --showfiles shows the files to be changed
30 manifest => File::Spec->catdir($Up, "MANIFEST"),
31 perlpod => "perl.pod",
32 vms => File::Spec->catfile($Up, "vms", "descrip_mms.template"),
33 nmake => File::Spec->catfile($Up, "win32", "Makefile"),
34 dmake => File::Spec->catfile($Up, "win32", "makefile.mk"),
35 podmak => File::Spec->catfile($Up, "win32", "pod.mak"),
36 # plan9 => File::Spec->catdir($Up, "plan9", "mkfile"),
37 unix => File::Spec->catfile($Up, "Makefile.SH"),
42 my @files = keys %Targets;
43 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
46 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
49 && GetOptions (verbose => \$Verbose,
51 showfiles => \$showfiles,
52 map {+"build-$_", \$Build{$_}} @files, 'all');
53 # Set them all to true
54 @Build{@files} = @files if ($Build{all});
58 sort { lc $a cmp lc $b }
60 my ($v, $d, $f) = File::Spec->splitpath($_);
62 @d = defined $d ? File::Spec->splitdir($d) : ();
64 File::Spec->catfile(@d ?
65 (@d == 1 && $d[0] eq '' ? () : @d)
67 } @Targets{grep { $_ ne 'all' && $Build{$_} } keys %Build}),
73 # Don't copy these top level READMEs
81 print "I'm building $_\n" foreach grep {$Build{$_}} keys %Build;
84 chdir $FindBin::Bin or die "$0: Can't chdir $FindBin::Bin: $!";
86 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
88 my ($delta_source, $delta_target);
93 # At least one upper case letter somewhere in the first group
94 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
98 my %flags = (header => 1);
99 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
100 $flags{aux} = 1 if $flags =~ tr/a//d;
101 die "$0: Unknown flag found in heading line: $_" if length $flags;
102 push @Master, [\%flags, $2];
104 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
106 my ($flags, $filename, $desc) = ($1, $2, $3);
108 my %flags = (indent => 0);
109 $flags{indent} = $1 if $flags =~ s/(\d+)//;
110 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
111 $flags{aux} = 1 if $flags =~ tr/a//d;
113 if ($flags =~ tr/D//d) {
114 $flags{manifest_omit} = 1;
115 $delta_source = "$filename.pod";
117 if ($flags =~ tr/d//d) {
118 $flags{perlpod_omit} = 1;
119 $delta_target = "$filename.pod";
121 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
123 if ($flags =~ tr/r//d) {
124 my $readme = $filename;
125 $readme =~ s/^perl//;
126 $Readmepods{$filename} = $Readmes{$readme} = $desc;
128 } elsif ($flags{aux}) {
129 $Aux{$filename} = $desc;
131 $Pods{$filename} = $desc;
133 die "$0: Unknown flag found in section line: $_" if length $flags;
134 push @Master, [\%flags, $filename, $desc];
138 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
141 if (defined $delta_source) {
142 if (defined $delta_target) {
143 # This way round so that keys can act as a MANIFEST skip list
144 # Targets will aways be in the pod directory. Currently we can only cope
145 # with sources being in the same directory.
146 $Copies{$delta_target} = $delta_source;
148 die "$0: delta source defined but not target";
150 } elsif (defined $delta_target) {
151 die "$0: delta target defined but not target";
158 my (%disk_pods, @disk_pods);
159 my (@manipods, %manipods);
160 my (@manireadmes, %manireadmes);
161 my (@perlpods, %perlpods);
164 # Convert these to a list of filenames.
165 foreach (keys %Pods, keys %Readmepods) {
166 $our_pods{"$_.pod"}++;
169 # None of these filenames will be boolean false
170 @disk_pods = glob("*.pod");
171 @disk_pods{@disk_pods} = @disk_pods;
173 # Things we copy from won't be in perl.pod
174 # Things we copy to won't be in MANIFEST
176 open(MANI, "../MANIFEST") || die "$0: opening ../MANIFEST failed: $!";
178 if (m!^pod/([^.]+\.pod)\s+!i) {
180 } elsif (m!^README\.(\S+)\s+!i) {
182 push @manireadmes, "perl$1.pod";
186 @manipods{@manipods} = @manipods;
187 @manireadmes{@manireadmes} = @manireadmes;
189 open(PERLPOD, "perl.pod") || die "$0: opening perl.pod failed: $!\n";
191 if (/^For ease of access, /../^\(If you're intending /) {
192 if (/^\s+(perl\S*)\s+\w/) {
193 push @perlpods, "$1.pod";
198 die "$0: could not find the pod listing of perl.pod\n"
200 @perlpods{@perlpods} = @perlpods;
202 foreach my $i (sort keys %disk_pods) {
203 warn "$0: $i exists but is unknown by buildtoc\n"
204 unless $our_pods{$i};
205 warn "$0: $i exists but is unknown by ../MANIFEST\n"
206 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
207 warn "$0: $i exists but is unknown by perl.pod\n"
208 if !$perlpods{$i} && !exists $Copies{$i};
210 my @BuildTargets = grep {defined} @Targets{grep $_ ne 'all', keys %Build};
212 @BuildFiles{@BuildTargets} = @BuildTargets;
214 foreach my $i (sort keys %our_pods) {
215 warn "$0: $i is known by buildtoc but does not exist\n"
216 unless $disk_pods{$i} or $BuildFiles{$i};
218 foreach my $i (sort keys %manipods) {
219 warn "$0: $i is known by ../MANIFEST but does not exist\n"
220 unless $disk_pods{$i};
221 warn "$0: $i is known by ../MANIFEST but is marked as generated\n"
224 foreach my $i (sort keys %perlpods) {
225 warn "$0: $i is known by perl.pod but does not exist\n"
226 unless $disk_pods{$i} or $BuildFiles{$i};
230 # Find all the modules
233 find \&getpods => qw(../lib ../ext);
237 my $file = $File::Find::name;
238 return if $file eq '../lib/Pod/Functions.pm'; # Used only by pod itself
239 return if $file =~ m!(?:^|/)t/!;
240 return if $file =~ m!lib/Attribute/Handlers/demo/!;
241 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
242 return if $file =~ m!lib/Math/BigInt/t/!;
243 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
244 return if $file =~ m!XS/(?:APItest|Typemap)!;
246 return if $pod =~ s/pm$/pod/ && -e $pod;
247 die "$0: tut $File::Find::name" if $file =~ /TUT/;
248 unless (open (F, "< $_\0")) {
249 warn "$0: bogus <$file>: $!";
250 system "ls", "-l", $file;
254 while ($line = <F>) {
255 if ($line =~ /^=head1\s+NAME\b/) {
256 push @modpods, $file;
257 #warn "GOOD $file\n";
263 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
268 die "$0: no pods" unless @modpods;
272 #($name) = /(\w+)\.p(m|od)$/;
273 my $name = path2modname($_);
274 if ($name =~ /^[a-z]/) {
275 $Pragmata{$name} = $_;
277 if ($done{$name}++) {
278 # warn "already did $_\n";
281 $Modules{$name} = $_;
286 # OK. Now a lot of ancillary function definitions follow
287 # Main program returns at "Do stuff"
301 open(OUT, ">perltoc.pod") || die "$0: creating perltoc.pod failed: $!";
305 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
307 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
308 # This file is autogenerated by buildtoc from all the other pods.
309 # Edit those files and run buildtoc --build-toc to effect changes.
313 perltoc - perl documentation table of contents
317 This page provides a brief table of contents for the rest of the Perl
318 documentation set. It is meant to be scanned quickly or grepped
319 through to locate the proper section you're looking for.
321 =head1 BASIC DOCUMENTATION
326 # All the things in the master list that happen to be pod filenames
327 podset(map {"$_->[1].pod"} grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master);
330 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
334 =head1 PRAGMA DOCUMENTATION
338 podset(sort values %Pragmata);
340 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
344 =head1 MODULE DOCUMENTATION
348 podset( @Modules{ sort keys %Modules } );
353 =head1 AUXILIARY DOCUMENTATION
355 Here should be listed all the extra programs' documentation, but they
356 don't all have manual pages yet:
362 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
369 Larry Wall <F<larry\@wall.org>>, with the help of oodles
377 output "\n"; # flush $LINE
381 # Below are all the auxiliary routines for generating perltoc.pod
383 my ($inhead1, $inhead2, $initem);
389 return unless scalar(@ARGV);
393 if (s/^=head1 (NAME)\s*/=head2 /) {
394 $pod = path2modname($ARGV);
396 output "\n \n\n=head2 ";
398 # Remove svn keyword expansions from the Perl FAQ
399 s/ \(\$Revision: \d+ \$\)//g;
400 if ( /^\s*$pod\b/ ) {
401 s/$pod\.pm/$pod/; # '.pm' in NAME !?
409 if (s/^=head1 (.*)/=item $1/) {
411 output "=over 4\n\n" unless $inhead1;
413 output $_; nl(); next;
415 if (s/^=head2 (.*)/=item $1/) {
417 output "=over 4\n\n" unless $inhead2;
419 output $_; nl(); next;
421 if (s/^=item ([^=].*)/$1/) {
422 next if $pod eq 'perldiag';
423 s/^\s*\*\s*$// && next;
428 next if $pod eq 'perlmodlib' && /^ftp:/;
429 ##print "=over 4\n\n" unless $initem;
430 output ", " if $initem;
436 if (s/^=cut\s*\n//) {
446 output "\n\n=back\n\n";
454 output "\n\n=back\n\n";
462 ##print "\n\n=back\n\n";
471 my $NEWLINE = 0; # how many newlines have we seen recently
472 my $LINE; # what remains to be printed
475 for (split /(\n)/, shift) {
478 print OUT wrap('', '', $LINE);
481 if (($NEWLINE) < 2) {
486 elsif (/\S/ && length) {
493 # End of original buildtoc. From here on are routines to generate new sections
494 # for and inplace edit other files
496 sub generate_perlpod {
501 next if $flags->{aux};
502 next if $flags->{perlpod_omit};
506 push @output, "=head2 $_->[1]\n";
509 my $start = " " x (4 + $flags->{indent}) . $_->[1];
510 $maxlength = length $start if length ($start) > $maxlength;
511 push @output, [$start, $_->[2]];
516 die "$0: Illegal length " . scalar @$_;
519 # want at least 2 spaces padding
521 $maxlength = ($maxlength + 3) & ~3;
522 # sprintf gives $1.....$2 where ... are spaces:
523 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
528 sub generate_manifest {
529 # Annyoingly unexpand doesn't consider it good form to replace a single
530 # space before a tab with a tab
531 # Annoyingly (2) it returns read only values.
532 my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
533 map {s/ \t/\t\t/g; $_} @temp;
535 sub generate_manifest_pod {
536 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
537 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
539 sub generate_manifest_readme {
540 generate_manifest sort {$a->[0] cmp $b->[0]}
541 ["README.vms", "Notes about installing the VMS port"],
542 map {["README.$_", $Readmes{$_}]} keys %Readmes;
545 sub generate_roffitall {
546 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
548 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
550 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
552 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
556 sub generate_descrip_mms_1 {
557 local $Text::Wrap::columns = 150;
559 my @lines = map {"pod" . $count++ . " = $_"}
560 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
561 sort keys %Pods, keys %Readmepods);
562 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
565 sub generate_descrip_mms_2 {
567 [.lib.pods]$_.pod : [.pod]$_.pod
568 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
569 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
571 sort keys %Pods, keys %Readmepods;
574 sub generate_descrip_mms_3 {
575 map qq{\t- If F\$Search("[.pod]$_").nes."" Then Delete/NoConfirm/Log [.pod]$_;*},
576 sort keys %Generated, keys %Copies;
579 sub generate_nmake_1 {
580 # XXX Fix this with File::Spec
581 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
583 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
586 # This doesn't have a trailing newline
587 sub generate_nmake_2 {
588 # Spot the special case
589 local $Text::Wrap::columns = 76;
590 my $line = wrap ("\t ", "\t ",
591 join " ", sort keys %Copies, keys %Generated,
592 map {"perl$_.pod"} keys %Readmes);
598 sub generate_pod_mak {
599 my $variable = shift;
601 my $line = join "\\\n", "\U$variable = ",
602 map {"\t$_.$variable\t"} sort keys %Pods;
604 $line =~ s/.*perltoc.html.*\n//m;
608 sub verify_contiguous {
609 my ($name, $content, $what) = @_;
610 my $sections = () = $content =~ m/\0+/g;
611 croak("$0: $name contains no $what") if $sections < 1;
612 croak("$0: $name contains discontiguous $what") if $sections > 1;
618 grep {! m!^pod/[^.]+\.pod.*\n!}
619 grep {! m!^README\.(\S+)! || $Ignore{$1}} @_;
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();
631 my $makefile = join '', @_;
632 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
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;
649 my $pod = join '', @_;
651 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
652 (?:\s+[a-z]{4,}.*\n # fooo
653 |=head.*\n # =head foo
657 {$1 . join "", &generate_perlpod}mxe) {
658 die "$0: Failed to insert amendments in do_perlpod";
665 my $body = join '', @_;
666 foreach my $variable (qw(pod man html tex)) {
667 die "$0: could not find $variable in $name"
668 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
669 {"\n" . generate_pod_mak ($variable)}se;
676 my $makefile = join '', @_;
677 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
678 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
679 verify_contiguous($name, $makefile, 'pod assignments');
680 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
682 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
684 # Looking for rules like this
685 # [.lib.pods]perl.pod : [.pod]perl.pod
686 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
687 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
689 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
690 [^\n]+\n # Another line
691 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
693 verify_contiguous($name, $makefile, 'copy rules');
694 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
696 # Looking for rules like this:
697 # - If F$Search("[.pod]perldelta.pod").nes."" Then Delete/NoConfirm/Log [.pod]perldelta.pod;*
698 $makefile =~ s!(?:\t- If F\$Search\("\[\.pod\]perl[a-z]+\Q.pod").nes."" Then Delete/NoConfirm/Log [.pod]perl\E[a-z]+\.pod;\*\n)+!\0!sg;
699 verify_contiguous($name, $makefile, 'delete rules');
700 $makefile =~ s/\0+/join "\n", &generate_descrip_mms_3, ''/se;
707 my $makefile_SH = join '', @_;
708 die "$0: $name contains NUL bytes" if $makefile_SH =~ /\0/;
710 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
711 {join ' ', $1, map "pod/$_",
712 sort keys %Copies, grep {!/perltoc/} keys %Generated
715 # pod/perl511delta.pod: pod/perldelta.pod
716 # cd pod && $(LNS) perldelta.pod perl511delta.pod
719 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
720 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
723 verify_contiguous($name, $makefile_SH, 'copy rules');
725 my @copy_rules = map "
726 pod/$_: pod/$Copies{$_}
727 \$(LNS) $Copies{$_} pod/$_
730 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
738 while (my ($target, $name) = each %Targets) {
739 print "Working on target $target\n" if $Verbose;
740 next unless $Build{$target};
742 if ($target eq "toc") {
743 print "Now processing $name\n" if $Verbose;
745 print "Finished\n" if $Verbose;
748 print "Now processing $name\n" if $Verbose;
749 open THING, $name or die "Can't open $name: $!";
752 my $orig = join '', @orig;
756 &{"do_$target"}($target, @orig);
758 my $new = join '', @new;
760 print "Was not modified\n" if $Verbose;
763 my $mode = (stat $name)[2] // die "$0: Can't stat $name: $!";
764 rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
765 open THING, ">$name" or die "$0: Can't open $name for writing: $!";
767 print THING $new or die "$0: print to $name failed: $!";
768 close THING or die "$0: close $name failed: $!";
769 chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
772 warn "$0: was not instructed to build anything\n" unless $built;