From 7157f97c2895cf0d1da50b53f3ed2701ac87dc8b Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Tue, 14 Apr 2026 00:43:17 -0500 Subject: [PATCH] Help compiler integrated with DVX. --- Makefile | 2 +- apps/progman/dvxhelp.hcf | 1 + core/help.png | 3 + loader/Makefile | 5 +- loader/loaderMain.c | 294 ++++++++++++++++++++++----------------- tools/Makefile | 15 +- tools/dvxhlpc.c | 261 ++++++++++++++++++++++++---------- tools/hlpcCompile.h | 38 +++++ 8 files changed, 410 insertions(+), 209 deletions(-) create mode 100644 core/help.png create mode 100644 tools/hlpcCompile.h diff --git a/Makefile b/Makefile index 7df99e7..c347737 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,7 @@ compile-help: apps/dvxhelp/help.dhs $(HLPC) -o bin/apps/kpunch/progman/dvxhelp.hlp \ --html docs/dvx_system_reference.html \ - -i assets \ + -i core \ $(SYSTEM_DHS) \ $$(find widgets -name "*.dhs" ! -path "widgets/wgtsys.dhs" | sort) $(HLPC) -o bin/apps/kpunch/dvxbasic/dvxbasic.hlp \ diff --git a/apps/progman/dvxhelp.hcf b/apps/progman/dvxhelp.hcf index 1afd19a..c123b06 100644 --- a/apps/progman/dvxhelp.hcf +++ b/apps/progman/dvxhelp.hcf @@ -1,5 +1,6 @@ # DVX System Help configuration # Compiled into DVXHELP.HLP in this directory output = DVXHELP.HLP +imagedir = LIBS/KPUNCH/LIBDVX source = LIBS/*.DHS source = WIDGETS/*.DHS diff --git a/core/help.png b/core/help.png new file mode 100644 index 0000000..fd695ce --- /dev/null +++ b/core/help.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:981b735d53aa99a96002a6630db4b7e285150242db070769d1bbf5d5b444cabe +size 3389 diff --git a/loader/Makefile b/loader/Makefile index d6e5e43..abcd951 100644 --- a/loader/Makefile +++ b/loader/Makefile @@ -18,7 +18,7 @@ BINDIR = ../bin SRCS = loaderMain.c OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -POBJS = $(POBJDIR)/dvxPlatformDos.o $(POBJDIR)/dvxPrefs.o +POBJS = $(POBJDIR)/dvxPlatformDos.o $(POBJDIR)/dvxPrefs.o $(OBJDIR)/dvxhlpc.o TARGET = $(BINDIR)/dvx.exe .PHONY: all clean @@ -40,6 +40,9 @@ $(POBJDIR)/dvxPlatformDos.o: ../core/platform/dvxPlatformDos.c | $(POBJDIR) $(POBJDIR)/dvxPrefs.o: ../core/dvxPrefs.c | $(POBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(OBJDIR)/dvxhlpc.o: ../tools/dvxhlpc.c ../tools/hlpcCompile.h ../apps/dvxhelp/hlpformat.h | $(OBJDIR) + $(CC) $(CFLAGS) -DHLPC_NO_MAIN -c -o $@ ../tools/dvxhlpc.c + $(OBJDIR): mkdir -p $(OBJDIR) diff --git a/loader/loaderMain.c b/loader/loaderMain.c index 5a1a99a..784f763 100644 --- a/loader/loaderMain.c +++ b/loader/loaderMain.c @@ -12,6 +12,7 @@ #include "dvxPlat.h" #include "dvxPrefs.h" +#include "../tools/hlpcCompile.h" #include #include @@ -36,7 +37,6 @@ #define WIDGET_DIR "WIDGETS" #define LOG_PATH "dvx.log" #define INI_PATH "CONFIG/DVX.INI" -#define HLPC_PATH "SYSTEM/DVXHLPC.EXE" // ============================================================ // Splash screen (delegates to platformSplash* in dvxPlatformDos.c) @@ -119,7 +119,6 @@ typedef struct { // ============================================================ static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods); -static int32_t countHcfFilesRecurse(const char *dirPath); static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize); static void *findSymbol(void **handles, const char *symbol); static void freeMods(ModuleT *mods); @@ -504,68 +503,8 @@ bool platformGlobMatch(const char *pattern, const char *name) { // excludes matching files. The loader expands these by scanning // directories and matching filenames. -// Recursively count .hcf files under the given directory -static int32_t countHcfFilesRecurse(const char *dirPath) { - DIR *dir = opendir(dirPath); - - if (!dir) { - return 0; - } - - char **names = NULL; - struct dirent *ent; - - while ((ent = readdir(dir)) != NULL) { - if (ent->d_name[0] == '.') { - continue; - } - - arrput(names, strdup(ent->d_name)); - } - - closedir(dir); - - int32_t count = 0; - int32_t nEntries = (int32_t)arrlen(names); - - for (int32_t i = 0; i < nEntries; i++) { - char fullPath[DVX_MAX_PATH]; - snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, names[i]); - - struct stat st; - - if (stat(fullPath, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - count += countHcfFilesRecurse(fullPath); - } else { - int32_t nameLen = (int32_t)strlen(names[i]); - - if (nameLen > 4 && strcasecmp(names[i] + nameLen - 4, ".hcf") == 0) { - count++; - } - } - } - - free(names[i]); - } - - arrfree(names); - return count; -} - - -// Count .hcf files under APPS/ -static int32_t countHcfFiles(void) { - return countHcfFilesRecurse("APPS"); -} - - -// Process a single .hcf file: parse it and invoke the compiler. -#define HELP_RESP_FILE "SYSTEM/HLPFILES.TMP" - -// Write matching filenames to the response file (one per line). -static void writeGlobToResp(FILE *resp, const char *pattern, const char *excludePattern) { - // Split pattern into directory and filename glob +// Collect matching filenames into an stb_ds array of strdup'd paths. +static void collectGlobFiles(char ***outFiles, const char *pattern, const char *excludePattern) { char dirPart[DVX_MAX_PATH]; const char *globPart = NULL; @@ -616,10 +555,10 @@ static void writeGlobToResp(FILE *resp, const char *pattern, const char *exclude if (S_ISDIR(st.st_mode)) { char subPattern[DVX_MAX_PATH]; snprintf(subPattern, sizeof(subPattern), "%s%c%s", fullPath, DVX_PATH_SEP, globPart); - writeGlobToResp(resp, subPattern, excludePattern); + collectGlobFiles(outFiles, subPattern, excludePattern); } else if (platformGlobMatch(globPart, names[i])) { if (!excludePattern || !platformGlobMatch(excludePattern, names[i])) { - fprintf(resp, "%s\n", fullPath); + arrput(*outFiles, strdup(fullPath)); } } } @@ -631,6 +570,80 @@ static void writeGlobToResp(FILE *resp, const char *pattern, const char *exclude } +// Progress callback for hlpcCompile — updates the splash progress bar. +static void hlpcProgressCallback(void *ctx, int32_t current, int32_t total) { + (void)ctx; + (void)total; + (void)current; + sSplashLoaded++; + splashUpdateProgress(); +} + + +// Count input files for a single .hcf config (for progress total calculation). +static int32_t countHcfInputFiles(const char *hcfPath) { + FILE *f = fopen(hcfPath, "r"); + + if (!f) { + return 0; + } + + char **files = NULL; + char line[512]; + + while (fgets(line, (int)sizeof(line), f)) { + int32_t len = (int32_t)strlen(line); + + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r' || line[len - 1] == ' ')) { + line[--len] = '\0'; + } + + if (line[0] == '#' || line[0] == '\0') { + continue; + } + + if (strncasecmp(line, "source", 6) == 0) { + const char *p = line + 6; + + while (*p == ' ' || *p == '=') { p++; } + + char pattern[DVX_MAX_PATH] = {0}; + char exclude[DVX_MAX_PATH] = {0}; + const char *bang = strchr(p, '!'); + + if (bang) { + int32_t patLen = (int32_t)(bang - p); + + while (patLen > 0 && p[patLen - 1] == ' ') { patLen--; } + + memcpy(pattern, p, patLen); + pattern[patLen] = '\0'; + bang++; + + while (*bang == ' ') { bang++; } + + snprintf(exclude, sizeof(exclude), "%s", bang); + } else { + snprintf(pattern, sizeof(pattern), "%s", p); + } + + collectGlobFiles(&files, pattern, exclude[0] ? exclude : NULL); + } + } + + fclose(f); + + int32_t count = (int32_t)arrlen(files); + + for (int32_t i = 0; i < count; i++) { + free(files[i]); + } + + arrfree(files); + return count; +} + + static void processHcf(const char *hcfPath, const char *hcfDir) { FILE *f = fopen(hcfPath, "r"); @@ -638,19 +651,10 @@ static void processHcf(const char *hcfPath, const char *hcfDir) { return; } - char outputFile[DVX_MAX_PATH] = {0}; - - // Write matching source files to a response file - FILE *resp = fopen(HELP_RESP_FILE, "w"); - - if (!resp) { - fclose(f); - dvxLog("helpRecompile: cannot create %s", HELP_RESP_FILE); - return; - } - - bool hasFiles = false; - char line[512]; + char outputFile[DVX_MAX_PATH] = {0}; + char imgDir[DVX_MAX_PATH] = {0}; + char **inputFiles = NULL; + char line[512]; while (fgets(line, (int)sizeof(line), f)) { int32_t len = (int32_t)strlen(line); @@ -666,72 +670,120 @@ static void processHcf(const char *hcfPath, const char *hcfDir) { if (strncasecmp(line, "output", 6) == 0) { const char *p = line + 6; - while (*p == ' ' || *p == '=') { - p++; - } + while (*p == ' ' || *p == '=') { p++; } snprintf(outputFile, sizeof(outputFile), "%s%c%s", hcfDir, DVX_PATH_SEP, p); + } else if (strncasecmp(line, "imagedir", 8) == 0) { + const char *p = line + 8; + + while (*p == ' ' || *p == '=') { p++; } + + snprintf(imgDir, sizeof(imgDir), "%s", p); } else if (strncasecmp(line, "source", 6) == 0) { const char *p = line + 6; - while (*p == ' ' || *p == '=') { - p++; - } + while (*p == ' ' || *p == '=') { p++; } char pattern[DVX_MAX_PATH] = {0}; char exclude[DVX_MAX_PATH] = {0}; - const char *bang = strchr(p, '!'); if (bang) { int32_t patLen = (int32_t)(bang - p); - while (patLen > 0 && p[patLen - 1] == ' ') { - patLen--; - } + while (patLen > 0 && p[patLen - 1] == ' ') { patLen--; } memcpy(pattern, p, patLen); pattern[patLen] = '\0'; bang++; - while (*bang == ' ') { - bang++; - } + while (*bang == ' ') { bang++; } snprintf(exclude, sizeof(exclude), "%s", bang); } else { snprintf(pattern, sizeof(pattern), "%s", p); } - long before = ftell(resp); - writeGlobToResp(resp, pattern, exclude[0] ? exclude : NULL); - - if (ftell(resp) > before) { - hasFiles = true; - } + collectGlobFiles(&inputFiles, pattern, exclude[0] ? exclude : NULL); } } fclose(f); - fclose(resp); - if (!outputFile[0] || !hasFiles) { - remove(HELP_RESP_FILE); + int32_t inputCount = (int32_t)arrlen(inputFiles); + + if (!outputFile[0] || inputCount == 0) { + for (int32_t i = 0; i < inputCount; i++) { free(inputFiles[i]); } + arrfree(inputFiles); return; } - // Short command: compiler + output + response file - char cmd[DVX_MAX_PATH * 2]; - snprintf(cmd, sizeof(cmd), "%s --quiet -o %s @%s", HLPC_PATH, outputFile, HELP_RESP_FILE); + dvxLog("helpRecompile: %s -> %s (%d files)", hcfPath, outputFile, (int)inputCount); - dvxLog("helpRecompile: %s -> %s", hcfPath, outputFile); - int rc = system(cmd); + int32_t rc = hlpcCompile((const char **)inputFiles, inputCount, outputFile, + imgDir[0] ? imgDir : NULL, NULL, 1, + hlpcProgressCallback, NULL); if (rc != 0) { - dvxLog("helpRecompile: FAILED (rc=%d)", rc); + dvxLog("helpRecompile: FAILED (rc=%d)", (int)rc); } - remove(HELP_RESP_FILE); + for (int32_t i = 0; i < inputCount; i++) { + free(inputFiles[i]); + } + + arrfree(inputFiles); +} + + +// Count total progress steps across all .hcf files under a directory. +static int32_t countTotalHelpSteps(const char *dirPath) { + DIR *dir = opendir(dirPath); + + if (!dir) { + return 0; + } + + char **names = NULL; + struct dirent *ent; + + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') { + continue; + } + + arrput(names, strdup(ent->d_name)); + } + + closedir(dir); + + int32_t total = 0; + int32_t nEntries = (int32_t)arrlen(names); + + for (int32_t i = 0; i < nEntries; i++) { + char fullPath[DVX_MAX_PATH]; + snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, names[i]); + + struct stat st; + + if (stat(fullPath, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + total += countTotalHelpSteps(fullPath); + } else { + int32_t nameLen = (int32_t)strlen(names[i]); + + if (nameLen > 4 && strcasecmp(names[i] + nameLen - 4, ".hcf") == 0) { + int32_t fileCount = countHcfInputFiles(fullPath); + total += hlpcProgressTotal(fileCount); + } + } + } + + free(names[i]); + } + + arrfree(names); + return total; } @@ -772,8 +824,6 @@ static void processHcfDir(const char *dirPath) { if (nameLen > 4 && strcasecmp(names[i] + nameLen - 4, ".hcf") == 0) { processHcf(fullPath, dirPath); - sSplashLoaded++; - splashUpdateProgress(); } } } @@ -816,25 +866,11 @@ static void helpRecompileIfNeeded(void) { dvxLog("helpRecompile: counts changed (libs: %d->%d, widgets: %d->%d)", (int)storedLibs, (int)sLibCount, (int)storedWidgets, (int)sWidgetCount); - // Check if compiler exists - FILE *test = fopen(HLPC_PATH, "rb"); + // Count total progress steps across all .hcf compilations + int32_t totalSteps = countTotalHelpSteps("APPS"); - if (!test) { - dvxLog("helpRecompile: %s not found, skipping", HLPC_PATH); - prefsSetInt(ini, "help", "libCount", sLibCount); - prefsSetInt(ini, "help", "widgetCount", sWidgetCount); - prefsSave(ini); - prefsClose(ini); - return; - } - - fclose(test); - - // Count .hcf files for progress bar - int32_t hcfCount = countHcfFiles(); - - if (hcfCount == 0) { - dvxLog("helpRecompile: no .hcf files found"); + if (totalSteps == 0) { + dvxLog("helpRecompile: no help sources found"); prefsSetInt(ini, "help", "libCount", sLibCount); prefsSetInt(ini, "help", "widgetCount", sWidgetCount); prefsSave(ini); @@ -844,7 +880,7 @@ static void helpRecompileIfNeeded(void) { // Reset progress bar for recompile phase sSplashLoaded = 0; - sSplashTotal = hcfCount; + sSplashTotal = totalSteps; platformSplashFillRect(PBAR_X, PBAR_Y, PBAR_W, PBAR_H, SPLASH_BAR_BG); // Recursively scan APPS/ for .hcf files and process each diff --git a/tools/Makefile b/tools/Makefile index d6d584e..228763c 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -50,12 +50,19 @@ $(BINDIR): $(CONFIGDIR): mkdir -p $(CONFIGDIR) -$(SYSTEMDIR)/DVXHLPC.EXE: dvxhlpc.c ../apps/dvxhelp/hlpformat.h | $(SYSTEMDIR) +$(SYSTEMDIR)/DVXHLPC.EXE: dvxhlpc.c hlpcCompile.h ../apps/dvxhelp/hlpformat.h | $(SYSTEMDIR) $(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxhlpc.exe dvxhlpc.c $(EXE2COFF) $(SYSTEMDIR)/dvxhlpc.exe cat $(CWSDSTUB) $(SYSTEMDIR)/dvxhlpc > $@ rm -f $(SYSTEMDIR)/dvxhlpc $(SYSTEMDIR)/dvxhlpc.exe +# Object file for linking into the loader (no main) +../obj/loader/dvxhlpc.o: dvxhlpc.c hlpcCompile.h ../apps/dvxhelp/hlpformat.h | ../obj/loader + $(DOSCC) $(DOSCFLAGS) -DHLPC_NO_MAIN -c -o $@ dvxhlpc.c + +../obj/loader: + mkdir -p ../obj/loader + $(SYSTEMDIR)/DVXRES.EXE: dvxres.c ../core/dvxResource.c ../core/dvxRes.h | $(SYSTEMDIR) $(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxres.exe dvxres.c ../core/dvxResource.c $(EXE2COFF) $(SYSTEMDIR)/dvxres.exe @@ -67,6 +74,12 @@ $(SYSTEMDIR): deploy-helpsrc: @echo "Deploying help source files..." + @# Deploy help images from core/ + @for f in ../core/*.png; do \ + [ -f "$$f" ] || continue; \ + name=$$(basename "$$f" | tr a-z A-Z); \ + cp "$$f" $(BINDIR)/libs/kpunch/libdvx/"$$name"; \ + done @for pair in \ "../core/*.dhs:$(BINDIR)/libs/kpunch/libdvx" \ "../tasks/*.dhs:$(BINDIR)/libs/kpunch/libtasks" \ diff --git a/tools/dvxhlpc.c b/tools/dvxhlpc.c index bbbe342..0fa0ec8 100644 --- a/tools/dvxhlpc.c +++ b/tools/dvxhlpc.c @@ -15,6 +15,7 @@ #define _POSIX_C_SOURCE 200809L +#include "hlpcCompile.h" #include "../apps/dvxhelp/hlpformat.h" #include @@ -135,6 +136,12 @@ static const char *htmlPath = NULL; static int32_t errorCount = 0; static bool quietMode = false; +// Progress callback (set by hlpcCompile, NULL for standalone) +static HlpcProgressFnT sProgressFn = NULL; +static void *sProgressCtx = NULL; +static int32_t sProgressCur = 0; +static int32_t sProgressTotal = 0; + // Parse state static const char *currentFile = NULL; static int32_t currentLine = 0; @@ -163,7 +170,6 @@ static void freeAll(void); static void hlpcInfo(const char *fmt, ...); static void parseDirective(const char *line, TopicT **curTopic, bool *inList, bool *inTable, bool *inCode, bool *inNote, uint8_t *noteFlags, char *para, int32_t *paraLen, int32_t includeDepth); static void parseFile(const char *path, TopicT **curTopic, bool *inList, bool *inTable, bool *inCode, bool *inNote, uint8_t *noteFlags, char *para, int32_t *paraLen, int32_t includeDepth); -static void pass1Parse(int32_t fileCount, char **files); static void pass2Wrap(void); static void regroupTocBySections(void); static void pass3StringTable(void); @@ -172,7 +178,9 @@ static int pass5Serialize(const char *outputPath); static int emitHtml(const char *outputPath); static int32_t strTableAdd(const char *str); static int32_t strTableFind(const char *str); +#ifndef HLPC_NO_MAIN static void usage(void); +#endif // --------------------------------------------------------------------------- // emitError / emitWarning @@ -214,10 +222,12 @@ static void hlpcInfo(const char *fmt, ...) { // usage // --------------------------------------------------------------------------- +#ifndef HLPC_NO_MAIN static void usage(void) { fprintf(stderr, "Usage: dvxhlpc -o output.hlp [-i imagedir] [--html out.html] [--quiet] input.dhs [@filelist] [...]\n"); exit(1); } +#endif // --------------------------------------------------------------------------- @@ -850,50 +860,6 @@ static void regroupTocBySections(void) { } -static void pass1Parse(int32_t fileCount, char **files) { - hlpcInfo("Pass 1: Parsing %d input file(s)...\n", fileCount); - - TopicT *curTopic = NULL; - bool inList = false; - bool inTable = false; - bool inCode = false; - bool inNote = false; - uint8_t noteFlags = 0; - - // Paragraph buffer (shared across files for multi-file topics) - char para[MAX_LINE_LEN * 64]; - int32_t paraLen = 0; - - for (int32_t i = 0; i < fileCount; i++) { - currentSection[0] = '\0'; - parseFile(files[i], &curTopic, &inList, &inTable, &inCode, &inNote, ¬eFlags, para, ¶Len, 0); - } - - // Flush any remaining paragraph - if (curTopic && paraLen > 0) { - uint8_t type = HLP_REC_TEXT; - uint8_t flags = 0; - if (inCode) { - type = HLP_REC_CODE; - } else if (inTable) { - type = HLP_REC_TABLE; - } else if (inNote) { - type = HLP_REC_NOTE; - flags = noteFlags; - } else if (inList) { - type = HLP_REC_LIST_ITEM; - } - flushParagraph(curTopic, para, paraLen, type, flags); - } - - // Regroup TOC entries by section - regroupTocBySections(); - - hlpcInfo(" %d topic(s), %d TOC entries, %d index entries, %d image(s)\n", - topicCount, tocCount, indexCount, imageCount); -} - - // --------------------------------------------------------------------------- // Pass 2: Word wrap // --------------------------------------------------------------------------- @@ -1475,9 +1441,15 @@ static int emitHtml(const char *outputPath) { static int pass5Serialize(const char *outputPath) { hlpcInfo("Pass 5: Serializing to %s...\n", outputPath); - FILE *f = fopen(outputPath, "wb"); + // Write to a temp file, rename on success. This prevents + // truncating the existing help file if compilation fails. + char tmpPath[260]; + snprintf(tmpPath, sizeof(tmpPath), "%s.tmp", outputPath); + + FILE *f = fopen(tmpPath, "wb"); + if (!f) { - fprintf(stderr, "error: cannot create '%s': %s\n", outputPath, strerror(errno)); + fprintf(stderr, "error: cannot create '%s': %s\n", tmpPath, strerror(errno)); return 1; } @@ -1530,8 +1502,9 @@ static int pass5Serialize(const char *outputPath) { hdr.imagePoolSize = offset - hdr.imagePoolOffset; // --- 2. Topic content records --- - uint32_t *topicContentOffsets = calloc(topicCount, sizeof(uint32_t)); - uint32_t *topicContentSizes = calloc(topicCount, sizeof(uint32_t)); + int32_t allocCount = topicCount > 0 ? topicCount : 1; + uint32_t *topicContentOffsets = calloc((size_t)allocCount, sizeof(uint32_t)); + uint32_t *topicContentSizes = calloc((size_t)allocCount, sizeof(uint32_t)); for (int32_t t = 0; t < topicCount; t++) { TopicT *topic = &topics[t]; @@ -1680,6 +1653,18 @@ static int pass5Serialize(const char *outputPath) { fclose(f); + // Rename temp file to final output (atomic on same filesystem) + remove(outputPath); + + if (rename(tmpPath, outputPath) != 0) { + fprintf(stderr, "error: cannot rename '%s' to '%s': %s\n", tmpPath, outputPath, strerror(errno)); + remove(tmpPath); + free(topicDir); + free(topicContentOffsets); + free(topicContentSizes); + return 1; + } + free(topicDir); free(topicContentOffsets); free(topicContentSizes); @@ -1708,10 +1693,151 @@ static void freeAll(void) { } +static void resetAll(void) { + topicCount = 0; + tocCount = 0; + indexCount = 0; + imageCount = 0; + trigramCount = 0; + strTab = NULL; + strTabSize = 0; + strTabCap = 0; + strEntries = NULL; + strEntryCount = 0; + strEntryCap = 0; + errorCount = 0; + currentFile = NULL; + currentLine = 0; + currentSection[0] = '\0'; + snprintf(imageDir, sizeof(imageDir), "."); + htmlPath = NULL; +} + + +static void progressStep(void) { + if (sProgressFn) { + sProgressCur++; + sProgressFn(sProgressCtx, sProgressCur, sProgressTotal); + } +} + + // --------------------------------------------------------------------------- -// main +// hlpcCompile -- library entry point // --------------------------------------------------------------------------- +int32_t hlpcCompile(const char **inputFiles, int32_t inputCount, const char *outputPath, const char *imgDir, const char *html, int32_t quiet, HlpcProgressFnT progressFn, void *progressCtx) { + resetAll(); + + if (imgDir) { + snprintf(imageDir, sizeof(imageDir), "%s", imgDir); + } + + htmlPath = html; + quietMode = quiet ? true : false; + + sProgressFn = progressFn; + sProgressCtx = progressCtx; + sProgressCur = 0; + sProgressTotal = hlpcProgressTotal(inputCount); + + if (progressFn) { + progressFn(progressCtx, 0, sProgressTotal); + } + + hlpcInfo("dvxhlpc: DVX Help Compiler\n"); + + // Pass 1: parse input files (one progress step per file) + { + TopicT *curTopic = NULL; + bool inList = false; + bool inTable = false; + bool inCode = false; + bool inNote = false; + uint8_t noteFlags = 0; + char para[MAX_LINE_LEN * 64]; + int32_t paraLen = 0; + + hlpcInfo("Pass 1: Parsing %d input file(s)...\n", inputCount); + + for (int32_t i = 0; i < inputCount; i++) { + currentSection[0] = '\0'; + parseFile(inputFiles[i], &curTopic, &inList, &inTable, &inCode, &inNote, ¬eFlags, para, ¶Len, 0); + progressStep(); + } + + if (curTopic && paraLen > 0) { + uint8_t type = HLP_REC_TEXT; + uint8_t flags = 0; + + if (inCode) { + type = HLP_REC_CODE; + } else if (inTable) { + type = HLP_REC_TABLE; + } else if (inNote) { + type = HLP_REC_NOTE; + flags = noteFlags; + } else if (inList) { + type = HLP_REC_LIST_ITEM; + } + + flushParagraph(curTopic, para, paraLen, type, flags); + } + + regroupTocBySections(); + + hlpcInfo(" %d topic(s), %d TOC entries, %d index entries, %d image(s)\n", + topicCount, tocCount, indexCount, imageCount); + } + + if (errorCount > 0) { + fprintf(stderr, "Aborting due to %d error(s).\n", (int)errorCount); + freeAll(); + return 1; + } + + // Pass 2: word-wrap + pass2Wrap(); + progressStep(); + + // HTML output (uses wrapped text, before binary passes) + if (htmlPath) { + if (emitHtml(htmlPath) == 0) { + hlpcInfo("HTML: wrote %s\n", htmlPath); + } + } + + // Pass 3: string table + pass3StringTable(); + progressStep(); + + // Pass 4: search index + pass4SearchIndex(); + progressStep(); + + // Pass 5: serialize + int32_t result = pass5Serialize(outputPath); + + if (result == 0) { + hlpcInfo("Done. %d topic(s), %d TOC entries, %d index keywords, %d trigrams.\n", + topicCount, tocCount, indexCount, trigramCount); + } + + progressStep(); + freeAll(); + + sProgressFn = NULL; + sProgressCtx = NULL; + + return result; +} + + +// --------------------------------------------------------------------------- +// main -- standalone executable wrapper (excluded when linking as library) +// --------------------------------------------------------------------------- + +#ifndef HLPC_NO_MAIN int main(int argc, char **argv) { const char *outputPath = NULL; char *inputFiles[256]; @@ -1789,33 +1915,14 @@ int main(int argc, char **argv) { usage(); } - hlpcInfo("dvxhlpc: DVX Help Compiler\n"); + // Copy imageDir and htmlPath before hlpcCompile resets the globals + char imgDirCopy[260]; + snprintf(imgDirCopy, sizeof(imgDirCopy), "%s", imageDir); - pass1Parse(inputCount, inputFiles); - if (errorCount > 0) { - fprintf(stderr, "Aborting due to %d error(s).\n", (int)errorCount); - freeAll(); - return 1; - } + const char *htmlCopy = htmlPath; - pass2Wrap(); - - // Emit HTML if requested (uses wrapped text, before binary passes) - if (htmlPath) { - if (emitHtml(htmlPath) == 0) { - hlpcInfo("HTML: wrote %s\n", htmlPath); - } - } - - pass3StringTable(); - pass4SearchIndex(); - - int result = pass5Serialize(outputPath); - if (result == 0) { - hlpcInfo("Done. %d topic(s), %d TOC entries, %d index keywords, %d trigrams.\n", - topicCount, tocCount, indexCount, trigramCount); - } - - freeAll(); - return result; + return hlpcCompile((const char **)inputFiles, inputCount, outputPath, + imgDirCopy[0] ? imgDirCopy : NULL, + htmlCopy, quietMode, NULL, NULL); } +#endif // HLPC_NO_MAIN diff --git a/tools/hlpcCompile.h b/tools/hlpcCompile.h new file mode 100644 index 0000000..a8fdfb4 --- /dev/null +++ b/tools/hlpcCompile.h @@ -0,0 +1,38 @@ +// hlpcCompile.h -- DVX Help Compiler library interface +// +// Allows the help compiler to be called as a function (from the +// loader for startup recompilation) or as a standalone executable. +// The progress callback enables real-time progress bar updates. + +#ifndef HLPC_COMPILE_H +#define HLPC_COMPILE_H + +#include + +// Progress callback: called after each input file in pass 1 and +// after each of passes 2-5. total is set on the first call and +// stays constant. current increments from 0 to total. +typedef void (*HlpcProgressFnT)(void *ctx, int32_t current, int32_t total); + +// Compile help source files into a binary .hlp file. +// +// inputFiles: array of .dhs source file paths +// inputCount: number of input files +// outputPath: path to write the .hlp file +// imageDir: directory to search for .image references (NULL = ".") +// htmlPath: if non-NULL, also emit HTML to this path +// quiet: suppress informational output +// progressFn: progress callback (NULL = no progress reporting) +// progressCtx: opaque context passed to progressFn +// +// Returns 0 on success, non-zero on error. +int32_t hlpcCompile(const char **inputFiles, int32_t inputCount, const char *outputPath, const char *imageDir, const char *htmlPath, int32_t quiet, HlpcProgressFnT progressFn, void *progressCtx); + +// Return the number of progress steps for a given input count. +// This is inputCount (one per file in pass 1) + 4 (passes 2-5). +// Useful for pre-computing the total across multiple compilations. +static inline int32_t hlpcProgressTotal(int32_t inputCount) { + return inputCount + 4; +} + +#endif // HLPC_COMPILE_H