tcsh.man2html revision 354195
1: # -*- perl -*-
2
3# tcsh.man2html, Dave Schweisguth <dcs@proton.chem.yale.edu>
4#
5# Notes:
6#
7# Always puts all files in the directory tcsh.html, creating it if necessary.
8# tcsh.html/top.html is the entry point, and tcsh.html/index.html is a symlink
9# to tcsh.html/top.html so one needn't specify a file at all if working through
10# a typically configured server.
11#
12# Designed for tcsh manpage. Guaranteed not to work on manpages not written
13# in the exact same style of nroff -man, i.e. any other manpage.
14#
15# Makes links FROM items which are both a) in particular sections (see
16# Configuration) and b) marked with .B or .I. Makes links TO items which
17# are marked with \fB ... \fR or \fI ... \fR.
18#
19# Designed with X Mosaic in mind and tested lightly with lynx. I've punted on
20# HTML's lack of a .PD equivalent and lynx's different <menu> handling.
21
22# Emulate #!/usr/local/bin/perl on systems without #!
23
24eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
25& eval 'exec perl -S $0 $argv:q' if 0;
26
27### Constants
28
29# Setup
30
31($whatami = $0)	=~ s|.*/||;	# `basename $0`
32$isatty		= -t STDIN;
33
34# Configuration
35
36$index		= 0;		# Don't make a searchable index CGI script
37$cgibin		= 0;		# Look for $cgifile in $dir, not $cgibindir
38$shortfiles	= 0;		# Use long filenames
39$single		= 0;		# Make single page instead of top and sections
40
41$host		= '';		# host:port part of server URL ***
42$updir		= '';		# Directories between $host and $dir ***
43$dir		= 'tcsh';	# Directory in which to put the pieces *
44$cgifile	= 'tcsh.cgi';	# CGI script name **
45$cgibindir	= 'cgi-bin';	# CGI directory ***
46$headerfile	= 'header';	# HTML file for initial comments *
47$indexfile	= 'index';	# Symlink to $topfile *
48$listsfile	= 'lists';	# Mailing list description HTML file *	
49$outfile	= 'tcsh.man';	# Default input file and copy of input file
50$script		= $whatami;	# Copy of script; filename length must be OK
51$topfile	= 'top';	# Top-level HTML file *
52
53# *   .htm or .html suffix added later
54# **  Only used with -i or -c
55# *** Only used with -c
56
57# Sections to inline in the top page
58
59%inline_me	= ('NAME',	1,
60		   'SYNOPSIS',	1);
61
62# Sections in which to put name anchors and the font in which to look for
63# links to those anchors
64
65%link_me	= ('Editor commands',		'I',
66		   'Builtin commands',		'I',
67		   'Special aliases',		'I',
68		   'Special shell variables',	'B',
69		   'ENVIRONMENT',		'B',
70		   'FILES',			'I');
71
72### Arguments and error-checking
73
74# Parse args
75
76while ($#ARGV > -1 && (($first, $rest) = ($ARGV[0] =~ /^-(.)(.*)/))) {
77    # Perl 5 lossage alert
78    if ($first =~ /[CdDGh]/) {	# Switches with arguments
79    	shift;
80    	$arg = $rest ne '' ? $rest : $ARGV[0] ne '' ? shift :
81      	    &usage("$whatami: -$first requires an argument.\n");
82    } elsif ($rest ne '') {
83    	$ARGV[0] = "-$rest";
84    } else {
85	shift;
86    }
87    if	  ($first eq '1')   { $single = 1; }
88    elsif ($first eq 'c')   { $cgibin = 1; }
89    elsif ($first eq 'C')   { $cgibindir = $arg; }
90    elsif ($first eq 'd')   { $updir = $arg; }
91    elsif ($first eq 'D')   { $dir = $arg; }
92    elsif ($first eq 'G')   { $cgifile = $arg; }
93    elsif ($first eq 'h')   { $host = $arg; }
94    elsif ($first eq 'i')   { $index = 1; }
95    elsif ($first eq 's')   { $shortfiles = 1; }
96    elsif ($first eq 'u')   { &usage(0); }
97    else		    { &usage("$whatami: -$first is not an option.\n"); }
98}
99
100if (@ARGV == 0) {
101    if ($isatty) {
102        $infile = $outfile;		# Default input file if interactive
103    } else {
104	$infile = 'STDIN';		# Read STDIN if no args and not a tty
105    }
106} elsif (@ARGV == 1) {
107    $infile = $ARGV[0];
108} else {
109    &usage("$whatami: Please specify one and only one file.\n");
110}
111
112$index = $index || $cgibin;		# $index is true if $cgibin is true
113
114if ($cgibin && ! $host) {
115    die "$whatami: Must specify host with -h if using -c.\n";
116}
117
118# Decide on HTML suffix and append it to filenames
119
120$html = $shortfiles ? 'htm' : 'html';	# Max 3-character extension
121$dir		.= ".$html";		# Directory in which to put the pieces
122$headerfile	.= ".$html";		# HTML file for initial comments
123$topfile	.= ".$html";		# Top-level HTML file (or moved notice)
124$indexfile	.= ".$html";		# Symlink to $topfile
125$listsfile	.= ".$html";		# Mailing list description HTML file
126
127# Check for input file
128
129unless ($infile eq 'STDIN') {
130    die "$whatami: $infile doesn't exist!\n"	unless -e $infile;
131    die "$whatami: $infile is unreadable!\n"	unless -r _;
132    die "$whatami: $infile is empty!\n"		unless -s _;
133}
134
135# Check for output directory and create if necessary
136
137if (-e $dir) {
138    -d _ || die "$whatami: $dir is not a directory!\n";
139    -r _ && -w _ && -x _ || die "$whatami: $dir is inaccessible!\n"
140} else {
141    mkdir($dir, 0755) || die "$whatami: Can't create $dir!\n";
142}
143
144# Slurp manpage
145
146if ($infile eq 'STDIN') {
147    @man = <STDIN>;
148} else {
149    open(MAN, $infile) || die "$whatami: Error opening $infile!\n";
150    @man = <MAN>;
151    close MAN;
152}
153
154# Print manpage to HTML directory (can't use cp if we're reading from STDIN)
155
156open(MAN, ">$dir/$outfile") || die "$whatami: Can't open $dir/$outfile!\n";
157print MAN @man;
158close MAN;
159
160# Copy script to HTML directory
161
162(system("cp $0 $dir") >> 8) && die "$whatami: Can't copy $0 to $dir!\n";
163
164# Link top.html to index.html in case someone looks at tcsh.html/
165
166system("rm -f $dir/$indexfile");    # Some systems can't ln -sf
167(system("ln -s $topfile $dir/$indexfile") >> 8)
168    && die "$whatami: Can't link $topfile to $dir/$indexfile!\n";
169
170### Get title and section headings
171
172$comment = 0;			    # 0 for text, 1 for ignored text
173@sectionlines = (0);		    # First line of section
174@sectiontypes = (0);		    # H or S
175@sectiontexts = ('Header');	    # Text of section heading
176@sectionfiles = ($headerfile);	    # Filename in which to store section
177%name = ();			    # Array of name anchors
178@name = () if $index;		    # Ordered array of name anchors
179$font = '';		    	    # '' to not make names, 'B' or 'I' to do so
180
181$line = 0;
182foreach (@man) {
183    if (/^\.ig/) {		    # Start ignoring
184	$comment = 1;
185    } elsif (/^\.\./) {		    # Stop ignoring
186	$comment = 0;
187    } elsif (! $comment) {	    # Not in .ig'ed section; do stuff
188	
189	# nroff special characters
190	
191	s/\\-/-/g;		    # \-
192	s/\\^//g;		    # \^
193	s/^\\'/'/;		    # leading ' escape
194	s/^\\(\s)/$1/;		    # leading space escape
195	s/\\(e|\\)/\\/g;	    # \e, \\; must do this after other escapes
196
197	# HTML special characters; deal with these before adding more
198	
199	s/&/&amp\;/g;
200	s/>/&gt\;/g;
201	s/</&lt\;/g;
202	
203	# Get title
204	
205	if (/^\.TH\s+(\w+)\s+(\w+)\s+\"([^\"]*)\"\s+\"([^\"]*)\"/) {
206	    $title = "$1($2) $4 ($3) $1($2)";
207	}
208	
209	# Build per-section info arrays
210	
211	if (($type, $text) = /^\.S([HS])\s+\"?([^\"]*)\"?/) {
212
213	    push(@sectionlines, $line);	    # Index of first line of section
214	    push(@sectiontypes, $type eq 'H' ? 0 : 1);	# Type of section
215	    $text =~ s/\s*$//;		    # Remove trailing whitespace
216	    push(@sectiontexts, $text);	    # Title of section (key for href)
217	    $text =~ s/\s*\(\+\)$//;	    # Remove (+)
218	    if ($shortfiles) {
219		$file = $#sectionlines;	    # Short filenames; use number
220	    } else {
221		$file = $text;		    # Long filenames; use title
222		$file =~ s/[\s\/]+/_/g;	    # Replace whitespace and / with _
223	    }
224	    $file .= ".$html" unless $single;
225	    push(@sectionfiles, $file);	    # File in which to store section
226	    $name{"$text B"} = ($single ? '#' : '') . $file;
227					    # Index entry for &make_hrefs
228	    push(@name, "$text\t" . $name{"$text B"}) if $index;
229					    # Index entry for CGI script
230	    # Look for anchors in the rest of this section if $link_me{$text}
231	    # is non-null, and mark them with the font which is its value
232
233	    $font = $link_me{$text};
234    	}
235	&make_name(*name, *font, *file, *index, *_) if $font;
236    }
237    $line++;
238}
239
240### Make top page
241
242open(TOP, ">$dir/$topfile");
243select TOP;
244
245# Top page header
246
247print <<EOP;
248<HEAD>
249<TITLE>$title</TITLE>
250</HEAD>
251<BODY>
252<A NAME="top"></A>
253<H1>$title</H1>
254<HR>
255EOP
256
257# FORM block, if we're making an index
258
259$action = $cgibin ? "http://$host/$cgibindir/$cgifile" : $cgifile;
260
261print <<EOP if $index;
262<FORM METHOD="GET" ACTION="$action">
263Go directly to a section, command or variable: <INPUT NAME="input">
264</FORM>
265EOP
266
267# Table of contents
268
269print <<EOP;
270<H2>
271EOP
272
273foreach $section (1 .. $#sectionlines) {
274    if ($sectiontypes[$section - 1] < $sectiontypes[$section]) {
275	print "</H2> <menu>\n";	    # Indent, smaller font
276    } elsif ($sectiontypes[$section - 1] > $sectiontypes[$section]) {
277	print "</menu> <H2>\n";	    # Outdent, larger font
278    }
279    if ($inline_me{$sectiontexts[$section]}) {    # Section is in %inline_me
280	
281	# Print section inline
282	
283	print "$sectiontexts[$section]\n";
284	print "</H2> <menu>\n";	    # Indent, smaller font
285	&printsectionbody(*man, *sectionlines, *section, *name);
286	print "</menu> <H2>\n";	    # Outdent, larger font
287    } else {
288	
289	# Print link to section
290	
291	print "<A HREF=\"", $single ? '#' : '',
292	    "$sectionfiles[$section]\">$sectiontexts[$section]</A><BR>\n";
293    }
294}
295
296print <<EOP;
297</H2>
298EOP
299
300print "<HR>\n" if $single;
301
302### Make sections
303
304foreach $section (0 .. $#sectionlines) {
305
306    # Skip inlined sections
307
308    next if $inline_me{$sectiontexts[$section]};
309    
310    if ($single) {
311
312	# Header
313    
314	print <<EOP if $section;	# Skip header section
315<H2><A NAME="$sectionfiles[$section]">$sectiontexts[$section]</A></H2>
316<menu>
317EOP
318	&printsectionbody(*man, *sectionlines, *section, *name);
319	print <<EOP if $section;	# Skip header section
320<A HREF="#top">Table of Contents</A>
321</menu>
322EOP
323
324    } else {
325
326	# Make pointer line for header and trailer
327	
328	$pointers  = "<A HREF=\"$topfile\">Up</A>";
329	$pointers .= "\n<A HREF=\"$sectionfiles[$section + 1]\">Next</A>"
330	    if ($section < $#sectionlines) &&
331	    ! $inline_me{$sectiontexts[$section + 1]};
332	$pointers .= "\n<A HREF=\"$sectionfiles[$section - 1]\">Previous</A>"
333	    if ($section > 1) &&		# section 0 is initial comments
334	    ! $inline_me{$sectiontexts[$section - 1]};
335    
336	# Header
337
338	open(OUT, ">$dir/$sectionfiles[$section]");
339	select OUT;
340	print <<EOP;
341<HEAD>
342<TITLE>$sectiontexts[$section]</TITLE>
343</HEAD>
344<BODY>
345$pointers
346<H2>$sectiontexts[$section]</H2>
347EOP
348	&printsectionbody(*man, *sectionlines, *section, *name);
349
350	# Trailer
351
352	print <<EOP;
353$pointers
354</BODY>
355EOP
356
357    }
358}
359
360select TOP unless $single;
361
362# Top page trailer
363
364print <<EOP;
365</H2>
366<HR>
367Here are the <A HREF="$outfile">nroff manpage</A> (175K)
368from which this HTML version was generated,
369the <A HREF="$script">Perl script</A> which did the conversion
370and the <A HREF="ftp://ftp.astron.com/pub/tcsh/">
371complete source code</A> for <I>tcsh</I>.
372<HR>
373<I>tcsh</I> is maintained by
374Christos Zoulas <A HREF="mailto:christos\@astron.com">&lt;christos\@astron.com&gt;</A>
375and the <A HREF="$listsfile"><I>tcsh</I> maintainers' mailing list</A>.
376Dave Schweisguth <A HREF="mailto:dcs\@proton.chem.yale.edu">&lt;dcs\@proton.chem.yale.edu&gt;</A>
377wrote the manpage and the HTML conversion script.
378</BODY>
379EOP
380
381close TOP;
382
383### Make lists page
384
385open(LISTS, ">$dir/$listsfile");
386select LISTS;
387while(($_ = <DATA>) ne "END\n") {   # Text stored after __END__
388    s/TOPFILEHERE/$topfile/;
389    print;
390}
391close LISTS;
392
393### Make search script
394
395if ($index) {
396
397    # URL of $dir; see comments in search script
398
399    $root = $cgibin
400	? "'http://$host/" . ($updir ? "$updir/" : '') . "$dir/'"
401	: '"http://$ENV{\'SERVER_NAME\'}:$ENV{\'SERVER_PORT\'}" . (($_ = $ENV{\'SCRIPT_NAME\'}) =~ s|[^/]*$||, $_)';
402
403    # String for passing @name to search script
404
405    $name = join("',\n'", @name);
406
407    open(TOP, ">$dir/$cgifile");
408    select TOP;
409    while(($_ = <DATA>) ne "END\n") {   # Text stored after __END__
410	s/ROOTHERE/$root/;
411	s/NAMEHERE/$name/;
412	s/TOPFILEHERE/$topfile/;
413	print;
414    }
415    close TOP;
416    chmod(0755, "$dir/$cgifile") ||
417	die "$whatami: Can't chmod 0755 $dir/$cgifile!\n";
418    warn "$whatami: Don't forget to move $dir/$cgifile to /$cgibindir.\n"
419	if $cgibin;
420}
421
422### That's all, folks
423
424exit;
425
426### Subroutines
427
428# Process and print the body of a section
429
430sub printsectionbody {
431
432    local(*man, *sectionlines, *sline, *name) = @_;	# Number of section
433    local($sfirst, $slast, @paralines, @paratypes, $comment, $dl, $pline,
434	  $comment, $pfirst, $plast, @para, @tag, $changeindent);
435
436    # Define section boundaries
437
438    $sfirst = $sectionlines[$sline] + 1;
439    if ($sline == $#sectionlines) {
440	$slast = $#man;
441    } else {
442	$slast = $sectionlines[$sline + 1] - 1;
443    }
444
445    # Find paragraph markers, ignoring those between '.ig' and '..'
446
447    if ($man[$sfirst] =~ /^\.[PIT]P/) {
448	@paralines = ();
449	@paratypes = ();
450    } else {
451	@paralines = ($sfirst - 1);		# .P follows .S[HS] by default
452	@paratypes = ('P');
453    }
454    $comment = 0;
455    foreach ($sfirst .. $slast) {
456	if ($man[$_] =~ /^\.ig/) {		# Start ignoring
457	    $comment = 1;
458	} elsif ($man[$_] =~ /^\.\./) {		# Stop ignoring
459	    $comment = 0;
460	} elsif (! $comment && $man[$_] =~ /^\.([PIT])P/) {
461	    push(@paralines, $_);
462	    push(@paratypes, $1);
463	}
464    }
465
466    # Process paragraphs
467
468    $changeindent = 0;
469    $dl = 0;
470    foreach $pline (0 .. $#paralines) {
471
472	@para = ();
473	$comment = 0;
474
475	# Define para boundaries
476
477	$pfirst = $paralines[$pline] + 1;
478	if ($pline == $#paralines) {
479	    $plast = $slast;
480	} else {
481	    $plast = $paralines[$pline + 1] - 1;
482	}
483
484	foreach (@man[$pfirst .. $plast]) {
485	    if (/^\.ig/) {		    # nroff begin ignore
486		if ($comment == 0) {
487		    $comment = 2;
488		    push(@para, "<!--\n");
489		} elsif ($comment == 1) {
490		    $comment = 2;
491		} elsif ($comment == 2) {
492		    s/--/-/g;		    # Remove double-dashes in comments
493		    push(@para, $_);
494		}
495	    } elsif (/^\.\./) {		    # nroff end ignore
496		if ($comment == 0) {
497		    ;
498		} elsif ($comment == 1) {
499		    ;
500		} elsif ($comment == 2) {
501		    $comment = 1;
502		}
503	    } elsif (/^\.\\\"/) {	    # nroff comment
504		if ($comment == 0) {
505		    $comment = 1;
506		    push(@para, "<!--\n");
507		    s/^\.\\\"//;
508		} elsif ($comment == 1) {
509		    s/^\.\\\"//;
510		} elsif ($comment == 2) {
511		    ;
512		}
513		s/--/-/g;		    # Remove double-dashes in comments
514		push(@para, $_);
515	    } else {			    # Nothing to do with comments
516		if ($comment == 0) {
517		    ;
518    		} elsif ($comment == 1) {
519		    $comment = 0;
520		    push(@para, "-->\n");
521		} elsif ($comment == 2) {
522		    s/--/-/g;		    # Remove double-dashes in comments
523		}
524
525		unless ($comment) {
526		
527		    if (/^\.TH/) {	    # Title; got this already
528			next;
529		    } elsif (/^\.PD/) {	    # Para spacing; unimplemented
530			next;
531		    } elsif (/^\.RS/) {	    # Indent (one width only)
532			$changeindent++;
533			next;
534		    } elsif (/^\.RE/) {	    # Outdent
535			$changeindent--;
536			next;
537		    }
538
539		    # Line break
540		    s/^\.br.*/<BR>/;
541
542		    # More nroff special characters
543
544		    s/^\\&amp\;//;	    # leading dot escape; save until
545					    #   now so leading dots aren't
546					    #   confused with ends of .igs
547
548		    &make_hrefs(*name, *_);			
549		}
550		push(@para, $_);
551	    }
552	}
553	
554	push(@para, "-->\n") if $comment;   # Close open comment
555	
556    	# Print paragraph
557
558	if ($paratypes[$pline] eq 'P') {
559	    &font(*para);
560	    print   @para;
561	} elsif ($paratypes[$pline] eq 'I') {
562	    &font(*para);
563	    print   "<menu>\n",
564		    @para,
565		    "</menu>\n";
566	} else {			# T
567	    @tag = shift(@para);
568	    &font(*tag);
569	    &font(*para);
570	    print   "<DL compact>\n" unless $dl;
571	    print   "<DT>\n",
572		    @tag,
573		    "<DD>\n",
574		    @para;
575	    if ($pline == $#paratypes || $paratypes[$pline + 1] ne 'T') {
576		# Perl 5 lossage alert
577		# Next para is not a definition list
578		$dl = 0;		    # Close open definition list
579		print "</DL>\n";
580	    } else {
581		$dl = 1;		    # Leave definition list open
582	    }
583	}
584	print "<P>\n";
585	
586	# Indent/outdent the *next* para
587	
588	while ($changeindent > 0) {
589	    print "<menu>\n";
590	    $changeindent--;
591	}
592	while ($changeindent < 0) {
593	    print "</menu>\n";
594	    $changeindent++;
595	}
596    }
597    1;
598}
599
600# Make one name anchor in a line; cue on fonts (.B or .I) but leave them alone
601
602sub make_name {
603
604    local(*name, *font, *file, *index, *line) = @_;
605    local($text);
606
607    if (($text) = ($line =~ /^\.[BI]\s+([^\s\\]+)/)) {	# Found pattern
608
609	if (
610	    $text !~ /^-/		    # Avoid lists of options
611	    && (length($text) > 1	    # and history escapes
612		||  $text =~ /^[%:@]$/)	    # Special pleading for %, :, @
613	    && ! $name{"$text $font"}	    # Skip if there's one already
614	) {
615	    # Record link
616	    
617	    $name{"$text $font"} = ($single ? '' : $file) . "#$text";
618	    push(@name, "$text\t" . $name{"$text $font"}) if $index;
619	    
620	    # Put in the name anchor
621    
622	    $line =~ s/^(\.[BI]\s+)([^\s\\]+)/$1<A NAME=\"$text\">$2<\/A>/;
623	}
624    }
625    $line;
626}
627
628# Make all the href anchors in a line; cue on fonts (\fB ... \fR or
629# \fI ... \fR) but leave them alone
630
631sub make_hrefs {
632
633    local(*name, *line) = @_;
634    local(@pieces, $piece);
635
636    @pieces = split(/(\\f[BI][^\\]*\\fR)/, $line);
637    
638    $piece = 0;
639    foreach (@pieces) {
640	if (/\\f([BI])([^\\]*)\\fR/	# Found a possibility
641
642	# It's not followed by (, i.e. it's not a manpage reference
643
644	&& substr($pieces[$piece + 1], 0, 1) ne '(') {
645	    $key = "$2 $1";
646	    if ($name{$key}) {			# If there's a matching name
647		s/(\\f[BI])([^\\]*)(\\fR)/$1<A HREF=\"$name{$key}\">$2<\/A>$3/;
648	    }
649	}
650	$piece++;
651    }
652    $line = join('', @pieces);
653}
654
655# Convert nroff font escapes to HTML
656# Expects comments and breaks to be in HTML form already
657
658sub font {
659
660    local(*para) = @_;
661    local($i, $j, @begin, @end, $part, @pieces, $bold, $italic);
662
663    return 0 if $#para == -1;   # Ignore empty paragraphs
664				# Perl 5 lossage alert
665
666    # Find beginning and end of each part between HTML comments
667
668    $i = 0;
669    @begin = ();
670    @end = ();
671    foreach (@para) {
672	push(@begin, $i + 1) if /^-->/ || /^<BR>/;
673	push(@end, $i - 1) if /^<!--/ || /^<BR>/;
674	$i++;
675    }
676    if ($para[0] =~ /^<!--/ || $para[0] =~ /^<BR>/) {
677	shift(@end);
678    } else {
679	unshift(@begin, 0);	# Begin at the beginning
680    }
681    if ($para[$#para] =~ /^-->/ || $para[$#para] =~ /^<BR>/) {
682	pop(@begin);
683    } else {
684	push(@end, $#para);	# End at the end
685    }
686
687    # Fontify each part
688
689    $bold = $italic = 0;
690    foreach $i (0 .. $#begin) {
691	$part = join('', @para[$begin[$i] .. $end[$i]]);
692	$part =~ s/^\.([BI])\s+(.*)$/\\f$1$2\\fR/gm;	    # .B, .I
693	@pieces = split(/(\\f[BIR])/m, $part);
694	$part = '';
695	foreach $j (@pieces) {
696	    if ($j eq '\fB') {
697		if ($italic) {
698		    $italic = 0;
699		    $part .= '</I>';
700		}
701		unless ($bold) {
702		    $bold = 1;
703		    $part .= '<B>';
704		}
705	    } elsif ($j eq '\fI') {
706		if ($bold) {
707		    $bold = 0;
708		    $part .= '</B>';
709		}
710		unless ($italic) {
711		    $italic = 1;
712		    $part .= '<I>';
713		}
714	    } elsif ($j eq '\fR') {
715		if ($bold) {
716		    $bold = 0;
717		    $part .= '</B>';
718		} elsif ($italic) {
719		    $italic = 0;
720		    $part .= '</I>';
721		}
722	    } else {
723		$part .= $j;	
724	    }
725	}
726
727	# Close bold/italic before break
728
729	if ($end[$i] == $#para || $para[$end[$i] + 1] =~ /^<BR>/) {
730	    # Perl 5 lossage alert
731	    if ($bold) {
732		$bold = 0;
733		$part =~ s/(\n)?$/<\/B>$1\n/;
734	    } elsif ($italic) {
735		$italic = 0;
736		$part =~ s/(\n)?$/<\/I>$1\n/;
737	    }
738	}
739
740	# Rebuild this section of @para
741
742	foreach $j ($begin[$i] .. $end[$i]) {
743	    $part =~ s/^([^\n]*(\n|$))//;
744	    $para[$j] = $1;
745	}
746    }
747
748    # Close bold/italic on last non-comment line
749    # Do this only here because fonts pass through comments
750
751    $para[$end[$#end]] =~ s/(\n)?$/<\/B>$1/ if $bold;
752    $para[$end[$#end]] =~ s/(\n)?$/<\/I>$1/ if $italic;
753}
754
755sub usage {
756    local ($message) = $_[0];
757
758    warn $message if $message;
759    warn <<EOP;
760Usage: $whatami [-1icsu] [-C dir] [-d dir] [-h host] [file]
761Without [file], reads from tcsh.man or stdin.
762-1	    Makes a single page instead of a table of contents and sections
763-i	    Makes a CGI searchable index script, tcsh.html/tcsh.cgi, intended
764	    for a server which respects the .cgi extension in any directory.
765-c	    Like -i,  but the CGI script is intended for a server which wants
766	    scripts in /cgi-bin (or some other privileged directory separate
767	    from the rest of the HTML) and must be moved there by hand.
768-C dir	    Uses /dir instead of /cgi-bin as the CGI bin dir.
769	    Meaningless without -c.
770-d dir	    Uses /dir/tcsh.html instead of /tcsh.html as the HTML dir.
771	    Meaningless without -c.
772-D dir	    Uses /dir.html instead of /tcsh.html as the HTML dir.
773	    Meaningless without -c.
774-G name	    Uses name instead of tcsh.cgi as the name of the CGI script.
775	    Meaningless without -c or -i.
776-h host	    Uses host as the host:port part of the URL to the entry point.
777	    Meaningless without -c.
778-s	    Filenames are shorter (max 8 + 3) but less descriptive.
779-u	    This message
780EOP
781    exit !! $message;
782}
783
784### Inlined documents. Watch for *HERE tokens.
785
786__END__
787<HEAD>
788<TITLE>The tcsh mailing lists</TITLE>
789</HEAD>
790<BODY>
791<A HREF="TOPFILEHERE">Up</A>
792<H2>The <I>tcsh</I> mailing lists</H2>
793There are three <I>tcsh</I> mailing lists:
794<DL>
795<DT>
796<I>tcsh@mailman.astron.com</I>
797<DD>
798The <I>tcsh</I> maintainers and testers' mailing list.
799<DT>
800<I>tcsh-bugs@astron.com</I>
801<DD>
802Open bug and user comment discussion.
803</DL>
804You can subscribe to either of these lists by visiting
805<I><A HREF="https://mailman.astron.com/">https://mailman.astron.com/</A></I>
806<P>
807To file a bug report or a feature suggestion (preferably
808with code), please visit
809<I><A HREF="https://bugs.astron.com/">https://bugs.astron.com/</A></I>
810<P>
811<A HREF="TOPFILEHERE">Up</A>
812</BODY>
813END
814: # -*- perl -*-
815
816# Emulate #!/usr/local/bin/perl on systems without #!
817
818eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
819& eval 'exec perl -S $0 $argv:q' if 0;
820
821# Setup
822
823# Location: doesn't work with relative URLs, so we need to know where to find
824#   the top and section files.
825# If the search engine is in /cgi-bin, we need a hard-coded URL.
826# If the search engine is in the same directory, we can figure it out from CGI
827#   environment variables.
828
829$root = ROOTHERE;
830$topfile = 'TOPFILEHERE';
831@name = (
832'NAMEHERE'
833);
834
835# Do the search
836
837$input = $ENV{'QUERY_STRING'};
838$input =~ s/^input=//;
839$input =~ s/\+/ /g;
840print "Status: 302 Found\n";
841if ($input ne '' && ($key = (grep(/^$input/,  @name))[0] ||
842			    (grep(/^$input/i, @name))[0] ||
843			    (grep( /$input/i, @name))[0]   )) {
844    $key =~ /\t([^\t]*)$/;
845    print "Location: $root$1\n\n";
846} else {
847    print "Location: $root$topfile\n\n";
848}
849END
850