#!/bin/sh # -*- perl -*- # $Id$ # tcsh.man2html, Dave Schweisguth # # Notes: # # Always puts all files in the directory tcsh.html, creating it if necessary. # tcsh.html/top.html is the entry point, and tcsh.html/index.html is a symlink # to tcsh.html/top.html so one needn't specify a file at all if working through # a typically configured server. # # Designed for tcsh manpage. Guaranteed not to work on manpages not written # in the exact same style of nroff -man, i.e. any other manpage. # # Makes links FROM items which are both a) in particular sections (see # Configuration) and b) marked with .B or .I. Makes links TO items which # are marked with \fB ... \fR or \fI ... \fR. # # Designed with X Mosaic in mind and tested lightly with lynx. I've punted on # HTML's lack of a .PD equivalent and lynx's different handling. # Emulate #!/usr/local/bin/perl on systems without #! eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; ### Constants # Setup ($whatami = $0) =~ s|.*/||; # `basename $0` $isatty = -t STDIN; # Configuration $index = 0; # Don't make a searchable index CGI script $cgibin = 0; # Look for $cgifile in $dir, not $cgibindir $shortfiles = 0; # Use long filenames $single = 0; # Make single page instead of top and sections $host = ''; # host:port part of server URL *** $updir = ''; # Directories between $host and $dir *** $dir = 'tcsh'; # Directory in which to put the pieces * $cgifile = 'tcsh.cgi'; # CGI script name ** $cgibindir = 'cgi-bin'; # CGI directory *** $headerfile = 'header'; # HTML file for initial comments * $indexfile = 'index'; # Symlink to $topfile * $listsfile = 'lists'; # Mailing list description HTML file * $outfile = 'tcsh.man'; # Default input file and copy of input file $script = $whatami; # Copy of script; filename length must be OK $topfile = 'top'; # Top-level HTML file * # * .htm or .html suffix added later # ** Only used with -i or -c # *** Only used with -c # Sections to inline in the top page %inline_me = ('NAME', 1, 'SYNOPSIS', 1); # Sections in which to put name anchors and the font in which to look for # links to those anchors %link_me = ('Editor commands', 'I', 'Builtin commands', 'I', 'Special aliases', 'I', 'Special shell variables', 'B', 'ENVIRONMENT', 'B', 'FILES', 'I'); ### Arguments and error-checking # Parse args while ($#ARGV > -1 && (($first, $rest) = ($ARGV[0] =~ /^-(.)(.*)/))) { # Perl 5 lossage alert if ($first =~ /[CdDGh]/) { # Switches with arguments shift; $arg = $rest ne '' ? $rest : $ARGV[0] ne '' ? shift : &usage("$whatami: -$first requires an argument.\n"); } elsif ($rest ne '') { $ARGV[0] = "-$rest"; } else { shift; } if ($first eq '1') { $single = 1; } elsif ($first eq 'c') { $cgibin = 1; } elsif ($first eq 'C') { $cgibindir = $arg; } elsif ($first eq 'd') { $updir = $arg; } elsif ($first eq 'D') { $dir = $arg; } elsif ($first eq 'G') { $cgifile = $arg; } elsif ($first eq 'h') { $host = $arg; } elsif ($first eq 'i') { $index = 1; } elsif ($first eq 's') { $shortfiles = 1; } elsif ($first eq 'u') { &usage(0); } else { &usage("$whatami: -$first is not an option.\n"); } } if (@ARGV == 0) { if ($isatty) { $infile = $outfile; # Default input file if interactive } else { $infile = 'STDIN'; # Read STDIN if no args and not a tty } } elsif (@ARGV == 1) { $infile = $ARGV[0]; } else { &usage("$whatami: Please specify one and only one file.\n"); } $index = $index || $cgibin; # $index is true if $cgibin is true if ($cgibin && ! $host) { die "$whatami: Must specify host with -h if using -c.\n"; } # Decide on HTML suffix and append it to filenames $html = $shortfiles ? 'htm' : 'html'; # Max 3-character extension $dir .= ".$html"; # Directory in which to put the pieces $headerfile .= ".$html"; # HTML file for initial comments $topfile .= ".$html"; # Top-level HTML file (or moved notice) $indexfile .= ".$html"; # Symlink to $topfile $listsfile .= ".$html"; # Mailing list description HTML file # Check for input file unless ($infile eq 'STDIN') { die "$whatami: $infile doesn't exist!\n" unless -e $infile; die "$whatami: $infile is unreadable!\n" unless -r _; die "$whatami: $infile is empty!\n" unless -s _; } # Check for output directory and create if necessary if (-e $dir) { -d _ || die "$whatami: $dir is not a directory!\n"; -r _ && -w _ && -x _ || die "$whatami: $dir is inaccessible!\n" } else { mkdir($dir, 0755) || die "$whatami: Can't create $dir!\n"; } # Slurp manpage if ($infile eq 'STDIN') { @man = ; } else { open(MAN, $infile) || die "$whatami: Error opening $infile!\n"; @man = ; close MAN; } # Print manpage to HTML directory (can't use cp if we're reading from STDIN) open(MAN, ">$dir/$outfile") || die "$whatami: Can't open $dir/$outfile!\n"; print MAN @man; close MAN; # Copy script to HTML directory (system("cp $0 $dir") >> 8) && die "$whatami: Can't copy $0 to $dir!\n"; # Link top.html to index.html in case someone looks at tcsh.html/ system("rm -f $dir/$indexfile"); # Some systems can't ln -sf (system("ln -s $topfile $dir/$indexfile") >> 8) && die "$whatami: Can't link $topfile to $dir/$indexfile!\n"; ### Get title and section headings $comment = 0; # 0 for text, 1 for ignored text @sectionlines = (0); # First line of section @sectiontypes = (0); # H or S @sectiontexts = ('Header'); # Text of section heading @sectionfiles = ($headerfile); # Filename in which to store section %name = (); # Array of name anchors @name = () if $index; # Ordered array of name anchors $font = ''; # '' to not make names, 'B' or 'I' to do so $line = 0; foreach (@man) { if (/^\.ig/) { # Start ignoring $comment = 1; } elsif (/^\.\./) { # Stop ignoring $comment = 0; } elsif (! $comment) { # Not in .ig'ed section; do stuff # nroff special characters s/\\-/-/g; # \- s/\\^//g; # \^ s/^\\'/'/; # leading ' escape s/^\\(\s)/$1/; # leading space escape s/\\(e|\\)/\\/g; # \e, \\; must do this after other escapes # HTML special characters; deal with these before adding more s/&/&\;/g; s/>/>\;/g; s/$dir/$topfile"); select TOP; # Top page header print < $title

$title


EOP # FORM block, if we're making an index $action = $cgibin ? "http://$host/$cgibindir/$cgifile" : $cgifile; print < Go directly to a section, command or variable: EOP # Table of contents print < EOP foreach $section (1 .. $#sectionlines) { if ($sectiontypes[$section - 1] < $sectiontypes[$section]) { print " \n"; # Indent, smaller font } elsif ($sectiontypes[$section - 1] > $sectiontypes[$section]) { print "

\n"; # Outdent, larger font } if ($inline_me{$sectiontexts[$section]}) { # Section is in %inline_me # Print section inline print "$sectiontexts[$section]\n"; print "

\n"; # Indent, smaller font &printsectionbody(*man, *sectionlines, *section, *name); print "

\n"; # Outdent, larger font } else { # Print link to section print "$sectiontexts[$section]
\n"; } } print < EOP print "
\n" if $single; ### Make sections foreach $section (0 .. $#sectionlines) { # Skip inlined sections next if $inline_me{$sectiontexts[$section]}; if ($single) { # Header print <$sectiontexts[$section]

EOP &printsectionbody(*man, *sectionlines, *section, *name); print <Table of Contents EOP } else { # Make pointer line for header and trailer $pointers = "Up"; $pointers .= "\nNext" if ($section < $#sectionlines) && ! $inline_me{$sectiontexts[$section + 1]}; $pointers .= "\nPrevious" if ($section > 1) && # section 0 is initial comments ! $inline_me{$sectiontexts[$section - 1]}; # Header open(OUT, ">$dir/$sectionfiles[$section]"); select OUT; print < $sectiontexts[$section] $pointers

$sectiontexts[$section]

EOP &printsectionbody(*man, *sectionlines, *section, *name); # Trailer print < EOP } } select TOP unless $single; # Top page trailer print <
Here are the nroff manpage (175K) from which this HTML version was generated, the Perl script which did the conversion and the complete source code for tcsh.
tcsh is maintained by Christos Zoulas <christos\@gw.com> and the tcsh maintainers' mailing list. Dave Schweisguth <dcs\@proton.chem.yale.edu> wrote the manpage and the HTML conversion script. EOP close TOP; ### Make lists page open(LISTS, ">$dir/$listsfile"); select LISTS; while(($_ = ) ne "END\n") { # Text stored after __END__ s/TOPFILEHERE/$topfile/; print; } close LISTS; ### Make search script if ($index) { # URL of $dir; see comments in search script $root = $cgibin ? "'http://$host/" . ($updir ? "$updir/" : '') . "$dir/'" : '"http://$ENV{\'SERVER_NAME\'}:$ENV{\'SERVER_PORT\'}" . (($_ = $ENV{\'SCRIPT_NAME\'}) =~ s|[^/]*$||, $_)'; # String for passing @name to search script $name = join("',\n'", @name); open(TOP, ">$dir/$cgifile"); select TOP; while(($_ = ) ne "END\n") { # Text stored after __END__ s/ROOTHERE/$root/; s/NAMEHERE/$name/; s/TOPFILEHERE/$topfile/; print; } close TOP; chmod(0755, "$dir/$cgifile") || die "$whatami: Can't chmod 0755 $dir/$cgifile!\n"; warn "$whatami: Don't forget to move $dir/$cgifile to /$cgibindir.\n" if $cgibin; } ### That's all, folks exit; ### Subroutines # Process and print the body of a section sub printsectionbody { local(*man, *sectionlines, *sline, *name) = @_; # Number of section local($sfirst, $slast, @paralines, @paratypes, $comment, $dl, $pline, $comment, $pfirst, $plast, @para, @tag, $changeindent); # Define section boundaries $sfirst = $sectionlines[$sline] + 1; if ($sline == $#sectionlines) { $slast = $#man; } else { $slast = $sectionlines[$sline + 1] - 1; } # Find paragraph markers, ignoring those between '.ig' and '..' if ($man[$sfirst] =~ /^\.[PIT]P/) { @paralines = (); @paratypes = (); } else { @paralines = ($sfirst - 1); # .P follows .S[HS] by default @paratypes = ('P'); } $comment = 0; foreach ($sfirst .. $slast) { if ($man[$_] =~ /^\.ig/) { # Start ignoring $comment = 1; } elsif ($man[$_] =~ /^\.\./) { # Stop ignoring $comment = 0; } elsif (! $comment && $man[$_] =~ /^\.([PIT])P/) { push(@paralines, $_); push(@paratypes, $1); } } # Process paragraphs $changeindent = 0; $dl = 0; foreach $pline (0 .. $#paralines) { @para = (); $comment = 0; # Define para boundaries $pfirst = $paralines[$pline] + 1; if ($pline == $#paralines) { $plast = $slast; } else { $plast = $paralines[$pline + 1] - 1; } foreach (@man[$pfirst .. $plast]) { if (/^\.ig/) { # nroff begin ignore if ($comment == 0) { $comment = 2; push(@para, "\n"); } elsif ($comment == 2) { s/--/-/g; # Remove double-dashes in comments } unless ($comment) { if (/^\.TH/) { # Title; got this already next; } elsif (/^\.PD/) { # Para spacing; unimplemented next; } elsif (/^\.RS/) { # Indent (one width only) $changeindent++; next; } elsif (/^\.RE/) { # Outdent $changeindent--; next; } # Line break s/^\.br.*/
/; # More nroff special characters s/^\\&\;//; # leading dot escape; save until # now so leading dots aren't # confused with ends of .igs &make_hrefs(*name, *_); } push(@para, $_); } } push(@para, "-->\n") if $comment; # Close open comment # Print paragraph if ($paratypes[$pline] eq 'P') { &font(*para); print @para; } elsif ($paratypes[$pline] eq 'I') { &font(*para); print "\n", @para, "\n"; } else { # T @tag = shift(@para); &font(*tag); &font(*para); print "
\n" unless $dl; print "
\n", @tag, "
\n", @para; if ($pline == $#paratypes || $paratypes[$pline + 1] ne 'T') { # Perl 5 lossage alert # Next para is not a definition list $dl = 0; # Close open definition list print "
\n"; } else { $dl = 1; # Leave definition list open } } print "

\n"; # Indent/outdent the *next* para while ($changeindent > 0) { print "

\n"; $changeindent--; } while ($changeindent < 0) { print "\n"; $changeindent++; } } 1; } # Make one name anchor in a line; cue on fonts (.B or .I) but leave them alone sub make_name { local(*name, *font, *file, *index, *line) = @_; local($text); if (($text) = ($line =~ /^\.[BI]\s+([^\s\\]+)/)) { # Found pattern if ( $text !~ /^-/ # Avoid lists of options && (length($text) > 1 # and history escapes || $text =~ /^[%:@]$/) # Special pleading for %, :, @ && ! $name{"$text $font"} # Skip if there's one already ) { # Record link $name{"$text $font"} = ($single ? '' : $file) . "#$text"; push(@name, "$text\t" . $name{"$text $font"}) if $index; # Put in the name anchor $line =~ s/^(\.[BI]\s+)([^\s\\]+)/$1$2<\/A>/; } } $line; } # Make all the href anchors in a line; cue on fonts (\fB ... \fR or # \fI ... \fR) but leave them alone sub make_hrefs { local(*name, *line) = @_; local(@pieces, $piece); @pieces = split(/(\\f[BI][^\\]*\\fR)/, $line); $piece = 0; foreach (@pieces) { if (/\\f([BI])([^\\]*)\\fR/ # Found a possibility # It's not followed by (, i.e. it's not a manpage reference && substr($pieces[$piece + 1], 0, 1) ne '(') { $key = "$2 $1"; if ($name{$key}) { # If there's a matching name s/(\\f[BI])([^\\]*)(\\fR)/$1$2<\/A>$3/; } } $piece++; } $line = join('', @pieces); } # Convert nroff font escapes to HTML # Expects comments and breaks to be in HTML form already sub font { local(*para) = @_; local($i, $j, @begin, @end, $part, @pieces, $bold, $italic); return 0 if $#para == -1; # Ignore empty paragraphs # Perl 5 lossage alert # Find beginning and end of each part between HTML comments $i = 0; @begin = (); @end = (); foreach (@para) { push(@begin, $i + 1) if /^-->/ || /^
/; push(@end, $i - 1) if /^/ || $para[$#para] =~ /^
/) { pop(@begin); } else { push(@end, $#para); # End at the end } # Fontify each part $bold = $italic = 0; foreach $i (0 .. $#begin) { $* = 1; $part = join('', @para[$begin[$i] .. $end[$i]]); $part =~ s/^\.([BI])\s+(.*)$/\\f$1$2\\fR/g; # .B, .I @pieces = split(/(\\f[BIR])/, $part); $part = ''; foreach $j (@pieces) { if ($j eq '\fB') { if ($italic) { $italic = 0; $part .= ''; } unless ($bold) { $bold = 1; $part .= ''; } } elsif ($j eq '\fI') { if ($bold) { $bold = 0; $part .= ''; } unless ($italic) { $italic = 1; $part .= ''; } } elsif ($j eq '\fR') { if ($bold) { $bold = 0; $part .= ''; } elsif ($italic) { $italic = 0; $part .= ''; } } else { $part .= $j; } } $* = 0; # Close bold/italic before break if ($end[$i] == $#para || $para[$end[$i] + 1] =~ /^
/) { # Perl 5 lossage alert if ($bold) { $bold = 0; $part =~ s/(\n)?$/<\/B>$1\n/; } elsif ($italic) { $italic = 0; $part =~ s/(\n)?$/<\/I>$1\n/; } } # Rebuild this section of @para foreach $j ($begin[$i] .. $end[$i]) { $part =~ s/^([^\n]*(\n|$))//; $para[$j] = $1; } } # Close bold/italic on last non-comment line # Do this only here because fonts pass through comments $para[$end[$#end]] =~ s/(\n)?$/<\/B>$1/ if $bold; $para[$end[$#end]] =~ s/(\n)?$/<\/I>$1/ if $italic; } sub usage { local ($message) = $_[0]; warn $message if $message; warn < The tcsh mailing lists
Up

The tcsh mailing lists

There are three tcsh mailing lists:
tcsh@mx.gw.com
The tcsh maintainers and testers' mailing list.
tcsh-diffs@mx.gw.com
The same as tcsh@mx.gw.com, plus diffs for each new patchlevel of tcsh.
tcsh-bugs@mx.gw.com
Bug reports.
You can subscribe to any of these lists by sending mail to listserv@mx.gw.com with the text "subscribe <list name> <your name>" on a line by itself in the body. <list name> is the name of the mailing list, without "@mx.gw.com", and <your name> is your real name, not your email address. You can also ask the list server for help by sending only the word "help".

Up END #!/bin/sh # Emulate #!/usr/local/bin/perl on systems without #! eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; # Setup # Location: doesn't work with relative URLs, so we need to know where to find # the top and section files. # If the search engine is in /cgi-bin, we need a hard-coded URL. # If the search engine is in the same directory, we can figure it out from CGI # environment variables. $root = ROOTHERE; $topfile = 'TOPFILEHERE'; @name = ( 'NAMEHERE' ); # Do the search $input = $ENV{'QUERY_STRING'}; $input =~ s/^input=//; $input =~ s/\+/ /g; print "Status: 302 Found\n"; if ($input ne '' && ($key = (grep(/^$input/, @name))[0] || (grep(/^$input/i, @name))[0] || (grep( /$input/i, @name))[0] )) { $key =~ /\t([^\t]*)$/; print "Location: $root$1\n\n"; } else { print "Location: $root$topfile\n\n"; } END