Help compiler integrated with DVX.

This commit is contained in:
Scott Duensing 2026-04-14 00:43:17 -05:00
parent 66952306df
commit 7157f97c28
8 changed files with 410 additions and 209 deletions

View file

@ -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 \

View file

@ -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

BIN
core/help.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -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)

View file

@ -12,6 +12,7 @@
#include "dvxPlat.h"
#include "dvxPrefs.h"
#include "../tools/hlpcCompile.h"
#include <ctype.h>
#include <dirent.h>
@ -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

View file

@ -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" \

View file

@ -15,6 +15,7 @@
#define _POSIX_C_SOURCE 200809L
#include "hlpcCompile.h"
#include "../apps/dvxhelp/hlpformat.h"
#include <ctype.h>
@ -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, &noteFlags, para, &paraLen, 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, &noteFlags, para, &paraLen, 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

38
tools/hlpcCompile.h Normal file
View file

@ -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 <stdint.h>
// 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