code wiki / _hdl_build / nx_site_pages.nx
nx_site_pages.nx source
↩ module page · 156 lines · 8027 B
1// nx_site_pages.nx -- DATA-DRIVEN multi-page site generator (operator: finish the andelinwest WEBSITE -- the
2// visitor content off the root domain). The homepage `.site` config already lists the practice areas as cards;
3// the onsite search links each to /practice/<slug>, but those pages did not exist (dangling search results). This
4// organ reuses the PROVEN composer (nx_site_compose compose_build -- the same block-walk that builds the homepage,
5// so practice pages match the brand by construction) to emit, into a per-domain docroot served by the content
6// router (nx_host_router hr_serve3):
7// /index.html -- the homepage (compose the original config)
8// /practice/<slug>.html -- one branded landing page per card; slug = lowercased title, spaces->'-', so
9// "Family Law" -> family-law EXACTLY matches the search URL (no dangling link).
10// Nishi emits the HTML (never hand-authored); pure composition over existing organs. The live /search ENDPOINT
11// (dynamic query->results) + the deploy are separate rungs. license_tier: ORIGINAL
12import "nx_site_compose.nx"
13import "nx_syscalls.nx"
14const K_MAGIC_16384: i64 = 16384
15
16func sp_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
17func sp_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 }
18
19// lowercase + space->'-' + keep [a-z0-9-]; "Family Law" -> "family-law". null-terminated. returns length.
20func sp_slug(title: *u8, out: *u8) -> i64 {
21 var o: i64 = 0; var i: i64 = 0
22 while title[i] != (0 as u8) {
23 var c: i64 = title[i] as i64
24 if c >= 65 { if c <= 90 { c = c + 32 } }
25 if c == 32 { out[o] = 45 as u8; o = o + 1 } else {
26 var keep: i64 = 0
27 if c >= 97 { if c <= 122 { keep = 1 } }
28 if c >= 48 { if c <= 57 { keep = 1 } }
29 if c == 45 { keep = 1 }
30 if keep == 1 { out[o] = c as u8; o = o + 1 }
31 }
32 i = i + 1
33 }
34 out[o] = 0 as u8
35 return o
36}
37
38// extract the idx-th '|'-separated field of val into out (null-terminated). returns length.
39func sp_field(val: *u8, idx: i64, out: *u8) -> i64 {
40 var f: i64 = 0; var i: i64 = 0; var o: i64 = 0
41 while val[i] != (0 as u8) {
42 if val[i] == (124 as u8) { f = f + 1 } else { if f == idx { out[o] = val[i]; o = o + 1 } }
43 i = i + 1
44 }
45 out[o] = 0 as u8
46 return o
47}
48
49// copy text[start..end) into out (null-terminated, trailing CR trimmed). returns length.
50func sp_slice(text: *u8, start: i64, end: i64, out: *u8) -> i64 {
51 var o: i64 = 0; var j: i64 = start
52 while j < end { out[o] = text[j]; o = o + 1; j = j + 1 }
53 if o > 0 { if (out[o - 1] as i64) == 13 { o = o - 1 } }
54 out[o] = 0 as u8
55 return o
56}
57
58// build a practice .site config = the homepage config with ONLY the title + hero lines swapped to this practice
59// (every other block copied verbatim, so the practice page passes the same s-class compliance as the homepage).
60// returns length.
61func sp_build_practice(text: *u8, tn: i64, ctitle: *u8, cdesc: *u8, firm: *u8, cta: *u8, href: *u8, out: *u8) -> i64 {
62 var o: i64 = 0
63 var ls: i64 = 0; var i: i64 = 0
64 while i <= tn {
65 var eol: i64 = 0
66 if i == tn { eol = 1 }
67 if i < tn { if (text[i] as i64) == 10 { eol = 1 } }
68 if eol == 1 {
69 if cmp_line_match(text, ls, i, "title" as *u8, 5) >= 0 {
70 o = sp_cat(out, o, "title|" as *u8); o = sp_cat(out, o, ctitle); o = sp_cat(out, o, " -- " as *u8); o = sp_cat(out, o, firm)
71 } else { if cmp_line_match(text, ls, i, "hero" as *u8, 4) >= 0 {
72 o = sp_cat(out, o, "hero|" as *u8); o = sp_cat(out, o, ctitle); out[o] = 124 as u8; o = o + 1
73 o = sp_cat(out, o, cdesc); out[o] = 124 as u8; o = o + 1
74 o = sp_cat(out, o, cta); out[o] = 124 as u8; o = o + 1
75 o = sp_cat(out, o, href)
76 } else {
77 var j: i64 = ls
78 while j < i { out[o] = text[j]; o = o + 1; j = j + 1 }
79 } }
80 if i < tn { out[o] = 10 as u8; o = o + 1 }
81 ls = i + 1
82 }
83 i = i + 1
84 }
85 return o
86}
87
88// GENERATE the homepage + a practice page per card from `config_path` into `docroot`. Returns the practice-page
89// count (>=0), or -1 if the config is unreadable.
90func sp_gen_file(config_path: *u8, docroot: *u8) -> i64 {
91 let szp: *i64 = sys_mmap(16) as *i64
92 let text: *u8 = sys_read_file(config_path, szp)
93 if (text as i64) == 0 { return 0 - 1 }
94 let tn: i64 = szp[0]
95 let dummy: *u8 = "web_assets/game_template.html" as *u8 // unused for the legal page type
96
97 sys_mkdir(docroot, 0x1ed)
98 let prdir: *u8 = sys_mmap(512); var pdo: i64 = sp_cat(prdir, 0, docroot); pdo = sp_cat(prdir, pdo, "/practice" as *u8); prdir[pdo] = 0 as u8
99 sys_mkdir(prdir, 0x1ed)
100
101 // homepage
102 let ipath: *u8 = sys_mmap(512); var io: i64 = sp_cat(ipath, 0, docroot); io = sp_cat(ipath, io, "/index.html" as *u8); ipath[io] = 0 as u8
103 let ifd: i64 = sys_openat_wr(ipath, 0x1a4)
104 if ifd >= 0 { compose_build(text, tn, ifd, dummy); sys_close(ifd) }
105
106 // shared brand chrome (firm / cta label / cta href / footer) -- data-driven from the config
107 let hdr: *u8 = sys_mmap(512); cmp_get(text, tn, "header" as *u8, hdr, 511)
108 let firm: *u8 = sys_mmap(128); sp_field(hdr, 0, firm)
109 let cta: *u8 = sys_mmap(128); sp_field(hdr, 1, cta)
110 let href: *u8 = sys_mmap(128); sp_field(hdr, 2, href)
111 let ftr: *u8 = sys_mmap(512); cmp_get(text, tn, "footer" as *u8, ftr, 511)
112
113 var count: i64 = 0
114 var ls: i64 = 0; var i: i64 = 0
115 while i <= tn {
116 var eol: i64 = 0
117 if i == tn { eol = 1 }
118 if i < tn { if (text[i] as i64) == 10 { eol = 1 } }
119 if eol == 1 {
120 let vs: i64 = cmp_line_match(text, ls, i, "card" as *u8, 4) // value start after "card|", or -1
121 if vs >= 0 {
122 var p1: i64 = vs
123 while p1 < i { if (text[p1] as i64) == 124 { break } p1 = p1 + 1 }
124 let ctitle: *u8 = sys_mmap(128); sp_slice(text, vs, p1, ctitle)
125 let cdesc: *u8 = sys_mmap(512); sp_slice(text, p1 + 1, i, cdesc)
126 let slug: *u8 = sys_mmap(128); sp_slug(ctitle, slug)
127
128 // build the practice .site config = the FULL homepage config with the title + hero lines swapped to
129 // THIS practice (keeps every block, so it is as s-class-compliant as the homepage by construction)
130 let pc: *u8 = sys_mmap(K_MAGIC_16384)
131 let pl: i64 = sp_build_practice(text, tn, ctitle, cdesc, firm, cta, href, pc)
132
133 let ppath: *u8 = sys_mmap(512); var po: i64 = sp_cat(ppath, 0, prdir); po = sp_cat(ppath, po, "/" as *u8); po = sp_cat(ppath, po, slug); po = sp_cat(ppath, po, ".html" as *u8); ppath[po] = 0 as u8
134 let pfd: i64 = sys_openat_wr(ppath, 0x1a4)
135 if pfd >= 0 { compose_build(pc, pl, pfd, dummy); sys_close(pfd); count = count + 1 }
136 }
137 ls = i + 1
138 }
139 i = i + 1
140 }
141 return count
142}
143
144func main(argc: i64, argv: *i64) -> i64 {
145 if argc < 3 {
146 sp_w("usage: nx_site_pages <config.site> <docroot>\n" as *u8)
147 sys_exit(2); return 2
148 }
149 let r: i64 = sp_gen_file(argv[1] as *u8, argv[2] as *u8)
150 if r < 0 { sp_w("SITE-PAGES FAIL: cannot read config\n" as *u8); sys_exit(1); return 1 }
151 sp_w("SITE-PAGES OK: homepage + " as *u8)
152 let db: *u8 = sys_mmap(8); var v: i64 = r; var k: i64 = 0; if v == 0 { db[0] = 48 as u8; k = 1 } else { while v > 0 { db[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 } }
153 let rv: *u8 = sys_mmap(8); var x: i64 = 0; while x < k { rv[x] = db[k - 1 - x]; x = x + 1 } sys_write(1, rv, k)
154 sp_w(" practice pages generated into the docroot (served by the content router; search /practice/<slug> resolves)\n" as *u8)
155 sys_exit(0); return 0
156}