authorgravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2022-08-18 18:05:11+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-08-18 18:05:11+02:00
log2523b443a482d78fd1a31f5e6b0224fec03835e3
treea9f5c0c14b2cd416d65b8e7ebd376153fb8d605a
parentb038dba06bfcedd9691142c6715884b7384623c2
parent53e971226dcc2fd0cb53e3442399b8724700b3e5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #12477 from der-teufel-programming/master

Autodoc: HTML files generation

3 files changed, 471 insertions(+), 2 deletions(-)

lib/docs/main.js+2-2
...@@ -51,7 +51,7 @@ var zigAnalysis;...@@ -51,7 +51,7 @@ var zigAnalysis;
51 const domHdrName = document.getElementById("hdrName");51 const domHdrName = document.getElementById("hdrName");
52 const domHelpModal = document.getElementById("helpModal");52 const domHelpModal = document.getElementById("helpModal");
53 const domSearchPlaceholder = document.getElementById("searchPlaceholder");53 const domSearchPlaceholder = document.getElementById("searchPlaceholder");
54 const sourceFileUrlTemplate = "/src-viewer/{{file}}#L{{line}}"54 const sourceFileUrlTemplate = "src/{{file}}#L{{line}}"
55 const domLangRefLink = document.getElementById("langRefLink");55 const domLangRefLink = document.getElementById("langRefLink");
5656
57 let lineCounter = 1;57 let lineCounter = 1;
...@@ -989,7 +989,7 @@ var zigAnalysis;...@@ -989,7 +989,7 @@ var zigAnalysis;
989 "switch(" +989 "switch(" +
990 cond +990 cond +
991 ") {" +991 ") {" +
992 '<a href="/src-viewer/' +992 '<a href="/src/' +
993 file_name +993 file_name +
994 "#L" +994 "#L" +
995 line +995 line +
src/Autodoc.zig+45
...@@ -9,6 +9,7 @@ const Package = @import("Package.zig");...@@ -9,6 +9,7 @@ const Package = @import("Package.zig");
9const Zir = @import("Zir.zig");9const Zir = @import("Zir.zig");
10const Ref = Zir.Inst.Ref;10const Ref = Zir.Inst.Ref;
11const log = std.log.scoped(.autodoc);11const log = std.log.scoped(.autodoc);
12const Docgen = @import("autodoc/render_source.zig");
1213
13module: *Module,14module: *Module,
14doc_location: Compilation.EmitLoc,15doc_location: Compilation.EmitLoc,
...@@ -242,6 +243,7 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -242,6 +243,7 @@ pub fn generateZirData(self: *Autodoc) !void {
242 try d.handle.openDir(self.doc_location.basename, .{})243 try d.handle.openDir(self.doc_location.basename, .{})
243 else244 else
244 try self.module.zig_cache_artifact_directory.handle.openDir(self.doc_location.basename, .{});245 try self.module.zig_cache_artifact_directory.handle.openDir(self.doc_location.basename, .{});
246
245 {247 {
246 const data_js_f = try output_dir.createFile("data.js", .{});248 const data_js_f = try output_dir.createFile("data.js", .{});
247 defer data_js_f.close();249 defer data_js_f.close();
...@@ -266,6 +268,29 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -266,6 +268,29 @@ pub fn generateZirData(self: *Autodoc) !void {
266 try buffer.flush();268 try buffer.flush();
267 }269 }
268270
271 {
272 output_dir.makeDir("src") catch |e| switch (e) {
273 error.PathAlreadyExists => {},
274 else => |err| return err,
275 };
276 const html_dir = try output_dir.openDir("src", .{});
277
278 var files_iterator = self.files.iterator();
279
280 while (files_iterator.next()) |entry| {
281 const new_html_path = entry.key_ptr.*.sub_file_path;
282
283 const html_file = try createFromPath(html_dir, new_html_path);
284 defer html_file.close();
285 var buffer = std.io.bufferedWriter(html_file.writer());
286
287 const out = buffer.writer();
288
289 try Docgen.genHtml(self.module.gpa, entry.key_ptr.*, out);
290 try buffer.flush();
291 }
292 }
293
269 // copy main.js, index.html294 // copy main.js, index.html
270 var docs_dir = try self.module.comp.zig_lib_directory.handle.openDir("docs", .{});295 var docs_dir = try self.module.comp.zig_lib_directory.handle.openDir("docs", .{});
271 defer docs_dir.close();296 defer docs_dir.close();
...@@ -273,6 +298,26 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -273,6 +298,26 @@ pub fn generateZirData(self: *Autodoc) !void {
273 try docs_dir.copyFile("index.html", output_dir, "index.html", .{});298 try docs_dir.copyFile("index.html", output_dir, "index.html", .{});
274}299}
275300
301fn createFromPath(base_dir: std.fs.Dir, path: []const u8) !std.fs.File {
302 var path_tokens = std.mem.tokenize(u8, path, std.fs.path.sep_str);
303 var dir = base_dir;
304 while (path_tokens.next()) |toc| {
305 if (path_tokens.peek() != null) {
306 dir.makeDir(toc) catch |e| switch (e) {
307 error.PathAlreadyExists => {},
308 else => |err| return err,
309 };
310 dir = try dir.openDir(toc, .{});
311 } else {
312 return dir.createFile(toc, .{}) catch |e| switch (e) {
313 error.PathAlreadyExists => try dir.openFile(toc, .{}),
314 else => |err| return err,
315 };
316 }
317 }
318 return error.EmptyPath;
319}
320
276/// Represents a chain of scopes, used to resolve decl references to the321/// Represents a chain of scopes, used to resolve decl references to the
277/// corresponding entry in `self.decls`.322/// corresponding entry in `self.decls`.
278const Scope = struct {323const Scope = struct {
src/autodoc/render_source.zig created+424
...@@ -0,0 +1,424 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const io = std.io;
4const fs = std.fs;
5const process = std.process;
6const ChildProcess = std.ChildProcess;
7const Progress = std.Progress;
8const print = std.debug.print;
9const mem = std.mem;
10const testing = std.testing;
11const Allocator = std.mem.Allocator;
12const Module = @import("../Module.zig");
13
14pub fn genHtml(
15 allocator: Allocator,
16 src: *Module.File,
17 out: anytype,
18) !void {
19 try out.writeAll(
20 \\<!doctype html>
21 \\<html lang="en">
22 \\<head>
23 \\ <meta charset="utf-8">
24 \\ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25 );
26 try out.print(" <title>{s} - source view</title>\n", .{src.sub_file_path});
27 try out.writeAll(
28 \\ <link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAgklEQVR4AWMYWuD7EllJIM4G4g4g5oIJ/odhOJ8wToOxSTXgNxDHoeiBMfA4+wGShjyYOCkG/IGqWQziEzYAoUAeiF9D5U+DxEg14DRU7jWIT5IBIOdCxf+A+CQZAAoopEB7QJwBCBwHiip8UYmRdrAlDpIMgApwQZNnNii5Dq0MBgCxxycBnwEd+wAAAABJRU5ErkJggg=="/>
29 \\ <style>
30 \\ body{
31 \\ font-family: system-ui, -apple-system, Roboto, "Segoe UI", sans-serif;
32 \\ margin: 0;
33 \\ line-height: 1.5;
34 \\ }
35 \\
36 \\ pre > code {
37 \\ display: block;
38 \\ overflow: auto;
39 \\ line-height: normal;
40 \\ margin: 0em;
41 \\ }
42 \\ .tok-kw {
43 \\ color: #333;
44 \\ font-weight: bold;
45 \\ }
46 \\ .tok-str {
47 \\ color: #d14;
48 \\ }
49 \\ .tok-builtin {
50 \\ color: #005C7A;
51 \\ }
52 \\ .tok-comment {
53 \\ color: #545454;
54 \\ font-style: italic;
55 \\ }
56 \\ .tok-fn {
57 \\ color: #900;
58 \\ font-weight: bold;
59 \\ }
60 \\ .tok-null {
61 \\ color: #005C5C;
62 \\ }
63 \\ .tok-number {
64 \\ color: #005C5C;
65 \\ }
66 \\ .tok-type {
67 \\ color: #458;
68 \\ font-weight: bold;
69 \\ }
70 \\ pre {
71 \\ counter-reset: line;
72 \\ }
73 \\ pre .line:before {
74 \\ counter-increment: line;
75 \\ content: counter(line);
76 \\ display: inline-block;
77 \\ padding-right: 1em;
78 \\ width: 2em;
79 \\ text-align: right;
80 \\ color: #999;
81 \\ }
82 \\
83 \\ @media (prefers-color-scheme: dark) {
84 \\ body{
85 \\ background:#222;
86 \\ color: #ccc;
87 \\ }
88 \\ pre > code {
89 \\ color: #ccc;
90 \\ background: #222;
91 \\ border: unset;
92 \\ }
93 \\ .tok-kw {
94 \\ color: #eee;
95 \\ }
96 \\ .tok-str {
97 \\ color: #2e5;
98 \\ }
99 \\ .tok-builtin {
100 \\ color: #ff894c;
101 \\ }
102 \\ .tok-comment {
103 \\ color: #aa7;
104 \\ }
105 \\ .tok-fn {
106 \\ color: #B1A0F8;
107 \\ }
108 \\ .tok-null {
109 \\ color: #ff8080;
110 \\ }
111 \\ .tok-number {
112 \\ color: #ff8080;
113 \\ }
114 \\ .tok-type {
115 \\ color: #68f;
116 \\ }
117 \\ }
118 \\ </style>
119 \\</head>
120 \\<body>
121 \\
122 );
123
124 const source = try src.getSource(allocator);
125 try tokenizeAndPrintRaw(allocator, out, source.bytes);
126 try out.writeAll(
127 \\</body>
128 \\</html>
129 );
130}
131
132const start_line = "<span class=\"line\" id=\"L{d}\">";
133const end_line = "</span>\n";
134
135var line_counter: usize = 1;
136
137pub fn tokenizeAndPrintRaw(
138 allocator: Allocator,
139 out: anytype,
140 raw_src: [:0]const u8,
141) !void {
142 const src = try allocator.dupeZ(u8, raw_src);
143 defer allocator.free(src);
144
145 line_counter = 1;
146
147 try out.print("<pre><code>" ++ start_line, .{line_counter});
148 var tokenizer = std.zig.Tokenizer.init(src);
149 var index: usize = 0;
150 var next_tok_is_fn = false;
151 while (true) {
152 const prev_tok_was_fn = next_tok_is_fn;
153 next_tok_is_fn = false;
154
155 const token = tokenizer.next();
156 if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
157 // render one comment
158 const comment_start = index + comment_start_off;
159 const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
160 const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
161
162 try writeEscapedLines(out, src[index..comment_start]);
163 try out.writeAll("<span class=\"tok-comment\">");
164 try writeEscaped(out, src[comment_start..comment_end]);
165 try out.writeAll("</span>\n");
166 index = comment_end;
167 tokenizer.index = index;
168 continue;
169 }
170
171 try writeEscapedLines(out, src[index..token.loc.start]);
172 switch (token.tag) {
173 .eof => break,
174
175 .keyword_addrspace,
176 .keyword_align,
177 .keyword_and,
178 .keyword_asm,
179 .keyword_async,
180 .keyword_await,
181 .keyword_break,
182 .keyword_catch,
183 .keyword_comptime,
184 .keyword_const,
185 .keyword_continue,
186 .keyword_defer,
187 .keyword_else,
188 .keyword_enum,
189 .keyword_errdefer,
190 .keyword_error,
191 .keyword_export,
192 .keyword_extern,
193 .keyword_for,
194 .keyword_if,
195 .keyword_inline,
196 .keyword_noalias,
197 .keyword_noinline,
198 .keyword_nosuspend,
199 .keyword_opaque,
200 .keyword_or,
201 .keyword_orelse,
202 .keyword_packed,
203 .keyword_anyframe,
204 .keyword_pub,
205 .keyword_resume,
206 .keyword_return,
207 .keyword_linksection,
208 .keyword_callconv,
209 .keyword_struct,
210 .keyword_suspend,
211 .keyword_switch,
212 .keyword_test,
213 .keyword_threadlocal,
214 .keyword_try,
215 .keyword_union,
216 .keyword_unreachable,
217 .keyword_usingnamespace,
218 .keyword_var,
219 .keyword_volatile,
220 .keyword_allowzero,
221 .keyword_while,
222 .keyword_anytype,
223 => {
224 try out.writeAll("<span class=\"tok-kw\">");
225 try writeEscaped(out, src[token.loc.start..token.loc.end]);
226 try out.writeAll("</span>");
227 },
228
229 .keyword_fn => {
230 try out.writeAll("<span class=\"tok-kw\">");
231 try writeEscaped(out, src[token.loc.start..token.loc.end]);
232 try out.writeAll("</span>");
233 next_tok_is_fn = true;
234 },
235
236 .string_literal,
237 .char_literal,
238 => {
239 try out.writeAll("<span class=\"tok-str\">");
240 try writeEscaped(out, src[token.loc.start..token.loc.end]);
241 try out.writeAll("</span>");
242 },
243
244 .multiline_string_literal_line => {
245 if (src[token.loc.end - 1] == '\n') {
246 try out.writeAll("<span class=\"tok-str\">");
247 try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
248 line_counter += 1;
249 try out.print("</span>" ++ end_line ++ "\n" ++ start_line, .{line_counter});
250 } else {
251 try out.writeAll("<span class=\"tok-str\">");
252 try writeEscaped(out, src[token.loc.start..token.loc.end]);
253 try out.writeAll("</span>");
254 }
255 },
256
257 .builtin => {
258 try out.writeAll("<span class=\"tok-builtin\">");
259 try writeEscaped(out, src[token.loc.start..token.loc.end]);
260 try out.writeAll("</span>");
261 },
262
263 .doc_comment,
264 .container_doc_comment,
265 => {
266 try out.writeAll("<span class=\"tok-comment\">");
267 try writeEscaped(out, src[token.loc.start..token.loc.end]);
268 try out.writeAll("</span>");
269 },
270
271 .identifier => {
272 const tok_bytes = src[token.loc.start..token.loc.end];
273 if (mem.eql(u8, tok_bytes, "undefined") or
274 mem.eql(u8, tok_bytes, "null") or
275 mem.eql(u8, tok_bytes, "true") or
276 mem.eql(u8, tok_bytes, "false"))
277 {
278 try out.writeAll("<span class=\"tok-null\">");
279 try writeEscaped(out, tok_bytes);
280 try out.writeAll("</span>");
281 } else if (prev_tok_was_fn) {
282 try out.writeAll("<span class=\"tok-fn\">");
283 try writeEscaped(out, tok_bytes);
284 try out.writeAll("</span>");
285 } else {
286 const is_int = blk: {
287 if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
288 break :blk false;
289 var i = token.loc.start + 1;
290 if (i == token.loc.end)
291 break :blk false;
292 while (i != token.loc.end) : (i += 1) {
293 if (src[i] < '0' or src[i] > '9')
294 break :blk false;
295 }
296 break :blk true;
297 };
298 if (is_int or isType(tok_bytes)) {
299 try out.writeAll("<span class=\"tok-type\">");
300 try writeEscaped(out, tok_bytes);
301 try out.writeAll("</span>");
302 } else {
303 try writeEscaped(out, tok_bytes);
304 }
305 }
306 },
307
308 .integer_literal,
309 .float_literal,
310 => {
311 try out.writeAll("<span class=\"tok-number\">");
312 try writeEscaped(out, src[token.loc.start..token.loc.end]);
313 try out.writeAll("</span>");
314 },
315
316 .bang,
317 .pipe,
318 .pipe_pipe,
319 .pipe_equal,
320 .equal,
321 .equal_equal,
322 .equal_angle_bracket_right,
323 .bang_equal,
324 .l_paren,
325 .r_paren,
326 .semicolon,
327 .percent,
328 .percent_equal,
329 .l_brace,
330 .r_brace,
331 .l_bracket,
332 .r_bracket,
333 .period,
334 .period_asterisk,
335 .ellipsis2,
336 .ellipsis3,
337 .caret,
338 .caret_equal,
339 .plus,
340 .plus_plus,
341 .plus_equal,
342 .plus_percent,
343 .plus_percent_equal,
344 .plus_pipe,
345 .plus_pipe_equal,
346 .minus,
347 .minus_equal,
348 .minus_percent,
349 .minus_percent_equal,
350 .minus_pipe,
351 .minus_pipe_equal,
352 .asterisk,
353 .asterisk_equal,
354 .asterisk_asterisk,
355 .asterisk_percent,
356 .asterisk_percent_equal,
357 .asterisk_pipe,
358 .asterisk_pipe_equal,
359 .arrow,
360 .colon,
361 .slash,
362 .slash_equal,
363 .comma,
364 .ampersand,
365 .ampersand_equal,
366 .question_mark,
367 .angle_bracket_left,
368 .angle_bracket_left_equal,
369 .angle_bracket_angle_bracket_left,
370 .angle_bracket_angle_bracket_left_equal,
371 .angle_bracket_angle_bracket_left_pipe,
372 .angle_bracket_angle_bracket_left_pipe_equal,
373 .angle_bracket_right,
374 .angle_bracket_right_equal,
375 .angle_bracket_angle_bracket_right,
376 .angle_bracket_angle_bracket_right_equal,
377 .tilde,
378 => try writeEscaped(out, src[token.loc.start..token.loc.end]),
379
380 .invalid, .invalid_periodasterisks => return error.ParseError,
381 }
382 index = token.loc.end;
383 }
384 try out.writeAll(end_line ++ "</code></pre>");
385}
386
387fn writeEscapedLines(out: anytype, text: []const u8) !void {
388 for (text) |char| {
389 if (char == '\n') {
390 try out.writeAll(end_line);
391 line_counter += 1;
392 try out.print(start_line, .{line_counter});
393 } else {
394 try writeEscaped(out, &[_]u8{char});
395 }
396 }
397}
398
399fn writeEscaped(out: anytype, input: []const u8) !void {
400 for (input) |c| {
401 try switch (c) {
402 '&' => out.writeAll("&amp;"),
403 '<' => out.writeAll("&lt;"),
404 '>' => out.writeAll("&gt;"),
405 '"' => out.writeAll("&quot;"),
406 else => out.writeByte(c),
407 };
408 }
409}
410
411const builtin_types = [_][]const u8{
412 "f16", "f32", "f64", "f128", "c_longdouble", "c_short",
413 "c_ushort", "c_int", "c_uint", "c_long", "c_ulong", "c_longlong",
414 "c_ulonglong", "c_char", "anyopaque", "void", "bool", "isize",
415 "usize", "noreturn", "type", "anyerror", "comptime_int", "comptime_float",
416};
417
418fn isType(name: []const u8) bool {
419 for (builtin_types) |t| {
420 if (mem.eql(u8, t, name))
421 return true;
422 }
423 return false;
424}