/* * 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 -d Debugging -b Blank lines in C source become .s -c C source files (default) -m Macro source files -r RSTS/E title hack flag; see below -u Usage -- output an abbreviated documentation containing only the information between "Usage" and "Description". (Note: ignored if -c option selected.) -w Wizard: output package internal calls, too. -o file Write output to "file", rather than stdout. -h file Process "file" as a header file and prepend to the output. .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: .t Anything (or .t ########Anything) Getrno will change it to: .t [[Anything]] and fixdoc.c will look for same for post processing. .tp 10 C source file format C source must have the following format: #ifdef DOCUMENTATION title tool_name header_text index index text section_head text for the section #endif Note that the comment leadin must be #ifdefDOCUMENTATION .tp 8 In order to define a uniform environment, getrno will insert the following commands at the beginning of the output file: .no autosubtitle .style headers 3,0,0 .pg.uc.ps 58,80.lm 8.rm 72 .hd .hd mixed .head mixed (Various flavors of runoff will flag one or more of these commands as errors, none of them fatal.) Getrno inserts the following commands between the data from each pair of files scanned: .lm 8.rm 72.nhy Within the body of the documentation, lines are handled depending on what column their text starts in (i.e. where the first non-, non- character falls); a is always assumed to put the next character at column 8. (And you thought card images were dead!) An empty line becomes a .space command. Section heads begin in column 1. They will be left-justified and printed in upper case with a trailing ":". The only section heads with any special meaning to getrno are "title", "index", and "internal"; they are recognized regardless of case. "Title" and "index" must come first, as shown in the example. "Internal" flags the beginning of data that is output only if the -w switch was used; the next section head ends the "wizards only" data. However, if "internal" appears before any other section head (not counting "index"), nothing at all will be output for this file unless -w appears. Normal text starts in column 8 with a character other than a or ; it will be justified and filled, and will start in column 16 on the paper. The 's and 's that filled the first 7 columns are stripped. A line that has a or in column 8 will be output with the 's and 's that filled the first 7 columns stripped, and with runoff set to nofill mode. A line that starts with a but has a non-blank before column 8 will have the leading 's stripped and will be output with a leading ".i -8;". Lines of this form are normally used between ".lm +8"/".lm -8" pairs, as shown in the example below. A line whose first character AFTER stripping leading blanks as defined above is a "." has leading runoff commands. It is your responsibility to maintain alignment and to quote runoff-specific characters on such lines; in all other cases, getrno will do it for you. For example: .tp 9 diagnostics .lm +8 .s.i-8;bad file _& other stuff .s.i-8;something else .lm -8 .s author etc... Avoid manipulating fill mode directly. Getrno keeps track of whether it has set runoff to fill or nofill mode, and can get confused if you shift modes on it. Hence, the results may then not be as you expect. Note that the above example can be more easily handled as: .tp 11 diagnostics .lm +8 bad file & other stuff something else .lm -8 author etc... Macro source file format The Macro input must have the following syntax: .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 ";" will be output in ; .no fill mode. The leading ";" will not be ; output. ; ; A line consisting of just ";" will generate a ; runoff .space while a line consisting of ; ";text" will generate ".indent -8 ;text". ; ;- (End of commentary) If the wizard flag (-w) is not given, a Macro source line of the format: ; Internal 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. Header file format The header file is assumed to be in runoff format. It should start as follows: .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 Because the left margin is set to 8, titles and subtitles should be written as: .t ########Title .st ########Subtitle The wizard flag may be used to select parts of the header file: .comment wizard *** For wizards only *** .comment else *** For non-wizards only *** .comment mundane *** Exit wizard section *** A line indicating the date on which the documentation was built, and optionally some other information, can be inserted by: .comment date[ info] The optional info will be included on the date line, which is centered. A single must appear after "date" if info appears; it is not part of info. The format of the date line is: Document compiled [info] The date inserted is in the format returned by ctime() (so it's actually the date and time). The format of all the comments is EXACTLY ".commentargument" - 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: nameTitle information Following this are lines containing .rno text. The last line is followed by a record containing "!!" in the first two bytes. 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 separate file. 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 Getrno was written to aid in documenting the Decus C libraries. (I.e., getrno and the library documentation are interdependent.) An attempt has been made to make the C-style processing generally useful; the Macro-style processing is more closely tied to the library documentation and is probably of less general interest. Further, the program, particularly in Macro mode, is very sensitive to the exact format of the input; minor variations can produce unexpected results. Some attempt has been made to avoid this in C-style processing; in Macro mode, you are on your own. Due to the size of the files involved, the usage option (-u) is no longer used in building library documentation, and is not supported in C-mode. Perhaps it should be. There should be a way to pass a "#" through to runoff within a line that is otherwise processed normally. All files are processed in the same mode. It might be useful to allow mixed-mode processing so that libraries consisting of both Macro and C modules could be handled. diagnostics A warning message is printed if a file does not have any documentation. There are many other messages, hopefully self-explanatory. author Martin Minow #endif /* Revision History * 0.0 ??-???-?? MM Invention * 1.0 ??-???-?? MM Added support for C source. * 1.1 24-Jun-82 JSL Added ".comment date" * 2.0 28-Jun-82 JSL Many minor cleanups. Support 8-bit ascii. * 2.1 30-Jun-82 JSL Go by columns instead of leading /space; * added -m, made C mode the default. * 2.2 16-Jul-82 JSL Indented lines have RUNOFF char's escaped. */ #include #ifndef nomacarg #include #endif #define EOS 0 #define FALSE 0 #define TRUE 1 #define REALSHARP '\001' #define NENTRIES 200 #define NAMESIZE 17 #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 = TRUE; /* Set if -c flagged */ int fill_flag = TRUE; /* If runoff will fill */ 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]; /* From title header */ char title_text[LINESIZE]; /* From title header */ 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 = TRUE; break; case 'd': debug++; break; case 'm': cflag = FALSE; break; case 'r': rstshack++; break; case 'u': uflag++; break; case 'w': wizard++; break; default: switch (c) { case 'h': if (++i >= argc) error("?No file after -h\n"); strcpy(header, argv[i]); break; case 'o': if (++i >= argc) error("?No file after -o\n"); if (freopen(argv[i], "w", stdout) == NULL) cant("output", argv[i]); break; default: error("?Illegal switch '%c'\n",c); } argv[i-1] = NULL; } argv[i] = NULL; /* Erase this argument */ } } /* * 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]) == NULL) 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 == EOS) tp = temptext; /* * Don't bother outputting the version number */ for (op = tp; (c = *op) && c != ';'; op++); *op = EOS; /* * Copy the file spec, forcing lowercase. */ for (op = outbuf; (c = *tp++) ;) { *op++ = tolower(c); } *op = EOS; } doheader() /* * Process the header file */ { register int inwizard; register int skipit; register char *lp; if (*header == EOS) return; if ((infd = fopen(header, "r")) == NULL) { fprintf(stderr, "can't open "); perror(header); return; } strcpy(macfile, header); inwizard = FALSE; skipit = FALSE; linect = 0; while (getline(infd)) { if (match(line, ".comment wizard")) { if (inwizard) bug("headfile: embedded .comment wizard", NULL); inwizard = TRUE; 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 = FALSE; skipit = FALSE; } if (!skipit) { if (rstshack && line[0] == '.' && tolower(line[1]) == 't' && (isspace(line[2]) || line[2] == ';')) { lp = &line[2]; while ((*lp != EOS) && isspace(*lp)) lp++; if (*lp == ';') lp++; printf(".t [[%s]]\n", lp); } else if ((lp = match(line, ".comment date")) && (isspace(*lp) || *lp == EOS)) { printf(".c ;Document compiled %s%s\n", ctime(0), lp); } else printf("%s\n", line); } } fclose(infd); macfile[0] = EOS; } process() /* * Process input text, saving it in the workfile. */ { register int flag; if (cflag) { if (docstuff()) return; } else { section = "Title scan"; title(); section = "Usage scan"; if ((flag = usage()) == 2) return; else if (flag && !uflag) { section = "Document scan"; rest(); } } fill_flag = TRUE; save("!!", FALSE); /* Terminate entry */ } docstuff() /* * Process C documentation. Return FALSE if all OK, TRUE if output for this * file should be discarded. */ { register int titlestate; register int skipit; skipit = FALSE; 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 (TRUE); } /* * Do some documentation */ titlestate = 0; /* No title seen yet */ while (getline(infd)) { if (match(line, "title")) { savetitle(skipbl(&line[5])); titlestate = 1; /* Title, no real */ /* section heads yet */ } 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, ""); /* * Fall through */ case 1: if (line[0] == EOS) continue; if (!wizard && match(line, "internal")) return (TRUE); savename(); save(".lm +8", FALSE); if (wizard) { concat(temptext, ".s.i -8;File name:\t", macfile, NULL); save(temptext, FALSE); save(".s 2", FALSE); } concat(temptext, ".s.i -8;NAME:\t", name_text, " -- ", title_text, NULL); save(temptext, FALSE); save(".s.f", FALSE); fill_flag = TRUE; titlestate = 2; /* * Fall through */ case 2: /* Write the .rno stuff */ if (match(line, "#endif")) return(FALSE); if (!wizard && isgraph(line[0])) skipit = match(line, "internal"); if (!skipit) docline(); } } } return (FALSE); } docline() /* * Process a line from a c document */ { register char *lp; register int skip; switch(line[0]) { case EOS: save("",FALSE); break; case ' ': for (skip = 1; skip < 8; skip++) if (line[skip] == '\t') goto tabcase; else if (line[skip] != ' ') break; fill(); /* The following two save()'s are done in two calls so that they end */ /* up on two lines; otherwise, runoff characters in the line won't */ /* get "escaped". */ save(".i -8",FALSE); save(&line[skip],FALSE); break; case '\t': skip = 0; tabcase: if (line[++skip] == ' ' || line[skip] == '\t') nofill(); else fill(); save(&line[skip],FALSE); break; default: /* * sub-header */ for (lp = line; *lp != EOS; lp++) *lp = toupper(*lp); if (lp[-1] != ':') { *lp++ = ':'; *lp = EOS; } fill(); concat(temptext, ".i -8;", line, NULL); save(temptext, FALSE); break; } } 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 - 2]) { *np++ = *lp; if (*lp++ == '_') /* Needed for runoff */ *np++ = '_'; } while (np < &name_text[sizeof name_text]) *np++ = EOS; 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 : "!", TRUE); /* Title line */ fill_flag = TRUE; } #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, 2 to skip. */ { register char *lp; register int usage_seen; register int skipit; skipit = FALSE; 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 = TRUE; continue; } else return(2); } else if (usage_seen == FALSE) { if (match(&line[2], "index")) continue; if (match(&line[2], "usage") || match(&line[2], "synopsis")) { savename(); usage_seen = TRUE; save(".lm +8.nf", FALSE); if (wizard) { concat(line, ".s.i -8;File name:\t", macfile, NULL); save(line, FALSE); save(".s 2", FALSE); } save(".i -8;Usage", FALSE); save("!b", FALSE); fill_flag = FALSE; continue; } } if (line[2] >= 'A') { if (usage_seen) save("!e", FALSE); 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], TRUE); 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", FALSE); fill_flag = 1; return; } else if (line[1] == EOS) { 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], TRUE); } else { fill(); save(&line[2], TRUE); } continue; } else bug("Ununderstandable line", line); } bug("Unexpected end of file", NULL); save(".lm -8.fill", FALSE); } unjust() /* * Unjustify the line */ { register char *lp; line[0] = REALSHARP; for (lp = line + 1; *lp == ' ';) *lp++ = REALSHARP; 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, FALSE); } fill() /* * Turn on fill mode */ { if (!fill_flag) { save(".fill", FALSE); fill_flag = TRUE; } } nofill() /* * Turn off fill mode */ { if (fill_flag) { save(".nf", FALSE); fill_flag = FALSE; } } int match(text, lookfor) register char *text; register char *lookfor; /* * If the beginning of text matches lookfor (ignoring case), followed by a * no-alphanumeric, returns pointer to the first thing after lookfor; * otherwise, returns NULL. lookfor must always be in lowercase. */ { while (*lookfor != EOS) { if (tolower(*text++) != *lookfor++) return(NULL); } return(isalnum(*text) ? NULL : text); } output() /* * Write out all records */ { register int namlen; register char *tp; 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 = 4; for (tp = ep->e_name; *tp != EOS; tp++) { if (*tp == '_') /* Hack */ tp++; namlen++; } 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 REALSHARP: putchar('#'); break; case '_': if (tp - text == 1 && *tp == '.') goto ignore; case '%': case '&': case '\\': case '^': case '#': putchar('_'); default: ignore: putchar(c); } } 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 , 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); }