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 @@
117117 overflow: visible;
118118 }
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
120162 .sidebar h2 {
121163 margin: 0.5rem;
122164 padding: 0;
......@@ -157,6 +199,14 @@
157199 font-family: var(--mono);
158200 }
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
160210 /* docs section */
161211 .docs {
162212 padding: 1rem 0.7rem 2.4rem 1.4rem;
......@@ -643,28 +693,42 @@
643693 </g>
644694 </svg>
645695 </div>
646 <div id="sectMainPkg" class="hidden">
647 <h2><span>Main Package</span></h2>
648 <ul class="packages">
649 <li><a id="mainPkg" class="" href=""></a></li>
696 <div id="sectGudeApiSwitch">
697 <ul class="guides-api-switch">
698 <li><a id="ApiSwitch" class="active" href="#A;">API</a></li>
699 <li><a id="guideSwitch" class="" href="#G;">Guides</a></li>
650700 </ul>
651701 </div>
652 <div id="sectPkgs" class="hidden">
653 <h2><span>Dependencies</span></h2>
654 <ul id="listPkgs" 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>
702 <div id="guidesMenu" class="hidden">
703 <h2><span>Guide List</span></h2>
704 <ul id="guidesList" class="packages"></ul>
659705 </div>
660 <div>
661 <input id="privDeclsBox" type="checkbox"/>
662 <label for="privDeclsBox">Internal Doc Mode</label>
706 <div id="apiMenu" class="hidden">
707 <div id="sectMainPkg" class="hidden">
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>
663725 </div>
664726 </nav>
665727 </div>
666 <div id="docs" class="flex-right">
667 <div class="wrap">
728 <div class="flex-right">
729 <div id="guides" class="wrap hidden">
730 </div>
731 <div id="docs" class="wrap hidden">
668732 <section class="docs">
669733 <div style="position: relative">
670734 <span id="searchPlaceholder"><kbd>s</kbd> to search, <kbd>?</kbd> for more options</span>
lib/docs/main.js+173-51
......@@ -2,10 +2,21 @@
22
33var zigAnalysis;
44
5const NAV_MODES = {
6 API: "#A;",
7 API_INTERNAL: "#a;",
8 GUIDES: "#G;",
9};
10
511(function () {
612 const domStatus = document.getElementById("status");
713 const domSectNav = document.getElementById("sectNav");
814 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");
920 const domSectMainPkg = document.getElementById("sectMainPkg");
1021 const domSectPkgs = document.getElementById("sectPkgs");
1122 const domListPkgs = document.getElementById("listPkgs");
......@@ -45,6 +56,7 @@ var zigAnalysis;
4556 const domSectSearchResults = document.getElementById("sectSearchResults");
4657 const domSectSearchAllResultsLink = document.getElementById("sectSearchAllResultsLink");
4758 const domDocs = document.getElementById("docs");
59 const domGuides = document.getElementById("guides");
4860 const domListSearchResults = document.getElementById("listSearchResults");
4961 const domSectSearchNoResults = document.getElementById("sectSearchNoResults");
5062 const domSectInfo = document.getElementById("sectInfo");
......@@ -83,7 +95,8 @@ var zigAnalysis;
8395 let canonTypeDecls = null; // lazy; use getCanonTypeDecl
8496
8597 let curNav = {
86 showPrivDecls: false,
98 mode: NAV_MODES.API,
99 activeGuide: "",
87100 // each element is a package name, e.g. @import("a") then within there @import("b")
88101 // starting implicitly from root package
89102 pkgNames: [],
......@@ -152,7 +165,7 @@ var zigAnalysis;
152165 );
153166
154167 if (location.hash == "") {
155 location.hash = "#root";
168 location.hash = "#A;";
156169 }
157170
158171 // make the modal disappear if you click outside it
......@@ -173,17 +186,21 @@ var zigAnalysis;
173186 domLangRefLink.href = `https://ziglang.org/documentation/${langRefVersion}/`;
174187
175188 function renderTitle() {
176 let list = curNav.pkgNames.concat(curNav.declNames);
177189 let suffix = " - Zig";
178 if (list.length === 0) {
179 if (rootIsStd) {
180 document.title = "std" + suffix;
181 } else {
182 document.title = zigAnalysis.params.rootName + suffix;
183 }
184 } else {
185 document.title = list.join(".") + suffix;
186 }
190 switch (curNav.mode) {
191 case NAV_MODES.API:
192 case NAV_MODES.API_INTERNAL:
193 let list = curNav.pkgNames.concat(curNav.declNames);
194 if (list.length === 0) {
195 document.title = zigAnalysis.packages[zigAnalysis.rootPkg].name + suffix;
196 } else {
197 document.title = list.join(".") + suffix;
198 }
199 return;
200 case NAV_MODES.GUIDES:
201 document.title = "[G] " + curNav.activeGuide + suffix;
202 return;
203 }
187204 }
188205
189206 function isDecl(x) {
......@@ -377,8 +394,80 @@ var zigAnalysis;
377394 // console.assert(false);
378395 // return ({});
379396 // }
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
382471 domStatus.classList.add("hidden");
383472 domFnProto.classList.add("hidden");
384473 domSectParams.classList.add("hidden");
......@@ -411,17 +500,16 @@ var zigAnalysis;
411500 renderInfo();
412501 renderPkgList();
413502
414 domPrivDeclsBox.checked = curNav.showPrivDecls;
503 domPrivDeclsBox.checked = curNav.mode == NAV_MODES.API_INTERNAL;
415504
416505 if (curNavSearch !== "") {
417506 return renderSearch();
418507 }
419508
420
421509 let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];
422510 let pkg = rootPkg;
423511 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) {
425513 let childPkg = zigAnalysis.packages[pkg.table[curNav.pkgNames[i]]];
426514 if (childPkg == null) {
427515 return render404();
......@@ -494,6 +582,19 @@ var zigAnalysis;
494582
495583 }
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
497598 function renderDocTest(decl) {
498599 if (!decl.decltest) return;
499600 const astNode = getAstNode(decl.decltest);
......@@ -705,7 +806,6 @@ var zigAnalysis;
705806 for (let i = 0; i < curNav.pkgNames.length; i += 1) {
706807 hrefPkgNames.push(curNav.pkgNames[i]);
707808 let name = curNav.pkgNames[i];
708 if (name == "root") name = zigAnalysis.rootPkgName;
709809 list.push({
710810 name: name,
711811 link: navLink(hrefPkgNames, hrefDeclNames),
......@@ -747,12 +847,12 @@ var zigAnalysis;
747847 }
748848
749849 function renderPkgList() {
750 let rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];
850 const rootPkg = zigAnalysis.packages[zigAnalysis.rootPkg];
751851 let list = [];
752852 for (let key in rootPkg.table) {
753853 let pkgIndex = rootPkg.table[key];
754854 if (zigAnalysis.packages[pkgIndex] == null) continue;
755 if (key == zigAnalysis.params.rootName) continue;
855 if (key == rootPkg.name) continue;
756856 list.push({
757857 name: key,
758858 pkg: pkgIndex,
......@@ -761,9 +861,9 @@ var zigAnalysis;
761861
762862 {
763863 let aDom = domSectMainPkg.children[1].children[0].children[0];
764 aDom.textContent = zigAnalysis.rootPkgName;
864 aDom.textContent = rootPkg.name;
765865 aDom.setAttribute("href", navLinkPkg(zigAnalysis.rootPkg));
766 if (zigAnalysis.params.rootName === curNav.pkgNames[0]) {
866 if (rootPkg.name === curNav.pkgNames[0]) {
767867 aDom.classList.add("active");
768868 } else {
769869 aDom.classList.remove("active");
......@@ -794,20 +894,17 @@ var zigAnalysis;
794894 }
795895
796896 function navLink(pkgNames, declNames, callName) {
797 let base = "#";
798 if (curNav.showPrivDecls) {
799 base += "*";
800 }
801
897 let base = curNav.mode;
898
802899 if (pkgNames.length === 0 && declNames.length === 0) {
803900 return base;
804901 } else if (declNames.length === 0 && callName == null) {
805902 return base + pkgNames.join(".");
806903 } else if (callName == null) {
807 return base + pkgNames.join(".") + ";" + declNames.join(".");
904 return base + pkgNames.join(".") + ":" + declNames.join(".");
808905 } else {
809906 return (
810 base + pkgNames.join(".") + ";" + declNames.join(".") + ";" + callName
907 base + pkgNames.join(".") + ":" + declNames.join(".") + ";" + callName
811908 );
812909 }
813910 }
......@@ -2734,9 +2831,10 @@ var zigAnalysis;
27342831 throw new Error("No type 'type' found");
27352832 }
27362833
2737 function updateCurNav() {
2834
2835 function updateCurNav() {
27382836 curNav = {
2739 showPrivDecls: false,
2837 mode: NAV_MODES.API,
27402838 pkgNames: [],
27412839 pkgObjs: [],
27422840 declNames: [],
......@@ -2745,30 +2843,54 @@ var zigAnalysis;
27452843 };
27462844 curNavSearch = "";
27472845
2748 if (location.hash[0] === "#" && location.hash.length > 1) {
2749 let query = location.hash.substring(1);
2750 if (query[0] === "*") {
2751 curNav.showPrivDecls = true;
2752 query = query.substring(1);
2753 }
2846 const mode = location.hash.substring(0, 3);
2847 let query = location.hash.substring(3);
2848
2849 const DEFAULT_HASH = NAV_MODES.API + zigAnalysis.packages[zigAnalysis.rootPkg].name;
2850 switch (mode) {
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("?");
2756 let nonSearchPart;
2757 if (qpos === -1) {
2758 nonSearchPart = query;
2759 } else {
2760 nonSearchPart = query.substring(0, qpos);
2761 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
2762 }
2872 if (parts[1] != null) {
2873 curNav.declNames = decodeURIComponent(parts[1]).split(".");
2874 }
27632875
2764 let parts = nonSearchPart.split(";");
2765 curNav.pkgNames = decodeURIComponent(parts[0]).split(".");
2766 if (parts[1] != null) {
2767 curNav.declNames = decodeURIComponent(parts[1]).split(".");
2768 }
2769 }
2770 }
2876 return;
2877 case NAV_MODES.GUIDES:
2878 const guides = Object.keys(zigAnalysis.guides);
2879 if (guides.length != 0 && query == "") {
2880 location.hash = NAV_MODES.GUIDES + guides[0];
2881 return;
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
27722894 function onHashChange() {
27732895 updateCurNav();
27742896 if (domSearch.value !== curNavSearch) {
src/Autodoc.zig+61-14
......@@ -28,6 +28,7 @@ decls: std.ArrayListUnmanaged(DocData.Decl) = .{},
2828exprs: std.ArrayListUnmanaged(DocData.Expr) = .{},
2929ast_nodes: std.ArrayListUnmanaged(DocData.AstNode) = .{},
3030comptime_exprs: std.ArrayListUnmanaged(DocData.ComptimeExpr) = .{},
31guides: std.StringHashMapUnmanaged([]const u8) = .{},
3132
3233// These fields hold temporary state of the analysis process
3334// and are mainly used by the decl path resolving algorithm.
......@@ -193,10 +194,15 @@ pub fn generateZirData(self: *Autodoc) !void {
193194 }
194195 }
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
196202 const main_type_index = self.types.items.len;
197203 {
198204 try self.packages.put(self.arena, self.module.main_pkg, .{
199 .name = "root",
205 .name = rootName,
200206 .main = main_type_index,
201207 .table = .{},
202208 });
......@@ -204,7 +210,7 @@ pub fn generateZirData(self: *Autodoc) !void {
204210 self.arena,
205211 self.module.main_pkg,
206212 .{
207 .name = "root",
213 .name = rootName,
208214 .value = 0,
209215 },
210216 );
......@@ -215,12 +221,13 @@ pub fn generateZirData(self: *Autodoc) !void {
215221 .enclosing_type = main_type_index,
216222 };
217223
218 const maybe_tldoc_comment = try self.getTLDocComment(file);
224 const tldoc_comment = try self.getTLDocComment(file);
219225 try self.ast_nodes.append(self.arena, .{
220226 .name = "(root)",
221 .docs = maybe_tldoc_comment,
227 .docs = tldoc_comment,
222228 });
223229 try self.files.put(self.arena, file, main_type_index);
230 try self.findGuidePaths(file, tldoc_comment);
224231
225232 _ = try self.walkInstruction(file, &root_scope, .{}, Zir.main_struct_inst, false);
226233
......@@ -236,13 +243,8 @@ pub fn generateZirData(self: *Autodoc) !void {
236243 @panic("some decl paths were never fully analized");
237244 }
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 };
243246 var data = DocData{
244 .rootPkgName = rootName,
245 .params = .{ .rootName = "root" },
247 .params = .{},
246248 .packages = self.packages.values(),
247249 .files = self.files,
248250 .calls = self.calls.items,
......@@ -251,6 +253,7 @@ pub fn generateZirData(self: *Autodoc) !void {
251253 .exprs = self.exprs.items,
252254 .astNodes = self.ast_nodes.items,
253255 .comptimeExprs = self.comptime_exprs.items,
256 .guides = self.guides,
254257 };
255258
256259 const base_dir = self.doc_location.directory orelse
......@@ -370,12 +373,10 @@ const Scope = struct {
370373const DocData = struct {
371374 typeKinds: []const []const u8 = std.meta.fieldNames(DocTypeKinds),
372375 rootPkg: u32 = 0,
373 rootPkgName: []const u8,
374376 params: struct {
375377 zigId: []const u8 = "arst",
376378 zigVersion: []const u8 = build_options.version,
377379 target: []const u8 = "arst",
378 rootName: []const u8,
379380 builds: []const struct { target: []const u8 } = &.{
380381 .{ .target = "arst" },
381382 },
......@@ -391,6 +392,9 @@ const DocData = struct {
391392 decls: []Decl,
392393 exprs: []Expr,
393394 comptimeExprs: []ComptimeExpr,
395
396 guides: std.StringHashMapUnmanaged([]const u8),
397
394398 const Call = struct {
395399 func: Expr,
396400 args: []Expr,
......@@ -410,6 +414,7 @@ const DocData = struct {
410414 try jsw.objectField(f_name);
411415 switch (f) {
412416 .files => try writeFileTableToJson(self.files, &jsw),
417 .guides => try writeGuidesToJson(self.guides, &jsw),
413418 else => {
414419 try std.json.stringify(@field(self, f_name), opts, w);
415420 jsw.state_index -= 1;
......@@ -852,7 +857,7 @@ fn walkInstruction(
852857
853858 const maybe_other_package: ?*Package = blk: {
854859 if (self.module.main_pkg_is_std and std.mem.eql(u8, path, "std")) {
855 path = "root";
860 path = "std";
856861 break :blk self.module.main_pkg;
857862 } else {
858863 break :blk file.pkg.table.get(path);
......@@ -4364,6 +4369,16 @@ fn writeFileTableToJson(map: std.AutoArrayHashMapUnmanaged(*File, usize), jsw: a
43644369 try jsw.endArray();
43654370}
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
43674382fn writePackageTableToJson(
43684383 map: std.AutoHashMapUnmanaged(*Package, DocData.DocPackage.TableEntry),
43694384 jsw: anytype,
......@@ -4422,8 +4437,40 @@ fn getTLDocComment(self: *Autodoc, file: *File) ![]const u8 {
44224437 var tok = tokenizer.next();
44234438 var comment = std.ArrayList(u8).init(self.arena);
44244439 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]);
44264441 }
44274442
44284443 return comment.items;
44294444}
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}