Replace podwrapper shell script with custom Perl script.

This uses Pod::Simple so it properly parses the input POD and can
generate proper custom output as required specifically by libguestfs.

One immediate benefit is that links between and within manual pages
now work mostly correctly.
This commit is contained in:
Richard W.M. Jones
2012-07-16 19:41:39 +01:00
parent 0bf1e5b665
commit 1e17a32060
5 changed files with 391 additions and 250 deletions

3
.gitignore vendored
View File

@@ -26,7 +26,6 @@ cscope.out
.libs
Makefile
Makefile.in
pod2htm?.tmp
/ABOUT-NLS
/aclocal.m4
@@ -301,7 +300,7 @@ pod2htm?.tmp
/po-docs/po4a.conf
/po-docs/*/*.pod
/po-docs/*/stamp-update-po
/podwrapper.sh
/podwrapper.pl
/po/*.gmo
/python/bindtests.py
/python/examples/guestfs-python.3

5
README
View File

@@ -90,8 +90,9 @@ For basic functionality and the C tools:
- systemtap/DTrace userspace probes (optional)
http://sourceware.org/systemtap/wiki/AddingUserSpaceProbingToApps
- perldoc (pod2man, pod2text, pod2html) to generate the manual pages
and other documentation.
- perl Pod::Man and Pod::Simple are required. These are used to
generate man pages and other documentation. Every recent Perl
distribution ought to include both.
- Readline to have nicer command-line editing in guestfish (optional)

View File

@@ -449,40 +449,22 @@ AC_CHECK_PROG([GPERF],[gperf],[gperf],[no])
test "x$GPERF" = "xno" &&
AC_MSG_ERROR([gperf must be installed])
dnl Check for pod2man, pod2text, pod2html.
AC_CHECK_PROG([POD2MAN],[pod2man],[pod2man],[no])
test "x$POD2MAN" = "xno" &&
AC_MSG_ERROR([pod2man must be installed])
AC_CHECK_PROG([POD2TEXT],[pod2text],[pod2text],[no])
test "x$POD2TEXT" = "xno" &&
AC_MSG_ERROR([pod2text must be installed])
AC_CHECK_PROG([POD2HTML],[pod2html],[pod2html],[no])
test "x$POD2HTML" = "xno" &&
AC_MSG_ERROR([pod2html must be installed])
dnl Check if pod2man, pod2text take --stderr and -u options (not in RHEL 5).
AC_MSG_CHECKING([if pod2man takes --stderr option])
if "$POD2MAN" --stderr >&AS_MESSAGE_LOG_FD 2>&1; then
AC_MSG_RESULT([yes])
POD2_STDERR_OPTION="--stderr"
dnl Check for Pod::Man, Pod::Simple.
AC_MSG_CHECKING([for Pod::Man])
if ! perl -MPod::Man -e1 >&AS_MESSAGE_LOG_FD 2>&1; then
AC_MSG_ERROR([perl Pod::Man must be installed])
else
AC_MSG_RESULT([yes])
fi
AC_MSG_CHECKING([for Pod::Simple])
if ! perl -MPod::Simple -e1 >&AS_MESSAGE_LOG_FD 2>&1; then
AC_MSG_ERROR([perl Pod::Simple must be installed])
else
AC_MSG_RESULT([no])
POD2_STDERR_OPTION=""
fi
AC_SUBST([POD2_STDERR_OPTION])
AC_MSG_CHECKING([if pod2man takes -u option])
if "$POD2MAN" -u >&AS_MESSAGE_LOG_FD 2>&1; then
AC_MSG_RESULT([yes])
POD2_UTF8_OPTION="-u"
else
AC_MSG_RESULT([no])
POD2_UTF8_OPTION=""
fi
AC_SUBST([POD2_UTF8_OPTION])
dnl Define the path to the podwrapper program.
PODWRAPPER="$(pwd)/podwrapper.sh"
PODWRAPPER="$(pwd)/podwrapper.pl"
AC_SUBST([PODWRAPPER])
dnl Check for genisoimage/mkisofs
@@ -1309,8 +1291,8 @@ AC_CONFIG_HEADERS([config.h])
dnl http://www.mail-archive.com/automake@gnu.org/msg10204.html
AC_CONFIG_FILES([appliance/libguestfs-make-fixed-appliance],
[chmod +x,-w appliance/libguestfs-make-fixed-appliance])
AC_CONFIG_FILES([podwrapper.sh],
[chmod +x,-w podwrapper.sh])
AC_CONFIG_FILES([podwrapper.pl],
[chmod +x,-w podwrapper.pl])
AC_CONFIG_FILES([run],
[chmod +x,-w run])
AC_CONFIG_FILES([Makefile

374
podwrapper.pl.in Executable file
View File

@@ -0,0 +1,374 @@
#!/usr/bin/perl -w
# podwrapper.pl
# Copyright (C) 2010-2012 Red Hat Inc.
# @configure_input@
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use warnings;
use strict;
use Pod::Usage;
use Getopt::Long;
use Pod::Man;
use Pod::Simple;
use Pod::Simple::Text;
use Pod::Simple::XHTML;
use File::Basename;
=encoding utf8
=head1 NAME
podwrapper.pl - Generate various output formats from POD input files
=head1 SYNOPSIS
virt-foo.1 $(top_builddir)/html/virt-foo.1.html: stamp-virt-foo.pod
stamp-virt-foo.pod: virt-foo.pod
$(PODWRAPPER) --man virt-foo.1 --html virt-foo.1.html $<
touch $@
=head1 DESCRIPTION
podwrapper is a Perl script that generates various output formats
from POD input files that libguestfs uses for most documentation.
You should specify an input file, and one or more output formats.
For example:
podwrapper.pl virt-foo.pod --man virt-foo.1
will turn C<virt-foo.pod> into a man page C<virt-foo.1>. The output
options are I<--man>, I<--html> and I<--text> (see below).
=head1 OPTIONS
=over 4
=cut
my $help;
=item B<--help>
Display brief help.
=cut
my $html;
=item B<--html=output.html>
Write a web page to C<output.html>. If this option is not
given, then no web page output is produced.
=cut
my @inserts;
=item B<--insert=filename:@PATTERN@>
In the input file, replace the literal text C<@PATTERN@> with the
replacement file C<filename>. You can give this option multiple
times.
The contents of C<filename> are treated as POD.
Compare and contrast with I<--verbatim>.
Although it is conventional to use C<@...@> for patterns, in fact
you can use any string as the pattern.
=cut
my $man;
=item B<--man=output.n>
Write a man page to C<output.n>. If this option is not
given, then no man page output is produced.
=cut
my $name;
=item B<--name=name>
Set the name of the man page. If not set, defaults to the basename
of the input file.
=cut
my $section;
=item B<--section=N>
Set the section of the man page (a number such as C<1> for
command line utilities or C<3> for C API documentation). If
not set, defaults to C<1>.
=cut
my $text;
=item B<--text=output.txt>
Write a text file to C<output.txt>. If this option is not
given, then no text output is produced.
=cut
my @verbatims;
=item B<--verbatim=filename:@PATTERN@>
In the input file, replace the literal text C<@PATTERN@> with the
replacement file C<filename>. You can give this option multiple
times.
The contents of C<filename> are inserted as verbatim text, and
are I<not> interpreted as POD.
Compare and contrast with I<--insert>.
Although it is conventional to use C<@...@> for patterns, in fact
you can use any string as the pattern.
=cut
=back
=cut
GetOptions ("help|?" => \$help,
"html=s" => \$html,
"insert=s" => \@inserts,
"man=s" => \$man,
"name=s" => \$name,
"section=s" => \$section,
"text=s" => \$text,
"verbatim=s" => \@verbatims
) or pod2usage (2);
pod2usage (1) if $help;
die "missing argument: podwrapper input.pod\n" unless @ARGV == 1;
my $input = $ARGV[0];
# There should be at least one output.
die "$0: no output format specified. Use --man and/or --html and/or --text.\n"
unless defined $man || defined $html || defined $text;
# Default for $name and $section.
$name = basename ($input, ".pod") unless defined $name;
$section = 1 unless defined $section;
# Note that these @...@ are substituted by ./configure.
my $abs_top_srcdir = "@abs_top_srcdir@";
my $abs_top_builddir = "@abs_top_builddir@";
my $package_name = "@PACKAGE_NAME@";
my $package_version = "@PACKAGE_VERSION@";
die "$0: ./configure substitutions were not performed"
unless $abs_top_srcdir && $abs_top_builddir &&
$package_name && $package_version;
# Create a stable date (thanks Hilko Bengen).
my $date;
my $filename = "$abs_top_srcdir/ChangeLog";
if (-r $filename) {
open FILE, $filename or die "$filename: $!";
$_ = <FILE>;
close FILE;
$date = $1 if /^(\d+-\d+-\d+)\s/;
}
$filename = "$abs_top_srcdir/.git";
if (!$date && -d $filename) {
$_ = `git show --git-dir=$filename -s --format=%ci`;
$date = $1 if /^(\d+-\d+-\d+)\s/;
}
if (!$date) {
my ($day, $month, $year) = (localtime)[3,4,5];
$date = sprintf ("%04d-%02d-%02d", $year+1900, $month+1, $day);
}
# Create a release string.
my $release = "$package_name-$package_version";
#print "input=$input\n";
#print "name=$name\n";
#print "section=$section\n";
#print "date=$date\n";
# Read the input.
my $content = read_whole_file ($input);
# Perform @inserts.
foreach (@inserts) {
my @a = split /:/, $_, 2;
die "$0: no colon in parameter of --insert\n" unless @a >= 2;
my $replacement = read_whole_file ($a[0]);
$content =~ s/$a[1]/$replacement/ge;
}
# Perform @verbatims.
foreach (@verbatims) {
my @a = split /:/, $_, 2;
die "$0: no colon in parameter of --verbatim\n" unless @a >= 2;
my $replacement = read_verbatim_file ($a[0]);
$content =~ s/$a[1]/$replacement/ge;
}
# Output man page.
if ($man) {
my $parser = Pod::Man->new (name => $name,
release => $release, section => $section,
center => "Virtualization Support",
date => $date);
my $output;
$parser->output_string (\$output);
$parser->parse_string_document ($content)
or die "$0: could not parse input document";
open OUT, ">$man" or die "$man: $!";
print OUT $output or die "$man: $!";
close OUT or die "$man: $!";
print "$0: wrote $man\n";
}
# Output HTML.
SUBCLASS: {
# Subclass Pod::Simple::XHTML. See the documentation.
package Podwrapper::XHTML;
use vars qw(@ISA);
@ISA = qw(Pod::Simple::XHTML);
# Pod::Simple::XHTML returns uppercase identifiers, whereas the
# old pod2html returns lowercase ones.
sub idify
{
my $self = shift;
my $id = $self->SUPER::idify (@_);
lc ($id);
}
sub is_a_libguestfs_page
{
local $_ = shift;
return 1 if /^Sys::Guestfs/;
return 1 if /^virt-/;
return 1 if /^libguestf/;
return 1 if /^guestf/;
return 1 if /^guestmount/;
return 1 if /^hivex/;
return 1 if /^febootstrap/;
return 0;
}
sub resolve_pod_page_link
{
my $self = shift;
my $podname = $_[0]; # eg. "Sys::Guestfs", can be undef
my $anchor = $_[1]; # eg. "SYNOPSIS", can be undef
my $r = "";
if (defined $podname) {
return $self->SUPER::resolve_pod_page_link (@_)
unless is_a_libguestfs_page ($podname);
$r .= "$podname.3.html"
}
$r .= "#" . $self->idify ($anchor, 1) if defined $anchor;
$r;
}
sub resolve_man_page_link
{
my $self = shift;
my $name = $_[0]; # eg. "virt-make-fs(1)", can be undef
my $anchor = $_[1]; # eg. "SYNOPSIS", can be undef
my $r = "";
if (defined $name) {
return $self->SUPER::resolve_man_page_link (@_)
unless is_a_libguestfs_page ($name);
$name =~ s/\((.*)\)$/.$1/;
$r .= "$name.html";
}
$r .= "#" . $self->idify ($anchor, 1) if defined $anchor;
$r;
}
}
if ($html) {
mkdir "$abs_top_builddir/html";
my $parser = Podwrapper::XHTML->new;
my $output;
$parser->output_string (\$output);
$parser->html_charset ("UTF-8");
$parser->html_css ("pod.css");
$parser->index (1);
$parser->parse_string_document ($content);
# Hack for Perl 5.16.
$output =~ s{/>pod.css<}{/>\n<};
open OUT, ">$html" or die "$html: $!";
print OUT $output or die "$html: $!";
close OUT or die "$html: $!";
print "$0: wrote $html\n";
}
# Output text.
if ($text) {
my $parser = Pod::Simple::Text->new;
my $output;
$parser->output_string (\$output);
$parser->parse_string_document ($content);
open OUT, ">$text" or die "$text: $!";
print OUT $output or die "$text: $!";
close OUT or die "$text: $!";
print "$0: wrote $text\n";
}
sub read_whole_file
{
my $input = shift;
local $/ = undef;
open FILE, $input or die "$input: $!";
$_ = <FILE>;
close FILE;
$_;
}
sub read_verbatim_file
{
my $input = shift;
my $r = "";
open FILE, $input or die "$input: $!";
while (<FILE>) {
$r .= " $_";
}
close FILE;
$r;
}
=head1 AUTHOR
Richard W.M. Jones.
=head1 SEE ALSO
libguestfs.git/README,
L<Pod::Simple>

View File

@@ -1,215 +0,0 @@
#!/bin/bash -
# podwrapper.sh
# Copyright (C) 2010-2012 Red Hat Inc.
# @configure_input@
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# Wrapper script around POD utilities which can include files in the
# POD and controls HTML generation.
unset CDPATH
set -e
#set -x
PACKAGE_NAME="@PACKAGE_NAME@"
PACKAGE_VERSION="@PACKAGE_VERSION@"
POD2MAN="@POD2MAN@"
POD2TEXT="@POD2TEXT@"
POD2HTML="@POD2HTML@"
POD2_STDERR_OPTION="@POD2_STDERR_OPTION@"
POD2_UTF8_OPTION="@POD2_UTF8_OPTION@"
# This script could be run with any current directory, so if a source
# or build path is required it must be relative to the following
# absolute paths:
abs_top_srcdir="@abs_top_srcdir@"
abs_top_builddir="@abs_top_builddir@"
if [ -z "$abs_top_srcdir" ]; then
echo "*** podwrapper.sh: error: abs_top_srcdir not defined"
echo "probably this is a very old version of autoconf and you need to"
echo "upgrade to a recent version"
exit 1
fi
if [ -z "$abs_top_builddir" ]; then
echo "*** podwrapper.sh: error: abs_top_builddir not defined"
echo "probably this is a very old version of autoconf and you need to"
echo "upgrade to a recent version"
exit 1
fi
if [ -e $abs_top_srcdir/ChangeLog ]; then
DATEPARAM=`awk '/^[0-9]+-[0-9]+-[0-9]+/ { print "--date=" $1; exit }' \
$abs_top_srcdir/ChangeLog`
elif [ -d $abs_top_srcdir/.git ]; then
DATEPARAM=`git show -s --format=%ci | awk '{print "--date=" $1}'`
fi
declare -a inserts
declare -a pattern
declare -a indent
nr_inserts=0
TEMP=`getopt \
-o '' \
--long section:,name:,man:,text:,html:,insert:,verbatim: \
-n podwrapper.sh -- "$@"`
if [ $? != 0 ]; then
echo "podwrapper.sh: problem parsing the command line arguments"
exit 1
fi
eval set -- "$TEMP"
while true; do
case "$1" in
--section)
section="$2"
shift 2;;
--name)
name="$2"
shift 2;;
--man)
[ -z "$man_output" ] || {
echo "podwrapper.sh: --text option specified more than once"
exit 1
}
man_output="$2"
shift 2;;
--text)
[ -z "$text_output" ] || {
echo "podwrapper.sh: --text option specified more than once"
exit 1
}
text_output="$2"
shift 2;;
--html)
[ -z "$html_output" ] || {
echo "podwrapper.sh: --html option specified more than once"
exit 1
}
html_output="$2"
shift 2;;
--insert)
inserts[$nr_inserts]=`echo "$2" | awk -F: '{print $1}'`
pattern[$nr_inserts]=`echo "$2" | awk -F: '{print $2}'`
indent[$nr_inserts]=no
((++nr_inserts))
shift 2;;
--verbatim)
inserts[$nr_inserts]=`echo "$2" | awk -F: '{print $1}'`
pattern[$nr_inserts]=`echo "$2" | awk -F: '{print $2}'`
indent[$nr_inserts]=yes
((++nr_inserts))
shift 2;;
--)
shift; break;;
*)
echo "podwrapper.sh: internal error in option parsing"
exit 1;;
esac
done
# The remaining argument is the input POD file.
if [ $# -ne 1 ]; then
echo "podwrapper.sh [--options] input.pod"
exit 1
fi
input="$1"
#echo "input=$input"
#echo "man_output=$man_output"
#echo "text_output=$text_output"
#echo "html_output=$html_output"
#for i in `seq 0 $(($nr_inserts-1))`; do
# echo "insert $i: ${inserts[$i]} (pattern: ${pattern[$i]} indent: ${indent[$i]})"
#done
# Should be at least one sort of output.
[ -z "$man_output" -a -z "$text_output" -a -z "$html_output" ] && {
echo "podwrapper.sh: no output specified"
exit 1
}
# If name and section are not set, make some sensible defaults.
[ -z "$section" ] && section=1
[ -z "$name" ] && name=$(basename "$input" .pod)
# Perform the insertions to produce a temporary POD file.
tmpdir="$(mktemp -d)"
trap "rm -rf $tmpdir; exit $?" EXIT
if [ $nr_inserts -gt 0 ]; then
cmd="sed"
for i in `seq 0 $(($nr_inserts-1))`; do
if [ "${indent[$i]}" = "yes" ]; then
sed 's/^/ /' < "${inserts[$i]}" > $tmpdir/$i
else
cp "${inserts[$i]}" $tmpdir/$i
fi
cmd="$cmd -e /${pattern[$i]}/r$tmpdir/$i -e s/${pattern[$i]}//"
done
$cmd < "$input" > $tmpdir/full.pod
else
cp "$input" $tmpdir/full.pod
fi
# Now generate the final output format(s).
if [ -n "$man_output" ]; then
"$POD2MAN" "$POD2_STDERR_OPTION" "$POD2_UTF8_OPTION" \
$DATEPARAM \
--section "$section" -c "Virtualization Support" --name "$name" \
--release "$PACKAGE_NAME-$PACKAGE_VERSION" \
< $tmpdir/full.pod > "$man_output".tmp
mv "$man_output".tmp "$man_output"
fi
if [ -n "$text_output" ]; then
"$POD2TEXT" "$POD2_STDERR_OPTION" "$POD2_UTF8_OPTION" \
< $tmpdir/full.pod > "$text_output".tmp
mv "$text_output".tmp "$text_output"
fi
if [ -n "$html_output" ]; then
mkdir -p "$abs_top_builddir/html"
"$POD2HTML" \
--css "pod.css" --htmldir "$abs_top_builddir/html" \
< $tmpdir/full.pod > "$html_output".tmp
mv "$html_output".tmp "$html_output"
# Fix up some of the mess in the HTML output, mainly to make links
# between man pages work properly.
# Rewrite <em>manpage(n)</em> to <a href=...>manpage(n)</a> if
# there is a linkable manual page.
sed_cmd="sed"
for f in $(cd "$abs_top_builddir/html" && ls -1 *.html); do
b=$(basename $f .html)
m=$(echo $b | sed 's/\(.*\)\.\([1-9]\)$/\1(\2)/')
sed_cmd="$sed_cmd -e 's,<em>$m</em>,<a href=$f>$m</a>,g'"
done
echo $sed_cmd
eval $sed_cmd < "$html_output" > "$html_output".tmp
mv "$html_output".tmp "$html_output"
# Fix links like L<guestfs-foo(3)>
sed 's,<a href="#\([a-z]\+\)">guestfs-\1(\([1-9]\)),<a href="guestfs-\1.\2.html">guestfs-\1(\2),g' < "$html_output" > "$html_output".tmp
mv "$html_output".tmp "$html_output"
fi