diff --git a/lib/docs/main.js b/lib/docs/main.js index 0a99432c67..a0b8001a9e 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -51,7 +51,7 @@ var zigAnalysis; const domHdrName = document.getElementById("hdrName"); const domHelpModal = document.getElementById("helpModal"); const domSearchPlaceholder = document.getElementById("searchPlaceholder"); - const sourceFileUrlTemplate = "/src-viewer/{{file}}#L{{line}}" + const sourceFileUrlTemplate = "src-viewer/{{file}}#L{{line}}" const domLangRefLink = document.getElementById("langRefLink"); let lineCounter = 1; diff --git a/src/Autodoc.zig b/src/Autodoc.zig index d90ebc3de8..2364a40f9f 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -9,6 +9,7 @@ const Package = @import("Package.zig"); const Zir = @import("Zir.zig"); const Ref = Zir.Inst.Ref; const log = std.log.scoped(.autodoc); +const Docgen = @import("Docgen.zig"); module: *Module, doc_location: Compilation.EmitLoc, @@ -266,6 +267,27 @@ pub fn generateZirData(self: *Autodoc) !void { try buffer.flush(); } + output_dir.makeDir("src-viewer") catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => |err| return err, + }; + const html_dir = try output_dir.openDir("src-viewer", .{}); + + var files_iterator = self.files.iterator(); + + while (files_iterator.next()) |entry| { + const new_html_path = entry.key_ptr.*.sub_file_path; + + const html_file = try createFromPath(html_dir, new_html_path); + defer html_file.close(); + var buffer = std.io.bufferedWriter(html_file.writer()); + + const out = buffer.writer(); + + try Docgen.genHtml(self.module.gpa, entry.key_ptr.*, out); + try buffer.flush(); + } + // copy main.js, index.html var docs_dir = try self.module.comp.zig_lib_directory.handle.openDir("docs", .{}); defer docs_dir.close(); @@ -273,6 +295,26 @@ pub fn generateZirData(self: *Autodoc) !void { try docs_dir.copyFile("index.html", output_dir, "index.html", .{}); } +fn createFromPath(base_dir: std.fs.Dir, path: []const u8) !std.fs.File { + var path_tokens = std.mem.tokenize(u8, path, std.fs.path.sep_str); + var dir = base_dir; + while (path_tokens.next()) |toc| { + if (path_tokens.peek() != null) { + dir.makeDir(toc) catch |e| switch (e) { + error.PathAlreadyExists => {}, + else => |err| return err, + }; + dir = try dir.openDir(toc, .{}); + } else { + return dir.createFile(toc, .{}) catch |e| switch (e) { + error.PathAlreadyExists => try dir.openFile(toc, .{}), + else => |e| return e, + }; + } + } + return error.EmptyPath; +} + /// Represents a chain of scopes, used to resolve decl references to the /// corresponding entry in `self.decls`. const Scope = struct { diff --git a/src/Docgen.zig b/src/Docgen.zig new file mode 100644 index 0000000000..294ff359f0 --- /dev/null +++ b/src/Docgen.zig @@ -0,0 +1,424 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const io = std.io; +const fs = std.fs; +const process = std.process; +const ChildProcess = std.ChildProcess; +const Progress = std.Progress; +const print = std.debug.print; +const mem = std.mem; +const testing = std.testing; +const Allocator = std.mem.Allocator; +const Module = @import("Module.zig"); + +pub fn genHtml( + allocator: Allocator, + src: *Module.File, + out: anytype, +) !void { + try out.writeAll( + \\ + \\ + \\
+ \\ + \\ + ); + try out.print("" ++ start_line, .{line_counter});
+ var tokenizer = std.zig.Tokenizer.init(src);
+ var index: usize = 0;
+ var next_tok_is_fn = false;
+ while (true) {
+ const prev_tok_was_fn = next_tok_is_fn;
+ next_tok_is_fn = false;
+
+ const token = tokenizer.next();
+ if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
+ // render one comment
+ const comment_start = index + comment_start_off;
+ const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
+ const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
+
+ try writeEscapedLines(out, src[index..comment_start]);
+ try out.writeAll("");
+ try writeEscaped(out, src[comment_start..comment_end]);
+ try out.writeAll("\n");
+ index = comment_end;
+ tokenizer.index = index;
+ continue;
+ }
+
+ try writeEscapedLines(out, src[index..token.loc.start]);
+ switch (token.tag) {
+ .eof => break,
+
+ .keyword_addrspace,
+ .keyword_align,
+ .keyword_and,
+ .keyword_asm,
+ .keyword_async,
+ .keyword_await,
+ .keyword_break,
+ .keyword_catch,
+ .keyword_comptime,
+ .keyword_const,
+ .keyword_continue,
+ .keyword_defer,
+ .keyword_else,
+ .keyword_enum,
+ .keyword_errdefer,
+ .keyword_error,
+ .keyword_export,
+ .keyword_extern,
+ .keyword_for,
+ .keyword_if,
+ .keyword_inline,
+ .keyword_noalias,
+ .keyword_noinline,
+ .keyword_nosuspend,
+ .keyword_opaque,
+ .keyword_or,
+ .keyword_orelse,
+ .keyword_packed,
+ .keyword_anyframe,
+ .keyword_pub,
+ .keyword_resume,
+ .keyword_return,
+ .keyword_linksection,
+ .keyword_callconv,
+ .keyword_struct,
+ .keyword_suspend,
+ .keyword_switch,
+ .keyword_test,
+ .keyword_threadlocal,
+ .keyword_try,
+ .keyword_union,
+ .keyword_unreachable,
+ .keyword_usingnamespace,
+ .keyword_var,
+ .keyword_volatile,
+ .keyword_allowzero,
+ .keyword_while,
+ .keyword_anytype,
+ => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ },
+
+ .keyword_fn => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ next_tok_is_fn = true;
+ },
+
+ .string_literal,
+ .char_literal,
+ => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ },
+
+ .multiline_string_literal_line => {
+ if (src[token.loc.end - 1] == '\n') {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
+ line_counter += 1;
+ try out.print("" ++ end_line ++ "\n" ++ start_line, .{line_counter});
+ } else {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ }
+ },
+
+ .builtin => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ },
+
+ .doc_comment,
+ .container_doc_comment,
+ => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ },
+
+ .identifier => {
+ const tok_bytes = src[token.loc.start..token.loc.end];
+ if (mem.eql(u8, tok_bytes, "undefined") or
+ mem.eql(u8, tok_bytes, "null") or
+ mem.eql(u8, tok_bytes, "true") or
+ mem.eql(u8, tok_bytes, "false"))
+ {
+ try out.writeAll("");
+ try writeEscaped(out, tok_bytes);
+ try out.writeAll("");
+ } else if (prev_tok_was_fn) {
+ try out.writeAll("");
+ try writeEscaped(out, tok_bytes);
+ try out.writeAll("");
+ } else {
+ const is_int = blk: {
+ if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
+ break :blk false;
+ var i = token.loc.start + 1;
+ if (i == token.loc.end)
+ break :blk false;
+ while (i != token.loc.end) : (i += 1) {
+ if (src[i] < '0' or src[i] > '9')
+ break :blk false;
+ }
+ break :blk true;
+ };
+ if (is_int or isType(tok_bytes)) {
+ try out.writeAll("");
+ try writeEscaped(out, tok_bytes);
+ try out.writeAll("");
+ } else {
+ try writeEscaped(out, tok_bytes);
+ }
+ }
+ },
+
+ .integer_literal,
+ .float_literal,
+ => {
+ try out.writeAll("");
+ try writeEscaped(out, src[token.loc.start..token.loc.end]);
+ try out.writeAll("");
+ },
+
+ .bang,
+ .pipe,
+ .pipe_pipe,
+ .pipe_equal,
+ .equal,
+ .equal_equal,
+ .equal_angle_bracket_right,
+ .bang_equal,
+ .l_paren,
+ .r_paren,
+ .semicolon,
+ .percent,
+ .percent_equal,
+ .l_brace,
+ .r_brace,
+ .l_bracket,
+ .r_bracket,
+ .period,
+ .period_asterisk,
+ .ellipsis2,
+ .ellipsis3,
+ .caret,
+ .caret_equal,
+ .plus,
+ .plus_plus,
+ .plus_equal,
+ .plus_percent,
+ .plus_percent_equal,
+ .plus_pipe,
+ .plus_pipe_equal,
+ .minus,
+ .minus_equal,
+ .minus_percent,
+ .minus_percent_equal,
+ .minus_pipe,
+ .minus_pipe_equal,
+ .asterisk,
+ .asterisk_equal,
+ .asterisk_asterisk,
+ .asterisk_percent,
+ .asterisk_percent_equal,
+ .asterisk_pipe,
+ .asterisk_pipe_equal,
+ .arrow,
+ .colon,
+ .slash,
+ .slash_equal,
+ .comma,
+ .ampersand,
+ .ampersand_equal,
+ .question_mark,
+ .angle_bracket_left,
+ .angle_bracket_left_equal,
+ .angle_bracket_angle_bracket_left,
+ .angle_bracket_angle_bracket_left_equal,
+ .angle_bracket_angle_bracket_left_pipe,
+ .angle_bracket_angle_bracket_left_pipe_equal,
+ .angle_bracket_right,
+ .angle_bracket_right_equal,
+ .angle_bracket_angle_bracket_right,
+ .angle_bracket_angle_bracket_right_equal,
+ .tilde,
+ => try writeEscaped(out, src[token.loc.start..token.loc.end]),
+
+ .invalid, .invalid_periodasterisks => return error.ParseError,
+ }
+ index = token.loc.end;
+ }
+ try out.writeAll(end_line ++ "");
+}
+
+fn writeEscapedLines(out: anytype, text: []const u8) !void {
+ for (text) |char| {
+ if (char == '\n') {
+ try out.writeAll(end_line);
+ line_counter += 1;
+ try out.print(start_line, .{line_counter});
+ } else {
+ try writeEscaped(out, &[_]u8{char});
+ }
+ }
+}
+
+fn writeEscaped(out: anytype, input: []const u8) !void {
+ for (input) |c| {
+ try switch (c) {
+ '&' => out.writeAll("&"),
+ '<' => out.writeAll("<"),
+ '>' => out.writeAll(">"),
+ '"' => out.writeAll("""),
+ else => out.writeByte(c),
+ };
+ }
+}
+
+const builtin_types = [_][]const u8{
+ "f16", "f32", "f64", "f128", "c_longdouble", "c_short",
+ "c_ushort", "c_int", "c_uint", "c_long", "c_ulong", "c_longlong",
+ "c_ulonglong", "c_char", "anyopaque", "void", "bool", "isize",
+ "usize", "noreturn", "type", "anyerror", "comptime_int", "comptime_float",
+};
+
+fn isType(name: []const u8) bool {
+ for (builtin_types) |t| {
+ if (mem.eql(u8, t, name))
+ return true;
+ }
+ return false;
+}