Help compiler integrated with DVX.
This commit is contained in:
parent
66952306df
commit
7157f97c28
8 changed files with 410 additions and 209 deletions
2
Makefile
2
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 \
|
||||
|
|
|
|||
|
|
@ -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
BIN
core/help.png
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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" \
|
||||
|
|
|
|||
261
tools/dvxhlpc.c
261
tools/dvxhlpc.c
|
|
@ -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, ¬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
|
||||
|
|
|
|||
38
tools/hlpcCompile.h
Normal file
38
tools/hlpcCompile.h
Normal 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
|
||||
Loading…
Add table
Reference in a new issue