Return to BSD News archive
Received: by minnie.vk1xwt.ampr.org with NNTP
id AA5415 ; Thu, 24 Dec 92 04:01:25 EST
Xref: sserve comp.unix.bsd:9285 comp.lang.perl:12862 alt.sources:4812
Newsgroups: comp.unix.bsd,comp.lang.perl,alt.sources
Path: sserve!manuel.anu.edu.au!munnari.oz.au!spool.mu.edu!cass.ma02.bull.com!melb.bull.oz.au!zen.void.oz.au!sjg
From: sjg@zen.void.oz.au (Simon J. Gerraty)
Subject: Re: Easy way to create unix man pages?
Message-ID: <1992Dec22.082736.21124@zen.void.oz.au>
Keywords: unix manual hypertext
Organization: zen programming...
References: <1992Dec17.165257.9439@oakhill.sps.mot.com> <1992Dec18.040514.11824@netcom.com> <id.FBYV.EU5@ferranti.com>
Date: Tue, 22 Dec 1992 08:27:36 GMT
Lines: 578
Want easy to write man pages?
Want to have them in your source?
Have you got perl installed? If you don't, this might tempt you :-)
[I have posted this before, but it appears upstream news problems
dropped it on the floor.]
cmt2doc.pl can generate pretty decent troff source from (almost)
plain text comments in your source. The comment at the start of
cmt2doc.pl IS the man page for cmt2doc. Just try:
cmt2doc.pl -pm cmt2doc.pl | groff -Tps -man | lpr -Plaser
or whatever works for you.
#!/bin/sh
# This is a shell archive.
# remove everything above the "#!/bin/sh" line
# and feed to /bin/sh
# Use -c option to overwrite existing files
#
# Contents:
# cmt2doc.pl
#
# packed by: <sjg@zen> on Fri Jun 5 22:40:38 EST 1992
#
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f cmt2doc.pl -a "${1}" != "-c" ; then
echo shar: Will not over-write existing file \"cmt2doc.pl\"
else
echo shar: Extracting \"cmt2doc.pl\" \(13166 characters\)
sed 's/^X//' >cmt2doc.pl << '!EOF'
X#!/usr/bin/perl --
Xeval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
X if $running_under_some_shell;
X#
X# NAME:
X# cmt2doc - extract documentation from source
X#
X# SYNOPSIS:
X# cmt2doc [-pamit][-e "oext"][-S "secn"][-D "secd"][-O "org"]
X# [-L "lang"][-C "cmt"][-E "ecmt"] "file"
X#
X# DESCRIPTION:
X# This 'Perl' script extracts documentation from comments in
X# source files. It allows manual pages to be written in ``plain
X# text'' in source files where they are most likely to be updated
X# when the source code is.
X#
X# 'cmt2doc' extracts the documentation as either clean text or as
X# input suitable for 'troff'(1) and friends. The results in
X# either case are usually quite adequate. Try the following
X# commands:
X#.nf
X#
X# 'perl cmt2doc.pl -p cmt2doc.pl | more'
X# 'perl cmt2doc.pl -pm cmt2doc.pl | nroff -man | more'
X#.fi
X#
X# 'cmt2doc' can usually work out for itself how to extract the
X# text from a comment. It looks for the regular expression
X# '.* NAME:$' which it treats as the start of a manual page, and
X# uses what ever is found before 'NAME' as the characters to
X# remove from the start of each line.
X#
X# Typographical conventions:
X#.nf
X# Words like \'this word\' will be type-set in 'bold'.
X# Words like \"this word\" will be set in "italics".
X# Words like ``this quote'' will not be touched.
X#.fi
X#
X# It is possible to put 'troff' commands at the start of an
X# otherwise blank line. Indeed they are sometimes needed such as
X# when setting out examples. They will be stripped if not
X# generating 'troff' output.
X#
X# 'cmt2doc' understands the format required for most manual page
X# sections and attempts to set them appropriately.
X#
X# OPTIONS:
X# -p print to stdout. By default documentation for
X# "file" will be printed to a file in the current
X# directory of the same name but with an extention
X# that represents the format (.doc,.man,.tex).
X#
X# -a print all documentation, not just the top level.
X#
X# -m Output for 'troff -man'.
X#
X# -i Output for texinfo (no yet implemented).
X#
X# -t ``Plain text'' strip single quotes. Leave double quotes
X# alone though.
X#
X# -e "oext"
X# Use "oext" as the extension for the output file.
X#
X# -S "secn"
X# Tell [nt]roff which section the man page belongs in.
X# Default is 'L'.
X#
X# -L "lang"
X# Select default values for "cmt" and "ecmt" based on
X# "lang" ('c','c++','lisp'). Most shell like languages
X# such as 'perl' and 'sh' are easily handled by the
X# defaults.
X#
X# -D "secd"
X# Use "secd" as the section description.
X#
X# -C "cmt"
X# Assume the comment lines start with "cmt". Otherwise
X# we attempt to work it out either based on the file
X# extention (.c,.h,.cc etc) or from the comment itself.
X#
X# -E "ecmt"
X# The comment ends when we see "ecmt" otherwise the first
X# line that does not start with "cmt".
X#
X# -O "org"
X# Use "org" as the organization identifier (printed bottom
X# left of each page).
X#
X# Some options only apply to certain output modes.
X#
X# FILES:
X# /usr/bin/perl The perl interpreter. This entry
X# is really just to show how 'cmt2doc'
X# handles the 'FILES' section.
X# /local/bin/cmt2doc.pl This script. "ditto".
X#
X# BUGS:
X# It probably does not handle nested quotes correctly.
X# Lines starting with a \'.\' are in trouble.
X# For good results it is hard to avoid using 'troff' commands,
X# particularly '.nf' and '.fi'.
X#
X# Handling of '.TH' seems to vary with different man macro sets.
X# You may have to hack 'man_init' to get good results.
X#
X
X#
X# RCSid:
X# $Id: cmt2doc.pl,v 1.7 1992/06/05 12:39:07 sjg Exp $
X#
X# @(#)Copyright (c) 1992, Simon J. Gerraty
X#
X# This file is provided in the hope that it will
X# be of use. There is absolutely NO WARRANTY.
X# Permission to copy, redistribute or otherwise
X# use this file is hereby granted provided that
X# the above copyright notice and this notice are
X# left intact.
X#
X# Please send copies of changes and bug-fixes to:
X# sjg@zen.void.oz.au
X#
X
X$Myname=$0;
X$Myname=~ s#^.*/([^/]*)$#$1#;
X
X# some defaults
X$do_init='txt_init';
X$do_fini='noop';
X$do_sec='txt_sec';
X$do_para='noop';
X$do_line='txt_line';
X
X$man_secn='L'; # local commands
X$oext='.doc';
X$Debug = 0;
X$start_para='';
X$indent=0;
X$defPD='.8v';
X
X$date=&get_date;
X$org='FreeWare'; # be sure to set this!
X
Xrequire 'getopts.pl';
Xdo Getopts('dpamite:L:S:C:E:D:O:');
X
X$org=$opt_O if defined($opt_O);
X$cmt=$opt_C if defined($opt_C);
X$ecmt=$opt_E if defined($opt_E);
X# redefine the necessary functions
Xif (defined($opt_m)) { # [tn]roff -man
X $oext = '.man';
X $do_init='man_init';
X $do_para='man_para';
X $do_sec='man_sec';
X $do_line='man_line';
X} elsif (defined($opt_i)) { # texinfo
X $oext = '.tex';
X $do_init='texi_init';
X $do_fini='texi_fini';
X $do_sec='texi_sec';
X $do_line='texi_line';
X}
X$man_secn=$opt_S if defined($opt_S);
Xif (defined($opt_D)) {
X $man_secd=$opt_D;
X} else {
X $man_secd=&lookup_mansec($man_secn);
X}
X$oext=$opt_e if defined($opt_e);
X$Debug = 1 if defined($opt_d);
X$Lang=$opt_L if defined($opt_L);
X
X
X$indoc=0;
X$in_para = 0;
X
XFILE: foreach $file (@ARGV) {
X print STDERR "doing $file\n" if $Debug > 0;
X $name="./$file";
X $name=~s#^.*/([^/]*)$#$1#;
X $ext=$name;
X $ext=~s/.*(\.[^.]*)$/\1/;
X
X if (!defined($opt_L)) {
X $Lang='c' if ($ext =~ m/\.[ch]$/);
X $Lang='c++' if ($ext =~ m/\.(cc|C|H)$/);
X $Lang='lisp' if ($ext =~ m/\.el$/);
X }
X if (defined($Lang)) {
X if ($Lang eq 'c') {
X $cmt = '[/ ]\*';
X $ecmt = ' *\*/';
X } elsif ($Lang eq 'c++') {
X $cmt = '(//|[/ ]\*)';
X $ecmt = ' *\*/';
X } elsif ($Lang eq 'lisp') {
X $cmt = ';+';
X }
X }
X if (!defined($opt_p)) {
X $ofile = $name; # we've already stripped dirname
X $ofile =~ s#\.[^/.]+$##;
X $ofile .= $oext;
X print STDERR "Output to $ofile\n" if $Debug > 0;
X open(STDOUT, "> $ofile") || die "can't redirect STDOUT: $!\n";
X }
X if (!open(F, "< $file")) {
X print STDERR "can't open $file: $!\n";
X next FILE;
X }
X LINE: while (<F>) {
X chop;
X if ($indoc == 0 && m/ NAME:$/) {
X if (!defined($cmt)) {
X $cmt = $_;
X $cmt =~ s/^(.*) NAME.*/\1/;
X }
X $indoc = 1;
X $in_para = 0;
X &$do_init;
X }
X next if ($indoc == 0);
X # we are inside doc section
X if ($_ !~ m@^$cmt@ || (defined($ecmt) && $_ =~ m@^$ecmt@)) {
X $indoc = 0;
X &$do_fini;
X if (defined($opt_a)) {
X next LINE;
X } else {
X next FILE;
X }
X }
X s@^$cmt ?@@;
X $needout = 1;
X if (m/^[A-Z][A-Za-z _-]+:$/) {
X &$do_sec;
X } elsif (m/^[ \t]*$/) {
X $in_para = 0;
X if (defined($opt_m)) {
X $needout = 0;
X }
X } else {
X if ($in_para == 0) {
X $in_para = 1;
X &$do_para;
X }
X &$do_line;
X }
X print "$_\n" if ($needout > 0);
X }
X close F;
X}
Xexit 0;
X
X# for plain text these are noops
Xsub noop {
X}
X
Xsub txt_init {
X local($i,$c);
X $llength = 65;
X $c = 0;
X
X $nm=$name;
X $nm=~s/\.[^.]*$//;
X $nm =~ tr/[a-z]/[A-Z]/;
X $nm = "$nm($man_secn)";
X print "\n$nm";
X $c += length($nm);
X $i = int(($llength - length($man_secd))/ 2);
X while ($c < $i) {
X $c++;
X print " ";
X }
X print "$man_secd";
X $c += length($man_secd);
X $i = $llength - length($nm);
X while ($c < $i) {
X $c++;
X print " ";
X }
X print "$nm\n\n\n";
X}
X
Xsub txt_sec {
X # just loose the trailing ':'
X $sec = $_;
X $sec =~ s/ *([A-Z][A-Za-z _-]*):/\1/;
X $_ = $sec;
X $in_para = 0;
X}
X
Xsub txt_line {
X $needout = 0 if (m/^\.\w+/); # strip nroff commands
X if (defined($opt_t)) {
X # strip 'word' to just word.
X # a bit of trickery to avoid ``quotes'' and \'word\'.
X s/^'([^']+)'/\1/g; # 'bold'
X s/([^'\\])'([^']+)'/\1\2/g; # 'bold'
X }
X s/\\(['"\\])/\1/g; # strip \\ \' and \" to ' " and \
X}
X
X
Xsub man_init {
X print ".\\\" extracted from $file $date by $Myname\n";
X
X $nm=$name;
X $nm=~s/\.[^.]*$//;
X $nm =~ tr/[a-z]/[A-Z]/;
X # some tmac.an macros don't support $org HP-UX for example.
X # But most do. Just comment out setting of $org above.
X if (defined($org)) {
X print ".TH $nm $man_secn \"$date\" \"$org\" \"$man_secd\"\n";
X } else {
X print ".TH $nm $man_secn \"$date\" \"$man_secd\"\n";
X }
X # just to be sure
X print ".PD $defPD\n";
X}
X
Xsub man_sec {
X &man_indent(0); # make sure indentation is back to 0
X
X if ($start_para eq '.nf') {
X print ".fi\n";
X }
X if ($sec eq 'FILES') {
X # previous section was FILES
X # restore inter-paragraph distance
X print ".PD $defPD\n";
X }
X # get new section name.
X $sec = $_;
X $sec =~ s/ *([A-Z][A-Za-z _-]*):/\1/;
X
X if ($sec ne 'NAME') {
X print "\n";
X }
X if ($sec =~ m/ /) {
X print ".SH \"$sec\"\n";
X } else {
X print ".SH $sec\n";
X }
X if ($sec eq 'FILES') {
X # little or no gap between paragraphs.
X # so it looks like it should.
X print ".PD .1v\n";
X }
X $needout = 0;
X $in_para = 0;
X}
X
X# this gets a little messy
Xsub man_para {
X if (m/^\.\w+/) {
X # a [tn]roff command, next line is start of para
X $in_para=0;
X return;
X }
X if ($sec =~ m/DESCRIPTION|OPTIONS/ && m/^[ \t]*-/) {
X $start_para = '.TP';
X } elsif ($sec eq 'FILES') {
X $start_para = '.TP 30';
X } elsif ($sec =~ m/NAME|SYNOPSIS/) {
X $start_para = '.nf';
X } elsif ($start_para =~ m/\.TP/) {
X $start_para = '.PP';
X } else {
X $start_para = '';
X }
X print "$start_para\n" if ($needout > 0);
X # handle indented paras
X if ($start_para !~ m/\.TP/ && m/^\t/) {
X &man_indent(-1);
X }
X}
X
X
X# we have to do more that we would like here, to
X# set 'bold' and "italics" but not to harm \'words\'
X# \"words\" and ``quotes''.
Xsub man_line {
X # man_para will have been called once already
X # so first time in after a new para, $in_para==1.
X # in here we can set it to other values to indicate
X # a need to force a new para, or adjust indentation.
X if ($in_para == 3) {
X &man_indent(-1);
X $in_para=1;
X }
X if (m/^\.\w+/) {
X # a [tn]roff command
X $in_para=3;
X return;
X }
X s/^[ \t]*//;
X if ($sec eq 'FILES') {
X # we assume file descriptions are formated
X # filename\tdecription
X if (m/^[^\t]+\t+[^\t]+/) {
X if ($in_para == 2) {
X &man_para;
X }
X $in_para = 2;
X s/^[ \t]*//;
X s/^([^\t]+)\t+([^\t]+)/\1\n\2/;
X }
X } elsif ($sec =~ m/DESCRIPTION|OPTIONS/ && m/^[ \t]*-/) {
X if ($in_para == 2) {
X &man_para;
X }
X $in_para = 2;
X s/^[ \t]*//;
X s/\t/ /g;
X # format options correctly
X s/^([^'" ]+)/'\1'/ if (m/^[^'"]/);
X s/^('[^']+' *"[^"]+") *([^'" ])/\1\n\2/;
X s/^('[^']+') *([^'" ])/\1\n\2/;
X }
X s/\t/ /g;
X if ($sec eq 'SYNOPSIS') {
X s/^(\w+)/'\1'/;
X s/(-\w+)/'\1'/g if (m/\[/);
X }
X s/([ '"])-/\1\\-/g;
X s/^"([^"]+)"/\\fI\1\\fR/g; # "italic"
X # avoid \"word\"
X s/([^\\])"([^"]*[^\\])"/\1\\fI\2\\fR/g; # "italic"
X # a bit of trickery to avoid ``quotes'' and \'word\'.
X s/^'([^']+)'/\\fB\1\\fR/g; # 'bold'
X s/([^'\\])'([^']+)'/\1\\fB\2\\fR/g; # 'bold'
X # now make \['"] into just ' or "
X s/\\(['"])/\1/g;
X}
X
X# adjust the indent level
Xsub man_indent {
X local($i) = @_;
X local($itabs,@tabs);
X
X if ($i < 0) {
X # calculate required indent level
X $itabs=$_;
X $itabs =~ s/^(\t+)[^\t].*/\1/;
X
X @tabs=split(/\t/,$itabs, 10);
X $i = $#tabs - 1;
X }
X if ($i >= 0) {
X while ($indent < $i) {
X $indent++;
X print ".RS\n";
X }
X while ($indent > $i) {
X $indent--;
X print ".RE\n";
X }
X }
X}
X
X
Xsub lookup_mansec {
X local($n) = @_;
X local($d);
X %s = &init_secd if (!defined(%s));
X
X $d = $s{$n};
X if (!defined($d)) {
X $d = $s{'default'};
X }
X $d;
X}
X
Xsub init_secd {
X local(%s);
X
X $s{'default'} = 'MISC. REFERENCE MANUAL PAGES';
X $s{'1'} = 'USER COMMANDS ';
X $s{'1C'} = 'USER COMMANDS';
X $s{'1G'} = 'USER COMMANDS';
X $s{'1S'} = 'USER COMMANDS';
X $s{'1V'} = 'USER COMMANDS ';
X $s{'2'} = 'SYSTEM CALLS';
X $s{'2V'} = 'SYSTEM CALLS';
X $s{'3'} = 'C LIBRARY FUNCTIONS';
X $s{'3C'} = 'COMPATIBILITY FUNCTIONS';
X $s{'3F'} = 'FORTRAN LIBRARY ROUTINES';
X $s{'3K'} = 'KERNEL VM LIBRARY FUNCTIONS';
X $s{'3L'} = 'LIGHTWEIGHT PROCESSES LIBRARY';
X $s{'3M'} = 'MATHEMATICAL LIBRARY';
X $s{'3N'} = 'NETWORK FUNCTIONS';
X $s{'3R'} = 'RPC SERVICES LIBRARY';
X $s{'3S'} = 'STANDARD I/O FUNCTIONS';
X $s{'3V'} = 'C LIBRARY FUNCTIONS';
X $s{'3X'} = 'MISCELLANEOUS LIBRARY FUNCTIONS';
X $s{'4'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'4F'} = 'PROTOCOL FAMILIES';
X $s{'4I'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'4M'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'4N'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'4P'} = 'PROTOCOLS';
X $s{'4S'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'4V'} = 'DEVICES AND NETWORK INTERFACES';
X $s{'5'} = 'FILE FORMATS';
X $s{'5V'} = 'FILE FORMATS';
X $s{'6'} = 'GAMES AND DEMOS';
X $s{'7'} = 'ENVIRONMENTS, TABLES, AND TROFF MACROS';
X $s{'7V'} = 'ENVIRONMENTS, TABLES, AND TROFF MACROS';
X $s{'8'} = 'MAINTENANCE COMMANDS';
X $s{'8C'} = 'MAINTENANCE COMMANDS';
X $s{'8S'} = 'MAINTENANCE COMMANDS';
X $s{'8V'} = 'MAINTENANCE COMMANDS';
X $s{'L'} = 'LOCAL COMMANDS';
X %s;
X}
X
Xsub get_date {
X @months = ('January','February','March','April','May',
X 'June','July','August','September','October',
X 'November','December');
X ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$idst) =
X localtime(time);
X if ($year < 70) {
X $cent='20';
X } else {
X $cent = '19';
X }
X $month = $months[$mon];
X "$mday $month $cent$year";
X}
!EOF
if test 13166 -ne `wc -c < cmt2doc.pl`; then
echo shar: \"cmt2doc.pl\" unpacked with wrong size!
fi
fi
exit 0
--
Simon J. Gerraty <sjg@zen.void.oz.au>
#include <disclaimer> /* imagine something _very_ witty here */