diff options
Diffstat (limited to 'src/LuaEngine')
| -rw-r--r-- | src/LuaEngine/LuaEngine.c | 273 | ||||
| -rw-r--r-- | src/LuaEngine/LuaEngine.h | 8 |
2 files changed, 281 insertions, 0 deletions
diff --git a/src/LuaEngine/LuaEngine.c b/src/LuaEngine/LuaEngine.c new file mode 100644 index 0000000..319f408 --- /dev/null +++ b/src/LuaEngine/LuaEngine.c @@ -0,0 +1,273 @@ +#include "LuaEngine/LuaEngine.h" + +#include "DirectoryWalker/DirectoryWalker.h" +#include "Dom/Dom.h" +#include "Logger/Logger.h" +#include "Logger/LuaLogger.h" +#include "Path/Path.h" +#include "Version/Version.h" + +#include <lauxlib.h> +#include <lua.h> +#include <lualib.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +typedef enum { SCRIPT_INLINE, SCRIPT_EXTERNAL } ScriptType; + +typedef struct { + char *source; + ScriptType type; + unsigned long line; +} LuaScript; + +typedef struct { + LuaScript *items; + size_t length; + size_t capacity; +} ScriptList; + +static int is_lua_element(const xmlNode *node) { + return node->type == XML_ELEMENT_NODE && + strcasecmp((const char *)node->name, "lua") == 0; +} + +static char *duplicate_string(const char *value) { + size_t length = strlen(value); + char *copy = malloc(length + 1); + + if (copy != NULL) { + memcpy(copy, value, length + 1); + } + return copy; +} + +static int grow_script_list(ScriptList *scripts) { + size_t larger_capacity; + LuaScript *larger_items; + + if (scripts->capacity > SIZE_MAX / (2 * sizeof(*scripts->items))) { + return -1; + } + + larger_capacity = scripts->capacity == 0 ? 4 : scripts->capacity * 2; + larger_items = + realloc(scripts->items, larger_capacity * sizeof(*scripts->items)); + if (larger_items == NULL) { + return -1; + } + + scripts->items = larger_items; + scripts->capacity = larger_capacity; + return 0; +} + +static int append_script(ScriptList *scripts, const char *source, + ScriptType type, unsigned long line) { + LuaScript *script; + + if (scripts->length == scripts->capacity && grow_script_list(scripts) != 0) { + return -1; + } + + script = &scripts->items[scripts->length]; + script->source = duplicate_string(source); + if (script->source == NULL) { + return -1; + } + + script->type = type; + script->line = line; + ++scripts->length; + return 0; +} + +static int collect_script(xmlNode *node, ScriptList *scripts) { + xmlChar *source_path = xmlGetProp(node, BAD_CAST "src"); + xmlChar *code; + int result; + + if (source_path != NULL) { + result = append_script(scripts, (const char *)source_path, SCRIPT_EXTERNAL, + node->line); + xmlFree(source_path); + return result; + } + + code = xmlNodeGetContent(node); + result = append_script(scripts, code == NULL ? "" : (const char *)code, + SCRIPT_INLINE, node->line); + xmlFree(code); + return result; +} + +static int collect_scripts(xmlNode *node, ScriptList *scripts) { + for (; node != NULL; node = node->next) { + if (is_lua_element(node)) { + if (collect_script(node, scripts) != 0) { + return -1; + } + } else if (collect_scripts(node->children, scripts) != 0) { + return -1; + } + } + return 0; +} + +static void free_scripts(ScriptList *scripts) { + size_t index; + + for (index = 0; index < scripts->length; ++index) { + free(scripts->items[index].source); + } + free(scripts->items); +} + +static char *make_chunk_name(const char *input_path, unsigned long line) { + size_t input_length = strlen(input_path); + size_t capacity; + char *chunk_name; + + if (input_length > SIZE_MAX - 32) { + return NULL; + } + capacity = input_length + 32; + chunk_name = malloc(capacity); + + if (chunk_name != NULL) { + snprintf(chunk_name, capacity, "@%s:%lu", input_path, line); + } + return chunk_name; +} + +static int run_script(lua_State *lua, const LuaScript *script, + const char *input_path) { + char *source_path = NULL; + char *chunk_name = NULL; + const char *display_path; + int load_result; + int result; + + if (script->type == SCRIPT_EXTERNAL) { + source_path = path_resolve_from_file(input_path, script->source); + if (source_path == NULL) { + logger_error("unable to allocate Lua source path"); + return -1; + } + display_path = source_path; + load_result = luaL_loadfile(lua, source_path); + } else { + chunk_name = make_chunk_name(input_path, script->line); + if (chunk_name == NULL) { + logger_error("unable to allocate Lua chunk name"); + return -1; + } + display_path = input_path; + load_result = luaL_loadbuffer(lua, script->source, strlen(script->source), + chunk_name); + } + + result = load_result == 0 ? lua_pcall(lua, 0, 0, 0) : load_result; + if (result != 0) { + const char *message = lua_tostring(lua, -1); + + logger_error("Lua error in %s: %s", display_path, + message == NULL ? "unknown error" : message); + lua_pop(lua, 1); + } + + free(chunk_name); + free(source_path); + return result == 0 ? 0 : -1; +} + +static int run_scripts(lua_State *lua, const ScriptList *scripts, + const char *input_path) { + size_t index; + + for (index = 0; index < scripts->length; ++index) { + if (run_script(lua, &scripts->items[index], input_path) != 0) { + return -1; + } + } + return 0; +} + +static void register_yapssg(lua_State *lua) { + lua_newtable(lua); + lua_pushstring(lua, yapssg_version); + lua_setfield(lua, -2, "version"); + lua_setglobal(lua, "yapssg"); +} + +static int replace_document_root(lua_State *lua, htmlDocPtr document, + const char *input_path) { + xmlNodePtr replacement; + xmlNodePtr old_root; + + lua_getglobal(lua, "page"); + if (!lua_istable(lua, -1)) { + logger_error("Lua global 'page' must remain a table: %s", input_path); + lua_pop(lua, 1); + return -1; + } + + replacement = dom_page_from_lua(lua, lua_gettop(lua)); + lua_pop(lua, 1); + if (replacement == NULL || replacement->type != XML_ELEMENT_NODE) { + logger_error("Lua produced an invalid page root: %s", input_path); + xmlFreeNode(replacement); + return -1; + } + + old_root = xmlDocSetRootElement(document, replacement); + xmlFreeNode(old_root); + return 0; +} + +int process_html_document(htmlDocPtr document, const char *input_path) { + xmlNodePtr root = xmlDocGetRootElement(document); + ScriptList scripts = {0}; + lua_State *lua; + int result; + + if (root == NULL) { + logger_error("HTML document has no root element: %s", input_path); + return -1; + } + if (collect_scripts(root, &scripts) != 0) { + logger_error("unable to collect Lua scripts: %s", input_path); + free_scripts(&scripts); + return -1; + } + if (scripts.length == 0) { + free_scripts(&scripts); + return 0; + } + + lua = luaL_newstate(); + if (lua == NULL) { + logger_error("unable to create Lua state"); + free_scripts(&scripts); + return -1; + } + + luaL_openlibs(lua); + register_yapssg(lua); + lua_logger_register(lua); + directory_walker_register(lua, input_path); + dom_push_page(lua, root); + lua_setglobal(lua, "page"); + + result = run_scripts(lua, &scripts, input_path); + if (result == 0) { + result = replace_document_root(lua, document, input_path); + } + + lua_close(lua); + free_scripts(&scripts); + return result; +} diff --git a/src/LuaEngine/LuaEngine.h b/src/LuaEngine/LuaEngine.h new file mode 100644 index 0000000..bd035b9 --- /dev/null +++ b/src/LuaEngine/LuaEngine.h @@ -0,0 +1,8 @@ +#ifndef YAPSSG_LUA_ENGINE_H +#define YAPSSG_LUA_ENGINE_H + +#include <libxml/HTMLparser.h> + +int process_html_document(htmlDocPtr document, const char *input_path); + +#endif |
