Mercurial
comparison mrjunejune/template_renderer.c @ 264:04fee26ecce0
add authenticated JRPG conversation platform
Add reusable auth/session storage, owned conversation recovery, guest quotas, admin workflows, URL-routed conversation UI, mobile frame support, and parallel browser acceptance.
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Fri, 07 Aug 2026 07:34:12 -0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 263:ee04e4e69fed | 264:04fee26ecce0 |
|---|---|
| 1 /* | |
| 2 * template_renderer.c — site-owned HTML template renderer for mrjunejune. | |
| 3 * | |
| 4 * Expands one level of {{/path}} include tokens found in HTML templates. | |
| 5 * File I/O is done with standard C fopen/fread so that tests can point | |
| 6 * the renderer at any root without depending on Seobeo server state. | |
| 7 */ | |
| 8 | |
| 9 #include "mrjunejune/template_renderer.h" | |
| 10 | |
| 11 #include "dowa/dowa.h" | |
| 12 #include "seobeo/seobeo.h" | |
| 13 | |
| 14 #include <stdio.h> | |
| 15 #include <stdlib.h> | |
| 16 #include <string.h> | |
| 17 | |
| 18 /* Default matches the path Seobeo_Web_Server_Start_On receives in main.c. */ | |
| 19 static char g_src_root[512] = "mrjunejune/src"; | |
| 20 | |
| 21 void Mjj_Template_Renderer_Init(const char *src_root) | |
| 22 { | |
| 23 if (!src_root || src_root[0] == '\0') | |
| 24 return; | |
| 25 strncpy(g_src_root, src_root, sizeof(g_src_root) - 1); | |
| 26 g_src_root[sizeof(g_src_root) - 1] = '\0'; | |
| 27 } | |
| 28 | |
| 29 /* Load a file using the renderer's own root; caller must free() result. */ | |
| 30 static char *renderer_load_file(const char *rel_path, size_t *p_size) | |
| 31 { | |
| 32 if (!rel_path) | |
| 33 return NULL; | |
| 34 | |
| 35 char full_path[1024]; | |
| 36 /* rel_path already starts with '/' when produced by canonical markers | |
| 37 (e.g. "/parts/base_head.html"), so concatenate directly. */ | |
| 38 if (rel_path[0] == '/') | |
| 39 snprintf(full_path, sizeof(full_path), "%s%s", g_src_root, rel_path); | |
| 40 else | |
| 41 snprintf(full_path, sizeof(full_path), "%s/%s", g_src_root, rel_path); | |
| 42 | |
| 43 FILE *f = fopen(full_path, "rb"); | |
| 44 if (!f) | |
| 45 return NULL; | |
| 46 | |
| 47 fseek(f, 0, SEEK_END); | |
| 48 long sz = ftell(f); | |
| 49 fseek(f, 0, SEEK_SET); | |
| 50 if (sz < 0) | |
| 51 { | |
| 52 fclose(f); | |
| 53 return NULL; | |
| 54 } | |
| 55 | |
| 56 char *buf = (char *)malloc((size_t)sz + 1); | |
| 57 if (!buf) | |
| 58 { | |
| 59 fclose(f); | |
| 60 return NULL; | |
| 61 } | |
| 62 | |
| 63 size_t n = fread(buf, 1, (size_t)sz, f); | |
| 64 fclose(f); | |
| 65 buf[n] = '\0'; | |
| 66 | |
| 67 if (p_size) | |
| 68 *p_size = n; | |
| 69 return buf; | |
| 70 } | |
| 71 | |
| 72 /* Write `len` bytes of `src` to out starting at *p_offset, respecting cap. | |
| 73 Returns FALSE and does a best-effort NUL-terminate on overflow. */ | |
| 74 static boolean safe_append( | |
| 75 char *out, size_t cap, size_t *p_offset, | |
| 76 const char *src, size_t len) | |
| 77 { | |
| 78 if (len == 0) | |
| 79 return TRUE; | |
| 80 if (*p_offset + len >= cap) | |
| 81 { | |
| 82 /* Partial write then NUL-terminate. */ | |
| 83 size_t avail = (cap > *p_offset + 1) ? (cap - *p_offset - 1) : 0; | |
| 84 if (avail > 0) | |
| 85 memcpy(out + *p_offset, src, avail); | |
| 86 out[cap - 1] = '\0'; | |
| 87 return FALSE; | |
| 88 } | |
| 89 memcpy(out + *p_offset, src, len); | |
| 90 *p_offset += len; | |
| 91 return TRUE; | |
| 92 } | |
| 93 | |
| 94 boolean Mjj_Template_Render( | |
| 95 char *out, size_t cap, const char *tmpl, Dowa_Arena *arena) | |
| 96 { | |
| 97 if (!out || cap == 0) | |
| 98 return FALSE; | |
| 99 out[0] = '\0'; | |
| 100 if (!tmpl) | |
| 101 return FALSE; | |
| 102 | |
| 103 size_t offset = 0; | |
| 104 const char *cursor = tmpl; | |
| 105 | |
| 106 while (1) | |
| 107 { | |
| 108 const char *open = strstr(cursor, "{{"); | |
| 109 if (!open) | |
| 110 break; | |
| 111 | |
| 112 const char *close = strstr(open + 2, "}}"); | |
| 113 if (!close) | |
| 114 break; | |
| 115 | |
| 116 /* Copy text before the token. */ | |
| 117 size_t lead = (size_t)(open - cursor); | |
| 118 if (!safe_append(out, cap, &offset, cursor, lead)) | |
| 119 return FALSE; | |
| 120 | |
| 121 /* Extract include path between {{ and }}. */ | |
| 122 size_t name_len = (size_t)(close - (open + 2)); | |
| 123 char *include_name = (char *)Dowa_Arena_Allocate(arena, name_len + 1); | |
| 124 if (!include_name) | |
| 125 return FALSE; | |
| 126 memcpy(include_name, open + 2, name_len); | |
| 127 include_name[name_len] = '\0'; | |
| 128 | |
| 129 /* Load and inline the included file; skip silently if missing. */ | |
| 130 size_t inc_size = 0; | |
| 131 char *inc = renderer_load_file(include_name, &inc_size); | |
| 132 Seobeo_Log(SEOBEO_DEBUG, | |
| 133 "[TEMPLATE] include '%s' -> %s (size=%zu)\n", | |
| 134 include_name, inc ? "OK" : "MISSING", inc_size); | |
| 135 if (inc) | |
| 136 { | |
| 137 boolean ok = safe_append(out, cap, &offset, inc, inc_size); | |
| 138 free(inc); | |
| 139 if (!ok) | |
| 140 return FALSE; | |
| 141 } | |
| 142 | |
| 143 cursor = close + 2; | |
| 144 } | |
| 145 | |
| 146 /* Copy the tail (after the last token, or the whole string if no tokens). */ | |
| 147 size_t tail = strlen(cursor); | |
| 148 if (!safe_append(out, cap, &offset, cursor, tail)) | |
| 149 return FALSE; | |
| 150 | |
| 151 out[offset] = '\0'; | |
| 152 return TRUE; | |
| 153 } | |
| 154 | |
| 155 boolean Mjj_Template_Render_File( | |
| 156 char *out, size_t cap, const char *path, Dowa_Arena *arena) | |
| 157 { | |
| 158 if (!out || cap == 0) | |
| 159 return FALSE; | |
| 160 out[0] = '\0'; | |
| 161 | |
| 162 Seobeo_Log(SEOBEO_DEBUG, "[TEMPLATE] loading file '%s'\n", path); | |
| 163 | |
| 164 size_t file_size = 0; | |
| 165 char *tmpl = renderer_load_file(path, &file_size); | |
| 166 if (!tmpl) | |
| 167 { | |
| 168 Seobeo_Log(SEOBEO_DEBUG, "[TEMPLATE] file '%s' not found\n", path); | |
| 169 return FALSE; | |
| 170 } | |
| 171 | |
| 172 boolean ok = Mjj_Template_Render(out, cap, tmpl, arena); | |
| 173 free(tmpl); | |
| 174 return ok; | |
| 175 } |