Commit aa765c1d70

Loris Cro <kappaloris@gmail.com>
2023-04-15 16:36:35
autodoc: add support for defining guide sections
For example: //!zig-autodoc-section: Advanced Topics
1 parent 61c08d3
Changed files (3)
lib/docs/index.html
@@ -700,8 +700,7 @@
             </ul>
           </div>
           <div id="guidesMenu" class="hidden">
-            <h2><span>Guide List</span></h2>
-            <ul id="guidesList" class="packages"></ul>
+            <div id="guidesList"></div>
           </div>
           <div id="apiMenu" class="hidden">
             <div id="sectMainPkg" class="hidden">
lib/docs/main.js
@@ -405,26 +405,45 @@ const NAV_MODES = {
     domApiMenu.classList.add("hidden");
 
     // sidebar guides list
-    const list = Object.keys(zigAnalysis.guides);
-    resizeDomList(domGuidesList, list.length, '<li><a href="#"></a></li>');
-    for (let i = 0; i < list.length; i += 1) {
-      let liDom = domGuidesList.children[i];
-      let aDom = liDom.children[0];
-      aDom.textContent = list[i];
-      aDom.setAttribute("href", NAV_MODES.GUIDES + list[i]);
-      if (list[i] === curNav.activeGuide) {
-        aDom.classList.add("active");
-      } else {
-        aDom.classList.remove("active");
-      }
+    const section_list = zigAnalysis.guide_sections;
+    resizeDomList(domGuidesList, section_list.length, '<div><h2><span></span></h2><ul class="packages"></ul></div>');
+    for (let j = 0; j < section_list.length; j += 1) {
+        const section = section_list[j];
+        const domSectionName = domGuidesList.children[j].children[0].children[0];
+        const domGuides = domGuidesList.children[j].children[1];
+        domSectionName.textContent = section.name;
+        resizeDomList(domGuides, section.guides.length, '<li><a href="#"></a></li>');
+        for (let i = 0; i < section.guides.length; i += 1) {
+          const guide = section.guides[i];
+          let liDom = domGuides.children[i];
+          let aDom = liDom.children[0];
+          aDom.textContent = guide.name;
+          aDom.setAttribute("href", NAV_MODES.GUIDES + guide.name);
+          if (guide.name === curNav.activeGuide) {
+            aDom.classList.add("active");
+          } else {
+            aDom.classList.remove("active");
+          }
+        }
     }
-
-    if (list.length > 0) {
+  
+    if (section_list.length > 0) {
       domGuidesMenu.classList.remove("hidden");
     }
 
     // main content
-    const activeGuide = zigAnalysis.guides[curNav.activeGuide];
+    let activeGuide = undefined;
+    outer: for (let i = 0; i < zigAnalysis.guide_sections.length; i += 1) {
+      const section = zigAnalysis.guide_sections[i];
+      for (let j = 0; j < section.guides.length; j += 1) {
+        const guide = section.guides[j];
+        if (guide.name == curNav.activeGuide) {
+          activeGuide = guide;
+          break outer;
+        }
+      }
+    }        
+    
     if (activeGuide == undefined) {
       const root_file_idx = zigAnalysis.packages[zigAnalysis.rootPkg].file;
       const root_file_name = zigAnalysis.files[root_file_idx];
@@ -446,6 +465,7 @@ const NAV_MODES = {
           \`\`\`
           //!zig-autodoc-guide: intro.md
           //!zig-autodoc-guide: quickstart.md
+          //!zig-autodoc-section: Advanced topics
           //!zig-autodoc-guide: ../advanced-docs/advanced-stuff.md
           \`\`\`
         
@@ -455,7 +475,7 @@ const NAV_MODES = {
           Happy writing!
         `);
     } else {
-      domGuides.innerHTML = markdown(activeGuide);
+      domGuides.innerHTML = markdown(activeGuide.body);
     }
   }
 
@@ -3104,9 +3124,9 @@ const NAV_MODES = {
 
         return;
       case NAV_MODES.GUIDES:
-        const guides = Object.keys(zigAnalysis.guides);
-        if (guides.length != 0 && query == "") {
-          location.hash = NAV_MODES.GUIDES + guides[0];
+        const sections = zigAnalysis.guide_sections;
+        if (sections.length != 0 && sections[0].guides.length != 0 && query == "") {
+          location.hash = NAV_MODES.GUIDES + sections[0].guides[0].name;
           return;
         }
 
src/Autodoc.zig
@@ -28,7 +28,7 @@ decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
 exprs: std.ArrayListUnmanaged(DocData.Expr) = .{},
 ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
 comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
-guides: std.StringHashMapUnmanaged([]const u8) = .{},
+guide_sections: std.ArrayListUnmanaged(Section) = .{},
 
 // These fields hold temporary state of the analysis process
 // and are mainly used by the decl path resolving algorithm.
@@ -63,6 +63,16 @@ const SrcLocInfo = struct {
     src_node: u32 = 0,
 };
 
+const Section = struct {
+    name: []const u8 = "", // empty string is the default section
+    guides: std.ArrayListUnmanaged(Guide) = .{},
+
+    const Guide = struct {
+        name: []const u8,
+        body: []const u8,
+    };
+};
+
 var arena_allocator: std.heap.ArenaAllocator = undefined;
 pub fn init(m: *Module, doc_location: Compilation.EmitLoc) Autodoc {
     arena_allocator = std.heap.ArenaAllocator.init(m.gpa);
@@ -253,7 +263,7 @@ pub fn generateZirData(self: *Autodoc) !void {
         .exprs = self.exprs.items,
         .astNodes = self.ast_nodes.items,
         .comptimeExprs = self.comptime_exprs.items,
-        .guides = self.guides,
+        .guide_sections = self.guide_sections,
     };
 
     const base_dir = self.doc_location.directory orelse
@@ -419,7 +429,7 @@ const DocData = struct {
     exprs: []Expr,
     comptimeExprs: []ComptimeExpr,
 
-    guides: std.StringHashMapUnmanaged([]const u8),
+    guide_sections: std.ArrayListUnmanaged(Section),
 
     const Call = struct {
         func: Expr,
@@ -440,7 +450,7 @@ const DocData = struct {
             try jsw.objectField(f_name);
             switch (f) {
                 .files => try writeFileTableToJson(self.files, &jsw),
-                .guides => try writeGuidesToJson(self.guides, &jsw),
+                .guide_sections => try writeGuidesToJson(self.guide_sections, &jsw),
                 else => {
                     try std.json.stringify(@field(self, f_name), opts, w);
                     jsw.state_index -= 1;
@@ -4613,14 +4623,39 @@ fn writeFileTableToJson(map: std.AutoArrayHashMapUnmanaged(*File, usize), jsw: a
     try jsw.endArray();
 }
 
-fn writeGuidesToJson(map: std.StringHashMapUnmanaged([]const u8), jsw: anytype) !void {
-    try jsw.beginObject();
-    var it = map.iterator();
-    while (it.next()) |entry| {
-        try jsw.objectField(entry.key_ptr.*);
-        try jsw.emitString(entry.value_ptr.*);
+/// Writes the data like so:
+/// ```
+/// {
+///    "<section name>": [{name: "<guide name>", text: "<guide contents>"},],
+/// }
+/// ```
+fn writeGuidesToJson(sections: std.ArrayListUnmanaged(Section), jsw: anytype) !void {
+    try jsw.beginArray();
+
+    for (sections.items) |s| {
+        // section name
+        try jsw.arrayElem();
+        try jsw.beginObject();
+        try jsw.objectField("name");
+        try jsw.emitString(s.name);
+        try jsw.objectField("guides");
+
+        // section value
+        try jsw.beginArray();
+        for (s.guides.items) |g| {
+            try jsw.arrayElem();
+            try jsw.beginObject();
+            try jsw.objectField("name");
+            try jsw.emitString(g.name);
+            try jsw.objectField("body");
+            try jsw.emitString(g.body);
+            try jsw.endObject();
+        }
+        try jsw.endArray();
+        try jsw.endObject();
     }
-    try jsw.endObject();
+
+    try jsw.endArray();
 }
 
 fn writePackageTableToJson(
@@ -4688,19 +4723,31 @@ fn getTLDocComment(self: *Autodoc, file: *File) ![]const u8 {
 }
 
 fn findGuidePaths(self: *Autodoc, file: *File, str: []const u8) !void {
-    const prefix = "zig-autodoc-guide:";
+    const guide_prefix = "zig-autodoc-guide:";
+    const section_prefix = "zig-autodoc-section:";
+
+    try self.guide_sections.append(self.arena, .{}); // add a default section
+    var current_section = &self.guide_sections.items[self.guide_sections.items.len - 1];
+
     var it = std.mem.tokenize(u8, str, "\n");
     while (it.next()) |line| {
         const trimmed_line = std.mem.trim(u8, line, " ");
-        if (std.mem.startsWith(u8, trimmed_line, prefix)) {
-            const path = trimmed_line[prefix.len..];
+        if (std.mem.startsWith(u8, trimmed_line, guide_prefix)) {
+            const path = trimmed_line[guide_prefix.len..];
             const trimmed_path = std.mem.trim(u8, path, " ");
-            try self.addGuide(file, trimmed_path);
+            try self.addGuide(file, trimmed_path, current_section);
+        } else if (std.mem.startsWith(u8, trimmed_line, section_prefix)) {
+            const section_name = trimmed_line[section_prefix.len..];
+            const trimmed_section_name = std.mem.trim(u8, section_name, " ");
+            try self.guide_sections.append(self.arena, .{
+                .name = trimmed_section_name,
+            });
+            current_section = &self.guide_sections.items[self.guide_sections.items.len - 1];
         }
     }
 }
 
-fn addGuide(self: *Autodoc, file: *File, guide_path: []const u8) !void {
+fn addGuide(self: *Autodoc, file: *File, guide_path: []const u8, section: *Section) !void {
     if (guide_path.len == 0) return error.MissingAutodocGuideName;
 
     const cur_pkg_dir_path = file.pkg.root_src_directory.path orelse ".";
@@ -4716,5 +4763,8 @@ fn addGuide(self: *Autodoc, file: *File, guide_path: []const u8) !void {
         else => |e| return e,
     };
 
-    try self.guides.put(self.arena, resolved_path, guide);
+    try section.guides.append(self.arena, .{
+        .name = resolved_path,
+        .body = guide,
+    });
 }