/*****************************************************************************/ /* wasDOC.C WASD document system. Yet Another Markup Language (YAML). In WASD's inimitable style of course :-/ A CLI/CGI/CGIplus utility to read directories containing files and output them as HTML documents. Suitable for VMS Apache, OSU and of course WASD. Directives use an embedded vertical bar symbol (Vbar). To escape a literal Vbar prefix it with a backslash. In between the Vbars are characters interpreted by the WASD document processor. See RENDER.C for a more detailed description of tag syntax. At startup wasDOC reads all .WASDOC files in the directory, assembling an in-memory copy of the text of those files, and from that creating an in-memory HTML representation of the content. As a CGIplus script, at a maximum default periuod of once a minute, each .WASDOC file is checked for modification since the document was processed and if modified the document is recreated. Table of content is generated by major and minor headings in the order processed. A referencing mechanism provides internal and external links. Use /cgi-bin/wasdoc/wasd_root/src/wasdoc/doc/ to view the wasDOC documentation. Alternatively, access /wasd_root/src/wasdoc/doc/index.html to access a static version of the same document. Copyright (C) 2019-2021 Mark G.Daniel ------------------------------------- Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. VERSION HISTORY --------------- 11-JUL-2020 MGD v2.0.0, multi-pass HTML generation (pleasing to see this reduce elapsed and CPU times to ~60% of v1.n - which was clunky(er) in spots) 26-JAN-2020 MGD v1.2.0, box drawing using |draw| bugfix; Navig8ReferText() if (ch == 'Z') ch = 0x7f; 14-JUL-2019 MGD v1.1.0, Bonne Fête Nationale expand conditionals bugfix; broken nested conditionals 28-JUN-2019 MGD v1.0.0, initial release 21-FEB-2019 MGD initial development */ /*****************************************************************************/ #define COPYRIGHT_DATE "2019,2020" #define SOFTWAREVN "2.0.0" #define SOFTWARENM "WASDOC" #ifdef __ALPHA # define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN #endif #ifdef __ia64 # define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN #endif #ifdef __VAX # define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN #endif #ifdef __x86_64 # define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN #endif #include "wasdoc.h" #include #include #include int dbug, isCgi, isCgiPlus, CliChunk; int bvbar = 166, ellip = 133, multx = 215; char *CliDocDir, *CliIndex, *CliOutput; char CopyrightDate [] = COPYRIGHT_DATE, DefaultType [64] = DEFAULT_FILE_TYPE, SoftwareID [64], SoftwareVersion [] = SOFTWAREVN, Utility [] = "WASDOC"; /*****************************************************************************/ /* */ int main (int argc, char *argv[]) { CgiAccess (NULL); sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CGILIB_SOFTWAREID); CgiLibEnvironmentInit (argc, argv, 0); isCgi = CgiLibEnvironmentIsWasd() || (isCgiPlus = CgiLibEnvironmentIsCgiPlus()) || CgiLibEnvironmentIsApache() || CgiLibEnvironmentIsOsu() || CgiLibVarNull ("SERVER_SOFTWARE"); if (isCgi) CgiDoc (); else CliDoc (argc, argv); } /*****************************************************************************/ /* Provide the infrastructure for the |insight@| facility. NOTE: writes directly to . */ void wasDocInsightAt (struct wasdoc_st *docptr) { char *cptr, *hptr, *sptr, *tptr, *zptr; if (!docptr->isDynamic || !docptr->insight) return; if (docptr->chunked > 0) { /* document provided in "chunks", check this chunk has the buttons */ for (hptr = docptr->html; *hptr; hptr++) { if (*hptr != '<' || !MATCH4 (hptr, "", 30)) continue; break; } /* if not found in this section */ if (!*hptr) return; } fprintf (stdout, "\n\n", docptr->ftotal, docptr->tlength, docptr->hlength, docptr->statistics); } /*****************************************************************************/ /* Initialise and return a document structure. */ struct wasdoc_st* InitDoc ( char *uri, char *pchunk, char *pinfo, char *dname ) { char *cptr, *sptr, *zptr; struct wasdoc_st *docptr; docptr = calloc (1, sizeof(struct wasdoc_st)); if (!docptr) exit (vaxc$errno); if (uri) { zptr = (sptr = docptr->uri) + sizeof(docptr->uri)-1; for (cptr = uri; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } if (pchunk && *pchunk) { docptr->chunked = 1; docptr->pchunk = atoi(pchunk); } if (pinfo) { zptr = (sptr = docptr->pinfo) + sizeof(docptr->pinfo)-1; for (cptr = pinfo; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; } zptr = (sptr = docptr->dname) + sizeof(docptr->dname)-1; for (cptr = dname; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; zptr = (sptr = docptr->ftype) + sizeof(docptr->ftype)-1; for (cptr = DefaultType; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; docptr->ftlength = sptr - docptr->ftype; docptr->setToc2 = DEFAULT_TOC2; docptr->setToc2Cols = DEFAULT_TOC2_COLS; docptr->setTocCols = DEFAULT_TOC_COLS; docptr->setIdxCols = DEFAULT_IDX_COLS; docptr->setIdxSort = DEFAULT_IDX_SORT; docptr->setNavigate = DEFAULT_NAVIGATE; docptr->setPaginate = DEFAULT_PAGINATE; strcpy (docptr->setTocForm, DEFAULT_TOC_SCHEMA); strcpy (docptr->iconBack, DEFAULT_ICON_BACK); strcpy (docptr->iconForw, DEFAULT_ICON_FORW); strcpy (docptr->iconNext, DEFAULT_ICON_NEXT); strcpy (docptr->iconPrev, DEFAULT_ICON_PREV); strcpy (docptr->iconTop, DEFAULT_ICON_TOP); strcpy (docptr->idxCollate, DEFAULT_COLLATE_EN); return (docptr); } /*****************************************************************************/ /* Free a document structure. */ void* FreeDoc (struct wasdoc_st *docptr) { int idx; struct wasdoc_st *tocptr; if (!docptr) return (NULL); free (docptr->html); free (docptr->html1); free (docptr->list); free (docptr->text); free (docptr->spawnOutPtr); for (idx = 0; idx < docptr->flagCount; idx++) free (docptr->flags[idx]); free (docptr); return (NULL); } /*****************************************************************************/ /* Formatted print to the document HTML buffer. */ int wasDocPrintf ( struct wasdoc_st* docptr, const char *fmt, ... ) { int count, size; char *bptr; va_list args; if (dbug>2) dbugThis (FI_LI, "wasDocPrintf() %d/%d %s", docptr->hlength, docptr->hsize, fmt); if (HMORE(docptr)) wasDocMoreHtml (docptr); vaxc$errno = 0; /* hmmm, shouldn't need doing */ docptr->errorValue = 0; for (;;) { size = docptr->hsize - docptr->hlength; bptr = docptr->html + docptr->hlength; va_start (args, fmt); if (dbug>99) { fputs ("+++++\n", stdout); vfprintf (stdout, fmt, args); fputs ("\n+++++", stdout); } count = vsnprintf (bptr, size, fmt, args); va_end (args); if (count < size) break; if (count < 0) break; wasDocMoreHtml (docptr); } if (count < 0) docptr->errorValue = vaxc$errno; else docptr->hlength += count; if (dbug>2) dbugThis (FI_LI, "%d %d/%d %d %d %s", count, docptr->hlength, docptr->hsize, docptr->errorValue, vaxc$errno, strerror(EVMSERR, vaxc$errno)); return (docptr->errorValue); } /*****************************************************************************/ /* Increase the size of the HTML buffer. More is set at 20% of the increment. */ void wasDocMoreHtml (struct wasdoc_st *docptr) { docptr->errorValue = 0; if (docptr->hlength > docptr->hsize) wasDocBugcheck (FI_LI); docptr->hsize += HTML_INCREMENT; docptr->hmore = docptr->hsize - (HTML_INCREMENT / 5); docptr->html = realloc (docptr->html, docptr->hsize); if (!docptr->html) EXIT_FI_LI (vaxc$errno); } /*****************************************************************************/ /* Move the first pass HTML to second from the current and reinitialise current. */ void wasDocHtmlShuffle (struct wasdoc_st *docptr) { docptr->html1 = docptr->html; docptr->hlength1 = docptr->hlength; docptr->html = NULL; docptr->hlength = docptr->hmore = docptr->hsize = 0; } /*****************************************************************************/ /* Insert (append) the supplied text without any other processing. */ void wasDocAsIs ( struct wasdoc_st *docptr, char *text ) { char *sptr, *tptr, *zptr; if (dbug>1) dbugThis (FI_LI, "wasDocAsIs() %s", dbugAll(text)); if (!text) return; tptr = text; sptr = docptr->html + docptr->hlength; zptr = docptr->html + docptr->hmore; while (*tptr) { if (HMORE(docptr)) { wasDocMoreHtml (docptr); sptr = docptr->html + docptr->hlength; zptr = docptr->html + docptr->hmore; } while (*tptr && sptr < zptr) *sptr++ = *tptr++; *sptr = '\0'; docptr->hlength = sptr - docptr->html; } } /*****************************************************************************/ /* Insert (append) the supplied text while entity-fying required characters. */ void wasDocEntify ( struct wasdoc_st *docptr, char *text ) { char *cptr, *sptr, *tptr, *zptr; if (dbug>1) dbugThis (FI_LI, "wasDocEntify() %s", dbugAll(text)); if (!text) return; tptr = text; sptr = docptr->html + docptr->hlength; zptr = docptr->html + docptr->hmore; while (*tptr) { if (HMORE(docptr)) { wasDocMoreHtml (docptr); sptr = docptr->html + docptr->hlength; zptr = docptr->html + docptr->hmore; } while (*tptr && sptr < zptr) { cptr = ""; switch (*tptr) { case '<' : cptr = "<"; break; case '>' : cptr = ">"; break; case '&' : cptr = "&"; break; case '|' : cptr = "|"; break; case '\"' : cptr = """; break; case '\\' : cptr = "\"; break; default : *sptr++ = *tptr; } tptr++; while (*cptr) *sptr++ = *cptr++; } *sptr = '\0'; docptr->hlength = sptr - docptr->html; } } /*****************************************************************************/ /* Parse to find the specified MAJOR section identified by the fragment ID leading digit. It automatically includes the document prologue (e.g. style). */ int wasDocChunkOut (struct wasdoc_st *docptr) { int count, fragnum, len, offset1, offset2; char *cptr, *lptr, *sptr, *title = NULL; char buf [1024]; if (dbug>1) dbugThis (FI_LI, "wasDocChunkOut() %d", docptr->pchunk); if (!docptr->list) RETURN_FI_LI (docptr, SS$_ABORT) /* first heading defines the extent of document prologue */ docptr->proLength = 0; lptr = docptr->list; count = *lptr++; if (count) { lptr += count + 1; count = *lptr++; lptr += count + 1; count = *lptr++; lptr += count + 1; docptr->proLength = *(ULONGPTR)lptr; } /* find the requested fragment ID */ lptr = docptr->list; cptr = ""; for (;;) { count = *lptr++; if (!count) break; cptr = lptr; lptr += count + 1; count = *lptr++; lptr += count + 1; count = *lptr++; title = lptr; lptr += count + 1; offset1 = *(ULONGPTR)lptr; lptr += sizeof(ulong); /* chunk of -1 specifically gets zeroeth chunk */ if (docptr->pchunk < 0 && atoi(cptr) == 0) break; /* if matches it breaks with offset set */ if (docptr->pchunk == atoi(cptr)) break; } /* always should find the specified section */ if (!count) RETURN_FI_LI (docptr, SS$_ABORT) /* note the offset and start of the in-document chunk */ docptr->chunk = docptr->html + offset1; /* now look for end of chunk (next major heading, or end of document) */ for (;;) { count = *lptr++; if (!count) break; cptr = lptr; lptr += count + 1; count = *lptr++; lptr += count + 1; count = *lptr++; lptr += count + 1; offset2 = *(ULONGPTR)lptr; lptr += sizeof(ulong); /* chunk of -1 specifically gets zeroeth chunk */ if (docptr->pchunk < 0) if (atoi(cptr) == 1) break; else continue; /* if matches it breaks with offset set */ if (docptr->pchunk+1 == atoi(cptr)) break; } /* if end of document */ if (!count) offset2 = docptr->hlength; /* determine the length of the in-document chunk */ docptr->clength = offset2 - offset1; /* first write the prologue */ wasDocWrite (docptr, docptr->html, docptr->proLength); /* the page title */ title = pass2strip (title); if (docptr->title[0] && strcmp (docptr->title, title)) len = sprintf (buf, "%s – %s\n", docptr->title, title); else len = sprintf (buf, "%s\n", title); wasDocWrite (docptr, buf, len); wasDocWrite (docptr, docptr->chunk, docptr->clength); return (0); } /*****************************************************************************/ /* Get the status/errno message and insert it into the document. */ void wasDocInsertStatus ( struct wasdoc_st *docptr, int status ) { ushort slen; char *cptr, *sptr, *zptr; char msg [256]; $DESCRIPTOR (MsgDsc, msg); status = sys$getmsg (status, &slen, &MsgDsc, 0, 0); if (status & 1) msg[slen] = '\0'; else sprintf (msg, "%%X%0808X", status); sptr = docptr->html + docptr->hlength; zptr = docptr->html + docptr->hsize; for (cptr = msg; *cptr && sptr < zptr; *sptr++ = *cptr++); docptr->hlength = sptr - docptr->html; } /*****************************************************************************/ /* Some basic rendering stats. */ void wasDocStatistics ( struct wasdoc_st *docptr, int init ) { /* bit of a fudge to make the elapsed and CPU times look alike! */ static $DESCRIPTOR (StatFaoDsc, "elapsed:!%T0 cpu:!UL.!UL0 dio:!UL bio:!UL faults:!UL\0"); static unsigned long dsecs = LIB$K_DELTA_SECONDS_F, LibStatTimerReal = 1, LibStatTimerCpu = 2, LibStatTimerBio = 3, LibStatTimerDio = 4, LibStatTimerFaults = 5; static $DESCRIPTOR (StatStringDsc, ""); int status; unsigned long CpuBinTime, CountBio, CountDio, CountFaults; unsigned long RealTime64 [2]; float fsecs; char *cptr, *sptr; if (init) { lib$init_timer (0); docptr->statistics[0] = '\0'; return; } lib$stat_timer (&LibStatTimerReal, &RealTime64, 0); lib$stat_timer (&LibStatTimerCpu, &CpuBinTime, 0); lib$stat_timer (&LibStatTimerBio, &CountBio, 0); lib$stat_timer (&LibStatTimerDio, &CountDio, 0); lib$stat_timer (&LibStatTimerFaults, &CountFaults, 0); StatStringDsc.dsc$a_pointer = docptr->statistics; StatStringDsc.dsc$w_length = sizeof(docptr->statistics); status = sys$fao (&StatFaoDsc, 0, &StatStringDsc, &RealTime64, CpuBinTime/100, CpuBinTime%100, CountDio, CountBio, CountFaults); cptr = sptr = docptr->statistics + sizeof("elapsed:")-1; if (*cptr == '0') cptr++; if (*cptr == '0') cptr++; if (*cptr == ':') cptr++; if (*cptr == '0') cptr++; if (*cptr == '0') cptr++; if (*cptr == ':') cptr++; if (*cptr == '0') cptr++; while (*cptr) *sptr++ = *cptr++; *sptr = '\0'; if (docptr->insight >= 2) wasDocInsight (docptr, "%s bytes:%d/%d", docptr->statistics, docptr->hlength, docptr->hsize); if (!(status & 1)) exit (status); } /*****************************************************************************/ /* Read a directory looking for source files. Append each source file to the source text. */ int ProcessDirectory (struct wasdoc_st *docptr) { int error = 0; char *cptr, *fptr, *sptr, *zptr; char fname [256], pname [sizeof(fname)]; struct dirent *entptr; DIR *dirptr; /* pre-fill the file name buffer with the directory name */ zptr = (sptr = fname) + sizeof(fname)-1; for (cptr = docptr->dname; *cptr && sptr < zptr; *sptr++ = *cptr++); while (sptr > docptr->dname && *sptr != '/' && *sptr != ']') sptr--; if (sptr > docptr->dname) sptr++; *(fptr = sptr) = '\0'; if (dbug>1) dbugThis (FI_LI, "ProcessDirectory() %s", fname); dirptr = opendir (fname); if (!dirptr) return (vaxc$errno); pname[0] = '\0'; while (entptr = readdir(dirptr)) { if (dbug>1) dbugThis (FI_LI, "%s", entptr->d_name); /* any file name not beginning with an alphanumeric is ignored */ if (!isalnum(*entptr->d_name)) continue; /* find the file type and ignore if not */ for (cptr = sptr = entptr->d_name; *sptr; sptr++); zptr = sptr; while (sptr > cptr && *sptr != ';' && *sptr != '.') sptr--; if (*sptr == ';') zptr = sptr; while (sptr > cptr && *sptr != '.') sptr--; /* if the type is not the same */ if (zptr - sptr != docptr->ftlength) continue; if (strncasecmp (sptr, docptr->ftype, docptr->ftlength)) continue; if (docptr->insight >= 2) wasDocInsight (docptr, "%s", fname); /* append the file name to the pre-filled directory name */ zptr = fname + sizeof(fname)-1; for (sptr = fptr; *cptr && *cptr != ';' && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; /* if another version of the name file */ if (dbug>1) dbugThis (FI_LI, "%s %s", pname, fname); if (!strcmp (fname, pname)) continue; strcpy (pname, fname); docptr->ftotal++; if (error = ProcessFile (docptr, fname)) break; } closedir (dirptr); return (error); } /*****************************************************************************/ /* Append the file content to the source text. Return 0 or vaxc$errno. */ int ProcessFile ( struct wasdoc_st *docptr, char *fname ) { int bytes; char *cptr, *sptr; FILE *ifptr; stat_t fstatbuf; if (dbug>1) dbugThis (FI_LI, "ProcessFile() %s", fname); if ((ifptr = fopen (fname, "r")) == NULL) return (vaxc$errno); if (fstat (fileno(ifptr), &fstatbuf) < 0) return (vaxc$errno); /* extra bytes allow EOF when reading past the EOF */ bytes = fstatbuf.st_size; bytes += strlen(fname) + 32; docptr->tsize += bytes; docptr->text = realloc (docptr->text, docptr->tsize); if (!docptr->text) return (vaxc$errno); sptr = docptr->text + docptr->tlength; /* the source should end up as an HTML comment */ for (cptr = "\\