[net.sources] A final

wise@cal-unix.UUCP (08/29/83)

I'd like to try one final rev of cpr.  It incorporates all the modifications
of the previous revisions, plus a few more.  It does not use dup2(), so it
should hopefully not cause SYSTEM [35].* people problems.  It has quite a
few options, but as I mention in the header of the code, below, I think
that a program can survive having lots of options if it defaults to
reasonable operation when no options are given.

It only prints one table of contents, which can optionally be sorted.  It
optionally numbers lines.  It is slightly more intelligent about what a
function is.  It can print one function per page.  It has been somewhat
thoroughly tested.

I built this file using the kit with which it was distributed by Lance
Shepard.  Since I've seen a number of these distribution kits, I assume that
there's a program around that creates them.  If this is true, would someone
please send me some mail about it?

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting cpr.n'
sed 's/^X//' <<'//go.sysin dd *' >cpr.n
X.TH CPR 1 
X.SH "NAME"
cpr  \-  print `C' files
X.SH "SYNOPSIS"
X.B cpr
[ -l# ] [-w# ] [ -nso ] [ - ]
[ file ] ...
X.SH "DESCRIPTION"
X.I Cpr
produces a printed listing of one or more
X.B `C'
files, preceeded by a table of contents.
The output is separated into pages headed by 
the name of the file (in bold face) a date, and the page number.
The standard input is read if no files are
given, or if dash (\-) is encountered.
Function names are output in bold face.
Lines greater than 132 columns in width (minus the width of
added line numbers, if any) are folded.
X.PP
The following options are allowed, and may be given in any order:
X.TP
X.BI \-l#
Take the length of the page to be
X.I #
lines instead of the default 66.
X.TP
X.BI \-w#
Take the width of the page to be
X.I #
columns instead of the default 132.
X.TP
X.BI \- s
Sort the table of contents.
X.TP
X.BI \- n
Print line numbers.
X.TP
X.BI \- o
Print
X.I one\-function\-per\-page.
X.PP
X.SH "FILES"
X.nf
X/tmp/cpr$$			temp files holding text
X.fi
X.SH "SEE ALSO"
X.nf
cat(1)
fold(1)
num(1)
pr(1)
X.fi
X.SH "DIAGNOSTICS"
Various messages about being unable to open files.
Self explanatory.
X.SH "BUGS"
Mistakes structure declarations as a function.
X.br
Unable to find function names that do not begin at the left margin.
X.SH "AUTHOR"
X.nf
Paul Breslin                            original
Human Computing Resources Corp.

Lance E. Shepard                        modified original
CCI & RIT

Rick Wise                               final (maybe)
CALCULON Corp.
X.fi
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 444 cpr.n
	/bin/echo -n '	'; /bin/ls -ld cpr.n
fi
/bin/echo 'Extracting cpr.c'
sed 's/^X//' <<'//go.sysin dd *' >cpr.c
X/*
 *	This program prints the files named in its argument list, preceding
 *	the output with a table of contents. Each file is assumed to be C
 *	source code (but doesn't have to be) in that the program searches
 *	for the beginning and end of functions. Function names are added to
 *	the table of contents, provided the name starts at the beginning of
 *	a line. The function name in the output is double striken. White space
 *	is inserted after every terminating '}' character. Thus functions
 *	and structure declarations are nicely isolated in the output. The only
 *	drawback to this is that structure initialization tables sometimes
 *	produce large quantities of white space.
 *
 *	Try it! You'll like it. (I call it cpr.c)
 *
 *	written by: Paul Breslin
 *		    Human Computing Resources Corp.
 *		    10 St. Mary St.
 *		    Toronto, Ontario
 *		    Canada, M4Y 1P9
 *
 *		    decvax!utcsrgv!hcr!phb
 */

X/*
 *	The following options have been added.  The `-n' option is used
 *	to number source lines.  Numbering starts at 1 (one) and is reset
 *	to this number for each file.  The `-o' option is used to put 1
 *	(one) function on each page instead of multiple functions per page.
 *	Line folding has been added.
 *
 *	The `-l' option has been changed so that the blank space between
 *	it and the following number has been eliminated. ("-l60" instead
 *	of "-l 60")
 *
 *	modified by:  Lance E. Shepard
 *		      CCI & RIT		(a lowly co-op)
 *		      Rochester, NY
 *
 *		...!rochester!ritcv!ritvp!les8070
 *
 *	NOTE:
 *		These modifications have been TESTED only ONCE. (And this
 *		one time was under IDEAL CONDITIONS.)  NO GUARANTEES!!
 *
 */

X/*      This might be the last crack at this.  I added the ability to read
 *      standard input, removed dup2() calls to improve transportability,
 *      and made sorting the table of contents an option (this was suggested
 *      by the original author, Paul Breslin, who pointed out that people
 *      (and VAXEN) will use the sorted or unsorted but probably not both).
 *      So now there's only one table of contents (at the beginning), which
 *      is sorted if the -s option is used.  I wanted to make sorting the
 *      default, but that would be inconsistant with the other options, so
 *      if you're like me, alias it to cpr -s.  I also robustized the command
 *      line decoding, and set it up so that -l expects the length as the next
 *      argument, but will accept it without an intervening space, so -l30
 *      means the same as -l 30.  The same is true of a new option, -w.
 *      -w allows the user to specify the line width.
 *
 *      The program should also grab fewer non-function constructs.  It now
 *      compares potential function names with the 'C' reserved words (from
 *      "The C Programming Language", Kernighan and Ritchie, 1978) and
 *      discards those that match.  No, this doesn't take forever.  Look
 *      at the code.
 *
 *      I agree with people  who dislike 3 million options for one little
 *      program, but plead that when reasonable defaults are provided it
 *      needn't make a program cumbersome.  Line width defaults to 132,
 *      since we print mostly on our line printer, but the default is a
 *      #define called WIDTH, so it's easy to change.  Likewise for page
 *      length (called LENGTH).
 *
 *      I added a feature similar to that suggested by Rich Johnson at
 *      Bell Labs, Whippany.  Rather than substituting eight spaces for
 *      every tab, it looks for the next tab stop in a string defining
 *      tabs.
 *
 *      I tested this rather thoroughly, but I also won't guarantee anything.
 *
 *      Rick Wise
 *      CALCULON Corp.
 *      Rockville MD, 20850
 *
 *      decvax!harpo!seismo!rlgvax!cal-unix!wise
 *
 */


#include <stdio.h>
#include <ctype.h>
#include <signal.h>

#define BP		0xC		/* Form feed			*/
#define	MAX_S		256		/* Maximum string length	*/
#define LENGTH          66              /* Default Page Length          */
#define WIDTH           132             /* Default page width           */
#define N_FILES         20              /* Maximum number of files      */
#define TOC_LEN         1024            /* Max no of T of C entries     */


X/*  The following string is the definition of tab stops.  Every 'T' is a
**  tab, any other character is not.  It is currently defined as a tab
**  every 8 spaces.  The comment below helps if you want to modify this,
**  each row being 0+n, 50+n, 100+n, and 150+n, where n is the number
**  above the declaration.  Don't try to number each row with a comment,
**  because you'll notice that the '\'s make it one big string.
*/


X/*       1         2         3         4         5
12345678901234567890123456789012345678901234567890      */
char   *TabDef = "\
-------T-------T-------T-------T-------T-------T--\
-----T-------T-------T-------T-------T-------T----\
---T-------T-------T-------T-------T-------T------\
-T-------T-------T-------T-------T-------T-------T";

X/*      'C' language reserved words (in alphabetical order, so if you   */
X/*      add something make sure you put it in the right place).         */

char    *ReservedWord[]  = { "int", "char", "float", "double", "struct",
			     "auto", "break", "case", "char", "continue",
			     "default", "do", "double", "else", "entry",
			     "extern", "float", "for", "goto", "if",
			     "int", "long", "register", "return", "short",
			     "sizeof", "static", "struct", "switch",
			     "typedef", "union", "unsigned", "while" };

FILE	*File;
int	Braces;				/* Keeps track of brace depth	*/
int	LineNumber;			/* Count output lines		*/
int	PageNumber = 1;			/* You figure this one out	*/
int     PageLength = LENGTH;            /* Normal paper length          */
int     PageWidth = WIDTH;              /* normal page width            */
int	PageEnd;			/* Accounts for space at bottom	*/
int	InComment;
int	InString;
int	OnePerPage = 0;
int	NumLines = 0;
int	Number = 1;
int     WantSorted = 0;                 /* Sort the table of contents   */
char	*Name;				/* Current file name		*/
char	FunctionName[40];
char	*ProgName;
char	*Today;
char    *STDIN = "\n";                  /* special string pointer value to */
					/* flag a file as being stdin      */

main(argc, argv)

int argc;
char	**argv;
  {
	register int    i,
			nextf = 0;      /* index into fname[]   */
	char   *fname[N_FILES];         /* file names to be worked on */
	char		*ctime();
	long		thetime, time();

	ProgName = argv[0];
	thetime	 = time(0);
	Today	 = ctime(&thetime);


	for (i = 1; i < argc; i++) {
	    if (argv[i][0] == '-')  {
		switch (argv[i][1])  {
			case 'l':
				if ( ! argv[i][2]) {
					if (++i >= argc)
						Usage();
					if ( ! isdigit (argv[i][0]))
						Usage();
					PageLength = atoi (argv[i]);
				}
				else {
					if (!isdigit(argv[i][2]))
						Usage();
					PageLength = atoi(&argv[i][2]);
				}
				break;
			case 'w':
				if ( ! argv[i][2]) {
					if (++i >= argc)
						Usage();
					if ( ! isdigit (argv[i][0]))
						Usage();
					PageWidth = atoi (argv[i]);
				}
				else {
					if (!isdigit(argv[i][2]))
						Usage();
					PageWidth = atoi(&argv[i][2]);
				}
				break;
			case 'n':
				NumLines = 1;
				break;
			case 'o':
				OnePerPage = 1;
				break;
			case 's':
				WantSorted = 1;
				break;
			case '\0':
				if (nextf >= N_FILES) {
					fprintf (stderr, "%s: too many files\n", argv[0]);
					exit (1);
				}
				fname[nextf++] = STDIN;
				break;
			default:
				Usage();
				break;
		}
	    }
	    else {
		if (nextf >= N_FILES) {
			fprintf (stderr, "%s: too many files\n", argv[0]);
			exit (1);
		}
		fname[nextf++] = argv[i];
	    }
	}

	if ( ! nextf)
		fname[nextf++] = STDIN; /* if no files specified, use stdin */

	PageEnd = PageLength - (1 + PageLength / 20);

	StartTempFile();

	for (i = 0; i < nextf; i++) {
		if (fname[i] == STDIN) {
			File = stdin;
			Name = "standard input";
		}
		else  {
			if( (File = fopen( Name = fname[i], "r" )) == NULL )  {
				fprintf (stderr, "%s: Can't open file \"%s\"\n",
					ProgName, Name );
				continue;
			}
		}

		List();
		if (File != stdin)
			fclose(File);
	}

	if( PageNumber > 1 || LineNumber > 0 )
		BreakPage();

	EndTempFile();

	DumpTableOfContents();
	DumpTempFile();
	Done();
  }

Usage()
  {
	fprintf (stderr, "Usage: %s [-lpagelength] [-wpagewidth] [-n] [-o] [-s] file ...\n", ProgName);
	exit(1);
  }

char	*TempName;
FILE    *TempFile;

StartTempFile()
  {
	extern char	*mktemp();

	CatchSignalsPlease();
	TempName = mktemp("/tmp/cprXXXXXX");
	if( (TempFile = fopen(TempName, "w")) == NULL )
	  {
		fprintf (stderr, "%s: Can't open temp file!\n", ProgName);
		exit(1);
	  }
  }

EndTempFile()
  {

	fclose (TempFile);
  }

DumpTempFile()
  {
	register int	pid, w;

	int     fd,
		n;
	char    buff[1024];

	if ((fd = open (TempName, 0)) == -1) {
		fprintf (stderr, "%s: can't open temp file\n", ProgName);
		exit (1);
	}
	while (n = read (fd, buff, 1024)) {
		if (write (1, buff, n) == -1) {
			fprintf (stderr, "%s: write error (1)\n", ProgName);
			exit (1);
		}
	}
  }

Done()
  {
	signal(SIGQUIT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);

	if( TempName )  unlink( TempName );
	exit(0);
  }

CatchSignalsPlease()
  {
	signal(SIGQUIT, Done);
	signal(SIGHUP, Done);
	signal(SIGINT, Done);
  }

List()
  {
	register int	bp;
	char		buffer[256];

	NewPage();
	NewFile();
	bp = Braces = 0;
	while( fgets(buffer, 256, File) != NULL )
	  {
		if( bp )
			NewFunction();
		if( ++LineNumber > PageEnd ) NewPage();
		if( (Braces == 0) && LooksLikeFunction(buffer) )
			AddToTableOfContents();
		bp = PutLine(buffer);
	  }
  }

PutLine(l)
register char	*l;
  {
	extern   char	*EndComment();
	extern   char	*EndString();
	char		*expand();
	char		*substr();
	register char	c;
	int		bp;
	char		*save;
	char		*section;
	int		offset;
	char		Digits[15];
	int		Size;
	int 		pos;

	bp = 0;
	for( save = expand(l); c = *l; ++l )
		if( InComment ) 
			l = EndComment(l);
		else if( InString )
			l = EndString(l);
		else
			switch(c)
			  {
			    case '{':
				++Braces;
				break;
	
			    case '}':
				if( --Braces == 0)
					bp = 1;
				break;

			    case '\'':
				++l;
				break;
			
			    case '"':
				InString = 1;
				break;

			    case '/':
				if( *(l+1) == '*' )
				  {
					InComment = 1;
					++l;
				  }
				break;
			  }

	if (NumLines)  {
		sprintf (Digits,"[%d]  ", Number);
		Size = strlen(Digits);
	}
	else
		Size = 0;

	if (strlen(save) + Size > PageWidth)  {
		section = substr(save, 0, PageWidth - Size);
		if (section[strlen(section) - 1] != ' ')  {
			if ((offset = (int) rindex(section, ' ') -
				    (int) section) < 0)
				offset = strlen (section) - 1;
		}
		else
			offset = strlen(section) - 1;
		section[offset] = NULL;

		if (NumLines)  {
			fprintf (TempFile, "[%d]  %s\n", Number++, section);
		}
		else  {
			fprintf (TempFile, "%s\n", section);
		}

		pos = offset + 1;
		do  {
			section = substr(save, pos, pos + PageWidth - 8);
			if (strlen(section) + 8 > PageWidth &&
				    section[strlen(section) - 1] != ' ')  {

				if ((offset = (int) rindex(section, ' ') -
					    (int) section) < 0)
					offset = strlen (section) - 1;
			}
			offset = strlen (section) - 1;
			section[offset] = NULL;
			if (section[strlen(section) - 1] == '\n')
				section[strlen(section) - 1] = NULL; 
			fprintf (TempFile, "C       %s\n", section);
			if (++LineNumber > PageEnd)
				NewPage();
		}  while ((pos += offset + 1) < strlen(save));
	}
	else  {
		if (NumLines)
			fprintf (TempFile, "[%d]  %s", Number++, save);
		else
			fprintf (TempFile, "%s", save);
	}

	return(bp);
  }

char *
EndComment(p)
register char	*p;
  {
	register char	c;

	while( c = *p++ )
		if( c == '*' && *p == '/' )
		  {
			InComment = 0;
			break;
		  }
	return(p-1);
  }

char *
EndString(p)
register char	*p;
  {
	register char	c;

	while( c = *p++ )
		if( c == '\\' ) 
		  {
			++p;
			continue;
		  }
		else if( c == '"' )
		  {
			InString = 0;
			break;
		  }
	return(p-1);
  }

NewFunction()
  {
	register int	i;

	if( LineNumber > (PageLength * 3 / 4) )
		NewPage();
	else
	  {
		if (!OnePerPage)  {
			for( i=0; i < (PageLength/7); ++i ) putc ('\n', TempFile);
			LineNumber += PageLength/7;
		}
		else
			NewPage();
	  }
  }

#define HEADER_SIZE 3

NewPage()
  {
	if( LineNumber > HEADER_SIZE )
	  {
		if( PageNumber >= 0 ) ++PageNumber;
		BreakPage();
		LineNumber = 0;
	  }
	if( LineNumber == 0 )
		PutHeader();
  }

BreakPage()
  {
	putc (BP, TempFile);
  }

PutHeader()
  {
	register int	i, l, j;

	putc ('\n', TempFile);
	l = strlen(Name);
	for( j=0; j < 3; ++j )
	  {
		fprintf (TempFile, "%s", Name);
		if( j < 2 )
			for( i=0; i < l; ++i ) putc ('\b', TempFile);
	  }
	if( PageNumber > 0 )
	  {
		for( i = (l+7)/8; i < 9; ++i ) putc ('\t', TempFile);
		fprintf (TempFile, "Page: %d\n\n", PageNumber);
	  }
	else
	  {
		for( i = (l+7)/8; i < 7; ++i ) putc ('\t', TempFile);
		fprintf (TempFile, "%s\n\n", Today);
	  }

	LineNumber += HEADER_SIZE;
  }

#define isidchr(c)	(isalnum(c) || (c == '_'))

LooksLikeFunction(s)
register char	*s;
  {
	register char	*p;
	char		*save;
	int             Cnt,
			i,
			result;
	char		Digits[15];
	int		AddOne = 0;

	if( InComment || InString ) return(0);

	p = FunctionName;
	save = s;
	if( *s == '*' )  {
		++s;
		AddOne = 1;
	}

	if( (*s == '_') || isalpha(*s) )
	  {
		while( isidchr(*s) )
			*p++ = *s++;
		*p = '\0';
		for (i = 0; ReservedWord[i][0]; i++) {
			if ( ! (result = strcmp (FunctionName, ReservedWord[i])))
				return (0);
			if  (result < 0)
				break;
		}
		while( *s == ' ' ) ++s;
		if( *s != '(' ) return(0);
		while( *s ) if( *s == ')' ) break; else ++s;
		if( !*s ) return(0);

		/*
		 * This will cause the function name part of the line to
		 * be double striken.
		 */
		
		if (NumLines)  {
			sprintf (Digits,"[%d]  ", Number);
			Cnt = strlen(Digits) + AddOne;
			while (Cnt-- > 0)  putc (' ', TempFile);
			AddOne = 0;
		}

		while (*save && *save != '(')  putc (*save++, TempFile);
		putc ('\r', TempFile);

		return(1);
	  }
	return(0);
  }

char    *Toc[TOC_LEN];
int     TocPages[TOC_LEN];
int	TocCount;

AddToTableOfContents()
  {
	register int	l;
	register char	*p;

	if (TocCount >= TOC_LEN) {
		fprintf (stderr, "%s: too many table of contents entries\n", ProgName);
		exit (1);
	}
	l = strlen(FunctionName);
	p = Toc[TocCount] = (char *)malloc(l+1);
	strcpy(p, FunctionName);
	TocPages[TocCount] = PageNumber;
	++TocCount;
  }

NewFile()
  {
	register int	i, l;
	char		temp[20];

	if (TocCount >= TOC_LEN) {
		fprintf (stderr, "%s: too many table of contents entries\n", ProgName);
		exit (1);
	}
	Toc[TocCount] = (char *)malloc(130);
	sprintf (Toc[TocCount], "\n\tFile: %s ", Name);
	l = strlen(Toc[TocCount]) - 1;
	if( l < 64 )
	  {
		i = (64 - l) / 8;
		for( l=0; l < i; ++l ) strcat(Toc[TocCount], "\t");
	  }
	sprintf (temp, "  Page %d\n", PageNumber);
	strcat(Toc[TocCount], temp);
	++TocCount;

	if (NumLines)
		Number  = 1;
  }

DumpTableOfContents()
  {
	register int	i, j, l;

	if( TocCount == 0 ) return;

	if (WantSorted)
		SortTableOfContents();

	Name = "Table of Contents";

	PageNumber = -1;
	LineNumber = 0;
	NewPage();

	for( i=0; i < TocCount; ++i )
	  {
		if( Toc[i][0] == '\n' )
		  {
			if( (LineNumber + 5) > PageEnd )
				NewPage();
			printf("%s", Toc[i]);
			LineNumber += 2;
			continue;
		  }
		if( ++LineNumber > PageEnd )
			NewPage();
		printf("\t\t%s ", Toc[i]);
		l = strlen(Toc[i]);
		for( j=l; j < 48; ++j ) putchar('.');
		printf(" %d\n", TocPages[i]);
	  }
	putchar(BP);
	fflush (stdout);
  }

SortTableOfContents()
{
	register int    i,
			tempint;
	char   *tempchar;
	int     flag;

	do {
		flag = 0;
		for (i = 0; i < TocCount - 1; i++) {
			if (Toc[i][0] == '\n' || Toc[i+1][0] == '\n')
				continue;       /* don't sort across file names */
			if (strcmp (Toc[i], Toc[i+1]) > 0) {
				tempchar = Toc[i];
				Toc[i] = Toc[i+1];
				Toc[i+1] = tempchar;
				tempint = TocPages[i];
				TocPages[i] = TocPages[i+1];
				TocPages[i+1] = tempint;
				flag = 1;
			}
		}
	} while (flag);
}

X/*
 *	This is the function substr().  The calling sequence is:
 *
 *			substr(string, startpos, endpos)
 *
 *	The function returns a pointer to a static string (written over
 *	on subsequent calls) which is a substring of the string `string'
 *	starting at `startpos' (the first position is 0 (zero)) and ending
 *	at `endpos' (non-inclusive).  All arguments must be present or
 *	strange things happen with the system stack.
 *
 *	An example of the use is:
 *
 *		x = substr(string, 2, 5);
 *		(where string == "This is a test.")
 *
 *	This call returns a pointer to:
 *
 *		"is "
 *
 *	An error code of -1 is returned is the `endpos' is greater than
 *	`startpos'
 *
 *
 *						Lance E. Shepard
 */

char *
substr(string, start, end)

char	*string;
int	start;
int	end;

{

	static char  retstr[MAX_S];
	int loop1;
	int loop2;

	if (end < start)  {
		exit(-1);
	}

	for (loop2 = 0; loop2 < MAX_S; loop2++)
		retstr[loop2] = NULL;

	for (loop1 = start, loop2 = 0; string[loop1] != NULL &&
		loop1 < end && loop2 <= MAX_S; loop1++, loop2++)

		retstr[loop2] = string[loop1];

	retstr[++loop2] = NULL;

	return(retstr);

}

X/*
 *	This is the function `char *expand().'  This function takes as
 *	an argument a NULL terminated string and replaces all occurances
 *	of the tab character with 8 (eight) spaces.  The function returns
 *	a pointer to a static string which is overwritten on subsequent
 *	calls.
 */

char *
expand(string)

char *string;

{

	int count;
	static char retstr[MAX_S];

	for (count = 0; count < MAX_S; retstr[count++] = NULL)  ;

	for (count = 0; *string != NULL; count++, string++)  {

		if (*string == '\t')  {
			retstr[count] = ' ';
			while (TabDef[count] != 'T')
				retstr[++count] = ' ';
		}
		else
			retstr[count] = *string;
	}

	retstr[count] = NULL;

	return(retstr);

}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 444 cpr.c
	/bin/echo -n '	'; /bin/ls -ld cpr.c
fi