aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfrosty <frostinator33@airmail.cc>2026-10-06 10:11:38 -0400
committerfrosty <frostinator33@airmail.cc>2026-10-06 10:11:38 -0400
commit99e1103b79b50b92a7784d4c538230f22e724f99 (patch)
tree115d9145e9ac593f03da81a659f3a9cbb8acd88e /src
parent65500761a632d22057d3b3e709b91dde168e125b (diff)
feat: add page:emitHEADmasterindev
Diffstat (limited to 'src')
-rw-r--r--src/FileProcessor/FileProcessor.c94
-rw-r--r--src/LuaEngine/LuaEngine.c124
-rw-r--r--src/LuaEngine/LuaEngine.h15
3 files changed, 230 insertions, 3 deletions
diff --git a/src/FileProcessor/FileProcessor.c b/src/FileProcessor/FileProcessor.c
index 84efece..8f76216 100644
--- a/src/FileProcessor/FileProcessor.c
+++ b/src/FileProcessor/FileProcessor.c
@@ -113,6 +113,44 @@ static int copy_regular_file(const char *input_path, const char *output_path,
return result;
}
+static int make_parent_directories(const char *path) {
+ char *copy;
+ size_t length = 0;
+ const char *slash = strrchr(path, '/');
+ char *cursor;
+
+ if (slash == NULL) {
+ return 0;
+ }
+
+ length = (size_t)(slash - path);
+ copy = malloc(length + 1);
+ if (copy == NULL) {
+ return -1;
+ }
+ memcpy(copy, path, length);
+ copy[length] = '\0';
+
+ for (cursor = copy + 1; *cursor != '\0'; ++cursor) {
+ if (*cursor == '/') {
+ *cursor = '\0';
+ if (mkdir(copy, 0777) != 0 && errno != EEXIST) {
+ free(copy);
+ return -1;
+ }
+ *cursor = '/';
+ }
+ }
+
+ if (mkdir(copy, 0777) != 0 && errno != EEXIST) {
+ free(copy);
+ return -1;
+ }
+
+ free(copy);
+ return 0;
+}
+
int process_file(const char *input_path, const char *output_path) {
const int parse_options = HTML_PARSE_RECOVER | HTML_PARSE_NONET |
HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING;
@@ -136,8 +174,11 @@ int process_file(const char *input_path, const char *output_path) {
return -1;
}
- if (process_html_document(document, input_path) != 0) {
+ EmitList emits = {0};
+
+ if (process_html_document(document, input_path, &emits) != 0) {
xmlFreeDoc(document);
+ emit_list_free(&emits);
return -1;
}
@@ -145,8 +186,59 @@ int process_file(const char *input_path, const char *output_path) {
xmlFreeDoc(document);
if (saved < 0) {
logger_error("unable to write HTML file: %s", output_path);
+ emit_list_free(&emits);
return -1;
}
+
+ for (size_t index = 0; index < emits.length; ++index) {
+ const char *emitted = emits.items[index].path;
+ char *emitted_output;
+ int emitted_saved;
+
+ if (emitted[0] == '/') {
+ emitted_output = strdup(emitted);
+ } else {
+ const char *slash = strrchr(output_path, '/');
+ char *directory;
+
+ if (slash == NULL) {
+ emitted_output = strdup(emitted);
+ } else {
+ directory = malloc((size_t)(slash - output_path) + 1);
+ if (directory != NULL) {
+ memcpy(directory, output_path, (size_t)(slash - output_path));
+ directory[slash - output_path] = '\0';
+ emitted_output = path_join(directory, emitted);
+ free(directory);
+ } else {
+ emitted_output = NULL;
+ }
+ }
+ }
+
+ if (emitted_output == NULL ||
+ make_parent_directories(emitted_output) != 0) {
+ logger_error("unable to prepare output path for %s: %s",
+ emitted, emitted_output == NULL ? strerror(errno)
+ : emitted_output);
+ free(emitted_output);
+ emit_list_free(&emits);
+ return -1;
+ }
+
+ logger_info("emitting %s -> %s", input_path, emitted_output);
+ emitted_saved = htmlSaveFileFormat(emitted_output, emits.items[index].document,
+ "UTF-8", 1);
+ if (emitted_saved < 0) {
+ logger_error("unable to write HTML file: %s", emitted_output);
+ free(emitted_output);
+ emit_list_free(&emits);
+ return -1;
+ }
+ free(emitted_output);
+ }
+
+ emit_list_free(&emits);
return 0;
}
diff --git a/src/LuaEngine/LuaEngine.c b/src/LuaEngine/LuaEngine.c
index c34fd68..af30403 100644
--- a/src/LuaEngine/LuaEngine.c
+++ b/src/LuaEngine/LuaEngine.c
@@ -143,6 +143,121 @@ static char *make_chunk_name(const char *input_path, unsigned long line) {
return chunk_name;
}
+typedef struct {
+ htmlDocPtr source;
+ EmitList *emits;
+} EmitContext;
+
+static int grow_emit_list(EmitList *emits) {
+ size_t larger_capacity;
+ EmittedPage *larger_items;
+
+ if (emits->capacity > SIZE_MAX / (2 * sizeof(*emits->items))) {
+ return -1;
+ }
+
+ larger_capacity = emits->capacity == 0 ? 4 : emits->capacity * 2;
+ larger_items =
+ realloc(emits->items, larger_capacity * sizeof(*emits->items));
+ if (larger_items == NULL) {
+ return -1;
+ }
+
+ emits->items = larger_items;
+ emits->capacity = larger_capacity;
+ return 0;
+}
+
+static int append_emit(EmitList *emits, const char *path,
+ htmlDocPtr document) {
+ EmittedPage *emitted;
+
+ if (emits->length == emits->capacity && grow_emit_list(emits) != 0) {
+ return -1;
+ }
+
+ emitted = &emits->items[emits->length];
+ emitted->path = duplicate_string(path);
+ if (emitted->path == NULL) {
+ return -1;
+ }
+
+ emitted->document = document;
+ ++emits->length;
+ return 0;
+}
+
+void emit_list_free(EmitList *emits) {
+ size_t index;
+
+ for (index = 0; index < emits->length; ++index) {
+ free(emits->items[index].path);
+ xmlFreeDoc(emits->items[index].document);
+ }
+ free(emits->items);
+ emits->items = NULL;
+ emits->length = 0;
+ emits->capacity = 0;
+}
+
+static htmlDocPtr snapshot_document(htmlDocPtr source, lua_State *lua) {
+ htmlDocPtr copy;
+ xmlDtdPtr dtd;
+ xmlNodePtr root;
+
+ lua_getglobal(lua, "page");
+ root = lua_istable(lua, -1) ? dom_page_from_lua(lua, lua_gettop(lua)) : NULL;
+ lua_pop(lua, 1);
+ if (root == NULL) {
+ return NULL;
+ }
+
+ copy = xmlNewDoc(BAD_CAST "1.0");
+ if (copy == NULL) {
+ xmlFreeNode(root);
+ return NULL;
+ }
+ if (source->encoding != NULL) {
+ copy->encoding = xmlStrdup(source->encoding);
+ }
+
+ dtd = xmlGetIntSubset(source);
+ if (dtd != NULL) {
+ dtd = xmlCopyDtd(dtd);
+ if (dtd != NULL) {
+ xmlAddChild((xmlNodePtr)copy, (xmlNodePtr)dtd);
+ copy->intSubset = dtd;
+ }
+ }
+
+ xmlAddChild((xmlNodePtr)copy, root);
+ return copy;
+}
+
+static int page_emit(lua_State *lua) {
+ EmitContext *context;
+ const char *path;
+ htmlDocPtr document;
+
+ luaL_checktype(lua, 1, LUA_TTABLE);
+ path = luaL_checkstring(lua, 2);
+ if (path[0] == '\0') {
+ return luaL_error(lua, "emit path cannot be empty");
+ }
+ context = lua_touserdata(lua, lua_upvalueindex(1));
+
+ document = snapshot_document(context->source, lua);
+ if (document == NULL) {
+ return luaL_error(lua, "unable to snapshot the current page");
+ }
+
+ if (append_emit(context->emits, path, document) != 0) {
+ xmlFreeDoc(document);
+ return luaL_error(lua, "unable to record emitted page");
+ }
+ return 0;
+}
+
static int run_script(lua_State *lua, const LuaScript *script,
const char *input_path) {
char *source_path = NULL;
@@ -228,7 +343,9 @@ static int replace_document_root(lua_State *lua, htmlDocPtr document,
return 0;
}
-int process_html_document(htmlDocPtr document, const char *input_path) {
+int process_html_document(htmlDocPtr document, const char *input_path,
+ EmitList *emits) {
+ EmitContext context = {document, emits};
xmlNodePtr root = xmlDocGetRootElement(document);
ScriptList scripts = {0};
lua_State *lua;
@@ -262,6 +379,11 @@ int process_html_document(htmlDocPtr document, const char *input_path) {
directory_walker_register(lua, input_path);
dom_push_page(lua, root);
lua_setglobal(lua, "page");
+ lua_getglobal(lua, "page");
+ lua_pushlightuserdata(lua, &context);
+ lua_pushcclosure(lua, page_emit, 1);
+ lua_setfield(lua, -2, "emit");
+ lua_pop(lua, 1);
result = run_scripts(lua, &scripts, input_path);
if (result == 0) {
diff --git a/src/LuaEngine/LuaEngine.h b/src/LuaEngine/LuaEngine.h
index bd035b9..2a5eea4 100644
--- a/src/LuaEngine/LuaEngine.h
+++ b/src/LuaEngine/LuaEngine.h
@@ -3,6 +3,19 @@
#include <libxml/HTMLparser.h>
-int process_html_document(htmlDocPtr document, const char *input_path);
+typedef struct {
+ char *path;
+ htmlDocPtr document;
+} EmittedPage;
+
+typedef struct {
+ EmittedPage *items;
+ size_t length;
+ size_t capacity;
+} EmitList;
+
+int process_html_document(htmlDocPtr document, const char *input_path,
+ EmitList *emits);
+void emit_list_free(EmitList *emits);
#endif