code wiki / _hdl_build / nx_site_build.nx

nx_site_build.nx source

↩ module page · 321 lines · 15766 B

1// nx_site_build.nx -- R1 WHOLE-SITE emitter. From ONE .site blueprint, emit a multi-PAGE site with a SHARED 2// cross-page nav, INTERNAL links between pages, and its own sitemap.xml + robots.txt -- all through the gorgeous 3// S-class kit (nx_sclass_kit sk_*), so every page is s-class-by-construction. Closes the whole-site gaps the 4// factory census named: shared nav, internal <a href> links, and wired sitemap/robots EMISSION over our output. 5// Reuses the andelinwest .site blueprint verbatim (title|/header|brand|cta|href /hero|headline|sub|cta|href 6// /search|action|placeholder /cards+card|title|body /steps+step| /trust+sig| /footer|): the page LIST is derived 7// from the cards (like nx_site_pages), but upgraded to the premium kit + real navigation + a self-emitted sitemap. 8// usage: nx_site_build <config.site> <docroot> <domain> [theme:0|1] 9// emits: <docroot>/index.html + <docroot>/practice/<slug>.html (one per card) + sitemap.xml + robots.txt 10// SCALE: one buffer is mmap'd ONCE and reused per page (no per-iteration mmap -> no bump-allocator starvation). 11// license_tier: ORIGINAL 12import "nx_sclass_kit.nx" 13import "nx_syscalls.nx" 14const SB_MAGIC_1024: i64 = 1024 15const SB_MAGIC_131072: i64 = 131072 16const SB_MAGIC_262144: i64 = 262144 17 18const SB_ICON: *u8 = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3l8 4v5c0 4-3.5 7-8 9-4.5-2-8-5-8-9V7l8-4z\"/></svg>" 19 20func sb_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 21func sb_wn(v: i64) -> i64 { 22 if v==0 { sys_write(1,"0" as *u8,1); return 0 } 23 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m } 24 let t: *u8=sys_mmap(24); var k: i64=0 25 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 26 let o2: *u8=sys_mmap(24); var w: i64=0; var q: i64=k-1 27 while q>=0 { o2[w]=t[q]; w=w+1; q=q-1 } 28 sys_write(1,o2,w); return 0 29} 30func sb_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 31func sb_cat(out: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){out[o]=s[i]; o=o+1; i=i+1} return o } 32 33// copy text[start..end) into arena at ap (trim a trailing CR), NUL-terminate, return new ap. 34func sb_copy(arena: *u8, ap: i64, text: *u8, start: i64, end: i64) -> i64 { 35 var o: i64=ap; var j: i64=start 36 while j<end { arena[o]=text[j]; o=o+1; j=j+1 } 37 if o>ap { if (arena[o-1] as i64)==13 { o=o-1 } } 38 arena[o]=0 as u8; o=o+1 39 return o 40} 41// does text[ls..le) start with key|? return value-start idx, or -1. 42func sb_lm(text: *u8, ls: i64, le: i64, key: *u8, klen: i64) -> i64 { 43 if ls+klen+1 > le { return 0 - 1 } 44 var i: i64=0 45 while i<klen { if (text[ls+i] as i64)!=(key[i] as i64) { return 0 - 1 } i=i+1 } 46 if (text[ls+klen] as i64)!=124 { return 0 - 1 } 47 return ls+klen+1 48} 49// value of the first "key|value" line -> out (NUL-term, trailing CR trimmed). return len, or -1. 50func sb_cg(text: *u8, tn: i64, key: *u8, out: *u8, cap: i64) -> i64 { 51 let klen: i64 = sb_slen(key) 52 var ls: i64=0; var i: i64=0 53 while i<=tn { 54 var eol: i64=0 55 if i==tn { eol=1 } 56 if i<tn { if (text[i] as i64)==10 { eol=1 } } 57 if eol==1 { 58 let vs: i64 = sb_lm(text, ls, i, key, klen) 59 if vs>=0 { 60 var o: i64=0; var j: i64=vs 61 while j<i { if o<cap-1 { out[o]=text[j]; o=o+1 } j=j+1 } 62 if o>0 { if (out[o-1] as i64)==13 { o=o-1 } } 63 out[o]=0 as u8 64 return o 65 } 66 ls=i+1 67 } 68 i=i+1 69 } 70 out[0]=0 as u8 71 return 0 - 1 72} 73// idx-th '|'-separated field of NUL-term val -> out (NUL-term). return len. 74func sb_fld(val: *u8, idx: i64, out: *u8) -> i64 { 75 var f: i64=0; var i: i64=0; var o: i64=0 76 while val[i]!=(0 as u8) { 77 if (val[i] as i64)==124 { f=f+1 } else { if f==idx { out[o]=val[i]; o=o+1 } } 78 i=i+1 79 } 80 out[o]=0 as u8 81 return o 82} 83// slug: lowercase, space->'-', keep [a-z0-9-]. "Family Law" -> "family-law". return len. 84func sb_slug(title: *u8, out: *u8) -> i64 { 85 var o: i64=0; var i: i64=0 86 while title[i]!=(0 as u8) { 87 var c: i64 = title[i] as i64 88 if c>=65 { if c<=90 { c=c+32 } } 89 if c==32 { out[o]=45 as u8; o=o+1 } else { 90 var keep: i64=0 91 if c>=97 { if c<=122 { keep=1 } } 92 if c>=48 { if c<=57 { keep=1 } } 93 if c==45 { keep=1 } 94 if keep==1 { out[o]=c as u8; o=o+1 } 95 } 96 i=i+1 97 } 98 out[o]=0 as u8 99 return o 100} 101 102// collect every "card|title|body" line: fill ct/cb/cs (arena ptrs). return count. 103func sb_cards(text: *u8, tn: i64, ct: *i64, cb: *i64, cs: *i64, arena: *u8, apP: *i64, maxc: i64) -> i64 { 104 var cnt: i64=0; var ls: i64=0; var i: i64=0 105 while i<=tn { 106 var eol: i64=0 107 if i==tn { eol=1 } 108 if i<tn { if (text[i] as i64)==10 { eol=1 } } 109 if eol==1 { 110 let vs: i64 = sb_lm(text, ls, i, "card" as *u8, 4) 111 if vs>=0 { if cnt<maxc { 112 var p1: i64=vs 113 while p1<i { if (text[p1] as i64)==124 { break } p1=p1+1 } 114 var ap: i64 = apP[0] 115 ct[cnt] = (arena + ap) as i64 116 ap = sb_copy(arena, ap, text, vs, p1) 117 cb[cnt] = (arena + ap) as i64 118 ap = sb_copy(arena, ap, text, p1+1, i) 119 cs[cnt] = (arena + ap) as i64 120 let sl: i64 = sb_slug(ct[cnt] as *u8, (arena + ap) as *u8) 121 ap = ap + sl + 1 122 apP[0] = ap 123 cnt = cnt + 1 124 } } 125 ls=i+1 126 } 127 i=i+1 128 } 129 return cnt 130} 131// collect the single value of every "key|value" line (steps, sigs). return count. 132func sb_single(text: *u8, tn: i64, key: *u8, klen: i64, ptrs: *i64, arena: *u8, apP: *i64, maxc: i64) -> i64 { 133 var cnt: i64=0; var ls: i64=0; var i: i64=0 134 while i<=tn { 135 var eol: i64=0 136 if i==tn { eol=1 } 137 if i<tn { if (text[i] as i64)==10 { eol=1 } } 138 if eol==1 { 139 let vs: i64 = sb_lm(text, ls, i, key, klen) 140 if vs>=0 { if cnt<maxc { 141 var ap: i64 = apP[0] 142 ptrs[cnt] = (arena + ap) as i64 143 ap = sb_copy(arena, ap, text, vs, i) 144 apP[0] = ap 145 cnt = cnt + 1 146 } } 147 ls=i+1 148 } 149 i=i+1 150 } 151 return cnt 152} 153 154// build "/practice/<slug>.html" into out. return len. 155func sb_phref(out: *u8, slug: *u8) -> i64 { 156 var o: i64 = sb_cat(out, 0, "/practice/" as *u8) 157 o = sb_cat(out, o, slug); o = sb_cat(out, o, ".html" as *u8) 158 out[o]=0 as u8 159 return o 160} 161// emit the SHARED nav (Home + one item per card) into buf. hrefbuf is reused scratch. return new off. 162func emit_nav(buf: *u8, off: i64, ncards: i64, ct: *i64, cs: *i64, hrefbuf: *u8) -> i64 { 163 var o: i64 = sk_nav_item(buf, off, "Home" as *u8, "/" as *u8) 164 var i: i64=0 165 while i<ncards { 166 sb_phref(hrefbuf, cs[i] as *u8) 167 o = sk_nav_item(buf, o, ct[i] as *u8, hrefbuf) 168 i=i+1 169 } 170 return o 171} 172// emit the trust band from the sig[] signals. return new off. 173func emit_trust(buf: *u8, off: i64, nsigs: i64, sigs: *i64) -> i64 { 174 if nsigs<=0 { return off } 175 var o: i64 = sk_trust_open(buf, off, "Why us" as *u8, "Trusted, by construction" as *u8, "Every page ships accessible, fast, and sovereign -- by construction." as *u8) 176 var i: i64=0 177 while i<nsigs { o = sk_trust_item(buf, o, sigs[i] as *u8, "Verified." as *u8); i=i+1 } 178 o = sk_trust_close(buf, o) 179 return o 180} 181 182// THE WHOLE-SITE BUILD as a callable core (composed by the CLI main below AND the site-edit 183// daemon): read the .site blueprint at `config`, emit index + practice pages + sitemap + robots 184// into `docroot`. Returns ncards (>=1) on success, negative on failure. NO sys_exit here. 185func sb_build_site(config: *u8, docroot: *u8, domain: *u8, theme: i64) -> i64 { 186 let szp: *i64 = sys_mmap(16) as *i64 187 let text: *u8 = sys_read_file(config, szp) 188 if (text as i64) == 0 { sb_w("SITE-BUILD FAIL: cannot read config\n" as *u8); return 0 - 1 } 189 let tn: i64 = szp[0] 190 191 // ---- parse scalars (data-driven; content is the caller's) ---- 192 let titlebase: *u8 = sys_mmap(512); sb_cg(text, tn, "title" as *u8, titlebase, 511) 193 let hdr: *u8 = sys_mmap(512); sb_cg(text, tn, "header" as *u8, hdr, 511) 194 let brand: *u8 = sys_mmap(160); sb_fld(hdr, 0, brand) 195 let hcta: *u8 = sys_mmap(160); sb_fld(hdr, 1, hcta) 196 let hhref: *u8 = sys_mmap(160); sb_fld(hdr, 2, hhref) 197 let hero: *u8 = sys_mmap(SB_MAGIC_1024); sb_cg(text, tn, "hero" as *u8, hero, 1023) 198 let hl: *u8 = sys_mmap(512); sb_fld(hero, 0, hl) 199 let hsub: *u8 = sys_mmap(512); sb_fld(hero, 1, hsub) 200 let search: *u8 = sys_mmap(512); sb_cg(text, tn, "search" as *u8, search, 511) 201 let saction: *u8 = sys_mmap(160); sb_fld(search, 0, saction) 202 let splace: *u8 = sys_mmap(256); sb_fld(search, 1, splace) 203 let footer: *u8 = sys_mmap(512); sb_cg(text, tn, "footer" as *u8, footer, 511) 204 205 // ---- collect cards / steps / sigs into one arena ---- 206 let arena: *u8 = sys_mmap(SB_MAGIC_131072) 207 let apP: *i64 = sys_mmap(16) as *i64; apP[0]=0 208 let ct: *i64 = sys_mmap(8*32) as *i64 209 let cb: *i64 = sys_mmap(8*32) as *i64 210 let cs: *i64 = sys_mmap(8*32) as *i64 211 let ncards: i64 = sb_cards(text, tn, ct, cb, cs, arena, apP, 32) 212 let steps: *i64 = sys_mmap(8*8) as *i64 213 let nsteps: i64 = sb_single(text, tn, "step" as *u8, 4, steps, arena, apP, 3) 214 let sigs: *i64 = sys_mmap(8*16) as *i64 215 let nsigs: i64 = sb_single(text, tn, "sig" as *u8, 3, sigs, arena, apP, 12) 216 217 // ---- shared scratch (hoisted ONCE; reused per page) ---- 218 let buf: *u8 = sys_mmap(SB_MAGIC_262144) 219 let pathbuf: *u8 = sys_mmap(512) 220 let hrefbuf: *u8 = sys_mmap(256) 221 let numbuf: *u8 = sys_mmap(8) 222 let stepttl: *u8 = sys_mmap(32) 223 let titlebuf2: *u8 = sys_mmap(320) 224 225 sys_mkdir(docroot, 0x1ed) 226 var pdo: i64 = sb_cat(pathbuf, 0, docroot); pdo = sb_cat(pathbuf, pdo, "/practice" as *u8); pathbuf[pdo]=0 as u8 227 sys_mkdir(pathbuf, 0x1ed) 228 229 var i: i64 = 0 230 231 // ================= INDEX ================= 232 var o: i64 = sk_open_site(buf, 0, titlebase, brand, theme, "/" as *u8) 233 o = emit_nav(buf, o, ncards, ct, cs, hrefbuf) 234 o = sk_header_close(buf, o) 235 o = sk_hero(buf, o, brand, hl, " " as *u8, hsub, hcta, hhref, "Explore services" as *u8, "#services" as *u8, "Free consultation -- no obligation." as *u8) 236 o = sk_search(buf, o, saction, splace) 237 o = sk_section_open(buf, o, "services" as *u8, "What we do" as *u8, "How we help" as *u8, "Choose the area that fits your matter -- each opens a dedicated page." as *u8) 238 i = 0 239 while i < ncards { sb_phref(hrefbuf, cs[i] as *u8); o = sk_card_link(buf, o, SB_ICON, ct[i] as *u8, cb[i] as *u8, hrefbuf); i=i+1 } 240 o = sk_section_close(buf, o) 241 if nsteps > 0 { 242 o = sk_section_open(buf, o, "how" as *u8, "Simple process" as *u8, "Resolve it in 3 steps" as *u8, "From first contact to resolution." as *u8) 243 i = 0 244 while i < nsteps { 245 numbuf[0]=(49+i) as u8; numbuf[1]=0 as u8 246 var so: i64 = sb_cat(stepttl, 0, "Step " as *u8); so = sb_cat(stepttl, so, numbuf); stepttl[so]=0 as u8 247 o = sk_card(buf, o, numbuf, stepttl, steps[i] as *u8) 248 i=i+1 249 } 250 o = sk_section_close(buf, o) 251 } 252 o = emit_trust(buf, o, nsigs, sigs) 253 o = sk_cta(buf, o, "Get started" as *u8, hcta, "Most matters start with one short call." as *u8, hcta, hhref, "Browse services" as *u8, "#services" as *u8) 254 o = sk_close(buf, o, footer, brand) 255 var io: i64 = sb_cat(pathbuf, 0, docroot); io = sb_cat(pathbuf, io, "/index.html" as *u8); pathbuf[io]=0 as u8 256 let ifd: i64 = sys_openat_wr(pathbuf, 0x1a4) 257 if ifd >= 0 { sys_write(ifd, buf, o); sys_close(ifd) } 258 259 // ================= PRACTICE PAGES ================= 260 var pc: i64 = 0 261 while pc < ncards { 262 o = 0 263 var pt: i64 = sb_cat(titlebuf2, 0, ct[pc] as *u8); pt = sb_cat(titlebuf2, pt, " -- " as *u8); pt = sb_cat(titlebuf2, pt, brand); titlebuf2[pt]=0 as u8 264 o = sk_open_site(buf, o, titlebuf2, brand, theme, "/" as *u8) 265 o = emit_nav(buf, o, ncards, ct, cs, hrefbuf) 266 o = sk_header_close(buf, o) 267 o = sk_hero(buf, o, brand, ct[pc] as *u8, " " as *u8, cb[pc] as *u8, hcta, hhref, "All services" as *u8, "/#services" as *u8, "Serving Utah -- free initial consultation." as *u8) 268 o = sk_search(buf, o, saction, splace) 269 o = sk_section_open(buf, o, "related" as *u8, "More help" as *u8, "Related services" as *u8, "Other ways we help Utah families and businesses." as *u8) 270 i = 0 271 while i < ncards { 272 if i != pc { sb_phref(hrefbuf, cs[i] as *u8); o = sk_card_link(buf, o, SB_ICON, ct[i] as *u8, cb[i] as *u8, hrefbuf) } 273 i=i+1 274 } 275 o = sk_section_close(buf, o) 276 o = emit_trust(buf, o, nsigs, sigs) 277 o = sk_cta(buf, o, "Get started" as *u8, hcta, "Tell us what happened -- we will call you back, usually the same day." as *u8, hcta, hhref, "Back to home" as *u8, "/" as *u8) 278 o = sk_close(buf, o, footer, brand) 279 var po: i64 = sb_cat(pathbuf, 0, docroot); po = sb_cat(pathbuf, po, "/practice/" as *u8); po = sb_cat(pathbuf, po, cs[pc] as *u8); po = sb_cat(pathbuf, po, ".html" as *u8); pathbuf[po]=0 as u8 280 let pfd: i64 = sys_openat_wr(pathbuf, 0x1a4) 281 if pfd >= 0 { sys_write(pfd, buf, o); sys_close(pfd) } 282 pc=pc+1 283 } 284 285 // ================= sitemap.xml ================= 286 o = sb_cat(buf, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" as *u8) 287 o = sb_cat(buf, o, "<url><loc>https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/</loc><priority>1.0</priority></url>\n" as *u8) 288 i = 0 289 while i < ncards { 290 o = sb_cat(buf, o, "<url><loc>https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/practice/" as *u8); o = sb_cat(buf, o, cs[i] as *u8); o = sb_cat(buf, o, ".html</loc><priority>0.8</priority></url>\n" as *u8) 291 i=i+1 292 } 293 o = sb_cat(buf, o, "</urlset>\n" as *u8) 294 var smo: i64 = sb_cat(pathbuf, 0, docroot); smo = sb_cat(pathbuf, smo, "/sitemap.xml" as *u8); pathbuf[smo]=0 as u8 295 let smfd: i64 = sys_openat_wr(pathbuf, 0x1a4) 296 if smfd >= 0 { sys_write(smfd, buf, o); sys_close(smfd) } 297 298 // ================= robots.txt ================= 299 o = sb_cat(buf, 0, "User-agent: *\nAllow: /\nSitemap: https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/sitemap.xml\n" as *u8) 300 var rbo: i64 = sb_cat(pathbuf, 0, docroot); rbo = sb_cat(pathbuf, rbo, "/robots.txt" as *u8); pathbuf[rbo]=0 as u8 301 let rbfd: i64 = sys_openat_wr(pathbuf, 0x1a4) 302 if rbfd >= 0 { sys_write(rbfd, buf, o); sys_close(rbfd) } 303 304 sb_w("SITE-BUILD OK: index + " as *u8); sb_wn(ncards); sb_w(" practice pages + sitemap.xml (" as *u8); sb_wn(ncards+1); sb_w(" urls) + robots.txt -> " as *u8); sb_w(docroot); sb_w("\n" as *u8) 305 return ncards 306} 307 308func main(argc: i64, argv: *i64) -> i64 { 309 if argc < 4 { 310 sb_w("usage: nx_site_build <config.site> <docroot> <domain> [theme:0|1]\n" as *u8) 311 sys_exit(2); return 2 312 } 313 let config: *u8 = argv[1] as *u8 314 let docroot: *u8 = argv[2] as *u8 315 let domain: *u8 = argv[3] as *u8 316 var theme: i64 = 0 317 if argc > 4 { let a4: *u8 = argv[4] as *u8; if (a4[0] as i64) == 49 { theme = 1 } } 318 let rc: i64 = sb_build_site(config, docroot, domain, theme) 319 if rc < 0 { sys_exit(1); return 1 } 320 sys_exit(0); return 0 321}