/*
 *	getrno -- Convert macro comments to .rno for documentation
 */

/*)BUILD	$(TKBOPTIONS) = {
			TASK	= ...GTR
		}
*/

#ifdef	DOCUMENTATION
title	getrno	Build Documentation Source
index		Convert Comments To Runoff Source Format

synopsis

	getrno [-options] -o output -h header input_files

description

	getrno reads a list of files, compiling comments to runoff source.
	Switches are:
	.lm +8
	.s.i -8;-d	Debugging
	.s.i -8;-c	C source files, rather than macro
	.s.i -8;-r	RSTS/E title hack flag.  See below
	.s.i -8;-u	Usage -- output an abbreviated documentation
	containing only the information between "Usage" and "Description".
	(Note: ignored if -c option selected.)
	.s.i -8;-w	Wizard:  output package internal calls, too.
	.s.i -8;-o file	Write output to "file", rather than stdout.
	.s.i -8;-h file	Prepend the contents of "file" to the output.
	.s.lm -8
	There is an error in the RSTS/E V7.0 runoff (RNO.TSK) such
	that title lines do not get correct page numbers.  If the -r
	flag is set, .t lines in the header program will be marked for
	the fixdoc.c program.  Note, for a title to be recognized, it
	must have the exact form:
	.s.nf
		.t Anything  (or .t ########Anything)
	.s.f
	Getrno will change it to:
	.s.nf
		.t [[Anything]]
	.s.f
	And fixdoc.c will look for same for post processing.

macro source file format

	The macro input MUST have the following syntax:
	.s.nf
		.title	name	Title line
	;
	;+				(Start of commentary)
	;
	; Index		Short description for kwik index
	; Index		(Another short description)
	;
	; Usage		(Start of usage information)
	;
	;	Usage information, output, starting in column 1
	;	exactly as it is written (rno .nf mode).  The
	;	leading semicolon is not output, but the tab (or
	;	spaces) is.
	;
	; Description
	;
	;	Description, output, starting in column 8 in
	;	runoff .fill mode, except that any line starting
	;	with ";<TAB><TAB or SPACE>" will be output in
	;	.no fill mode.  The leading ";<TAB>" will not be
	;	output.
	;
	;	A line consisting of just ";" will generate a
	;	runoff .space while a line consisting of
	;	";<SPACE>text" will generate ".indent -8 ;text".
	;
	;-	(End of commentary)
	.s.f
	If the wizard flag (-w) is not given, a Macro source line of
	the format:
	.s.nf
	; Internal
	.s.f
	may be used to signal the start of package-internal information.
	If "Internal" preceeds "Usage", no information will be output for
	this file.  If it follows "Usage" or "Description", text following
	(up to the next section initiator) will not be output.

C source file format

	C source must have the following format:
	.s.nf
		#ifdef	DOCUMENTATION
		title	tool_name	header_text
		index	index text
		section_head
			text for the section
		#endif
	.s.f
	Note that the comment leadin must be
	.s
		#ifdef<tab>DOCUMENTATION
	.s
	Section heads begin in column 1.  They will be left-justified
	and printed in upper case with a trailing ':'.
	.s
	All text will start in column 8.  Note:  text may contain runoff
	commands (the first char in col. 8 is '.', but it is your
	responsibililty to maintain alignment and to quote runoff-specific
	characters on lines which have a '.' in column 1.  For example:
	.s.nf
		diagnostics
			.lm +8
			.s.i-8;bad file _& other stuff
			.s.i-8;something else
			.lm -8
		author
			...
	.s.f
	Note: blank lines will not be passed to runoff:  if one
	is needed, use the runoff .space command.

Header file format

	The header file is assumed to be in runoff format.  It
	should start as follows:
	.s.nf
		.comment Set top-of-form at 4 lines
		.comment below perforation.
		.ps 58,80	! 58 lines per page, 80 columns.
		.lm 8		! left margin at 8
		.rm 72		! right margin at 72
		.hd Mixed	! Mixed case headings
	.s.f
	Because the left margin is set to 8, titles and subtitles should
	be written as:
	.s.nf 
		.t  ########Title
		.st ########Subtitle
	.s.f
	The wizard flag may be used to select parts of the header file:
	.s.nf
		.comment wizard
			*** For wizards only ***
		.comment else
			*** For non-wizards only ***
		.comment mundane
			*** Exit wizard section ***
	.s.f
	The format of the comments is EXACTLY ".comment<space>argument"
	One space only.

Workfile format:

	Each file builds one or more records (lines) in the workfile.
	The first has the title line, (information following .title).
	To allow building an index, this should be in the format:
	.s
		name<TAB>Title information
	.s
	Following this are lines containing .rno text.  The last
	line is followed by a record containing "!!" in the first
	two bytes.
	.s
	Records with "!" in the first byte may be used to communicate
	information between passes:  they are not written to the output.
	This allows writing Usage information as a seperate file.
	.s
	Note that there is a bug in the RSTS RNO (as distributed with Version
	7.0).  Consequently, if your runoff source has chapter headings,
	You should not have explicit titles, or have page numbers.)

bugs

	Wizard is ignored in C source mode.
	.s
	getrno was written to aid in documenting the Decus C libraries.
	It may not be particularily useful in other environments.
	(I.e., getrno and the library documentation are interdependent.)

diagnostics

	A warning message is printed if a file does not have any
	documentation.
	.s
	There are many other messages, hopefully self-explanatory.

author

	Martin Minow

#endif

#include	<stdio.h>
#define	EOS		0
#define	NENTRIES	200
#define	NAMESIZE	8
#define	WORKFILE	"getrno.tmp"
#define	LINESIZE	257

typedef struct {
	char	e_name[NAMESIZE];	/* Entry name		*/
	long	e_place;		/* File position	*/
} ITEM;

ITEM	entries[NENTRIES];
ITEM	*free_entry = entries;
#define	ELAST	&entries[NENTRIES]

extern	long	ftell();

int		rstshack = 0;		/* If -r flag set	*/
unsigned	linect = 0;		/* Needed for debugging	*/
int		debug = 0;		/* Set for debugging	*/
int		uflag = 0;		/* Set if -u flagged	*/
int		wizard = 0;		/* Set if -w flagged	*/
int		cflag = 0;		/* Set if -c flagged	*/

char		line[LINESIZE];
char		temptext[LINESIZE];
char		macfile[81];
char		*section;		/* For bug()		*/
char		header[LINESIZE];	/* Header file name	*/
FILE		*infd;			/* Input file		*/
FILE		*workfd;		/* Temporary file	*/
long		place;			/* For work file	*/
char		name_text[NAMESIZE];	/* for .title		*/
char		title_text[LINESIZE];	/* for .title		*/
extern char	*skipbl();

/*
 * Define  C documentation layout:
 *	.left margin	8	(16 in documentation body)
 *	.right margin	72
 * Note: various flavors of runoff give various error messages,
 * none fatal.
 */

char	*layout = ".no autosubtitle .style headers 3,0,0\n\
.pg.uc.ps 58,80.lm 8.rm 72\n.hd\n.hd mixed\n.head mixed\n";

char	*interfile = ".lm 8.rm 72.nhy\n";


main(argc, argv)
int		argc;
char		*argv[];
{
	register int	i;
	register char	*ap;
	register char	c;

	for (i = 1; i < argc; i++) {
		ap = argv[i];
		if (*ap == '-') {
			switch (c = tolower(ap[1])) {
			case 'c':
					cflag++;
					break;

			case 'd':	debug++;
					break;;

			case 'r':	rstshack++;
					break;

			case 'u':	uflag++;
					break;;

			case 'w':	wizard++;
					break;;

			default:
				argv[i] = 0;
				if (++i >= argc)
					error("?No file after \"%s\"\n", ap);
				switch (c) {

				case 'h':
					strcpy(header, argv[i]);
					break;

				case 'o':
					if (freopen(argv[i], "w", stdout)
							== NULL)
						cant("output", argv[i]);
					break;

				default:
					error("?Illegal switch '%c'\n", *ap);
				}
			}
			argv[i] = 0;		/* Erase this argument	*/
		}
	}
	if (cflag && wizard)
		fprintf(stderr, "Warning: wizard ignored in C source mode\n");
	/*
	 * Now open the work file and process all files
	 */
	if ((workfd = fopen(WORKFILE, "w")) == NULL)
		cant("work", WORKFILE);

	for (i = 1; i < argc; i++) {
		if ((ap = argv[i]) == 0)
			continue;		/* 'Twas a switch	*/
		if ((infd = fwild(ap, "r")) == NULL)
			cant("wild card input", ap);
		else {
			while (fnext(infd) != NULL) {
				filename(infd, macfile);
				if (debug)
					fprintf(stderr, "* %s\n", macfile);
				linect = 0;
				process();
			}
		}
	}
	if (debug)
		fprintf(stderr, "* EOF\n");
	/*
	 * All file information read, now write it out
	 */
	fclose(workfd);
	doheader();
	if (debug)
		fprintf(stderr, "* Header processed\n");
	if ((workfd = fopen(WORKFILE, "r")) == NULL)
		cant("work (reopening)", WORKFILE);
	puts(interfile);
	output();
	fgetname(workfd, line);
	fclose(workfd);
	delete(line);
}

filename(fd, outbuf)
FILE		*fd;
char		*outbuf;
/*
 * Get the file name, account number, and extension.
 * Remove the device name and the version number.
 */
{
	register char	*tp;
	register char	*op;
	register int	c;

	fgetname(fd, temptext);
	/*
	 * Skip over the device name
	 */
	for (tp = temptext; (c = *tp++) && c != ':';);
	if (c == 0)
		tp = temptext;
	/*
	 * Don't bother outputting the version number
	 */
	for (op = tp; (c = *op) && c != ';'; op++);
	*op = 0;
	/*
	 * Copy the file spec, forcing lowercase.
	 */
	for (op = outbuf; (c = *tp++) ;) {
		*op++ = tolower(c);
	}
	*op = 0;
}

doheader()
/*
 * Process the header file
 */
{
	register int	inwizard;
	register int	skipit;
	register char	*lp;

	if (*header == '\0')
		return;
	if ((infd = fopen(header, "r")) == NULL) {
		fprintf(stderr, "can't open ");
		perror(header);
		return;
	}
	strcpy(macfile, header);
	inwizard = 0;
	skipit = 0;
	linect = 0;
	while (getline(infd)) {
		if (match(line, ".comment wizard")) {
			if (inwizard)
				bug("headfile: embedded .comment wizard",
						NULL);
			inwizard = 1;
			skipit = !wizard;
		}
		else if (match(line, ".comment else")) {
			if (!inwizard)
				bug("headfile: else, no .comment wizard",
						NULL);
			else
				skipit = !skipit;
		}
		else if (match(line, ".comment mundane")) {
			if (!inwizard)
				bug("headfile: mundane, no .comment wizard",
						NULL);
			inwizard = 0;
			skipit = 0;
		}
		if (!skipit) {
			if (rstshack	&& line[0] == '.'
					&& tolower(line[1]) == 't'
					&& (line[2] == ' '
						|| line[2] == '\t'
						|| line[2] == ';')) {
				lp = &line[2];
				while (*lp && (*lp == ' ' || *lp == '\t'))
					lp++;
				if (*lp == ';')
					lp++;
				printf(".t [[%s]]\n", lp);
			}
			else
				printf("%s\n", line);
		}
	}
	fclose(infd);
	macfile[0] = 0;
}

static	int	fill_flag = 1;

process()
/*
 * Process input text, saving it in the workfile.
 */
{
	register int	flag;

	if (cflag)
		docstuff();
	else {
		section = "Title scan";
		title();
		section = "Usage scan";
		if ((flag = usage()) == 2)
			return;

		else if (flag && !uflag) {
			section = "Document scan";
			rest();
		}
	}
	fill_flag = 1;
	save("!!", 0);				/* Terminate entry	*/
}

docstuff()
/*
 * Process C documentation -- hack
 */
{
	register int		titlestate;

	while (getline(infd)) {
		if (match(line, "#ifdef") &&
				streq(skipbl(line+6), "DOCUMENTATION"))
			break;
	}
	if (feof(infd)) {
		fprintf(stderr, "Warning: no documentation in %s\n", macfile);
		return;
	}
	/*
	 * Do some documentation
	 */
	titlestate = 0;
	while (getline(infd)) {
		if (match(line, "title")) {
			savetitle(skipbl(&line[5]));
			titlestate = 1;
		}
		else if (match(line, "index"))
			continue;
		else {
			switch (titlestate) {
			case 0:
				if (line[0] == EOS)
					continue;
				section = "document scan";
				bug("Need a title, using", macfile);
				strcpy(name_text, macfile);
				strcpy(title_text, "");

			case 1:
				savename();
				save(".lm +8", 0);
				if (wizard) {
					concat(title_text,
						".s.i -8;File name:\t",
						macfile, NULL);
					save(title_text, 0);
					save(".s 2", 0);
				}
				concat(temptext,
					".s.i -8;NAME:\t", name_text,
					" -- ", title_text, NULL);
				save(temptext, 0);
				save(".s.f", 0);
				fill_flag = 1;
				titlestate = 2;

			case 2:			/* Write the .rno stuff	*/
				if (match(line, "#endif"))
					return;
				docline();
			}
		}
	}
}

docline()
/*
 * Process a line from a c document
 */
{
	register char		*lp;

	if (line[0] == '\t')
		save(&line[1], 0);
	else if (line[0] == ' ') {
		if (((lp = skipbl(line)) - line) == 8)
			save(lp, 0);
		else	save(&line[8], 0);
	}
	else if (line[0] != EOS) {
		/*
		 * sub-header
		 */
		for (lp = line; *lp != EOS; lp++)
			*lp = toupper(*lp);
		if (lp[-1] != ':') {
			*lp++ = ':';
			*lp = EOS;
		}
		concat(temptext, ".s.i -8;", line, NULL);
		save(temptext, 0);
		save(".s", 0);
	}
	else
		;				/* Ignore blank lines	*/
}

title()
/*
 * First, skip to the title
 */
{
	register char		*lp;
	register char		*np;

	while (getline(infd)) {
		lp = skipbl(line);
		if (!match(lp, ".title"))
			continue;
		savetitle(skipbl(lp + 6));
		break;
	}
}

savetitle(lp)
register char		*lp;
/*
 * Save the title text, on entry lp -> just after "title "
 */
{
	register char		*np;

	place = ftell(workfd);
	np = name_text;
	while (*lp > ' ' && np < &name_text[sizeof name_text - 1])
		*np++ = *lp++;
	while (np < &name_text[sizeof name_text])
		*np++ = 0;
	strcpy(title_text, skipbl(lp));
}

savename()
/*
 * Save info. in name/place which were setup by title()
 */
{
	register ITEM	*ep;
	register ITEM	*lastep;

	/*
	 * Save in sorted order
	 */
	if ((lastep = free_entry++) >= ELAST)
		error("?Too many files, %d maximum", NENTRIES);
	for (ep = entries; ep < lastep &&
			strcmp(ep->e_name, name_text) <= 0; ep++);
	for (; lastep > ep; lastep--) {
		lastep->e_place = (lastep-1)->e_place;
		copy(lastep->e_name, (lastep-1)->e_name, NAMESIZE);
	}
	ep->e_place = place;
	copy(ep->e_name, name_text, NAMESIZE);
	/*
	 * Save the title line
	 */
	save((title_text[0]) ? title_text : "!", 1);	/* Title line	*/
	fill_flag = 1;
}
#ifdef	vms
copy(out, in, count)
char		*out;
char		*in;
int		count;
/*
 * Copy a buffer -- not in vms library
 */
{
	while (--count >= 0) {
		*out++ = *in++;
	}
}
#endif

int
usage()
/*
 * Skip to ";+" (ignored what preceeds), then to "; Usage"
 * and put out usage section.  Return 1 if ok, 0 if trouble.
 */
{
	register char	*lp;
	register int	usage_seen;
	register int	skipit;

	skipit = 0;
	for (;;) {				/* Skip to ;+		*/
		if (!getline(infd)) {
			fprintf(stderr, "Warning, no documentation for %s\n",
				macfile);
			return(0);		/* Ignore if none	*/
		}
		if (line[0] == ';' && line[1] == '+')
			break;
	}
	usage_seen = 0;
	while (getline(infd)) {
		if (line[0] != ';') {
			bug("Expecting ';', got", line);
			return(0);
		}
		else if (line[1] == 0) {
			if (usage_seen && !skipit)
				save(&line[1], 1);
			continue;
		}
		else if (line[1] == '-') {
			bug("No Description, etc.", NULL);
			return(0);
		}
		else if (line[1] == ' ') {
			if (match(&line[2], "internal")) {
				if (wizard) {		/* Do internal	*/
					if (usage_seen)
						unjust();
					continue;
				}
				else if (usage_seen) {
					skipit++;
					continue;
				}
				else	return(2);
			}
			else if (usage_seen == 0) {
				if (match(&line[2], "index"))
					continue;
				if (match(&line[2], "usage")
				 || match(&line[2], "synopsis")) {
					savename();
					usage_seen++;
					save(".lm +8.nf", 0);
					if (wizard) {
						concat(line,
						    ".s.i -8;File name:\t",
						    macfile, 0);
						save(line, 0);
						save(".s 2", 0);
					}
					save(".i -8;Usage", 0);
					save("!b", 0);
					fill_flag = 0;
					continue;
				}
			}
			if (line[2] >= 'A') {
				if (usage_seen)
					save("!e", 0);
				return(1);
			}
			else if (usage_seen && line[2] == ' ') {
				if (!skipit) {
					unjust();
				}
				continue;
			}
		}
		else if (usage_seen) {
			if (line[1] == '\t') {
				if (!skipit)
					save(&line[2], 1);
				continue;
			}
		}
		bug("Ununderstandable line", line);
		return(0);
	}
	bug("No ;- at end of file", NULL);
	return(0);
}

rest()
/*
 * Output the remainder of the commentary.
 * The line buffer contains "; Description"
 */
{
	register int	skipit;

	skipit = 0;
	indent(&line[2]);
	while (getline(infd)) {
		if (line[0] != ';') {
			bug("Line doesn't start with a ';'", line);
			continue;
		}
		else if (line[1] == '-') {
			save(".lm -8.fill", 0);
			fill_flag = 1;
			return;
		}
		else if (line[1] == 0) {
			if (!skipit)
				save(&line[1], 1);
		}
		else if (line[1] == ' ') {
			if (line[2] < 'A') {
				if (!skipit) {
					unjust();
				}
			}
			else {
				if (!wizard) {
					if (match(&line[2], "internal"))
						skipit++;
					else	skipit = 0;
				}
				if (!skipit)
					indent(&line[2]);
			}
			continue;
		}
		else if (line[1] == '\t') {
			if (skipit)
				continue;
			else if (line[2] <= ' ') {
				nofill();
				save(&line[2], 1);
			}
			else {
				fill();
				save(&line[2], 1);
			}
			continue;
		}
		else
			bug("Ununderstandable line", line);
	}
	bug("Unexpected end of file", NULL);
	save(".lm -8.fill", 0);
}

unjust()
/*
 * Unjustify the line
 */
{
	register char	*lp;

	line[0] = '#' | 0200;
	for (lp = line + 1; *lp == ' ';)
		*lp++ = '#' | 0200;
	indent(line);
}

save(text, dotflag)
char		*text;
int		dotflag;
/*
 * Write the text to the work file.  If dotflag is set, initial '.'
 * is quoted.
 */
{
	if (dotflag && *text == '.')
		putc('_', workfd);
	fprintf(workfd, "%s\n", (*text != 0) ? text : ".s");
}

indent(text)
char		*text;
/*
 * Save an indented text item
 */
{

	sprintf(temptext, ".i -8;%s", text);
	save(temptext, 0);
}

fill()
/*
 * Turn on fill mode
 */
{
	if (!fill_flag) {
		save(".fill", 0);
		fill_flag = 1;
	}
}

nofill()
/*
 * Turn off fill mode
 */
{
	if (fill_flag) {
		save(".nf", 0);
		fill_flag = 0;
	}
}

int
match(
text, lookfor)
char		*text;
register char	*lookfor;
/*
 * If the beginning of text matches lookfor (ignoring case),
 * return true.  lookfor must always be in lowercase.
 */
{
	register int	c;
	register int	l;

	while ((l = *lookfor++) != 0) {
		if ((c = *text++) != l && (c + 040) != l)
			return(0);
	}
	return(1);
}

output()
/*
 * Write out all records
 */
{
	register int		namlen;
	register int		stars;
	register ITEM		*ep;

	if (cflag) {
		puts(layout);
	}
	for (ep = entries; ep < free_entry; ep++) {
		fseek(workfd, ep->e_place, 0);
		if (!getline(workfd)) {
			bug("Can't read record", NULL);
			fprintf(stderr, "rfa = %08o\n", ep->e_place);
			continue;
		}
		if (uflag) {
			printf(".tp 10.s 4.lm +8.nf\n.i -8 ;%s",
				ep->e_name);
			if (line[0] != '!')
				printf("\t%s", line);
		}
		else {
			printf(".st ########%s", ep->e_name);
			if (line[0] != '!') {
				namlen = 8 - (strlen(ep->e_name) & 7);
				while (--namlen >= 0)
					putchar('#');
				printf("%s", line);
			}
			printf("\n.pg\n.hl 1 ");
			if (line[0] != '!')
				printf("^&%s\\&\n", line);
			else
				printf("#\n");
			printf(".s 2\n.c ;");
			namlen = strlen(ep -> e_name) + 4;
			for (stars = namlen; --stars >= 0;)
				putchar('*');
			printf("\n.c ;* %s *\n.c ;", ep -> e_name);
			for (stars = namlen; --stars >= 0;)
				putchar('*');
		}
		printf("\n.s 2\n");
		if (uflag) {
			/*
			 * Usage
			 */
			while (getline(workfd) &&
					line[0] != '!' && line[1] != 'b');
			while (getline(workfd)) {
				if (line[0] == '!' && (line[1] == 'e'
						|| line[1] == '!'))
					break;
				writeout(line);
			}
		}
		else {
			/*
			 * Not usage
			 */
			while (getline(workfd)) {
				if (line[0] == '!') {
					if (line[1] == '!') {
						break;
					}
				}
				else if (cflag && line[0] == '.')
					puts(line);
				else
					writeout(line);
			}
		}
		puts(interfile);
	}
}

writeout(text)
char	*text;
/*
 * Write this line to the .rno output, watching out for weird .rno
 * characters.  Note: to put a wierd .rno character out, flag it with
 * an initial underline.
 */
{
	register char	c;
	register char	*tp;

	tp = text;
	while ((c = *tp++) != 0) {
		switch (c) {
		case	'_':
				if (tp - text == 1 && *tp == '.')
					goto ignore;
		case	'%':
		case	'&':
		case	'\\':
		case	'^':
		case	'#':
				putchar('_');
		default:
ignore:				putchar(c & 0177);
		}
	}
	putchar('\n');
}

getline(fd)
FILE	*fd;
/*
 * Read a line from fd into line[].  Return 0 at end of file.
 */
{
	register char	*lp;

	if (fgets(line, sizeof line, fd) == NULL) {
		if (debug)
			fprintf(stderr, "* end of \"%s\" after %u lines\n",
				macfile, linect);
		return(0);
	}
	linect++;
	/*
	 * Erase trailing <Newline>, spaces, and tabs.  Note that
	 * line[strlen(line)] is the newline.
	 */
	for (lp = &line[strlen(line)]; lp > line && lp[-1] <= ' '; lp--);
	*lp = 0;
	if (debug > 1)
		fprintf(stderr, "\"%s\"\n", line);
	return(1);
}

char
*skipbl(text)
register char	*text;
/*
 * Skip blanks, return -> first non blank (or -> trailing null)
 */
{
	register char	c;

	while ((c = *text) && c <= ' ')
		text++;
	return(text);
}

bug(mess, arg)
char		*mess;
char		*arg;
{
	fprintf(stderr, "?GETRNO-E-Confused at %s", section);
	if (macfile[0])
		fprintf(stderr, " at line %u in file \"%s\"\n",
			linect, macfile);
	fprintf(stderr, "%s", mess);
	if (arg != NULL)
		fprintf(stderr, ": \"%s\"", arg);
	fprintf(stderr, "\n");
}

cant(what, who)
char		*what;
char		*who;
/*
 * Can't open the file, die
 */
{
	error("?Can't open %s file \"%s\", fatal.\n", what, who);
}
