NetBSD/gnu/dist/cvs/doc/mkman.pl

315 lines
6.9 KiB
Perl

#! @PERL@
#
# Generate a man page from sections of a Texinfo manual.
#
# Copyright 2004 The Free Software Foundation,
# Derek R. Price,
# & Ximbiot <http://ximbiot.com>
#
# 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Need Perl 5.005 or greater for re 'eval'.
require 5.005;
# The usual.
use strict;
use IO::File;
###
### GLOBALS
###
my $texi_num = 0; # Keep track of how many texinfo files have been encountered.
my @parent; # This needs to be global to be used inside of a regex later.
###
### FUNCTIONS
###
sub keyword_mode
{
my ($keyword, $file) = @_;
return "\\fR"
if $keyword =~ /^(|r|t)$/;
return "\\fB"
if $keyword =~ /^(strong|sc|code|file|samp)$/;
return "\\fI"
if $keyword =~ /^(emph|var|dfn)$/;
die "no handler for keyword \`$keyword', found at line $. of file \`$file'\n";
}
# Return replacement for \@$keyword{$content}.
sub do_keyword
{
my ($file, $parent, $keyword, $content) = @_;
return "see node \`$content\\(aq in the CVS manual"
if $keyword =~ /^(p?x)?ref$/;
return "\\fP\\fP$content"
if $keyword =~ /^splitrcskeyword$/;
my $endmode = keyword_mode $parent;
my $startmode = keyword_mode $keyword, $file;
return "$startmode$content$endmode";
}
###
### MAIN
###
for my $file (@ARGV)
{
my $fh = new IO::File "< $file"
or die "Failed to open file \`$file': $!";
if ($file !~ /\.(texinfo|texi|txi)$/)
{
print stderr "Passing \`$file' through unprocessed.\n";
# Just cat any file that doesn't look like a Texinfo source.
while (my $line = $fh->getline)
{
print $line;
}
next;
}
print stderr "Processing \`$file'.\n";
$texi_num++;
my $gotone = 0;
my $inblank = 0;
my $indent = 0;
my $inexample = 0;
my $inmenu = 0;
my $intable = 0;
my $last_header = "";
my @table_headers;
my @table_footers;
my $table_header = "";
my $table_footer = "";
my $last;
while ($_ = $fh->getline)
{
if (!$gotone && /^\@c ----- START MAN $texi_num -----$/)
{
$gotone = 1;
next;
}
# Skip ahead until our man section.
next unless $gotone;
# If we find the end tag we are done.
last if /^\@c ----- END MAN $texi_num -----$/;
# Need to do this everywhere. i.e., before we print example
# lines, since literal back slashes can appear there too.
s/\\/\\\\/g;
s/^\./\\&./;
s/([\s])\./$1\\&./;
s/'/\\(aq/g;
s/`/\\`/g;
s/(?<!-)---(?!-)/\\(em/g;
s/\@bullet({}|\b)/\\(bu/g;
s/\@dots({}|\b)/\\&.../g;
# Examples should be indented and otherwise untouched
if (/^\@example$/)
{
$indent += 2;
print qq{.SP\n.PD 0\n};
$inexample = 1;
next;
}
if ($inexample)
{
if (/^\@end example$/)
{
$indent -= 2;
print qq{\n.PD\n.IP "" $indent\n};
$inexample = 0;
next;
}
if (/^[ ]*$/)
{
print ".SP\n";
next;
}
# Preserve the newline.
$_ = qq{.IP "" $indent\n} . $_;
}
# Compress blank lines into a single line. This and its
# corresponding skip purposely bracket the @menu and comment
# removal so that blanks on either side of a menu are
# compressed after the menu is removed.
if (/^[ ]*$/)
{
$inblank = 1;
next;
}
# Not used
if (/^\@(ignore|menu)$/)
{
$inmenu++;
next;
}
# Delete menu contents.
if ($inmenu)
{
next unless /^\@end (ignore|menu)$/;
$inmenu--;
next;
}
# Remove comments
next if /^\@c(omment)?\b/;
# It's okay to ignore this keyword - we're not using any
# first-line indent commands at all.
next if s/^\@noindent\s*$//;
# @need is only significant in printed manuals.
next if s/^\@need\s+.*$//;
# If we didn't hit the previous check and $inblank is set, then
# we just finished with some number of blanks. Print the man
# page blank symbol before continuing processing of this line.
if ($inblank)
{
print ".SP\n";
$inblank = 0;
}
# Chapter headers.
$last_header = $1 if s/^\@node\s+(.*)$/.SH "$1"/;
if (/^\@appendix\w*\s+(.*)$/)
{
my $content = $1;
$content =~ s/^$last_header(\\\(em|\s+)?//;
next if $content =~ /^\s*$/;
s/^\@appendix\w*\s+.*$/.SS "$content"/;
}
# Tables are similar to examples, except we need to handle the
# keywords.
if (/^\@(itemize|table)(\s+(.*))?$/)
{
$indent += 2;
push @table_headers, $table_header;
push @table_footers, $table_footer;
my $content = $3;
if (/^\@itemize/)
{
my $bullet = $content;
$table_header = qq{.IP "$bullet" $indent\n};
$table_footer = "";
}
else
{
my $hi = $indent - 2;
$table_header = qq{.IP "" $hi\n};
$table_footer = qq{\n.IP "" $indent};
if ($content)
{
$table_header .= "$content\{";
$table_footer = "\}$table_footer";
}
}
$intable++;
next;
}
if ($intable)
{
if (/^\@end (itemize|table)$/)
{
$table_header = pop @table_headers;
$table_footer = pop @table_footers;
$indent -= 2;
$intable--;
next;
}
s/^\@itemx?(\s+(.*))?$/$table_header$2$table_footer/;
# Fall through so the rest of the table lines are
# processed normally.
}
# Index entries.
s/^\@cindex\s+(.*)$/.IX "$1"/;
$_ = "$last$_" if $last;
undef $last;
# Trap keywords
my $nk = qr/
\@(\w+)\{
(?{ push @parent, $1 }) # Keep track of the last keyword
# keyword we encountered.
((?:
(?> (?:[^{}]|(?<=\@)[{}])*) # Non-braces without backtracking
|
(??{ $nk }) # Nested keywords
)*)
\}
(?{ pop (@parent) }) # Lose track of the current keyword.
/x;
@parent = ("");
while (s/$nk/do_keyword $file, $parent[$#parent], $1, $2/e)
{
# Do nothing except replace our last-replacement
# tracker - the replacement regex above is handling
# everything else.
@parent = ("");
}
s/$nk/do_keyword $file, $parent[$#parent], $1, $2/ge;
if (/\@\w+\{/)
{
# If there is still an opening keyword left, we need to
# find the close bracket. Set $last to append the next
# line in the next pass.
$last = $_;
next;
}
# Finally, unprotect texinfo special characters.
s/\@://g;
s/\@([{}])/$1/g;
# Verify we haven't left commands unprocessed.
die "Unprocessed command at line $. of file \`$file': "
. ($1 ? "$1\n" : "<EOL>\n")
if /^(?>(?:[^\@]|\@\@)*)\@(\w+|.|$)/;
# Unprotect @@.
s/\@\@/\@/g;
# And print whatever's left.
print $_;
}
}