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}