#!/usr/bin/perl -w
use strict;
-use Digest::MD5 'md5';
use File::Find;
+=head1 NAME
+
+Porting/pod_lib.pl - functions for building and installing POD
+
+=head1 SYNOPSIS
+
+ require './Porting/pod_lib.pl';
+
+=cut
+
+=head1 DESCRIPTION
+
+This program, when C<require>d into other programs in the Perl 5 core
+distribution, provides functions useful during building and, secondarily,
+testing.
+
+As of this writing, the functions in this program are used in these other
+programs:
+
+ installman
+ installperl
+ pod/buildtoc
+ pod/perl.pod
+ Porting/new-perldelta.pl
+ Porting/pod_rules.pl
+
+Note: Since these functions are used during the Perl build process, they must
+work with F<miniperl>. That necessarily implies that these functions must not
+rely on XS modules, either directly or indirectly (e.g., C<autodie>).
+
+=head1 SUBROUTINES
+
+=head2 C<my_die()>
+
+=over 4
+
+=item * Purpose
+
+Exit from a process with an error code and a message.
+
+=item * Arguments
+
+List of arguments to be passed with the error message. Example:
+
+ close $fh or my_die("close 'utils.lst': $!");
+
+=item * Return Value
+
+Exit code C<255>.
+
+=item * Comment
+
+Prints C<ABORTED> to STDERR.
+
+=back
+
+=cut
+
+# In some situations, eg cross-compiling, we get run with miniperl, so we can't use Digest::MD5
+my $has_md5;
+BEGIN {
+ use Carp;
+ $has_md5 = eval { require Digest::MD5; Digest::MD5->import('md5'); 1; };
+}
+
+
# make it clearer when we haven't run to completion, as we can be quite
# noisy when things are working ok
exit 255;
}
+=head2 C<open_or_die()>
+
+=over 4
+
+=item * Purpose
+
+Opens a file or fails if it cannot.
+
+=item * Arguments
+
+String holding filename to be opened. Example:
+
+ $fh = open_or_die('utils.lst');
+
+=item * Return Value
+
+Handle to opened file.
+
+=back
+
+=cut
+
sub open_or_die {
my $filename = shift;
open my $fh, '<', $filename or my_die "Can't open $filename: $!";
return $fh;
}
+=head2 C<slurp_or_die()>
+
+=over 4
+
+=item * Purpose
+
+Read the contents of a file into memory as a single string.
+
+=item * Arguments
+
+String holding name of file to be read into memory.
+
+ $olddelta = slurp_or_die('pod/perldelta.pod');
+
+=item * Return Value
+
+String holding contents of file.
+
+=back
+
+=cut
+
sub slurp_or_die {
my $filename = shift;
my $fh = open_or_die($filename);
return $contents;
}
+=head2 C<write_or_die()>
+
+=over 4
+
+=item * Purpose
+
+Write out a string to a file.
+
+=item * Arguments
+
+List of two arguments: (i) String holding name of file to be written to; (ii)
+String holding contents to be written.
+
+ write_or_die($olddeltaname, $olddelta);
+
+=item * Return Value
+
+Implicitly returns true value upon success.
+
+=back
+
+=cut
+
sub write_or_die {
my ($filename, $contents) = @_;
open my $fh, '>', $filename or die "Can't open $filename for writing: $!";
close $fh or die "Can't close $filename: $!";
}
+=head2 C<verify_contiguous()>
+
+=over 4
+
+=item * Purpose
+
+Verify that a file contains exactly one contiguous run of lines which matches
+the passed in pattern. C<croak()>s if the pattern is not found, or found in
+more than one place.
+
+=item * Arguments
+
+=over 4
+
+=item * Name of file
+
+=item * Contents of file
+
+=item * Pattern of interest
+
+=item * Name to report on error
+
+=back
+
+=item * Return Value
+
+The contents of the file, with C<qr/\0+/> substituted for the pattern.
+
+=back
+
+=cut
+
+sub verify_contiguous {
+ my ($name, $content, $re, $what) = @_;
+ require Carp;
+ $content =~ s/$re/\0/g;
+ my $sections = () = $content =~ m/\0+/g;
+ Carp::croak("$0: $name contains no $what") if $sections < 1;
+ Carp::croak("$0: $name contains discontiguous $what") if $sections > 1;
+ return $content;
+}
+
+=head2 C<process()>
+
+=over 4
+
+=item * Purpose
+
+Read a file from disk, pass the contents to the callback, and either update
+the file on disk (if changed) or generate TAP output to confirm that the
+version on disk is up to date. C<die>s if the file contains any C<NUL> bytes.
+This permits the callback routine to use C<NUL> bytes as placeholders while
+manipulating the file's contents.
+
+=item * Arguments
+
+=over 4
+
+=item * Description for use in error messages
+
+=item * Name of file
+
+=item * Callback
+
+Passed description and file contents, should return updated file contents.
+
+=item * Test number
+
+If defined, generate TAP output to C<STDOUT>. If defined and false, generate
+an unnumbered test. Otherwise this is the test number in the I<ok> line.
+
+=item * Verbose flag
+
+If true, generate verbose output.
+
+=back
+
+=item * Return Value
+
+Does not return anything.
+
+=back
+
+=cut
+
+sub process {
+ my ($desc, $filename, $callback, $test, $verbose) = @_;
+
+ print "Now processing $filename\n" if $verbose;
+ my $orig = slurp_or_die($filename);
+ my_die "$filename contains NUL bytes" if $orig =~ /\0/;
+
+ my $new = $callback->($desc, $orig);
+
+ if (defined $test) {
+ printf "%s%s # $filename is up to date\n",
+ ($new eq $orig ? 'ok' : 'not ok'), ($test ? " $test" : '');
+ return;
+ } elsif ($new eq $orig) {
+ print "Was not modified\n"
+ if $verbose;
+ return;
+ }
+
+ my $mode = (stat $filename)[2];
+ my_die "Can't stat $filename: $!"
+ unless defined $mode;
+ rename $filename, "$filename.old"
+ or my_die "Can't rename $filename to $filename.old: $!";
+
+ write_or_die($filename, $new);
+ chmod $mode & 0777, $filename or my_die "can't chmod $mode $filename: $!";
+}
+
+=head2 C<pods_to_install()>
+
+=over 4
+
+=item * Purpose
+
+Create a lookup table holding information about PODs to be installed.
+
+=item * Arguments
+
+None.
+
+=item * Return Value
+
+Reference to a hash with a structure like this:
+
+ $found = {
+ 'MODULE' => {
+ 'CPAN::Bundle' => 'lib/CPAN/Bundle.pm',
+ 'Locale::Codes::Script_Retired' =>
+ 'lib/Locale/Codes/Script_Retired.pm',
+ 'Pod::Simple::DumpAsText' =>
+ 'lib/Pod/Simple/DumpAsText.pm',
+ # ...
+ 'Locale::Codes::LangVar' =>
+ 'lib/Locale/Codes/LangVar.pod'
+ },
+ 'PRAGMA' => {
+ 'fields' => 'lib/fields.pm',
+ 'subs' => 'lib/subs.pm',
+ # ...
+ },
+
+=item * Comment
+
+Broadly speaking, the function assembles a list of all F<.pm> and F<.pod>
+files in the distribution and then excludes certain files from installation.
+
+=back
+
+=cut
+
sub pods_to_install {
# manpages not to be installed
my %do_not_install = map { ($_ => 1) }
sub is_duplicate_pod {
my $file = shift;
+ local $_;
+
+ return if !$has_md5;
# Initialise the list of possible source files on the first call.
unless (%Lengths) {
}
}
close $master or my_die("close pod/perl.pod: $!");
- # This has to be special-cased somewhere. Turns out this is cleanest:
- push @{$state{master}}, ['a2p', 'x2p/a2p.pod', {toc_omit => 1}];
my_die("perl.pod sets flags for unknown pods: "
. join ' ', sort keys %flag_set)
if keys %flag_set;
}
+=head2 C<get_pod_metadata()>
+
+=over 4
+
+=item * Purpose
+
+=item * Arguments
+
+List of one or more arguments.
+
+=over 4
+
+=item * Boolean true or false
+
+=item * Reference to a subroutine.
+
+=item * Various other arguments.
+
+=back
+
+Example:
+
+ $state = get_pod_metadata(
+ 0, sub { warn @_ if @_ }, 'pod/perltoc.pod');
+
+ get_pod_metadata(
+ 1, sub { warn @_ if @_ }, values %Build);
+
+=item * Return Value
+
+Hash reference; each element provides either a list or a lookup table for
+information about various types of POD files.
+
+ 'aux' => [ # utility programs like
+ 'h2xs' and 'perlbug' ]
+ 'generated' => { # lookup table for generated POD files
+ like 'perlapi.pod' }
+ 'ignore' => { # lookup table for files to be ignored }
+ 'pods' => { # lookup table in "name" =>
+ "short description" format }
+ 'readmes' => { # lookup table for OS-specific
+ and other READMEs }
+ 'delta_version' => [ # major version number, minor no.,
+ patch no. ]
+ 'delta_target' => 'perl<Mmmpp>delta.pod',
+ 'master' => [ # list holding entries for files callable
+ by 'perldoc' ]
+ 'copies' => { # patch version perldelta =>
+ minor version perldelta }
+
+=back
+
+=cut
+
sub get_pod_metadata {
# Do we expect to find generated pods on disk?
my $permit_missing_generated = shift;
# Do they want a consistency report?
my $callback = shift;
+ local $_;
__prime_state() unless $state{master};
return \%state unless $callback;
my (%cpanpods, %cpanpods_leaf);
my (%our_pods);
- # These are stub files for deleted documents. We don't want them to show up
- # in perl.pod, they just exist so that if someone types "perldoc perltoot"
- # they get some sort of pointer to the new docs.
- my %ignoredpods
- = map { ( "$_.pod" => 1 ) } qw( perlboot perlbot perltooc perltoot );
+ # There are files that we don't want to list in perl.pod.
+ # Maybe the various stub manpages should be listed there.
+ my %ignoredpods = map { ( "$_.pod" => 1 ) } qw( );
# Convert these to a list of filenames.
++$our_pods{"$_.pod"} foreach keys %{$state{pods}};
my @inconsistent;
foreach my $i (sort keys %disk_pods) {
push @inconsistent, "$0: $i exists but is unknown by buildtoc\n"
- unless $our_pods{$i};
+ unless $our_pods{$i} || $ignoredpods{$i};
push @inconsistent, "$0: $i exists but is unknown by MANIFEST\n"
if !$BuildFiles{'MANIFEST'} # Ignore if we're rebuilding MANIFEST
&& !$manipods{$i} && !$manireadmes{$i} && !$state{copies}{$i}