[net.sources] another version of cpr

wck@ccieng5.UUCP ( BILL K. William C. King) (08/24/83)

Here is a version of the program `cpr' which has been floating around
which I have hacked up.  I've added a few options.  The first is the
`-n' option which causes lines in the source file to be numbered.  The
second option is `-o' which is used for putting One-Function-Per-Page.
In addition I have added line folding.  (A default line width of 80 is
used instead of having an option to specify the line length because I
did not feel like changing things in the table of contents which 
use this as a default.)

I have also written up a man page since I had not seen any others
floating around.

By the way, for some reason unknown to me now I changed the `-l' option
to take the space from between that and the supplied page length.

		Lance E. Shepard

		a lowly co-op from:
			Computer Consoles, Inc.
				&
			Rochester Institute of Technology

			...!seismo!rochester!ritcv!ritvp!les8070

				or for a short time longer
			...!seismo!rochester!ritcv!ccieng5!wck

________________
: 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# ] [ -n ] [ -o ]
[ file ] ...
X.SH "DESCRIPTION"
X.I Cpr
produces a printed listing of one or more
X.B `C'
X.I files.
The output is separated into pages headed by 
the name of the file (in bold face) a date, and the page number.
Function names are output in bold face.
An index (same format as table of contents, except in alphabetical order)
now follows the output file.
Lines greater than 80 columns in width (minus the width of added line numbers,
if any) are folded.
X.PP
The following options are allowed:
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 \- 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/tmp/CPR$$
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.

Rick Wise						modified original
CALCULON Corp.

Lance E. Shepard				modified original
CCI & RIT
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.
 *
 *	The single option "-l" indicates that the following argument is to be
 *	the page length used for output (changing the page length hasn't been
 *	tested much).
 *
 *	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/*

X/*
 *      Index and standard input reading added by Rick Wise, CALCULON Corp.,
 *      Rockville, MD. -- decvax!harpo!seismo!rlgvax!cvl!umcp-cs!cal-unix!wise
 */

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!!
 *
 */

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

#define BP		0xC		/* Form feed			*/
#define	MAX_S		256		/* Maximum string length	*/
#define WIDTH		80		/* Page Width			*/

FILE	*File;
int	Braces;				/* Keeps track of brace depth	*/
int	LineNumber;			/* Count output lines		*/
int	PageNumber = 1;			/* You figure this one out	*/
int	PageLength = 66;		/* Normal paper length		*/
int	PageEnd;			/* Accounts for space at bottom	*/
int	InComment;
int	InString;
int	OnePerPage = 0;
int	NumLines = 0;
int	Number = 1;
char	*Name;				/* Current file name		*/
char	FunctionName[40];
char	*ProgName;
char	*Today;

main(argc, argv)

int argc;
char	**argv;
  {
	register int	i = 1;
	char		*ctime();
	long		thetime, time();

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


	while (argv[i][0] == '-')  {
		switch (argv[i][1])  {
			case 'l':
				if (!isdigit(argv[i][2]))  {
					Usage();
					break;
				}
				PageLength = atoi(&argv[i][2]);
				break;
			case 'n':
				NumLines = 1;
				break;
			case 'o':
				OnePerPage = 1;
				break;
			default:
				Usage();
				break;
		}
		i++;
	}

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

	StartTempFile();

	if (i == argc) {        /* no file names */
		File = stdin;
		Name = "standard input";
		List();
	}

	for(; i < argc; ++i )  {
		if (!strcmp(argv[i], "-"))  {
			File = stdin;
			Name = "standard input";
		}
		else  {
			if( (File = fopen( Name = argv[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 )
		putchar(BP);
	ListIndex();
	EndTempFile();

	DumpTableOfContents();
	DumpTempFiles();
	Done();
  }

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

int	SaveOut;
char	*TempName;
char	*Temp2Name;

StartTempFile()
  {
	extern char	*mktemp();

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

EndTempFile()
  {
	Temp2Name = mktemp("/tmp/CPRXXXXXX");
	if( freopen(Temp2Name, "w", stdout) == NULL )
	  {
		fprintf(stderr, "%s: Can't open temp file!\n", ProgName);
		exit(1);
	  }
  }

DumpTempFiles()
  {
	register int	pid, w;

	fclose(stdout);
	dup2(SaveOut, 1);

	while( (pid = fork()) < 0 ) sleep(1);
	if( pid )
		while ((w = wait(0)) != pid && w != -1);
	else
	  {
		execl( "/bin/cat", "cat", Temp2Name, TempName, 0 );
		fprintf(stderr, "%s: exec failed!\n", ProgName);
		exit(0);
	  }
  }

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

	if( TempName )  unlink( TempName );
	if( Temp2Name ) unlink( Temp2Name );
	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 > WIDTH)  {
		section = substr(save, 0, WIDTH - Size);
		if (section[strlen(section) - 1] != ' ')  {
			offset = (int) rindex(section, ' ') - (int) section;
			section[offset] = NULL;
		}
		else
			offset = strlen(section) - 1;

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

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

				offset = (int) rindex(section, ' ') -
					(int) section;
				section[offset] = NULL;
			}
			if (section[strlen(section) - 1] == '\n')
				section[strlen(section) - 1] = NULL; 
			printf("C	%s\n", section);
			if (++LineNumber > PageEnd)
				NewPage();
		}  while ((pos += offset + 1) < strlen(save));
	}
	else  {
		if (NumLines)
			printf("[%d]  %s", Number++, save);
		else
			printf("%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 ) putchar('\n');
			LineNumber += PageLength/7;
		}
		else
			NewPage();
	  }
  }

#define HEADER_SIZE 3

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

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

	putchar('\n');
	l = strlen(Name);
	for( j=0; j < 3; ++j )
	  {
		printf("%s", Name);
		if( j < 2 )
			for( i=0; i < l; ++i ) putchar('\b');
	  }
	if( PageNumber > 0 )
	  {
		for( i = (l+7)/8; i < 9; ++i ) putchar('\t');
		printf("Page: %d\n\n", PageNumber);
	  }
	else
	  {
		for( i = (l+7)/8; i < 7; ++i ) putchar('\t');
		printf("%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;
	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';
		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)  putchar(' ');
			AddOne = 0;
		}

		while (*save && *save != '(')  putchar(*save++);
		putchar('\r');

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

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

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

	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];

	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;
	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);
  }

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')  {
			strcpy(&retstr[count], "        ");
			count += 7;
		}
		else
			retstr[count] = *string;
	}

	retstr[count] = NULL;

	return(retstr);

}

ListIndex()

{
	register int	i, j, l;
	int     index[1024],
		temp,
		flag;

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


	Name = "Index";

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

	for( i=0; i < TocCount; ++i )
	  {
		if( Toc[index[i]][0] == '\n' )
		  {
			if( (LineNumber + 5) > PageEnd )
				NewPage();
			printf("%s", Toc[index[i]]);
			LineNumber += 2;
			continue;
		  }
		if( ++LineNumber > PageEnd )
			NewPage();
		printf("\t\t%s ", Toc[index[i]]);
		l = strlen(Toc[index[i]]);
		for( j=l; j < 48; ++j ) putchar('.');
		printf(" %d\n", TocPages[index[i]]);
	  }
	putchar(BP);
  }
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 444 cpr.c
	/bin/echo -n '	'; /bin/ls -ld cpr.c
fi

raj@zeppo.UUCP (Richard A. Johnson) (08/26/83)

I tried the cpr (C print) program and noticed two problems.  First, and
mentioned in the manual page, structure declarations are treated like
functions (i.e., given extra white space after the '}').  Second, tabs
are always converted to exactly 8 spaces (this is annoying if you use tabs
to line up columns).

I fixed the first problem by looking for a ';' after the '}' (most structure
declarations that I have seen end with "};" whereas functions just end with
'}').  If there is a ';' there, the '}' is treated like any other character.
If not, then it is a function and given extra white space.

I fixed the second problem by substituting one space for the tab.  Then I add
more spaces, if necessary, until the next character position is a tab stop.

My fixes to cpr.c follow:

273c273,276
< 					bp = 1;
---
> 					if( *(l+1) == ';' )
> 						++l;
> 					else
> 						bp = 1;
613c616,617
<  *	of the tab character with 8 (eight) spaces.  The function returns
---
>  *	of the tab character with enough spaces to put the next character
>  *	at the next tab stop.  The function returns
633,634c637,639
< 			strcpy(&retstr[count], "        ");
< 			count += 7;
---
> 			retstr[count] = ' ';
> 			while (((count + 1) % 8) != 0)
> 				retstr[++count] = ' ';

Rich Johnson
BTL - Whippany
zeppo!raj