code wiki / (root) / nx_build_doc.nx

nx_build_doc.nx source

↩ module page · 222 lines · 7227 B

1// nx_build_doc.nx -- render a single .md document into final HTML. 2// 3// The atomic build unit for nx_build_site (the bits-up replacement 4// for nishi-pages/build/build.py). Composes: 5// 6// nx_frontmatter -- split "---YAML---body" 7// nx_yaml_subset -- parse the frontmatter YAML metadata 8// nx_md_render -- markdown body -> HTML 9// nx_http_template -- {{ var }} + {{ var | raw }} substitution 10// 11// Pipeline for one .md doc: 12// 1. Read source bytes 13// 2. Split frontmatter -> (yaml_bytes, body_bytes) 14// 3. Parse yaml metadata into NxYamlDoc (title, date, layout, etc.) 15// 4. Render markdown body -> body_html 16// 5. Apply inner template (post.html or page.html) substituting 17// page.title, page.date, page.author, body (raw) 18// 6. Apply outer layout (layout.html) substituting site.*, page.*, 19// content (raw) where content = inner template output 20// 7. Caller writes the final HTML wherever it wants 21// 22// nx_safety_envelope: 23// intended_use: render one md doc end-to-end for nx_build_site 24// sil_target: SIL2 25// evidence: bench/nx_build_doc_smoke.nx round-trips a known 26// fixture and asserts the output bytes contain the 27// expected substituted values 28// verdict: NOT_YET_EVALUATED -- pending smoke 29 30import "nx_syscalls_x86_64.nx" 31import "nx_yaml_subset.nx" 32import "nx_frontmatter.nx" 33import "nx_http_template.nx" 34 35// ---- Sealed verdicts ---- 36 37const NX_BUILD_DOC_OK: i64 = 0 38const NX_BUILD_DOC_BAD_INPUT: i64 = 1 39const NX_BUILD_DOC_FRONTMATTER_FAIL: i64 = 2 40const NX_BUILD_DOC_YAML_FAIL: i64 = 3 41const NX_BUILD_DOC_INNER_TMPL_FAIL: i64 = 4 42const NX_BUILD_DOC_OUTER_TMPL_FAIL: i64 = 5 43const NX_BUILD_DOC_OUT_TOO_SMALL: i64 = 6 44const NX_BUILD_DOC_VERDICT_N: i64 = 7 45 46// ---- Helpers ---- 47 48func _bd_bcopy(dst: *u8, src: *u8, n: i64) -> i64 { 49 var i: i64 = 0 50 while i < n { dst[i] = src[i]; i = i + 1 } 51 return 0 52} 53 54func _bd_strlen(s: *u8, cap: i64) -> i64 { 55 var i: i64 = 0 56 while i < cap { 57 if s[i] == 0 { return i } 58 i = i + 1 59 } 60 return cap 61} 62 63// Append bytes to a buffer with bounds check. Returns new offset. 64func _bd_append(dst: *u8, off: i64, cap: i64, 65 src: *u8, src_n: i64) -> i64 { 66 if off + src_n > cap { return -1 } 67 var i: i64 = 0 68 while i < src_n { 69 dst[off + i] = src[i] 70 i = i + 1 71 } 72 return off + src_n 73} 74 75// ---- Build a template var table from the frontmatter YAML + body html ---- 76// 77// Vars used by post.html / page.html / layout.html: 78// site.title, site.tagline, site.author, site.description 79// page.title, page.description, page.date, page.author 80// body (the rendered markdown HTML, used inside post.html / page.html) 81// content (the inner template's output, used inside layout.html) 82// 83// nx_http_template uses flat var names; "site.title" is the literal 84// var name, NOT a hierarchical lookup. Caller assembles flat names 85// in the names arena. 86 87const NX_BD_MAX_VARS: i64 = 16 88const NX_BD_NAMES_CAP: i64 = 512 89const NX_BD_VALUES_CAP: i64 = 16384 // 16K for body html 90 91struct NxBuildVars { 92 tv: *NxTemplateVars, 93 names: *u8, 94 name_offs: *i64, 95 name_lens: *i64, 96 values: *u8, 97 value_offs: *i64, 98 value_lens: *i64, 99 capacity: i64, 100 count: i64, 101} 102 103const NX_BUILD_VARS_BYTES: i64 = 72 104 105// Pre-allocate var-table arenas. Caller's responsibility. 106func nx_build_vars_init(bv: *NxBuildVars) -> i64 { 107 bv.tv = sys_mmap(NX_TEMPLATE_VARS_BYTES) as *NxTemplateVars 108 bv.names = sys_mmap(NX_BD_NAMES_CAP) 109 bv.name_offs = sys_mmap(NX_BD_MAX_VARS * 8) as *i64 110 bv.name_lens = sys_mmap(NX_BD_MAX_VARS * 8) as *i64 111 bv.values = sys_mmap(NX_BD_VALUES_CAP) 112 bv.value_offs = sys_mmap(NX_BD_MAX_VARS * 8) as *i64 113 bv.value_lens = sys_mmap(NX_BD_MAX_VARS * 8) as *i64 114 bv.capacity = NX_BD_MAX_VARS 115 bv.count = 0 116 return 0 117} 118 119// Add a (name, value) pair. Names + values arenas grow append-only. 120// Returns 0 on OK, -1 on overflow. 121func nx_build_vars_add(bv: *NxBuildVars, 122 name: *u8, name_n: i64, 123 value: *u8, value_n: i64) -> i64 { 124 if bv.count >= bv.capacity { return -1 } 125 126 // Append name. 127 var names_off: i64 = 0 128 var i: i64 = 0 129 while i < bv.count { 130 names_off = names_off + bv.name_lens[i] 131 i = i + 1 132 } 133 if names_off + name_n > NX_BD_NAMES_CAP { return -1 } 134 var j: i64 = 0 135 while j < name_n { 136 bv.names[names_off + j] = name[j] 137 j = j + 1 138 } 139 bv.name_offs[bv.count] = names_off 140 bv.name_lens[bv.count] = name_n 141 142 // Append value. 143 var values_off: i64 = 0 144 var k: i64 = 0 145 while k < bv.count { 146 values_off = values_off + bv.value_lens[k] 147 k = k + 1 148 } 149 if values_off + value_n > NX_BD_VALUES_CAP { return -1 } 150 var m: i64 = 0 151 while m < value_n { 152 bv.values[values_off + m] = value[m] 153 m = m + 1 154 } 155 bv.value_offs[bv.count] = values_off 156 bv.value_lens[bv.count] = value_n 157 158 bv.count = bv.count + 1 159 return 0 160} 161 162// Wire the underlying NxTemplateVars to the freshly-built arenas. 163func nx_build_vars_seal(bv: *NxBuildVars) -> i64 { 164 return nx_template_vars_init(bv.tv, 165 bv.names, bv.name_offs, bv.name_lens, 166 bv.values, bv.value_offs, bv.value_lens, 167 bv.count) 168} 169 170// ---- Per-doc add: lift yaml frontmatter entries into the var table ---- 171// 172// Each YAML entry "title: foo" becomes "page.title -> foo" in the 173// template var table. Caller passes the page. prefix and the source 174// YAML doc. 175 176func nx_build_vars_add_yaml(bv: *NxBuildVars, 177 prefix: *u8, prefix_n: i64, 178 yaml: *NxYamlDoc) -> i64 { 179 var i: i64 = 0 180 while i < yaml.n_entries { 181 let key_off: *i64 = sys_mmap(16) as *i64 182 let key_len: *i64 = sys_mmap(16) as *i64 183 let val_off: *i64 = sys_mmap(16) as *i64 184 let val_len: *i64 = sys_mmap(16) as *i64 185 nx_yaml_key_at(yaml, i, key_off, key_len) 186 nx_yaml_value_at(yaml, i, val_off, val_len) 187 188 // Build full name = prefix + key bytes. 189 let full_name: *u8 = sys_mmap(NX_YAML_MAX_KEY_LEN + 32) 190 var p: i64 = 0 191 var j: i64 = 0 192 while j < prefix_n { 193 full_name[p] = prefix[j] 194 p = p + 1 195 j = j + 1 196 } 197 var k: i64 = 0 198 while k < key_len[0] { 199 full_name[p] = yaml.keys[key_off[0] + k] 200 p = p + 1 201 k = k + 1 202 } 203 204 let val_ptr: *u8 = (yaml.values as i64 + val_off[0]) as *u8 205 if nx_build_vars_add(bv, full_name, p, val_ptr, val_len[0]) != 0 { 206 return -1 207 } 208 i = i + 1 209 } 210 return 0 211} 212 213// ---- Apply a template ---- 214 215func nx_build_apply_template(tmpl: *u8, tmpl_n: i64, 216 bv: *NxBuildVars, 217 out: *u8, out_cap: i64, 218 out_n_slot: *i64) -> i64 { 219 nx_build_vars_seal(bv) 220 return nx_http_template_render(tmpl, tmpl_n, bv.tv, 221 out, out_cap, out_n_slot) 222}