/* * 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 ";" 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) .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 #ifdefDOCUMENTATION .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 ".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: .s nameTitle 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 #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 , 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); }