4 use vars qw($masterpodfile %Build %Targets $Verbose $Quiet $Up %Ignore
5 @Master %Readmes %Pods %Aux %Readmepods %Pragmata %Modules
6 %Copies %Generated $Test);
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
26 # --test exit early, exit if perl.pod, pod.lst, MANIFEST are
27 # consistent, die otherwise.
32 manifest => File::Spec->catdir($Up, "MANIFEST"),
33 perlpod => "perl.pod",
34 vms => File::Spec->catfile($Up, "vms", "descrip_mms.template"),
35 nmake => File::Spec->catfile($Up, "win32", "Makefile"),
36 dmake => File::Spec->catfile($Up, "win32", "makefile.mk"),
37 podmak => File::Spec->catfile($Up, "win32", "pod.mak"),
38 # plan9 => File::Spec->catdir($Up, "plan9", "mkfile"),
39 unix => File::Spec->catfile($Up, "Makefile.SH"),
44 my @files = keys %Targets;
45 my $filesopts = join(" | ", map { "--build-$_" } "all", sort @files);
48 $0: Usage: $0 [--verbose] [--showfiles] $filesopts
51 && GetOptions (verbose => \$Verbose,
53 showfiles => \$showfiles,
55 map {+"build-$_", \$Build{$_}} @files, 'all');
56 # Set them all to true
57 @Build{@files} = @files if ($Build{all});
61 sort { lc $a cmp lc $b }
63 my ($v, $d, $f) = File::Spec->splitpath($_);
65 @d = defined $d ? File::Spec->splitdir($d) : ();
67 File::Spec->catfile(@d ?
68 (@d == 1 && $d[0] eq '' ? () : @d)
70 } @Targets{grep { $_ ne 'all' && $Build{$_} } keys %Build}),
76 # Don't copy these top level READMEs
84 print "I'm building $_\n" foreach grep {$Build{$_}} keys %Build;
87 chdir $FindBin::Bin or die "$0: Can't chdir $FindBin::Bin: $!";
89 open MASTER, $masterpodfile or die "$0: Can't open $masterpodfile: $!";
91 my ($delta_source, $delta_target);
96 # At least one upper case letter somewhere in the first group
97 if (/^(\S+)\s(.*)/ && $1 =~ tr/h//) {
101 my %flags = (header => 1);
102 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
103 $flags{aux} = 1 if $flags =~ tr/a//d;
104 die "$0: Unknown flag found in heading line: $_" if length $flags;
105 push @Master, [\%flags, $2];
107 } elsif (/^(\S*)\s+(\S+)\s+(.*)/) {
109 my ($flags, $filename, $desc) = ($1, $2, $3);
111 my %flags = (indent => 0);
112 $flags{indent} = $1 if $flags =~ s/(\d+)//;
113 $flags{toc_omit} = 1 if $flags =~ tr/o//d;
114 $flags{aux} = 1 if $flags =~ tr/a//d;
116 if ($flags =~ tr/D//d) {
117 $flags{manifest_omit} = 1;
118 $delta_source = "$filename.pod";
120 if ($flags =~ tr/d//d) {
121 $flags{perlpod_omit} = 1;
122 $delta_target = "$filename.pod";
124 $Generated{"$filename.pod"}++ if $flags =~ tr/g//d;
126 if ($flags =~ tr/r//d) {
127 my $readme = $filename;
128 $readme =~ s/^perl//;
129 $Readmepods{$filename} = $Readmes{$readme} = $desc;
131 } elsif ($flags{aux}) {
132 $Aux{$filename} = $desc;
134 $Pods{$filename} = $desc;
136 die "$0: Unknown flag found in section line: $_" if length $flags;
137 push @Master, [\%flags, $filename, $desc];
141 die "$0: Malformed line: $_" if $1 =~ tr/A-Z//;
144 if (defined $delta_source) {
145 if (defined $delta_target) {
146 # This way round so that keys can act as a MANIFEST skip list
147 # Targets will aways be in the pod directory. Currently we can only cope
148 # with sources being in the same directory.
149 $Copies{$delta_target} = $delta_source;
151 die "$0: delta source defined but not target";
153 } elsif (defined $delta_target) {
154 die "$0: delta target defined but not target";
161 my (%disk_pods, @disk_pods);
162 my (@manipods, %manipods);
163 my (@manireadmes, %manireadmes);
164 my (@perlpods, %perlpods);
167 # Convert these to a list of filenames.
168 foreach (keys %Pods, keys %Readmepods) {
169 $our_pods{"$_.pod"}++;
172 # None of these filenames will be boolean false
173 @disk_pods = glob("*.pod");
174 @disk_pods{@disk_pods} = @disk_pods;
176 # Things we copy from won't be in perl.pod
177 # Things we copy to won't be in MANIFEST
179 open(MANI, "../MANIFEST") || die "$0: opening ../MANIFEST failed: $!";
181 if (m!^pod/([^.]+\.pod)\s+!i) {
183 } elsif (m!^README\.(\S+)\s+!i) {
185 push @manireadmes, "perl$1.pod";
189 @manipods{@manipods} = @manipods;
190 @manireadmes{@manireadmes} = @manireadmes;
192 open(PERLPOD, "perl.pod") || die "$0: opening perl.pod failed: $!\n";
194 if (/^For ease of access, /../^\(If you're intending /) {
195 if (/^\s+(perl\S*)\s+\w/) {
196 push @perlpods, "$1.pod";
201 die "$0: could not find the pod listing of perl.pod\n"
203 @perlpods{@perlpods} = @perlpods;
206 foreach my $i (sort keys %disk_pods) {
207 push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
208 unless $our_pods{$i};
209 push @inconsistent, "$0: $i exists but is unknown by ../MANIFEST\n"
210 if !$manipods{$i} && !$manireadmes{$i} && !$Copies{$i} && !$Generated{$i};
211 push @inconsistent, "$0: $i exists but is unknown by perl.pod\n"
212 if !$perlpods{$i} && !exists $Copies{$i};
214 my @BuildTargets = grep {defined} @Targets{grep $_ ne 'all', keys %Build};
216 @BuildFiles{@BuildTargets} = @BuildTargets;
218 foreach my $i (sort keys %our_pods) {
219 push @inconsistent, "$0: $i is known by buildtoc but does not exist\n"
220 unless $disk_pods{$i} or $BuildFiles{$i};
222 foreach my $i (sort keys %manipods) {
223 push @inconsistent, "$0: $i is known by ../MANIFEST but does not exist\n"
224 unless $disk_pods{$i};
225 push @inconsistent, "$0: $i is known by ../MANIFEST but is marked as generated\n"
228 foreach my $i (sort keys %perlpods) {
229 push @inconsistent, "$0: $i is known by perl.pod but does not exist\n"
230 unless $disk_pods{$i} or $BuildFiles{$i};
242 warn @inconsistent if @inconsistent;
246 # Find all the modules
249 find \&getpods => qw(../lib ../ext);
253 my $file = $File::Find::name;
254 return if $file eq '../lib/Pod/Functions.pm'; # Used only by pod itself
255 return if $file =~ m!(?:^|/)t/!;
256 return if $file =~ m!lib/Attribute/Handlers/demo/!;
257 return if $file =~ m!lib/Net/FTP/.+\.pm!; # Hi, Graham! :-)
258 return if $file =~ m!lib/Math/BigInt/t/!;
259 return if $file =~ m!/Devel/PPPort/[Hh]arness|lib/Devel/Harness!i;
260 return if $file =~ m!XS/(?:APItest|Typemap)!;
262 return if $pod =~ s/pm$/pod/ && -e $pod;
263 die "$0: tut $File::Find::name" if $file =~ /TUT/;
264 unless (open (F, "< $_\0")) {
265 warn "$0: bogus <$file>: $!";
266 system "ls", "-l", $file;
270 while ($line = <F>) {
271 if ($line =~ /^=head1\s+NAME\b/) {
272 push @modpods, $file;
273 #warn "GOOD $file\n";
279 warn "$0: $file: cannot find =head1 NAME\n" unless $Quiet;
284 die "$0: no pods" unless @modpods;
288 #($name) = /(\w+)\.p(m|od)$/;
289 my $name = path2modname($_);
290 if ($name =~ /^[a-z]/) {
291 $Pragmata{$name} = $_;
293 if ($done{$name}++) {
294 # warn "already did $_\n";
297 $Modules{$name} = $_;
302 # OK. Now a lot of ancillary function definitions follow
303 # Main program returns at "Do stuff"
317 open(OUT, ">perltoc.pod") || die "$0: creating perltoc.pod failed: $!";
321 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
323 # !!!!!!! DO NOT EDIT THIS FILE !!!!!!!
324 # This file is autogenerated by buildtoc from all the other pods.
325 # Edit those files and run buildtoc --build-toc to effect changes.
329 perltoc - perl documentation table of contents
333 This page provides a brief table of contents for the rest of the Perl
334 documentation set. It is meant to be scanned quickly or grepped
335 through to locate the proper section you're looking for.
337 =head1 BASIC DOCUMENTATION
342 # All the things in the master list that happen to be pod filenames
343 podset(map {"$_->[1].pod"} grep {defined $_ && @$_ == 3 && !$_->[0]{toc_omit}} @Master);
346 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
350 =head1 PRAGMA DOCUMENTATION
354 podset(sort values %Pragmata);
356 ($_= <<"EOPOD2B") =~ s/^\t//gm && output($_);
360 =head1 MODULE DOCUMENTATION
364 podset( @Modules{ sort keys %Modules } );
369 =head1 AUXILIARY DOCUMENTATION
371 Here should be listed all the extra programs' documentation, but they
372 don't all have manual pages yet:
378 $_ .= join "\n", map {"\t=item $_\n"} sort keys %Aux;
385 Larry Wall <F<larry\@wall.org>>, with the help of oodles
393 output "\n"; # flush $LINE
397 # Below are all the auxiliary routines for generating perltoc.pod
399 my ($inhead1, $inhead2, $initem);
405 return unless scalar(@ARGV);
409 if (s/^=head1 (NAME)\s*/=head2 /) {
410 $pod = path2modname($ARGV);
412 output "\n \n\n=head2 ";
414 # Remove svn keyword expansions from the Perl FAQ
415 s/ \(\$Revision: \d+ \$\)//g;
416 if ( /^\s*$pod\b/ ) {
417 s/$pod\.pm/$pod/; # '.pm' in NAME !?
425 if (s/^=head1 (.*)/=item $1/) {
427 output "=over 4\n\n" unless $inhead1;
429 output $_; nl(); next;
431 if (s/^=head2 (.*)/=item $1/) {
433 output "=over 4\n\n" unless $inhead2;
435 output $_; nl(); next;
437 if (s/^=item ([^=].*)/$1/) {
438 next if $pod eq 'perldiag';
439 s/^\s*\*\s*$// && next;
444 next if $pod eq 'perlmodlib' && /^ftp:/;
445 ##print "=over 4\n\n" unless $initem;
446 output ", " if $initem;
452 if (s/^=cut\s*\n//) {
462 output "\n\n=back\n\n";
470 output "\n\n=back\n\n";
478 ##print "\n\n=back\n\n";
487 my $NEWLINE = 0; # how many newlines have we seen recently
488 my $LINE; # what remains to be printed
491 for (split /(\n)/, shift) {
494 print OUT wrap('', '', $LINE);
497 if (($NEWLINE) < 2) {
502 elsif (/\S/ && length) {
509 # End of original buildtoc. From here on are routines to generate new sections
510 # for and inplace edit other files
512 sub generate_perlpod {
517 next if $flags->{aux};
518 next if $flags->{perlpod_omit};
522 push @output, "=head2 $_->[1]\n";
525 my $start = " " x (4 + $flags->{indent}) . $_->[1];
526 $maxlength = length $start if length ($start) > $maxlength;
527 push @output, [$start, $_->[2]];
532 die "$0: Illegal length " . scalar @$_;
535 # want at least 2 spaces padding
537 $maxlength = ($maxlength + 3) & ~3;
538 # sprintf gives $1.....$2 where ... are spaces:
539 return unexpand (map {ref $_ ? sprintf "%-${maxlength}s%s\n", @$_ : $_}
544 sub generate_manifest {
545 # Annoyingly, unexpand doesn't consider it good form to replace a single
546 # space before a tab with a tab
547 # Annoyingly (2) it returns read only values.
548 my @temp = unexpand (map {sprintf "%-32s%s\n", @$_} @_);
549 map {s/ \t/\t\t/g; $_} @temp;
551 sub generate_manifest_pod {
552 generate_manifest map {["pod/$_.pod", $Pods{$_}]}
553 sort grep {!$Copies{"$_.pod"}} grep {!$Generated{"$_.pod"}} keys %Pods;
555 sub generate_manifest_readme {
556 generate_manifest sort {$a->[0] cmp $b->[0]}
557 ["README.vms", "Notes about installing the VMS port"],
558 map {["README.$_", $Readmes{$_}]} keys %Readmes;
561 sub generate_roffitall {
562 (map ({"\t\$maindir/$_.1\t\\"}sort keys %Pods),
564 map ({"\t\$maindir/$_.1\t\\"}sort keys %Aux),
566 map ({"\t\$libdir/$_.3\t\\"}sort keys %Pragmata),
568 map ({"\t\$libdir/$_.3\t\\"}sort keys %Modules),
572 sub generate_descrip_mms_1 {
573 local $Text::Wrap::columns = 150;
575 my @lines = map {"pod" . $count++ . " = $_"}
576 split /\n/, wrap('', '', join " ", map "[.lib.pods]$_.pod",
577 sort keys %Pods, keys %Readmepods);
578 @lines, "pod = " . join ' ', map {"\$(pod$_)"} 0 .. $count - 1;
581 sub generate_descrip_mms_2 {
583 [.lib.pods]$_.pod : [.pod]$_.pod
584 \@ If F\$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
585 Copy/NoConfirm/Log \$(MMS\$SOURCE) [.lib.pods]
587 sort keys %Pods, keys %Readmepods;
590 sub generate_descrip_mms_3 {
591 map qq{\t- If F\$Search("[.pod]$_").nes."" Then Delete/NoConfirm/Log [.pod]$_;*},
592 sort keys %Generated, keys %Copies;
595 sub generate_nmake_1 {
596 # XXX Fix this with File::Spec
597 (map {sprintf "\tcopy ..\\README.%-8s ..\\pod\\perl$_.pod\n", $_}
599 (map {"\tcopy ..\\pod\\$Copies{$_} ..\\pod\\$_\n"} sort keys %Copies);
602 # This doesn't have a trailing newline
603 sub generate_nmake_2 {
604 # Spot the special case
605 local $Text::Wrap::columns = 76;
606 my $line = wrap ("\t ", "\t ",
607 join " ", sort keys %Copies, keys %Generated,
608 map {"perl$_.pod"} keys %Readmes);
614 sub generate_pod_mak {
615 my $variable = shift;
617 my $line = join "\\\n", "\U$variable = ",
618 map {"\t$_.$variable\t"} sort keys %Pods;
620 $line =~ s/.*perltoc.html.*\n//m;
624 sub verify_contiguous {
625 my ($name, $content, $what) = @_;
626 my $sections = () = $content =~ m/\0+/g;
627 croak("$0: $name contains no $what") if $sections < 1;
628 croak("$0: $name contains discontiguous $what") if $sections > 1;
634 grep {! m!^pod/[^.]+\.pod.*\n!}
635 grep {! m!^README\.(\S+)! || $Ignore{$1}} @_;
636 # Dictionary order - fold and handle non-word chars as nothing
638 sort { $a->[1] cmp $b->[1] || $a->[0] cmp $b->[0] }
639 map { my $f = lc $_; $f =~ s/[^a-z0-9\s]//g; [ $_, $f ] }
641 &generate_manifest_pod(),
642 &generate_manifest_readme();
647 my $makefile = join '', @_;
648 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
649 $makefile =~ s/^\tcopy \.\.\\README.*\n/\0/gm;
650 verify_contiguous($name, $makefile, 'README copies');
651 # Now remove the other copies that follow
652 1 while $makefile =~ s/\0\tcopy .*\n/\0/gm;
653 $makefile =~ s/\0+/join ("", &generate_nmake_1)/se;
655 $makefile =~ s{(-cd \$\(PODDIR\) && del /f[^\n]+).*?(-cd \.\.\\utils && del /f)}
656 {"$1\n" . &generate_nmake_2."\n\t$2"}se;
660 # shut up used only once warning
661 *do_dmake = *do_dmake = \&do_nmake;
665 my $pod = join '', @_;
667 unless ($pod =~ s{(For\ ease\ of\ access,\ .*\n)
668 (?:\s+[a-z]{4,}.*\n # fooo
669 |=head.*\n # =head foo
673 {$1 . join "", &generate_perlpod}mxe) {
674 die "$0: Failed to insert amendments in do_perlpod";
681 my $body = join '', @_;
682 foreach my $variable (qw(pod man html tex)) {
683 die "$0: could not find $variable in $name"
684 unless $body =~ s{\n\U$variable\E = (?:[^\n]*\\\n)*[^\n]*}
685 {"\n" . generate_pod_mak ($variable)}se;
692 my $makefile = join '', @_;
693 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
694 $makefile =~ s/\npod\d* =[^\n]*/\0/gs;
695 verify_contiguous($name, $makefile, 'pod assignments');
696 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_1/se;
698 die "$0: $name contains NUL bytes" if $makefile =~ /\0/;
700 # Looking for rules like this
701 # [.lib.pods]perl.pod : [.pod]perl.pod
702 # @ If F$Search("[.lib]pods.dir").eqs."" Then Create/Directory [.lib.pods]
703 # Copy/NoConfirm/Log $(MMS$SOURCE) [.lib.pods]
705 $makefile =~ s/\n\Q[.lib.pods]\Eperl[^\n\.]*\.pod[^\n]+\n
706 [^\n]+\n # Another line
707 [^\n]+\Q[.lib.pods]\E\n # ends [.lib.pods]
709 verify_contiguous($name, $makefile, 'copy rules');
710 $makefile =~ s/\0+/join "\n", '', &generate_descrip_mms_2/se;
712 # Looking for rules like this:
713 # - If F$Search("[.pod]perldelta.pod").nes."" Then Delete/NoConfirm/Log [.pod]perldelta.pod;*
714 $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;
715 verify_contiguous($name, $makefile, 'delete rules');
716 $makefile =~ s/\0+/join "\n", &generate_descrip_mms_3, ''/se;
723 my $makefile_SH = join '', @_;
724 die "$0: $name contains NUL bytes" if $makefile_SH =~ /\0/;
726 $makefile_SH =~ s{^(perltoc_pod_prereqs = extra.pods).*}
727 {join ' ', $1, map "pod/$_",
728 sort keys %Copies, grep {!/perltoc/} keys %Generated
731 # pod/perl511delta.pod: pod/perldelta.pod
732 # cd pod && $(LNS) perldelta.pod perl511delta.pod
735 pod/perl[a-z0-9_]+\.pod: pod/perl[a-z0-9_]+\.pod
736 \$\(LNS\) perl[a-z0-9_]+\.pod pod/perl[a-z0-9_]+\.pod
739 verify_contiguous($name, $makefile_SH, 'copy rules');
741 my @copy_rules = map "
742 pod/$_: pod/$Copies{$_}
743 \$(LNS) $Copies{$_} pod/$_
746 $makefile_SH =~ s/\0+/join '', @copy_rules/se;
754 while (my ($target, $name) = each %Targets) {
755 print "Working on target $target\n" if $Verbose;
756 next unless $Build{$target};
758 if ($target eq "toc") {
759 print "Now processing $name\n" if $Verbose;
761 print "Finished\n" if $Verbose;
764 print "Now processing $name\n" if $Verbose;
765 open THING, $name or die "Can't open $name: $!";
768 my $orig = join '', @orig;
772 &{"do_$target"}($target, @orig);
774 my $new = join '', @new;
776 print "Was not modified\n" if $Verbose;
779 my $mode = (stat $name)[2] // die "$0: Can't stat $name: $!";
780 rename $name, "$name.old" or die "$0: Can't rename $name to $name.old: $!";
781 open THING, ">$name" or die "$0: Can't open $name for writing: $!";
783 print THING $new or die "$0: print to $name failed: $!";
784 close THING or die "$0: close $name failed: $!";
785 chmod $mode & 0777, $name or die "$0: can't chmod $mode $name: $!";
788 warn "$0: was not instructed to build anything\n" unless $built;