authorgravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2023-01-24 18:56:35+01:00
committergravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2023-01-24 18:56:35+01:00
logaf820bbb94a06c5bae34a38bda0f964620413e92
treefbbf9d8c5bc54b0cad676f5a1369ace0fe5a231b
parent5d46addd255e980ad86e7cc2ad077c6964a73b85

autodoc: init support for guides


3 files changed, 314 insertions(+), 81 deletions(-)

lib/docs/index.html+80-16
...@@ -117,6 +117,48 @@...@@ -117,6 +117,48 @@
117 overflow: visible;117 overflow: visible;
118 }118 }
119119
120 .sidebar ul.guides-api-switch {
121 display: flex;
122 flex-direction: row;
123 justify-content: center;
124 text-align: center;
125 list-style-type: none;
126 margin: 0;
127 padding: 0;
128 }
129
130 .sidebar .guides-api-switch a {
131 display: block;
132 padding: 0.5rem 1rem;
133 color: var(--sidebar-pkglnk-tx-color);
134 background-color: var(--sidebar-pkglnk-bg-color);
135 border: 1px solid var(--tx-color);
136 }
137
138
139 #ApiSwitch {
140 border-radius: 10px 0 0 10px;
141 }
142
143 #guideSwitch {
144 border-radius: 0 10px 10px 0;
145 }
146
147
148 #ApiSwitch:hover, #guideSwitch:hover {
149 text-decoration: none;
150 }
151
152 #ApiSwitch:hover:not(.active), #guideSwitch:hover:not(.active) {
153 color: var(--sidebar-pkglnk-tx-color-hover);
154 background-color: var(--sidebar-pkglnk-bg-color-hover);
155 }
156
157 .sidebar .guides-api-switch .active {
158 color: var(--sidebar-pkglnk-tx-color-active);
159 background-color: var(--sidebar-pkglnk-bg-color-active);
160 }
161
120 .sidebar h2 {162 .sidebar h2 {
121 margin: 0.5rem;163 margin: 0.5rem;
122 padding: 0;164 padding: 0;
...@@ -157,6 +199,14 @@...@@ -157,6 +199,14 @@
157 font-family: var(--mono);199 font-family: var(--mono);
158 }200 }
159201
202 #guides {
203 padding: 1rem 0.7rem 2.4rem 1.4rem;
204 box-sizing: border-box;
205 font-size: 1rem;
206 background-color: var(--bg-color);
207 overflow-wrap: break-word;
208 }
209
160 /* docs section */210 /* docs section */
161 .docs {211 .docs {
162 padding: 1rem 0.7rem 2.4rem 1.4rem;212 padding: 1rem 0.7rem 2.4rem 1.4rem;
...@@ -643,28 +693,42 @@...@@ -643,28 +693,42 @@
643 </g>693 </g>
644 </svg>694 </svg>
645 </div>695 </div>
646 <div id="sectMainPkg" class="hidden">696 <div id="sectGudeApiSwitch">
647 <h2><span>Main Package</span></h2>697 <ul class="guides-api-switch">
648 <ul class="packages">698 <li><a id="ApiSwitch" class="active" href="#A;">API</a></li>
649 <li><a id="mainPkg" class="" href=""></a></li>699 <li><a id="guideSwitch" class="" href="#G;">Guides</a></li>
650 </ul>700 </ul>
651 </div>701 </div>
652 <div id="sectPkgs" class="hidden">702 <div id="guidesMenu" class="hidden">
653 <h2><span>Dependencies</span></h2>703 <h2><span>Guide List</span></h2>
654 <ul id="listPkgs" class="packages"></ul>704 <ul id="guidesList" class="packages"></ul>
655 </div>
656 <div id="sectInfo" class="hidden">
657 <h2><span>Zig Version</span></h2>
658 <p class="str" id="tdZigVer"></p>
659 </div>705 </div>
660 <div>706 <div id="apiMenu" class="hidden">
661 <input id="privDeclsBox" type="checkbox"/>707 <div id="sectMainPkg" class="hidden">
662 <label for="privDeclsBox">Internal Doc Mode</label>708 <h2><span>Main Package</span></h2>
709 <ul class="packages">
710 <li><a id="mainPkg" class="" href=""></a></li>
711 </ul>
712 </div>
713 <div id="sectPkgs" class="hidden">
714 <h2><span>Dependencies</span></h2>
715 <ul id="listPkgs" class="packages"></ul>
716 </div>
717 <div id="sectInfo" class="hidden">
718 <h2><span>Zig Version</span></h2>
719 <p class="str" id="tdZigVer"></p>
720 </div>
721 <div>
722 <input id="privDeclsBox" type="checkbox"/>
723 <label for="privDeclsBox">Internal Doc Mode</label>
724 </div>
663 </div>725 </div>
664 </nav>726 </nav>
665 </div>727 </div>
666 <div id="docs" class="flex-right">728 <div class="flex-right">
667 <div class="wrap">729 <div id="guides" class="wrap hidden">
730 </div>
731 <div id="docs" class="wrap hidden">
668 <section class="docs">732 <section class="docs">
669 <div style="position: relative">733 <div style="position: relative">
670 <span id="searchPlaceholder"><kbd>s</kbd> to search, <kbd>?</kbd> for more options</span>734 <span id="searchPlaceholder"><kbd>s</kbd> to search, <kbd>?</kbd> for more options</span>
lib/docs/main.js+173-51
...@@ -2,10 +2,21 @@...@@ -2,10 +2,21 @@
22
3var zigAnalysis;3var zigAnalysis;
44
5const NAV_MODES = {
6 API: "#A;",
7 API_INTERNAL: "#a;",
8 GUIDES: "#G;",
9};
10
5(function () {11(function () {
6 const domStatus = document.getElementById("status");12 const domStatus = document.getElementById("status");
7 const domSectNav = document.getElementById("sectNav");13 const domSectNav = document.getElementById("sectNav");
8 const domListNav = document.getElementById("listNav");14 const domListNav = document.getElementById("listNav");
15 const domApiSwitch = document.getElementById("ApiSwitch");
16 const domGuideSwitch = document.getElementById("guideSwitch");
17 const domGuidesMenu = document.getElementById("guidesMenu");
18 const domApiMenu = document.getElementById("apiMenu");
19 const domGuidesList = document.getElementById("guidesList");
9 const domSectMainPkg = document.getElementById("sectMainPkg");20 const domSectMainPkg = document.getElementById("sectMainPkg");
10 const domSectPkgs = document.getElementById("sectPkgs");21 const domSectPkgs = document.getElementById("sectPkgs");
11 const domListPkgs = document.getElementById("listPkgs");22 const domListPkgs = document.getElementById("listPkgs");
...@@ -45,6 +56,7 @@ var zigAnalysis;...@@ -45,6 +56,7 @@ var zigAnalysis;
45 const domSectSearchResults = document.getElementById("sectSearchResults");56 const domSectSearchResults = document.getElementById("sectSearchResults");
46 const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink");57 const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink");
47 const domDocs = document.getElementById("docs");58 const domDocs = document.getElementById("docs");
59 const domGuides = document.getElementById("guides");
48 const domListSearchResults = document.getElementById("listSearchResults");60 const domListSearchResults = document.getElementById("listSearchResults");
49 const domSectSearchNoResults = document.getElementById("sectSearchNoResults");61 const domSectSearchNoResults = document.getElementById("sectSearchNoResults");
50 const domSectInfo = document.getElementById("sectInfo");62 const domSectInfo = document.getElementById("sectInfo");
...@@ -83,7 +95,8 @@ var zigAnalysis;...@@ -83,7 +95,8 @@ var zigAnalysis;
83 let canonTypeDecls = null; // lazy; use getCanonTypeDecl95 let canonTypeDecls = null; // lazy; use getCanonTypeDecl
8496
85 let curNav = {97 let curNav = {
86 showPrivDecls: false,98 mode: NAV_MODES.API,
99 activeGuide: "",
87 // each element is a package name, e.g. @import("a") then within there @import("b")100 // each element is a package name, e.g. @import("a") then within there @import("b")
88 // starting implicitly from root package101 // starting implicitly from root package
89 pkgNames: [],102 pkgNames: [],
...@@ -152,7 +165,7 @@ var zigAnalysis;...@@ -152,7 +165,7 @@ var zigAnalysis;
152 );165 );
153166
154 if (location.hash == "") {167 if (location.hash == "") {
155 location.hash = "#root";168 location.hash = "#A;";
156 }169 }
157170
158 // make the modal disappear if you click outside it171 // make the modal disappear if you click outside it
...@@ -173,17 +186,21 @@ var zigAnalysis;...@@ -173,17 +186,21 @@ var zigAnalysis;
173 domLangRefLink.href = `https://ziglang.org/documentation/${langRefVersion}/`;186 domLangRefLink.href = `https://ziglang.org/documentation/${langRefVersion}/`;
174187
175 function renderTitle() {188 function renderTitle() {
176 let list = curNav.pkgNames.concat(curNav.declNames);
177 let suffix = " - Zig";189 let suffix = " - Zig";
178 if (list.length === 0) {190 switch (curNav.mode) {
179 if (rootIsStd) {191 case NAV_MODES.API:
180 document.title = "std" + suffix;192 case NAV_MODES.API_INTERNAL:
181 } else {193 let list = curNav.pkgNames.concat(curNav.declNames);
182 document.title = zigAnalysis.params.rootName + suffix;194 if (list.length === 0) {
183 }195 document.title = zigAnalysis.packages[zigAnalysis.rootPkg].name + suffix;
184 } else {196 } else {
185 document.title = list.join(".") + suffix;197 document.title = list.join(".") + suffix;
186 }198 }
199 return;
200 case NAV_MODES.GUIDES:
201 document.title = "[G] " + curNav.activeGuide + suffix;
202 return;
203 }
187 }204 }
188205
189 function isDecl(x) {206 function isDecl(x) {
...@@ -377,8 +394,80 @@ var zigAnalysis;...@@ -377,8 +394,80 @@ var zigAnalysis;
377 // console.assert(false);394 // console.assert(false);
378 // return ({});395 // return ({});
379 // }396 // }
397 function renderGuides() {
398 renderTitle();
380399
381 function render() {400 // set guide mode
401 domGuideSwitch.classList.add("active");
402 domApiSwitch.classList.remove("active");
403 domDocs.classList.add("hidden");
404 domGuides.classList.remove("hidden");
405 domApiMenu.classList.add("hidden");
406
407 // sidebar guides list
408 const list = Object.keys(zigAnalysis.guides);
409 resizeDomList(domGuidesList, list.length, '<li><a href="#"></a></li>');
410 for (let i = 0; i < list.length; i += 1) {
411 let liDom = domGuidesList.children[i];
412 let aDom = liDom.children[0];
413 aDom.textContent = list[i];
414 aDom.setAttribute("href", NAV_MODES.GUIDES + list[i]);
415 if (list[i] === curNav.activeGuide) {
416 aDom.classList.add("active");
417 } else {
418 aDom.classList.remove("active");
419 }
420 }
421
422 if (list.length > 0) {
423 domGuidesMenu.classList.remove("hidden");
424 }
425
426 // main content
427 const activeGuide = zigAnalysis.guides[curNav.activeGuide];
428 if (activeGuide == undefined) {
429 const root_file_idx = zigAnalysis.packages[zigAnalysis.rootPkg].file;
430 const root_file_name = zigAnalysis.files[root_file_idx];
431 domGuides.innerHTML = markdown(`
432 # Zig Guides
433 These autodocs don't contain any guide.
434
435 While the API section is a reference guide autogenerated from Zig source code,
436 guides are meant to be handwritten explanations that provide for example:
437
438 - how-to explanations for common use-cases
439 - technical documentation
440 - information about advanced usage patterns
441
442 You can add guides by specifying which markdown files to include
443 in the top level doc comment of your root file, like so:
444
445 (At the top of \`${root_file_name}\`)
446 \`\`\`
447 //!zig-autodoc-guide: intro.md
448 //!zig-autodoc-guide: quickstart.md
449 //!zig-autodoc-guide: ../advanced-docs/advanced-stuff.md
450 \`\`\`
451
452 **Note that this feature is still under heavy development so expect bugs**
453 **and missing features!**
454
455 Happy writing!
456 `);
457 } else {
458 domGuides.innerHTML = markdown(activeGuide);
459 }
460 }
461
462 function renderApi() {
463 // set Api mode
464 domApiSwitch.classList.add("active");
465 domGuideSwitch.classList.remove("active");
466 domGuides.classList.add("hidden");
467 domDocs.classList.remove("hidden");
468 domApiMenu.classList.remove("hidden");
469 domGuidesMenu.classList.add("hidden");
470
382 domStatus.classList.add("hidden");471 domStatus.classList.add("hidden");
383 domFnProto.classList.add("hidden");472 domFnProto.classList.add("hidden");
384 domSectParams.classList.add("hidden");473 domSectParams.classList.add("hidden");
...@@ -411,17 +500,16 @@ var zigAnalysis;...@@ -411,17 +500,16 @@ var zigAnalysis;
411 renderInfo();500 renderInfo();
412 renderPkgList();501 renderPkgList();
413502
414 domPrivDeclsBox.checked = curNav.showPrivDecls;503 domPrivDeclsBox.checked = curNav.mode == NAV_MODES.API_INTERNAL;
415504
416 if (curNavSearch !== "") {505 if (curNavSearch !== "") {
417 return renderSearch();506 return renderSearch();
418 }507 }
419508
420
421 let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];509 let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];
422 let pkg = rootPkg;510 let pkg = rootPkg;
423 curNav.pkgObjs = [pkg];511 curNav.pkgObjs = [pkg];
424 for (let i = 0; i < curNav.pkgNames.length; i += 1) {512 for (let i = 1; i < curNav.pkgNames.length; i += 1) {
425 let childPkg = zigAnalysis.packages[pkg.table[curNav.pkgNames[i]]];513 let childPkg = zigAnalysis.packages[pkg.table[curNav.pkgNames[i]]];
426 if (childPkg == null) {514 if (childPkg == null) {
427 return render404();515 return render404();
...@@ -494,6 +582,19 @@ var zigAnalysis;...@@ -494,6 +582,19 @@ var zigAnalysis;
494582
495 }583 }
496584
585 function render() {
586 switch (curNav.mode) {
587 case NAV_MODES.API:
588 case NAV_MODES.API_INTERNAL:
589 return renderApi();
590 case NAV_MODES.GUIDES:
591 return renderGuides();
592 default:
593 throw "?";
594 }
595 }
596
597
497 function renderDocTest(decl) {598 function renderDocTest(decl) {
498 if (!decl.decltest) return;599 if (!decl.decltest) return;
499 const astNode = getAstNode(decl.decltest);600 const astNode = getAstNode(decl.decltest);
...@@ -705,7 +806,6 @@ var zigAnalysis;...@@ -705,7 +806,6 @@ var zigAnalysis;
705 for (let i = 0; i < curNav.pkgNames.length; i += 1) {806 for (let i = 0; i < curNav.pkgNames.length; i += 1) {
706 hrefPkgNames.push(curNav.pkgNames[i]);807 hrefPkgNames.push(curNav.pkgNames[i]);
707 let name = curNav.pkgNames[i];808 let name = curNav.pkgNames[i];
708 if (name == "root") name = zigAnalysis.rootPkgName;
709 list.push({809 list.push({
710 name: name,810 name: name,
711 link: navLink(hrefPkgNames, hrefDeclNames),811 link: navLink(hrefPkgNames, hrefDeclNames),
...@@ -747,12 +847,12 @@ var zigAnalysis;...@@ -747,12 +847,12 @@ var zigAnalysis;
747 }847 }
748848
749 function renderPkgList() {849 function renderPkgList() {
750 let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];850 const rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];
751 let list = [];851 let list = [];
752 for (let key in rootPkg.table) {852 for (let key in rootPkg.table) {
753 let pkgIndex = rootPkg.table[key];853 let pkgIndex = rootPkg.table[key];
754 if (zigAnalysis.packages[pkgIndex] == null) continue;854 if (zigAnalysis.packages[pkgIndex] == null) continue;
755 if (key == zigAnalysis.params.rootName) continue;855 if (key == rootPkg.name) continue;
756 list.push({856 list.push({
757 name: key,857 name: key,
758 pkg: pkgIndex,858 pkg: pkgIndex,
...@@ -761,9 +861,9 @@ var zigAnalysis;...@@ -761,9 +861,9 @@ var zigAnalysis;
761861
762 {862 {
763 let aDom = domSectMainPkg.children[1].children[0].children[0];863 let aDom = domSectMainPkg.children[1].children[0].children[0];
764 aDom.textContent = zigAnalysis.rootPkgName;864 aDom.textContent = rootPkg.name;
765 aDom.setAttribute("href", navLinkPkg(zigAnalysis.rootPkg));865 aDom.setAttribute("href", navLinkPkg(zigAnalysis.rootPkg));
766 if (zigAnalysis.params.rootName === curNav.pkgNames[0]) {866 if (rootPkg.name === curNav.pkgNames[0]) {
767 aDom.classList.add("active");867 aDom.classList.add("active");
768 } else {868 } else {
769 aDom.classList.remove("active");869 aDom.classList.remove("active");
...@@ -794,20 +894,17 @@ var zigAnalysis;...@@ -794,20 +894,17 @@ var zigAnalysis;
794 }894 }
795895
796 function navLink(pkgNames, declNames, callName) {896 function navLink(pkgNames, declNames, callName) {
797 let base = "#";897 let base = curNav.mode;
798 if (curNav.showPrivDecls) {898
799 base += "*";
800 }
801
802 if (pkgNames.length === 0 && declNames.length === 0) {899 if (pkgNames.length === 0 && declNames.length === 0) {
803 return base;900 return base;
804 } else if (declNames.length === 0 && callName == null) {901 } else if (declNames.length === 0 && callName == null) {
805 return base + pkgNames.join(".");902 return base + pkgNames.join(".");
806 } else if (callName == null) {903 } else if (callName == null) {
807 return base + pkgNames.join(".") + ";" + declNames.join(".");904 return base + pkgNames.join(".") + ":" + declNames.join(".");
808 } else {905 } else {
809 return (906 return (
810 base + pkgNames.join(".") + ";" + declNames.join(".") + ";" + callName907 base + pkgNames.join(".") + ":" + declNames.join(".") + ";" + callName
811 );908 );
812 }909 }
813 }910 }
...@@ -2734,9 +2831,10 @@ var zigAnalysis;...@@ -2734,9 +2831,10 @@ var zigAnalysis;
2734 throw new Error("No type 'type' found");2831 throw new Error("No type 'type' found");
2735 }2832 }
27362833
2737 function updateCurNav() {2834
2835 function updateCurNav() {
2738 curNav = {2836 curNav = {
2739 showPrivDecls: false,2837 mode: NAV_MODES.API,
2740 pkgNames: [],2838 pkgNames: [],
2741 pkgObjs: [],2839 pkgObjs: [],
2742 declNames: [],2840 declNames: [],
...@@ -2745,30 +2843,54 @@ var zigAnalysis;...@@ -2745,30 +2843,54 @@ var zigAnalysis;
2745 };2843 };
2746 curNavSearch = "";2844 curNavSearch = "";
27472845
2748 if (location.hash[0] === "#" && location.hash.length > 1) {2846 const mode = location.hash.substring(0, 3);
2749 let query = location.hash.substring(1);2847 let query = location.hash.substring(3);
2750 if (query[0] === "*") {2848
2751 curNav.showPrivDecls = true;2849 const DEFAULT_HASH = NAV_MODES.API + zigAnalysis.packages[zigAnalysis.rootPkg].name;
2752 query = query.substring(1);2850 switch (mode) {
2753 }2851 case NAV_MODES.API:
2852 case NAV_MODES.API_INTERNAL:
2853 // #A;PACKAGE:decl.decl.decl?search-term
2854 curNav.mode = mode;
2855
2856 let qpos = query.indexOf("?");
2857 let nonSearchPart;
2858 if (qpos === -1) {
2859 nonSearchPart = query;
2860 } else {
2861 nonSearchPart = query.substring(0, qpos);
2862 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
2863 }
2864
2865 let parts = nonSearchPart.split(":");
2866 if (parts[0] == "") {
2867 location.hash = DEFAULT_HASH;
2868 } else {
2869 curNav.pkgNames = decodeURIComponent(parts[0]).split(".");
2870 }
27542871
2755 let qpos = query.indexOf("?");2872 if (parts[1] != null) {
2756 let nonSearchPart;2873 curNav.declNames = decodeURIComponent(parts[1]).split(".");
2757 if (qpos === -1) {2874 }
2758 nonSearchPart = query;
2759 } else {
2760 nonSearchPart = query.substring(0, qpos);
2761 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
2762 }
27632875
2764 let parts = nonSearchPart.split(";");2876 return;
2765 curNav.pkgNames = decodeURIComponent(parts[0]).split(".");2877 case NAV_MODES.GUIDES:
2766 if (parts[1] != null) {2878 const guides = Object.keys(zigAnalysis.guides);
2767 curNav.declNames = decodeURIComponent(parts[1]).split(".");2879 if (guides.length != 0 && query == "") {
2768 }2880 location.hash = NAV_MODES.GUIDES + guides[0];
2769 }2881 return;
2770 }2882 }
27712883
2884 curNav.mode = mode;
2885 curNav.activeGuide = query;
2886
2887 return;
2888 default:
2889 location.hash = DEFAULT_HASH;
2890 return;
2891 }
2892 }
2893
2772 function onHashChange() {2894 function onHashChange() {
2773 updateCurNav();2895 updateCurNav();
2774 if (domSearch.value !== curNavSearch) {2896 if (domSearch.value !== curNavSearch) {
src/Autodoc.zig+61-14
...@@ -28,6 +28,7 @@ decls: std.ArrayListUnmanaged(DocData.Decl) = .{},...@@ -28,6 +28,7 @@ decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
28exprs: std.ArrayListUnmanaged(DocData.Expr) = .{},28exprs: std.ArrayListUnmanaged(DocData.Expr) = .{},
29ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},29ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
30comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},30comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
31guides: std.StringHashMapUnmanaged([]const u8) = .{},
3132
32// These fields hold temporary state of the analysis process33// These fields hold temporary state of the analysis process
33// and are mainly used by the decl path resolving algorithm.34// and are mainly used by the decl path resolving algorithm.
...@@ -193,10 +194,15 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -193,10 +194,15 @@ pub fn generateZirData(self: *Autodoc) !void {
193 }194 }
194 }195 }
195196
197 const rootName = blk: {
198 const rootName = std.fs.path.basename(self.module.main_pkg.root_src_path);
199 break :blk rootName[0 .. rootName.len - 4];
200 };
201
196 const main_type_index = self.types.items.len;202 const main_type_index = self.types.items.len;
197 {203 {
198 try self.packages.put(self.arena, self.module.main_pkg, .{204 try self.packages.put(self.arena, self.module.main_pkg, .{
199 .name = "root",205 .name = rootName,
200 .main = main_type_index,206 .main = main_type_index,
201 .table = .{},207 .table = .{},
202 });208 });
...@@ -204,7 +210,7 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -204,7 +210,7 @@ pub fn generateZirData(self: *Autodoc) !void {
204 self.arena,210 self.arena,
205 self.module.main_pkg,211 self.module.main_pkg,
206 .{212 .{
207 .name = "root",213 .name = rootName,
208 .value = 0,214 .value = 0,
209 },215 },
210 );216 );
...@@ -215,12 +221,13 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -215,12 +221,13 @@ pub fn generateZirData(self: *Autodoc) !void {
215 .enclosing_type = main_type_index,221 .enclosing_type = main_type_index,
216 };222 };
217223
218 const maybe_tldoc_comment = try self.getTLDocComment(file);224 const tldoc_comment = try self.getTLDocComment(file);
219 try self.ast_nodes.append(self.arena, .{225 try self.ast_nodes.append(self.arena, .{
220 .name = "(root)",226 .name = "(root)",
221 .docs = maybe_tldoc_comment,227 .docs = tldoc_comment,
222 });228 });
223 try self.files.put(self.arena, file, main_type_index);229 try self.files.put(self.arena, file, main_type_index);
230 try self.findGuidePaths(file, tldoc_comment);
224231
225 _ = try self.walkInstruction(file, &root_scope, .{}, Zir.main_struct_inst, false);232 _ = try self.walkInstruction(file, &root_scope, .{}, Zir.main_struct_inst, false);
226233
...@@ -236,13 +243,8 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -236,13 +243,8 @@ pub fn generateZirData(self: *Autodoc) !void {
236 @panic("some decl paths were never fully analized");243 @panic("some decl paths were never fully analized");
237 }244 }
238245
239 const rootName = blk: {
240 const rootName = std.fs.path.basename(self.module.main_pkg.root_src_path);
241 break :blk rootName[0 .. rootName.len - 4];
242 };
243 var data = DocData{246 var data = DocData{
244 .rootPkgName = rootName,247 .params = .{},
245 .params = .{ .rootName = "root" },
246 .packages = self.packages.values(),248 .packages = self.packages.values(),
247 .files = self.files,249 .files = self.files,
248 .calls = self.calls.items,250 .calls = self.calls.items,
...@@ -251,6 +253,7 @@ pub fn generateZirData(self: *Autodoc) !void {...@@ -251,6 +253,7 @@ pub fn generateZirData(self: *Autodoc) !void {
251 .exprs = self.exprs.items,253 .exprs = self.exprs.items,
252 .astNodes = self.ast_nodes.items,254 .astNodes = self.ast_nodes.items,
253 .comptimeExprs = self.comptime_exprs.items,255 .comptimeExprs = self.comptime_exprs.items,
256 .guides = self.guides,
254 };257 };
255258
256 const base_dir = self.doc_location.directory orelse259 const base_dir = self.doc_location.directory orelse
...@@ -370,12 +373,10 @@ const Scope = struct {...@@ -370,12 +373,10 @@ const Scope = struct {
370const DocData = struct {373const DocData = struct {
371 typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds),374 typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds),
372 rootPkg: u32 = 0,375 rootPkg: u32 = 0,
373 rootPkgName: []const u8,
374 params: struct {376 params: struct {
375 zigId: []const u8 = "arst",377 zigId: []const u8 = "arst",
376 zigVersion: []const u8 = build_options.version,378 zigVersion: []const u8 = build_options.version,
377 target: []const u8 = "arst",379 target: []const u8 = "arst",
378 rootName: []const u8,
379 builds: []const struct { target: []const u8 } = &.{380 builds: []const struct { target: []const u8 } = &.{
380 .{ .target = "arst" },381 .{ .target = "arst" },
381 },382 },
...@@ -391,6 +392,9 @@ const DocData = struct {...@@ -391,6 +392,9 @@ const DocData = struct {
391 decls: []Decl,392 decls: []Decl,
392 exprs: []Expr,393 exprs: []Expr,
393 comptimeExprs: []ComptimeExpr,394 comptimeExprs: []ComptimeExpr,
395
396 guides: std.StringHashMapUnmanaged([]const u8),
397
394 const Call = struct {398 const Call = struct {
395 func: Expr,399 func: Expr,
396 args: []Expr,400 args: []Expr,
...@@ -410,6 +414,7 @@ const DocData = struct {...@@ -410,6 +414,7 @@ const DocData = struct {
410 try jsw.objectField(f_name);414 try jsw.objectField(f_name);
411 switch (f) {415 switch (f) {
412 .files => try writeFileTableToJson(self.files, &jsw),416 .files => try writeFileTableToJson(self.files, &jsw),
417 .guides => try writeGuidesToJson(self.guides, &jsw),
413 else => {418 else => {
414 try std.json.stringify(@field(self, f_name), opts, w);419 try std.json.stringify(@field(self, f_name), opts, w);
415 jsw.state_index -= 1;420 jsw.state_index -= 1;
...@@ -852,7 +857,7 @@ fn walkInstruction(...@@ -852,7 +857,7 @@ fn walkInstruction(
852857
853 const maybe_other_package: ?*Package = blk: {858 const maybe_other_package: ?*Package = blk: {
854 if (self.module.main_pkg_is_std and std.mem.eql(u8, path, "std")) {859 if (self.module.main_pkg_is_std and std.mem.eql(u8, path, "std")) {
855 path = "root";860 path = "std";
856 break :blk self.module.main_pkg;861 break :blk self.module.main_pkg;
857 } else {862 } else {
858 break :blk file.pkg.table.get(path);863 break :blk file.pkg.table.get(path);
...@@ -4364,6 +4369,16 @@ fn writeFileTableToJson(map: std.AutoArrayHashMapUnmanaged(*File, usize), jsw: a...@@ -4364,6 +4369,16 @@ fn writeFileTableToJson(map: std.AutoArrayHashMapUnmanaged(*File, usize), jsw: a
4364 try jsw.endArray();4369 try jsw.endArray();
4365}4370}
43664371
4372fn writeGuidesToJson(map: std.StringHashMapUnmanaged([]const u8), jsw: anytype) !void {
4373 try jsw.beginObject();
4374 var it = map.iterator();
4375 while (it.next()) |entry| {
4376 try jsw.objectField(entry.key_ptr.*);
4377 try jsw.emitString(entry.value_ptr.*);
4378 }
4379 try jsw.endObject();
4380}
4381
4367fn writePackageTableToJson(4382fn writePackageTableToJson(
4368 map: std.AutoHashMapUnmanaged(*Package, DocData.DocPackage.TableEntry),4383 map: std.AutoHashMapUnmanaged(*Package, DocData.DocPackage.TableEntry),
4369 jsw: anytype,4384 jsw: anytype,
...@@ -4422,8 +4437,40 @@ fn getTLDocComment(self: *Autodoc, file: *File) ![]const u8 {...@@ -4422,8 +4437,40 @@ fn getTLDocComment(self: *Autodoc, file: *File) ![]const u8 {
4422 var tok = tokenizer.next();4437 var tok = tokenizer.next();
4423 var comment = std.ArrayList(u8).init(self.arena);4438 var comment = std.ArrayList(u8).init(self.arena);
4424 while (tok.tag == .container_doc_comment) : (tok = tokenizer.next()) {4439 while (tok.tag == .container_doc_comment) : (tok = tokenizer.next()) {
4425 try comment.appendSlice(source[tok.loc.start + 3 .. tok.loc.end + 1]);4440 try comment.appendSlice(source[tok.loc.start + "//!".len .. tok.loc.end + 1]);
4426 }4441 }
44274442
4428 return comment.items;4443 return comment.items;
4429}4444}
4445
4446fn findGuidePaths(self: *Autodoc, file: *File, str: []const u8) !void {
4447 const prefix = "zig-autodoc-guide:";
4448 var it = std.mem.tokenize(u8, str, "\n");
4449 while (it.next()) |line| {
4450 const trimmed_line = std.mem.trim(u8, line, " ");
4451 if (std.mem.startsWith(u8, trimmed_line, prefix)) {
4452 const path = trimmed_line[prefix.len..];
4453 const trimmed_path = std.mem.trim(u8, path, " ");
4454 try self.addGuide(file, trimmed_path);
4455 }
4456 }
4457}
4458
4459fn addGuide(self: *Autodoc, file: *File, guide_path: []const u8) !void {
4460 if (guide_path.len == 0) return error.MissingAutodocGuideName;
4461
4462 const cur_pkg_dir_path = file.pkg.root_src_directory.path orelse ".";
4463 const resolved_path = try std.fs.path.resolve(self.arena, &[_][]const u8{
4464 cur_pkg_dir_path, file.sub_file_path, "..", guide_path,
4465 });
4466
4467 var guide_file = try file.pkg.root_src_directory.handle.openFile(resolved_path, .{});
4468 defer guide_file.close();
4469
4470 const guide = guide_file.reader().readAllAlloc(self.arena, 1 * 1024 * 1024) catch |err| switch (err) {
4471 error.StreamTooLong => @panic("stream too long"),
4472 else => |e| return e,
4473 };
4474
4475 try self.guides.put(self.arena, resolved_path, guide);
4476}