code wiki / _hdl_build / nx_ims_page.nx

nx_ims_page.nx source

↩ module page · 203 lines · 9004 B

1// nx_ims_page.nx -- THE REUSABLE TYPE-AWARE PAGE GENERATOR. ONE function, ims_page, 2// renders ANY real Nishi content into a fully WALKABLE, S-class typed wiki page -- 3// replacing the per-page cloned emitters (nx_wiki_nist_stem_page-style copies). It 4// reuses nx_wiki_shell VERBATIM for every piece of furniture (head, nav, the walk 5// block = breadcrumb+tree+prev/next+related+backlinks, anchored sections, infobox, 6// toc, footer) and reads the page's nav relationships from wiki_links.tsv BY SLUG, 7// so a new page becomes part of the walkable tree the moment its wiki_links.tsv row 8// exists. The Diataxis doc_type drives a small, DATA-DRIVEN layout difference read 9// from knowledge/registry/ims_typemap.tsv (a type label + which optional sections 10// show) -- no layout magic in code (CLAUDE.md #11). The caller supplies title + 11// body_html (already valid, escaped HTML for the page's own content) + the corpus 12// set used by the guarded publish step (the generator itself only WRITES the file). 13// 14// REUSE (zero furniture reinvented): nx_wiki_shell sh_head/sh_nav/sh_walk_furniture/ 15// sh_section/sh_infobox_*/sh_toc_*/sh_footer_wl + sh_links_load/sh_corpus_store + 16// sh_cat/sh_catn + NxWikiLinks. nx_syscalls for I/O. license_tier: ORIGINAL 17import "nx_syscalls.nx" 18import "nx_wiki_shell.nx" 19import "nx_native_config.nx" 20 21// ===== sealed verdict surface (codes 2820-2829; distinct from WM_*/SH_*) ======= 22const IP_OK: i64 = 0 23const IP_BAD_INPUT: i64 = 2820 24const IP_WRITE_FAIL: i64 = 2821 25 26// ===== named sizing constants (M7) ============================================ 27const IP_CAP: i64 = 262144 // 256 KB per emitted page (>> any real page) 28const IP_MODE_0644: i64 = 0x1a4 29const IP_LINKS_PATH: *u8 = "knowledge/registry/wiki_links.tsv" 30const IP_TYPEMAP_PREFIX: *u8 = "knowledge/store/ims-typemap-" 31const IP_OUT_DIR: *u8 = "web_assets/" 32 33// ============================================================================= 34// ims_typemap.tsv reader (doc_type -> label + 3 layout flags). Mirrors the 35// sh_links_load TSV parse exactly: skip '#'/blank/header, split on TAB, "-"=>0. 36// Columns: doc_type<TAB>label<TAB>show_toc<TAB>show_infobox<TAB>show_steps (5). 37// ============================================================================= 38const IP_TM_MAXROWS: i64 = 16 39const IP_TM_MAXBYTES: i64 = 8192 40const IP_TM_NCOLS: i64 = 5 41 42struct NxTypeMap { 43 raw: *u8 44 nrows: i64 45 cells: *i64 // IP_TM_MAXROWS * IP_TM_NCOLS i64s (field pointers; 0 => empty) 46 valid: i64 47} 48 49func ip_tm_field(tm: *NxTypeMap, row: i64, col: i64) -> *u8 { 50 if tm.valid != 1 { return 0 as *u8 } 51 if row < 0 { return 0 as *u8 } 52 if row >= tm.nrows { return 0 as *u8 } 53 if col < 0 { return 0 as *u8 } 54 if col >= IP_TM_NCOLS { return 0 as *u8 } 55 return tm.cells[row * IP_TM_NCOLS + col] as *u8 56} 57 58// parse the numeric flag in a cell (NUL-terminated "0"/"1"); 0 if empty/non-numeric. 59func ip_tm_flag(tm: *NxTypeMap, row: i64, col: i64) -> i64 { 60 let s: *u8 = ip_tm_field(tm, row, col) 61 if (s as i64) == 0 { return 0 } 62 if s[0] == 49 as u8 { return 1 } 63 return 0 64} 65 66func ip_typemap_load(tm: *NxTypeMap, prefix: *u8) -> i64 { 67 tm.valid = 0 68 tm.nrows = 0 69 tm.cells = sys_mmap(IP_TM_MAXROWS * IP_TM_NCOLS * 8) as *i64 70 tm.raw = 0 as *u8 71 // read the Diataxis layout rows from the NATIVE seg_store (no TSV) via nx_native_config. 72 let hh: *i64 = ncfg_open(prefix) 73 if (hh as i64) == 0 { return 0 - 1 } 74 let cnt: i64 = ncfg_count(hh, "tm\x00" as *u8) 75 let rk: *i64 = sys_mmap(8 * 8) as *i64 76 let rv: *i64 = sys_mmap(8 * 8) as *i64 77 var rows: i64 = 0 78 var i: i64 = 0 79 while i < cnt { 80 if rows < IP_TM_MAXROWS { 81 let rf: i64 = ncfg_row(hh, "tm\x00" as *u8, i, rk, rv, 8) 82 if rf > 0 { 83 tm.cells[rows * IP_TM_NCOLS + 0] = ncfg_field(rk, rv, rf, "doc_type\x00" as *u8) as i64 84 tm.cells[rows * IP_TM_NCOLS + 1] = ncfg_field(rk, rv, rf, "label\x00" as *u8) as i64 85 tm.cells[rows * IP_TM_NCOLS + 2] = ncfg_field(rk, rv, rf, "show_toc\x00" as *u8) as i64 86 tm.cells[rows * IP_TM_NCOLS + 3] = ncfg_field(rk, rv, rf, "show_infobox\x00" as *u8) as i64 87 tm.cells[rows * IP_TM_NCOLS + 4] = ncfg_field(rk, rv, rf, "show_steps\x00" as *u8) as i64 88 rows = rows + 1 89 } 90 } 91 i = i + 1 92 } 93 tm.nrows = rows 94 tm.valid = 1 95 return rows 96} 97 98// find the typemap row for a doc_type; -1 if absent. 99func ip_type_row(tm: *NxTypeMap, doc_type: *u8) -> i64 { 100 var r: i64 = 0 101 while r < tm.nrows { 102 let dt: *u8 = ip_tm_field(tm, r, 0) 103 if sh_streq(dt, doc_type) == 1 { return r } 104 r = r + 1 105 } 106 return 0 - 1 107} 108 109// ============================================================================= 110// ims_page: render slug.html. doc_type drives layout via the typemap; title + 111// body_html supply the page content; corpus_path lets a gate point at a fixture 112// store (production passes the live snapshot path; 0 => default snapshot). 113// Writes web_assets/<slug>.html. Returns bytes written (>0) or a negative IP_*. 114// ============================================================================= 115func ip_render(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8, 116 epoch: i64, store: *NxWikiDocStore, h: *u8) -> i64 { 117 if (slug as i64) == 0 { return 0 - IP_BAD_INPUT } 118 if (title as i64) == 0 { return 0 - IP_BAD_INPUT } 119 if (body_html as i64) == 0 { return 0 - IP_BAD_INPUT } 120 121 // curated nav model (tree/path/related/tags) + the per-type layout. 122 let wl: *NxWikiLinks = sys_mmap(128) as *NxWikiLinks 123 sh_links_load(wl, IP_LINKS_PATH) 124 let tm: *NxTypeMap = sys_mmap(128) as *NxTypeMap 125 ip_typemap_load(tm, IP_TYPEMAP_PREFIX) 126 127 let trow: i64 = ip_type_row(tm, doc_type) 128 // layout flags (defaults if the doc_type is unknown: full prose page). 129 var label: *u8 = doc_type 130 var show_ib: i64 = 1 131 var show_steps: i64 = 0 132 if trow >= 0 { 133 let lb: *u8 = ip_tm_field(tm, trow, 1) 134 if (lb as i64) != 0 { label = lb } 135 show_ib = ip_tm_flag(tm, trow, 3) 136 show_steps = ip_tm_flag(tm, trow, 4) 137 } 138 139 var w: i64 = 0 140 // ---- head + nav + the WALK FURNITURE (breadcrumb+tree+prev/next+related+backlinks) ---- 141 w = sh_head(h, w, title, slug) 142 w = sh_nav(h, w, slug) 143 w = sh_walk_furniture(h, w, wl, store, slug) 144 145 // ---- page H1 + freshness chip stamping the Diataxis type ---- 146 w = sh_cat(h, w, "<h1>" as *u8); w = sh_cat(h, w, title); w = sh_cat(h, w, "</h1>\n" as *u8) 147 w = sh_cat(h, w, "<p class=\"fresh\">&#10227; Generated LIVE by <code>nx_ims_page</code> at epoch=" as *u8) 148 w = sh_catn(h, w, epoch) 149 w = sh_cat(h, w, " &mdash; a Diataxis <strong>" as *u8); w = sh_cat(h, w, label) 150 w = sh_cat(h, w, "</strong> page (type-aware layout, reusable generator).</p>\n" as *u8) 151 152 // ---- optional infobox (type-driven) ---- 153 if show_ib == 1 { 154 w = sh_infobox_open(h, w, title) 155 w = sh_infobox_row(h, w, "Type" as *u8, label) 156 w = sh_infobox_row(h, w, "Generated by" as *u8, "nx_ims_page (reusable)" as *u8) 157 w = sh_infobox_row(h, w, "Slug" as *u8, slug) 158 if show_steps == 1 { 159 w = sh_infobox_row(h, w, "Layout" as *u8, "task-oriented (steps)" as *u8) 160 } else { 161 w = sh_infobox_row(h, w, "Layout" as *u8, "knowledge-oriented (prose)" as *u8) 162 } 163 w = sh_infobox_close(h, w) 164 } 165 166 // ---- the page's own content: one anchored section wrapping the caller body ---- 167 // (step pages get a task-cue heading; prose pages an overview heading -- the 168 // data-driven layout difference, kept deliberately small.) 169 if show_steps == 1 { 170 w = sh_section(h, w, "steps" as *u8, "Steps" as *u8) 171 } else { 172 w = sh_section(h, w, "overview" as *u8, "Overview" as *u8) 173 } 174 w = sh_cat(h, w, body_html) 175 176 // ---- footer (see-also + categories from wiki_links.tsv, provenance) ---- 177 w = sh_footer_wl(h, w, wl, slug, epoch) 178 return w 179} 180 181// production convenience: build the live corpus store + a wall-clock epoch, render, 182// and WRITE web_assets/<slug>.html. Returns bytes written (>0) or negative IP_*. 183func ims_page(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8) -> i64 { 184 let now: i64 = sys_now_realtime_sec() 185 let store: *NxWikiDocStore = sh_corpus_store() 186 let h: *u8 = sys_mmap(IP_CAP) 187 let w: i64 = ip_render(slug, doc_type, title, body_html, now, store, h) 188 if w < 0 { return w } 189 190 // build the output path web_assets + slug + .html (via sh_cat, NUL-terminated). 191 let path: *u8 = sys_mmap(256) 192 var po: i64 = 0 193 po = sh_cat(path, po, IP_OUT_DIR) 194 po = sh_cat(path, po, slug) 195 po = sh_cat(path, po, ".html" as *u8) 196 path[po] = 0 as u8 197 198 let fd: i64 = sys_openat_wr(path, IP_MODE_0644) 199 if fd < 0 { return 0 - IP_WRITE_FAIL } 200 sys_write(fd, h, w) 201 sys_close(fd) 202 return w 203}