@nova-lang/cli 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/renderer.zig DELETED
@@ -1,678 +0,0 @@
1
- const std = @import("std");
2
- const types = @import("types.zig");
3
- const Node = types.Node;
4
- const AstPool = types.AstPool;
5
-
6
- pub const Renderer = struct {
7
- allocator: std.mem.Allocator,
8
- result: std.ArrayList(u8),
9
- indent_level: u32,
10
-
11
- pub fn init(allocator: std.mem.Allocator) Renderer {
12
- return .{
13
- .allocator = allocator,
14
- .result = std.ArrayList(u8).init(allocator),
15
- .indent_level = 0,
16
- };
17
- }
18
-
19
- pub fn deinit(self: *Renderer) void {
20
- self.result.deinit();
21
- }
22
-
23
- pub fn render(self: *Renderer, doc: Node) ![]const u8 {
24
- try self.emitRaw("<!DOCTYPE html>");
25
- try self.emitRaw("<html lang=\"zh-CN\">");
26
- try self.emitRaw("<head>");
27
- try self.emitRaw("<meta charset=\"UTF-8\">");
28
- try self.emitRaw("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
29
-
30
- var title: []const u8 = "Nova Document";
31
- if (doc.tag == .document) {
32
- if (doc.data.document.meta.get("title")) |t| title = t;
33
- }
34
- try self.emitLine("<title>");
35
- try self.result.appendSlice(title);
36
- try self.emitLine("</title>");
37
-
38
- try self.emitRaw("<style>");
39
- try self.emitRaw(DEFAULT_STYLES);
40
- try self.emitRaw("</style>");
41
- try self.emitRaw("<script>");
42
- try self.emitRaw("MathJax = {tex: {inlineMath: [['$','$'],['\\\\(','\\\\)']],displayMath:[['$$','$$'],['\\\\[','\\\\]']]},svg:{fontCache:'global'}};");
43
- try self.emitRaw("</script>");
44
- try self.emitRaw("<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js\" async></script>");
45
- try self.emitRaw("</head>");
46
- try self.emitRaw("<body>");
47
- try self.emitLine("<div class=\"nova-document\">");
48
-
49
- if (doc.tag == .document) {
50
- if (doc.data.document.meta.count() > 0) {
51
- try self.emitIndent(1);
52
- try self.emitLine("<div class=\"nova-meta\">");
53
- var it = doc.data.document.meta.iterator();
54
- while (it.next()) |entry| {
55
- try self.emitIndent(2);
56
- try self.result.appendSlice("<span class=\"nova-meta-key\">");
57
- try self.result.appendSlice(try self.escape(entry.key_ptr.*));
58
- try self.result.appendSlice("</span>: <span class=\"nova-meta-val\">");
59
- try self.result.appendSlice(try self.escape(entry.value_ptr.*));
60
- try self.emitLine("</span>");
61
- }
62
- try self.emitIndent(1);
63
- try self.emitLine("</div>");
64
- }
65
- for (doc.data.document.children.items) |child| {
66
- try self.renderNode(child, 1);
67
- }
68
- }
69
-
70
- try self.emitLine("</div>");
71
- try self.emitRaw("</body>");
72
- try self.emitRaw("</html>");
73
- return self.result.items;
74
- }
75
-
76
- fn renderNode(self: *Renderer, node: Node, indent: u32) !void {
77
- switch (node.tag) {
78
- .block => try self.renderBlock(&node.data.block, indent),
79
- .inline_block => {
80
- const html = try self.renderInline(&node.data.inline_block);
81
- try self.result.appendSlice(html);
82
- },
83
- .text => try self.result.appendSlice(try self.escape(node.data.text.value)),
84
- .math_inline => {
85
- try self.emitIndent(indent);
86
- try self.result.appendSlice("<span class=\"math\">\\(");
87
- try self.result.appendSlice(try self.escape(node.data.math_inline.source));
88
- try self.result.appendSlice("\\)</span>");
89
- },
90
- .math_display => {
91
- try self.emitIndent(indent);
92
- try self.result.appendSlice("<div class=\"math-display\">\\[");
93
- try self.result.appendSlice(try self.escape(node.data.math_display.source));
94
- try self.result.appendSlice("\\]</div>");
95
- },
96
- .interpolation => {
97
- try self.result.appendSlice("<code>#{");
98
- try self.result.appendSlice(try self.escape(node.data.interpolation.expression));
99
- try self.result.appendSlice("}</code>");
100
- },
101
- .list_block => try self.renderList(&node.data.list_block, indent),
102
- .table_block => try self.renderTable(&node.data.table_block, indent),
103
- .schema_def => try self.renderSchema(&node.data.schema_def),
104
- .service_def => try self.renderService(&node.data.service_def),
105
- .biblio_entry => try self.renderBiblioEntry(&node.data.biblio_entry),
106
- .if_block => {
107
- for (node.data.if_block.then_body.items) |c| try self.renderNode(c, indent);
108
- },
109
- .meta_block => {},
110
- .for_block => {
111
- for (node.data.for_block.body.items) |c| try self.renderNode(c, indent);
112
- },
113
- .macro_def => {},
114
- .document => {
115
- for (node.data.document.children.items) |c| try self.renderNode(c, indent);
116
- },
117
- else => {},
118
- }
119
- }
120
-
121
- fn renderBlock(self: *Renderer, block: *types.Block, indent: u32) !void {
122
- const name = block.name;
123
-
124
- if (std.mem.eql(u8, name, "page")) {
125
- for (block.children.items) |child| try self.renderNode(child, indent);
126
- return;
127
- }
128
- if (std.mem.eql(u8, name, "else")) {
129
- for (block.children.items) |child| try self.renderNode(child, indent);
130
- return;
131
- }
132
- if (std.mem.eql(u8, name, "ul")) {
133
- try self.emitIndent(indent);
134
- try self.emitLine("<ul>");
135
- for (block.children.items) |child| {
136
- try self.emitIndent(indent + 1);
137
- try self.emitLine("<li>");
138
- try self.renderNode(child, indent + 2);
139
- try self.emitIndent(indent + 1);
140
- try self.emitLine("</li>");
141
- }
142
- try self.emitIndent(indent);
143
- try self.emitLine("</ul>");
144
- return;
145
- }
146
- if (std.mem.eql(u8, name, "ol")) {
147
- try self.emitIndent(indent);
148
- try self.emitLine("<ol>");
149
- for (block.children.items) |child| {
150
- try self.emitIndent(indent + 1);
151
- try self.emitLine("<li>");
152
- try self.renderNode(child, indent + 2);
153
- try self.emitIndent(indent + 1);
154
- try self.emitLine("</li>");
155
- }
156
- try self.emitIndent(indent);
157
- try self.emitLine("</ol>");
158
- return;
159
- }
160
- if (std.mem.eql(u8, name, "title")) {
161
- try self.emitIndent(indent);
162
- try self.result.appendSlice("<h1 class=\"nova-title\">");
163
- try self.renderInlineList(block.inline_content);
164
- try self.emitLine("</h1>");
165
- return;
166
- }
167
- if (std.mem.eql(u8, name, "header") or std.mem.eql(u8, name, "body")) {
168
- for (block.children.items) |child| try self.renderNode(child, indent);
169
- return;
170
- }
171
- if (std.mem.eql(u8, name, "abstract")) {
172
- try self.emitIndent(indent);
173
- try self.emitLine("<blockquote class=\"abstract\">");
174
- try self.emitIndent(indent + 1);
175
- try self.emitLine("<strong>摘要</strong>");
176
- for (block.children.items) |child| try self.renderNode(child, indent + 1);
177
- try self.emitIndent(indent);
178
- try self.emitLine("</blockquote>");
179
- return;
180
- }
181
- if (std.mem.eql(u8, name, "chapter") or std.mem.eql(u8, name, "section")) {
182
- const level: u8 = if (std.mem.eql(u8, name, "chapter")) 2 else 3;
183
- var tag_buf: [4]u8 = undefined;
184
- const tag = std.fmt.bufPrint(&tag_buf, "h{d}", .{level}) catch "h3";
185
-
186
- var title_text: []const u8 = "";
187
- for (block.children.items) |child| {
188
- if (child.tag == .block and std.mem.eql(u8, child.data.block.name, "title")) {
189
- title_text = try self.collectText(&child.data.block.inline_content);
190
- }
191
- }
192
- try self.emitIndent(indent);
193
- try self.result.appendSlice("<");
194
- try self.result.appendSlice(tag);
195
- if (block.attrs.get("id")) |sec_id| {
196
- try self.result.appendSlice(" id=\"");
197
- try self.result.appendSlice(try self.escape(sec_id));
198
- try self.result.appendByte('"');
199
- }
200
- try self.result.appendByte('>');
201
- try self.emitLine("");
202
- if (title_text.len > 0) {
203
- try self.emitIndent(indent + 1);
204
- try self.result.appendSlice(try self.escape(title_text));
205
- try self.emitLine("");
206
- }
207
- try self.emitIndent(indent);
208
- try self.result.appendSlice("</");
209
- try self.result.appendSlice(tag);
210
- try self.emitLine(">");
211
- for (block.children.items) |child| {
212
- if (child.tag == .block and std.mem.eql(u8, child.data.block.name, "title")) continue;
213
- try self.renderNode(child, indent);
214
- }
215
- return;
216
- }
217
- if (std.mem.eql(u8, name, "p") or std.mem.eql(u8, name, "para")) {
218
- try self.emitIndent(indent);
219
- try self.result.appendSlice("<p>");
220
- try self.renderInlineList(block.inline_content);
221
- try self.renderChildrenInline(block.children);
222
- try self.emitLine("</p>");
223
- return;
224
- }
225
- if (std.mem.eql(u8, name, "div")) {
226
- try self.emitIndent(indent);
227
- try self.emitLine("<div>");
228
- for (block.children.items) |child| try self.renderNode(child, indent + 1);
229
- try self.emitIndent(indent);
230
- try self.emitLine("</div>");
231
- return;
232
- }
233
- if (std.mem.eql(u8, name, "meta")) {
234
- try self.emitIndent(indent);
235
- try self.emitLine("<div class=\"nova-meta\">");
236
- var it = block.attrs.iterator();
237
- while (it.next()) |entry| {
238
- try self.emitIndent(indent + 1);
239
- try self.result.appendSlice("<span class=\"nova-meta-key\">");
240
- try self.result.appendSlice(try self.escape(entry.key_ptr.*));
241
- try self.result.appendSlice("</span>: <span class=\"nova-meta-val\">");
242
- try self.result.appendSlice(try self.escape(entry.value_ptr.*));
243
- try self.emitLine("</span>");
244
- }
245
- try self.emitIndent(indent);
246
- try self.emitLine("</div>");
247
- return;
248
- }
249
- if (std.mem.eql(u8, name, "config")) {
250
- try self.emitIndent(indent);
251
- try self.emitLine("<dl class=\"nova-config\">");
252
- var it = block.attrs.iterator();
253
- while (it.next()) |entry| {
254
- try self.emitIndent(indent + 1);
255
- try self.result.appendSlice("<dt>");
256
- try self.result.appendSlice(try self.escape(entry.key_ptr.*));
257
- try self.emitLine("</dt>");
258
- try self.emitIndent(indent + 1);
259
- try self.result.appendSlice("<dd>");
260
- try self.result.appendSlice(try self.escape(entry.value_ptr.*));
261
- try self.emitLine("</dd>");
262
- }
263
- try self.emitIndent(indent);
264
- try self.emitLine("</dl>");
265
- return;
266
- }
267
- if (std.mem.eql(u8, name, "note") or std.mem.eql(u8, name, "warn")) {
268
- try self.emitIndent(indent);
269
- try self.result.appendSlice("<blockquote class=\"");
270
- try self.result.appendSlice(name);
271
- try self.emitLine("\">");
272
- for (block.children.items) |child| try self.renderNode(child, indent + 1);
273
- try self.emitIndent(indent);
274
- try self.emitLine("</blockquote>");
275
- return;
276
- }
277
- if (std.mem.eql(u8, name, "bibliography")) {
278
- try self.emitIndent(indent);
279
- try self.emitLine("<hr>");
280
- try self.emitIndent(indent);
281
- try self.emitLine("<h2>参考文献</h2>");
282
- for (block.children.items) |child| {
283
- if (child.tag == .biblio_entry) try self.renderBiblioEntry(&child.data.biblio_entry);
284
- }
285
- return;
286
- }
287
- if (std.mem.eql(u8, name, "equation")) {
288
- var math_text = std.ArrayList(u8).init(self.allocator);
289
- defer math_text.deinit();
290
- for (block.inline_content.items) |c| {
291
- if (c.tag == .text) try math_text.appendSlice(c.data.text.value);
292
- }
293
- for (block.children.items) |c| {
294
- if (c.tag == .text) try math_text.appendSlice(c.data.text.value);
295
- }
296
- try self.emitIndent(indent);
297
- try self.result.appendSlice("<div class=\"math-display\">\\[");
298
- try self.result.appendSlice(try self.escape(math_text.items));
299
- try self.emitLine("\\]</div>");
300
- return;
301
- }
302
- if (std.mem.eql(u8, name, "img") or std.mem.eql(u8, name, "image")) {
303
- const src = block.attrs.get("src") orelse "";
304
- const alt = block.attrs.get("alt") orelse "image";
305
- try self.emitIndent(indent);
306
- try self.result.appendSlice("<img src=\"");
307
- try self.result.appendSlice(try self.escape(src));
308
- try self.result.appendSlice("\" alt=\"");
309
- try self.result.appendSlice(try self.escape(alt));
310
- try self.emitLine("\">");
311
- return;
312
- }
313
-
314
- try self.emitIndent(indent);
315
- try self.result.appendSlice("<div class=\"nova-");
316
- try self.result.appendSlice(name);
317
- try self.result.appendByte('"');
318
- if (block.attrs.count() > 0) {
319
- try self.result.appendByte('>');
320
- } else {
321
- try self.result.appendByte('>');
322
- }
323
- try self.emitLine("");
324
- for (block.children.items) |child| try self.renderNode(child, indent + 1);
325
- try self.emitIndent(indent);
326
- try self.emitLine("</div>");
327
- }
328
-
329
- fn renderInline(self: *Renderer, inl: *types.InlineBlock) ![]const u8 {
330
- var buf = std.ArrayList(u8).init(self.allocator);
331
- const name = inl.name;
332
-
333
- if (std.mem.eql(u8, name, "em")) {
334
- try buf.appendSlice("<em>");
335
- try buf.appendSlice(try self.renderInlineListText(inl.content));
336
- try buf.appendSlice("</em>");
337
- } else if (std.mem.eql(u8, name, "strong")) {
338
- try buf.appendSlice("<strong>");
339
- try buf.appendSlice(try self.renderInlineListText(inl.content));
340
- try buf.appendSlice("</strong>");
341
- } else if (std.mem.eql(u8, name, "code")) {
342
- try buf.appendSlice("<code>");
343
- try buf.appendSlice(try self.renderInlineListText(inl.content));
344
- try buf.appendSlice("</code>");
345
- } else if (std.mem.eql(u8, name, "a")) {
346
- const href = inl.attrs.get("href") orelse "";
347
- try buf.appendSlice("<a href=\"");
348
- try buf.appendSlice(try self.escape(href));
349
- try buf.appendSlice("\">");
350
- try buf.appendSlice(try self.renderInlineListText(inl.content));
351
- try buf.appendSlice("</a>");
352
- } else if (std.mem.eql(u8, name, "ref")) {
353
- const key = inl.attrs.get("key") orelse inl.attrs.get("id") orelse "";
354
- try buf.appendSlice("<span class=\"nova-ref\">[");
355
- try buf.appendSlice(try self.escape(key));
356
- try buf.appendSlice("]</span>");
357
- } else if (std.mem.eql(u8, name, "cite")) {
358
- const key = inl.attrs.get("key") orelse "";
359
- try buf.appendSlice("<span class=\"nova-cite\">[");
360
- try buf.appendSlice(try self.escape(key));
361
- try buf.appendSlice("]</span>");
362
- } else if (std.mem.eql(u8, name, "label")) {
363
- const key = inl.attrs.get("id") orelse inl.attrs.get("key") orelse "";
364
- try buf.appendSlice("<span id=\"");
365
- try buf.appendSlice(try self.escape(key));
366
- try buf.appendSlice("\" class=\"nova-label\"></span>");
367
- } else {
368
- const text = try self.renderInlineListText(inl.content);
369
- if (text.len > 0) {
370
- try buf.appendSlice("<span class=\"");
371
- try buf.appendSlice(try self.escape(name));
372
- try buf.appendSlice("\">");
373
- try buf.appendSlice(text);
374
- try buf.appendSlice("</span>");
375
- }
376
- }
377
- return try self.allocator.dupe(u8, buf.items);
378
- }
379
-
380
- fn renderInlineList(self: *Renderer, nodes: std.ArrayList(Node)) !void {
381
- for (nodes.items) |node| {
382
- switch (node.tag) {
383
- .text => try self.result.appendSlice(try self.escape(node.data.text.value)),
384
- .inline_block => {
385
- const html = try self.renderInline(&node.data.inline_block);
386
- try self.result.appendSlice(html);
387
- },
388
- .math_inline => {
389
- try self.result.appendSlice("<span class=\"math\">\\(");
390
- try self.result.appendSlice(try self.escape(node.data.math_inline.source));
391
- try self.result.appendSlice("\\)</span>");
392
- },
393
- .math_display => {
394
- try self.result.appendSlice("<div class=\"math-display\">\\[");
395
- try self.result.appendSlice(try self.escape(node.data.math_display.source));
396
- try self.result.appendSlice("\\]</div>");
397
- },
398
- .interpolation => try self.result.appendSlice(try self.escape(node.data.interpolation.expression)),
399
- else => {},
400
- }
401
- }
402
- }
403
-
404
- fn renderInlineListText(self: *Renderer, nodes: std.ArrayList(Node)) ![]const u8 {
405
- var buf = std.ArrayList(u8).init(self.allocator);
406
- for (nodes.items) |node| {
407
- switch (node.tag) {
408
- .text => try buf.appendSlice(try self.escape(node.data.text.value)),
409
- .inline_block => {
410
- const html = try self.renderInline(&node.data.inline_block);
411
- try buf.appendSlice(html);
412
- },
413
- .math_inline => {
414
- try buf.appendSlice("<span class=\"math\">\\(");
415
- try buf.appendSlice(try self.escape(node.data.math_inline.source));
416
- try buf.appendSlice("\\)</span>");
417
- },
418
- .interpolation => {
419
- try buf.appendSlice("<code>#{");
420
- try buf.appendSlice(try self.escape(node.data.interpolation.expression));
421
- try buf.appendSlice("}</code>");
422
- },
423
- else => {},
424
- }
425
- }
426
- return try self.allocator.dupe(u8, buf.items);
427
- }
428
-
429
- fn renderChildrenInline(self: *Renderer, children: std.ArrayList(Node)) !void {
430
- for (children.items) |child| {
431
- if (child.tag == .text) try self.result.appendSlice(try self.escape(child.data.text.value));
432
- }
433
- }
434
-
435
- fn collectText(self: *Renderer, nodes: *std.ArrayList(Node)) ![]const u8 {
436
- var buf = std.ArrayList(u8).init(self.allocator);
437
- for (nodes.items) |node| {
438
- switch (node.tag) {
439
- .text => try buf.appendSlice(node.data.text.value),
440
- .inline_block => {
441
- const sub = try self.collectText(&node.data.inline_block.content);
442
- try buf.appendSlice(sub);
443
- },
444
- else => {},
445
- }
446
- }
447
- return try self.allocator.dupe(u8, buf.items);
448
- }
449
-
450
- fn renderList(self: *Renderer, list: *types.ListBlock, indent: u32) !void {
451
- const tag = if (list.ordered) "ol" else "ul";
452
- try self.emitIndent(indent);
453
- try self.result.appendByte('<');
454
- try self.result.appendSlice(tag);
455
- try self.emitLine(">");
456
- for (list.items.items) |item| {
457
- try self.emitIndent(indent + 1);
458
- try self.emitLine("<li>");
459
- if (item.tag == .block) {
460
- for (item.data.block.children.items) |child| try self.renderNode(child, indent + 2);
461
- try self.renderInlineList(item.data.block.inline_content);
462
- } else {
463
- try self.renderNode(item, indent + 2);
464
- }
465
- try self.emitIndent(indent + 1);
466
- try self.emitLine("</li>");
467
- }
468
- try self.emitIndent(indent);
469
- try self.result.appendSlice("</");
470
- try self.result.appendSlice(tag);
471
- try self.emitLine(">");
472
- }
473
-
474
- fn renderTable(self: *Renderer, table: *types.TableBlock, indent: u32) !void {
475
- if (table.caption) |cap| {
476
- try self.emitIndent(indent);
477
- try self.result.appendSlice("<p><strong>");
478
- try self.result.appendSlice(try self.escape(cap));
479
- try self.emitLine("</strong></p>");
480
- }
481
- try self.emitIndent(indent);
482
- try self.emitLine("<table>");
483
-
484
- if (table.headers) |headers| {
485
- try self.emitIndent(indent + 1);
486
- try self.emitLine("<thead>");
487
- try self.emitIndent(indent + 2);
488
- try self.emitLine("<tr>");
489
- for (headers.items) |h| {
490
- try self.result.appendSlice("<th>");
491
- try self.result.appendSlice(try self.escape(h));
492
- try self.result.appendSlice("</th>");
493
- }
494
- try self.emitLine("");
495
- try self.emitIndent(indent + 2);
496
- try self.emitLine("</tr>");
497
- try self.emitIndent(indent + 1);
498
- try self.emitLine("</thead>");
499
- }
500
-
501
- if (table.rows.items.len > 0) {
502
- try self.emitIndent(indent + 1);
503
- try self.emitLine("<tbody>");
504
- for (table.rows.items) |row| {
505
- try self.emitIndent(indent + 2);
506
- try self.emitLine("<tr>");
507
- for (row.items) |cell| {
508
- try self.result.appendSlice("<td>");
509
- try self.result.appendSlice(try self.escape(cell));
510
- try self.result.appendSlice("</td>");
511
- }
512
- try self.emitLine("");
513
- try self.emitIndent(indent + 2);
514
- try self.emitLine("</tr>");
515
- }
516
- try self.emitIndent(indent + 1);
517
- try self.emitLine("</tbody>");
518
- }
519
-
520
- if (table.data) |data| {
521
- try self.emitIndent(indent + 1);
522
- try self.emitLine("<tbody>");
523
- for (data.items) |row| {
524
- try self.emitIndent(indent + 2);
525
- try self.emitLine("<tr>");
526
- for (row.items) |cell| {
527
- try self.result.appendSlice("<td>");
528
- try self.result.appendSlice(try self.escape(cell));
529
- try self.result.appendSlice("</td>");
530
- }
531
- try self.emitLine("");
532
- try self.emitIndent(indent + 2);
533
- try self.emitLine("</tr>");
534
- }
535
- try self.emitIndent(indent + 1);
536
- try self.emitLine("</tbody>");
537
- }
538
-
539
- try self.emitIndent(indent);
540
- try self.emitLine("</table>");
541
- }
542
-
543
- fn renderSchema(self: *Renderer, schema: *types.SchemaDef) !void {
544
- try self.emitLine("<div class=\"nova-schema\">");
545
- try self.result.appendSlice("<strong>schema</strong> ");
546
- try self.result.appendSlice(try self.escape(schema.name));
547
- try self.emitLine(" {");
548
- for (schema.fields.items) |field| {
549
- try self.result.appendSlice(" <span class=\"nova-field\">");
550
- try self.result.appendSlice(try self.escape(field.name));
551
- try self.result.appendSlice(": <span class=\"nova-type\">");
552
- try self.result.appendSlice(try self.escape(field.type_name));
553
- if (field.optional) try self.result.appendByte('?');
554
- try self.result.appendSlice("</span>");
555
- if (field.tag > 0) {
556
- try self.result.appendSlice(" <span class=\"nova-tag\">@");
557
- var buf: [16]u8 = undefined;
558
- const tag_str = std.fmt.bufPrint(&buf, "{}", .{field.tag}) catch "0";
559
- try self.result.appendSlice(tag_str);
560
- try self.result.appendSlice("</span>");
561
- }
562
- if (field.default) |d| {
563
- try self.result.appendSlice(" = ");
564
- try self.result.appendSlice(try self.escape(d));
565
- }
566
- try self.emitLine("</span>");
567
- }
568
- try self.emitLine("}");
569
- try self.emitLine("</div>");
570
- }
571
-
572
- fn renderService(self: *Renderer, service: *types.ServiceDef) !void {
573
- try self.emitLine("<div class=\"nova-service\">");
574
- try self.result.appendSlice("<strong>service</strong> ");
575
- try self.result.appendSlice(try self.escape(service.name));
576
- try self.emitLine(" {");
577
- for (service.methods.items) |method| {
578
- try self.result.appendSlice(" <span class=\"nova-method\">");
579
- try self.result.appendSlice(try self.escape(method.name));
580
- try self.result.appendByte('(');
581
- for (method.params.items, 0..) |p, i| {
582
- if (i > 0) try self.result.appendSlice(", ");
583
- try self.result.appendSlice(try self.escape(p.name));
584
- try self.result.appendSlice(": ");
585
- try self.result.appendSlice(try self.escape(p.type_name));
586
- }
587
- try self.result.appendByte(')');
588
- if (method.returns) |r| {
589
- try self.result.appendSlice(" → ");
590
- try self.result.appendSlice(try self.escape(r));
591
- }
592
- try self.emitLine("</span>");
593
- }
594
- try self.emitLine("}");
595
- try self.emitLine("</div>");
596
- }
597
-
598
- fn renderBiblioEntry(self: *Renderer, entry: *types.BiblioEntry) !void {
599
- const author = entry.attrs.get("author") orelse "";
600
- const title = entry.attrs.get("title") orelse "";
601
- const year = entry.attrs.get("year") orelse "";
602
- try self.result.appendSlice("<p id=\"bib-");
603
- try self.result.appendSlice(try self.escape(entry.key));
604
- try self.result.appendSlice("\">[");
605
- try self.result.appendSlice(try self.escape(entry.key));
606
- try self.result.appendSlice("] ");
607
- try self.result.appendSlice(try self.escape(author));
608
- try self.result.appendSlice(", <em>");
609
- try self.result.appendSlice(try self.escape(title));
610
- try self.result.appendSlice("</em>, ");
611
- try self.result.appendSlice(try self.escape(year));
612
- try self.emitLine(".</p>");
613
- }
614
-
615
- fn emitRaw(self: *Renderer, s: []const u8) !void {
616
- try self.result.appendSlice(s);
617
- try self.result.appendByte('\n');
618
- }
619
-
620
- fn emitLine(self: *Renderer, s: []const u8) !void {
621
- try self.result.appendSlice(s);
622
- try self.result.appendByte('\n');
623
- }
624
-
625
- fn emitIndent(self: *Renderer, level: u32) !void {
626
- var i: u32 = 0;
627
- while (i < level) : (i += 1) {
628
- try self.result.appendSlice(" ");
629
- }
630
- }
631
-
632
- fn escape(self: *Renderer, s: []const u8) ![]const u8 {
633
- var buf = std.ArrayList(u8).init(self.allocator);
634
- for (s) |c| {
635
- switch (c) {
636
- '&' => try buf.appendSlice("&amp;"),
637
- '<' => try buf.appendSlice("&lt;"),
638
- '>' => try buf.appendSlice("&gt;"),
639
- '"' => try buf.appendSlice("&quot;"),
640
- '\'' => try buf.appendSlice("&#39;"),
641
- else => try buf.append(c),
642
- }
643
- }
644
- return try self.allocator.dupe(u8, buf.items);
645
- }
646
-
647
- const DEFAULT_STYLES =
648
- \\body { font-family: 'Georgia', 'Noto Serif CJK', serif; line-height: 1.7; color: #333; max-width: 900px; margin: 0 auto; padding: 2rem; background: #fafafa; }
649
- \\.nova-document { background: #fff; padding: 2.5rem; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,0,0,0.1); }
650
- \\h1, h2, h3, h4 { color: #1a1a1a; margin-top: 1.5em; margin-bottom: 0.5em; font-weight: 600; }
651
- \\h1 { font-size: 2em; border-bottom: 2px solid #eee; padding-bottom: 0.3em; }
652
- \\h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 0.2em; }
653
- \\p { margin: 0.8em 0; text-align: justify; }
654
- \\em { font-style: italic; }
655
- \\strong { font-weight: bold; }
656
- \\code { font-family: 'Fira Code', 'Consolas', monospace; background: #f0f0f0; padding: 0.15em 0.3em; border-radius: 3px; font-size: 0.9em; }
657
- \\pre { background: #f5f5f5; padding: 1em; border-radius: 4px; overflow-x: auto; }
658
- \\a { color: #0366d6; text-decoration: none; }
659
- \\a:hover { text-decoration: underline; }
660
- \\table { border-collapse: collapse; width: 100%; margin: 1em 0; }
661
- \\th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
662
- \\th { background: #f5f5f5; font-weight: 600; }
663
- \\tr:nth-child(even) { background: #fafafa; }
664
- \\ul, ol { padding-left: 2em; margin: 0.5em 0; }
665
- \\li { margin: 0.3em 0; }
666
- \\blockquote { border-left: 4px solid #ddd; margin: 1em 0; padding: 0.5em 1em; color: #666; }
667
- \\.math { font-family: 'STIX', 'Latin Modern Math', serif; }
668
- \\.math-display { display: block; text-align: center; margin: 1em 0; overflow-x: auto; }
669
- \\.nova-meta { font-size: 0.9em; color: #888; margin: 1em 0; padding: 0.5em; background: #f9f9f9; border-radius: 4px; }
670
- \\.nova-schema, .nova-service { background: #f8f8ff; border: 1px solid #dde; padding: 1em; margin: 1em 0; border-radius: 4px; font-family: 'Fira Code', monospace; font-size: 0.9em; }
671
- \\.nova-field { margin: 0.3em 0; }
672
- \\.nova-tag { color: #888; }
673
- \\.nova-def { color: #444; }
674
- \\.nova-type { color: #b44; }
675
- \\.nova-ref { color: #0366d6; cursor: pointer; border-bottom: 1px dotted #0366d6; }
676
- \\.footnotes { margin-top: 2em; padding-top: 1em; border-top: 1px solid #ddd; font-size: 0.9em; color: #666; }
677
- ;
678
- };