sourcey 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -10
- package/dist/cli.js +122 -3
- package/dist/components/layout/Page.d.ts.map +1 -1
- package/dist/components/layout/Page.js +4 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -3
- package/dist/core/godoc-introspector.d.ts +26 -0
- package/dist/core/godoc-introspector.d.ts.map +1 -0
- package/dist/core/godoc-introspector.js +144 -0
- package/dist/core/godoc-loader.d.ts +34 -0
- package/dist/core/godoc-loader.d.ts.map +1 -0
- package/dist/core/godoc-loader.js +491 -0
- package/dist/core/godoc-types.d.ts +109 -0
- package/dist/core/godoc-types.d.ts.map +1 -0
- package/dist/core/godoc-types.js +8 -0
- package/dist/core/markdown-loader.d.ts +10 -0
- package/dist/core/markdown-loader.d.ts.map +1 -1
- package/dist/core/navigation.d.ts +3 -2
- package/dist/core/navigation.d.ts.map +1 -1
- package/dist/core/navigation.js +15 -5
- package/dist/core/search-indexer.d.ts.map +1 -1
- package/dist/core/search-indexer.js +9 -0
- package/dist/core/sourcey-godoc/cmd/sourcey-godoc/main.go +736 -0
- package/dist/core/sourcey-godoc/cmd/sourcey-godoc/site.go +497 -0
- package/dist/core/sourcey-godoc/cmd/sourcey-godoc/site_test.go +89 -0
- package/dist/core/sourcey-godoc/doc.go +11 -0
- package/dist/core/sourcey-godoc/go.mod +3 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +44 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/site-assembly.d.ts +3 -0
- package/dist/site-assembly.d.ts.map +1 -1
- package/dist/site-assembly.js +33 -4
- package/dist/themes/default/sourcey.css +244 -0
- package/package.json +13 -4
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/doc/comment"
|
|
5
|
+
"html"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"regexp"
|
|
9
|
+
"sort"
|
|
10
|
+
"strings"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const siteCSS = `:root {
|
|
14
|
+
color-scheme: light dark;
|
|
15
|
+
--bg: #f8f8f5;
|
|
16
|
+
--fg: #161616;
|
|
17
|
+
--muted: #656565;
|
|
18
|
+
--border: #deded8;
|
|
19
|
+
--panel: #ffffff;
|
|
20
|
+
--accent: #2558d4;
|
|
21
|
+
--code-bg: #f1f1ed;
|
|
22
|
+
}
|
|
23
|
+
@media (prefers-color-scheme: dark) {
|
|
24
|
+
:root {
|
|
25
|
+
--bg: #111111;
|
|
26
|
+
--fg: #f3f3ef;
|
|
27
|
+
--muted: #a7a7a0;
|
|
28
|
+
--border: #30302c;
|
|
29
|
+
--panel: #171717;
|
|
30
|
+
--accent: #8eadff;
|
|
31
|
+
--code-bg: #20201c;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
* { box-sizing: border-box; }
|
|
35
|
+
body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
background: var(--bg);
|
|
38
|
+
color: var(--fg);
|
|
39
|
+
font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
40
|
+
}
|
|
41
|
+
a { color: var(--accent); text-decoration: none; }
|
|
42
|
+
a:hover { text-decoration: underline; }
|
|
43
|
+
.shell {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
|
|
46
|
+
min-height: 100vh;
|
|
47
|
+
}
|
|
48
|
+
aside {
|
|
49
|
+
border-right: 1px solid var(--border);
|
|
50
|
+
padding: 28px 22px;
|
|
51
|
+
background: var(--panel);
|
|
52
|
+
}
|
|
53
|
+
main {
|
|
54
|
+
max-width: 1040px;
|
|
55
|
+
padding: 44px min(6vw, 72px);
|
|
56
|
+
}
|
|
57
|
+
.brand { font-weight: 700; margin: 0 0 8px; }
|
|
58
|
+
.module { color: var(--muted); font-size: 0.9rem; word-break: break-all; }
|
|
59
|
+
.nav { list-style: none; margin: 24px 0 0; padding: 0; }
|
|
60
|
+
.nav li { margin: 6px 0; }
|
|
61
|
+
h1, h2, h3 { line-height: 1.2; }
|
|
62
|
+
h1 { font-size: clamp(2rem, 4vw, 3rem); margin: 0 0 12px; }
|
|
63
|
+
h2 { margin-top: 44px; padding-top: 20px; border-top: 1px solid var(--border); }
|
|
64
|
+
h3 { margin-top: 28px; }
|
|
65
|
+
.import-path, .synopsis, .meta { color: var(--muted); }
|
|
66
|
+
.card {
|
|
67
|
+
display: block;
|
|
68
|
+
border: 1px solid var(--border);
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
padding: 16px;
|
|
71
|
+
margin: 12px 0;
|
|
72
|
+
background: var(--panel);
|
|
73
|
+
}
|
|
74
|
+
pre {
|
|
75
|
+
overflow-x: auto;
|
|
76
|
+
padding: 14px;
|
|
77
|
+
border-radius: 8px;
|
|
78
|
+
background: var(--code-bg);
|
|
79
|
+
border: 1px solid var(--border);
|
|
80
|
+
}
|
|
81
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
82
|
+
.doc { max-width: 78ch; }
|
|
83
|
+
.field-list { padding-left: 18px; }
|
|
84
|
+
.field-list li { margin: 8px 0; }
|
|
85
|
+
@media (max-width: 780px) {
|
|
86
|
+
.shell { display: block; }
|
|
87
|
+
aside { border-right: 0; border-bottom: 1px solid var(--border); }
|
|
88
|
+
main { padding: 28px 20px; }
|
|
89
|
+
}`
|
|
90
|
+
|
|
91
|
+
var slugCleaner = regexp.MustCompile(`[^a-zA-Z0-9_-]+`)
|
|
92
|
+
|
|
93
|
+
func writeSite(cfg *config, snap *snapshot) error {
|
|
94
|
+
outDir := cfg.out
|
|
95
|
+
if outDir == "" {
|
|
96
|
+
outDir = "site"
|
|
97
|
+
}
|
|
98
|
+
if err := os.MkdirAll(outDir, 0o755); err != nil {
|
|
99
|
+
return err
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if err := writeSnapshot(filepath.Join(outDir, "sourcey-godoc.json"), snap); err != nil {
|
|
103
|
+
return err
|
|
104
|
+
}
|
|
105
|
+
if err := writeFile(outDir, "sourcey-godoc.css", siteCSS); err != nil {
|
|
106
|
+
return err
|
|
107
|
+
}
|
|
108
|
+
if err := writeFile(outDir, "index.html", renderIndex(cfg, snap)); err != nil {
|
|
109
|
+
return err
|
|
110
|
+
}
|
|
111
|
+
for _, pkg := range snap.Packages {
|
|
112
|
+
if err := writeFile(outDir, packageFile(pkg, snap.ModulePath), renderPackage(cfg, snap, pkg)); err != nil {
|
|
113
|
+
return err
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if err := writeFile(outDir, "llms.txt", renderLLMSText(cfg, snap)); err != nil {
|
|
117
|
+
return err
|
|
118
|
+
}
|
|
119
|
+
if err := writeFile(outDir, "llms-full.txt", renderLLMSFull(cfg, snap)); err != nil {
|
|
120
|
+
return err
|
|
121
|
+
}
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
func writeFile(root, rel, text string) error {
|
|
126
|
+
path := filepath.Join(root, filepath.FromSlash(rel))
|
|
127
|
+
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
128
|
+
return err
|
|
129
|
+
}
|
|
130
|
+
return os.WriteFile(path, []byte(text), 0o644)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func renderIndex(cfg *config, snap *snapshot) string {
|
|
134
|
+
title := siteTitle(cfg, snap)
|
|
135
|
+
var body strings.Builder
|
|
136
|
+
body.WriteString(`<h1>`)
|
|
137
|
+
body.WriteString(esc(title))
|
|
138
|
+
body.WriteString(`</h1>`)
|
|
139
|
+
if snap.ModulePath != "" {
|
|
140
|
+
body.WriteString(`<p class="module"><code>`)
|
|
141
|
+
body.WriteString(esc(snap.ModulePath))
|
|
142
|
+
body.WriteString(`</code></p>`)
|
|
143
|
+
}
|
|
144
|
+
body.WriteString(`<p class="synopsis">Native Go package documentation generated by sourcey-godoc.</p>`)
|
|
145
|
+
for _, pkg := range snap.Packages {
|
|
146
|
+
body.WriteString(`<a class="card" href="`)
|
|
147
|
+
body.WriteString(esc(packageFile(pkg, snap.ModulePath)))
|
|
148
|
+
body.WriteString(`"><strong>`)
|
|
149
|
+
body.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
|
|
150
|
+
body.WriteString(`</strong>`)
|
|
151
|
+
if pkg.Synopsis != "" {
|
|
152
|
+
body.WriteString(`<p>`)
|
|
153
|
+
body.WriteString(esc(pkg.Synopsis))
|
|
154
|
+
body.WriteString(`</p>`)
|
|
155
|
+
}
|
|
156
|
+
body.WriteString(`<p class="import-path"><code>`)
|
|
157
|
+
body.WriteString(esc(pkg.ImportPath))
|
|
158
|
+
body.WriteString(`</code></p></a>`)
|
|
159
|
+
}
|
|
160
|
+
return page(title, snap, body.String())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
func renderPackage(cfg *config, snap *snapshot, pkg pkgOut) string {
|
|
164
|
+
var body strings.Builder
|
|
165
|
+
body.WriteString(`<h1>`)
|
|
166
|
+
body.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
|
|
167
|
+
body.WriteString(`</h1><p class="import-path"><code>import "`)
|
|
168
|
+
body.WriteString(esc(pkg.ImportPath))
|
|
169
|
+
body.WriteString(`"</code></p>`)
|
|
170
|
+
if pkg.Doc != "" {
|
|
171
|
+
body.WriteString(`<div class="doc">`)
|
|
172
|
+
body.WriteString(renderDoc(pkg.Doc))
|
|
173
|
+
body.WriteString(`</div>`)
|
|
174
|
+
}
|
|
175
|
+
if len(pkg.Consts) > 0 {
|
|
176
|
+
body.WriteString(`<h2 id="constants">Constants</h2>`)
|
|
177
|
+
body.WriteString(renderValues("const", pkg.Consts))
|
|
178
|
+
}
|
|
179
|
+
if len(pkg.Vars) > 0 {
|
|
180
|
+
body.WriteString(`<h2 id="variables">Variables</h2>`)
|
|
181
|
+
body.WriteString(renderValues("var", pkg.Vars))
|
|
182
|
+
}
|
|
183
|
+
if len(pkg.Funcs) > 0 {
|
|
184
|
+
body.WriteString(`<h2 id="functions">Functions</h2>`)
|
|
185
|
+
for _, fn := range pkg.Funcs {
|
|
186
|
+
body.WriteString(renderFunc(fn))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if len(pkg.Types) > 0 {
|
|
190
|
+
body.WriteString(`<h2 id="types">Types</h2>`)
|
|
191
|
+
for _, typ := range pkg.Types {
|
|
192
|
+
body.WriteString(renderType(typ))
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if len(pkg.Examples) > 0 {
|
|
196
|
+
body.WriteString(`<h2 id="examples">Examples</h2>`)
|
|
197
|
+
for _, ex := range pkg.Examples {
|
|
198
|
+
body.WriteString(renderExample(ex))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return page(packageTitle(pkg, snap.ModulePath), snap, body.String())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func renderValues(kind string, values []valueOut) string {
|
|
205
|
+
seen := map[string]bool{}
|
|
206
|
+
var body strings.Builder
|
|
207
|
+
for _, value := range values {
|
|
208
|
+
body.WriteString(`<section id="`)
|
|
209
|
+
body.WriteString(esc(kind + "-" + symbolSlug(value.Name)))
|
|
210
|
+
body.WriteString(`">`)
|
|
211
|
+
if value.Doc != "" {
|
|
212
|
+
body.WriteString(`<div class="doc">`)
|
|
213
|
+
body.WriteString(renderDoc(value.Doc))
|
|
214
|
+
body.WriteString(`</div>`)
|
|
215
|
+
}
|
|
216
|
+
if !seen[value.Declaration] {
|
|
217
|
+
seen[value.Declaration] = true
|
|
218
|
+
body.WriteString(codeBlock(value.Declaration))
|
|
219
|
+
}
|
|
220
|
+
body.WriteString(`</section>`)
|
|
221
|
+
}
|
|
222
|
+
return body.String()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
func renderFunc(fn funcOut) string {
|
|
226
|
+
var body strings.Builder
|
|
227
|
+
body.WriteString(`<section id="func-`)
|
|
228
|
+
body.WriteString(esc(symbolSlug(fn.Name)))
|
|
229
|
+
body.WriteString(`"><h3>`)
|
|
230
|
+
body.WriteString(esc(fn.Signature))
|
|
231
|
+
body.WriteString(`</h3>`)
|
|
232
|
+
if fn.Doc != "" {
|
|
233
|
+
body.WriteString(`<div class="doc">`)
|
|
234
|
+
body.WriteString(renderDoc(fn.Doc))
|
|
235
|
+
body.WriteString(`</div>`)
|
|
236
|
+
}
|
|
237
|
+
for _, ex := range fn.Examples {
|
|
238
|
+
body.WriteString(renderExample(ex))
|
|
239
|
+
}
|
|
240
|
+
body.WriteString(`</section>`)
|
|
241
|
+
return body.String()
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func renderType(typ typeOut) string {
|
|
245
|
+
var body strings.Builder
|
|
246
|
+
body.WriteString(`<section id="type-`)
|
|
247
|
+
body.WriteString(esc(symbolSlug(typ.Name)))
|
|
248
|
+
body.WriteString(`"><h3>type `)
|
|
249
|
+
body.WriteString(esc(typ.Name))
|
|
250
|
+
body.WriteString(`</h3>`)
|
|
251
|
+
if typ.Doc != "" {
|
|
252
|
+
body.WriteString(`<div class="doc">`)
|
|
253
|
+
body.WriteString(renderDoc(typ.Doc))
|
|
254
|
+
body.WriteString(`</div>`)
|
|
255
|
+
}
|
|
256
|
+
body.WriteString(codeBlock(typ.Declaration))
|
|
257
|
+
if len(typ.Fields) > 0 {
|
|
258
|
+
heading := "Fields"
|
|
259
|
+
if typ.Kind == "interface" {
|
|
260
|
+
heading = "Methods"
|
|
261
|
+
}
|
|
262
|
+
body.WriteString(`<h4>`)
|
|
263
|
+
body.WriteString(heading)
|
|
264
|
+
body.WriteString(`</h4><ul class="field-list">`)
|
|
265
|
+
for _, field := range typ.Fields {
|
|
266
|
+
body.WriteString(`<li><code>`)
|
|
267
|
+
body.WriteString(esc(field.Name))
|
|
268
|
+
if !field.Embedded && field.Type != "" {
|
|
269
|
+
body.WriteString(` `)
|
|
270
|
+
body.WriteString(esc(field.Type))
|
|
271
|
+
}
|
|
272
|
+
body.WriteString(`</code>`)
|
|
273
|
+
if field.Tag != "" {
|
|
274
|
+
body.WriteString(` <code>`)
|
|
275
|
+
body.WriteString(esc("`" + field.Tag + "`"))
|
|
276
|
+
body.WriteString(`</code>`)
|
|
277
|
+
}
|
|
278
|
+
if field.Doc != "" {
|
|
279
|
+
body.WriteString(`<div class="doc">`)
|
|
280
|
+
body.WriteString(renderDoc(field.Doc))
|
|
281
|
+
body.WriteString(`</div>`)
|
|
282
|
+
}
|
|
283
|
+
body.WriteString(`</li>`)
|
|
284
|
+
}
|
|
285
|
+
body.WriteString(`</ul>`)
|
|
286
|
+
}
|
|
287
|
+
for _, method := range typ.Methods {
|
|
288
|
+
body.WriteString(renderFunc(method))
|
|
289
|
+
}
|
|
290
|
+
for _, ex := range typ.Examples {
|
|
291
|
+
body.WriteString(renderExample(ex))
|
|
292
|
+
}
|
|
293
|
+
body.WriteString(`</section>`)
|
|
294
|
+
return body.String()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func renderExample(ex exOut) string {
|
|
298
|
+
var body strings.Builder
|
|
299
|
+
body.WriteString(`<details><summary>Example`)
|
|
300
|
+
if ex.Suffix != "" {
|
|
301
|
+
body.WriteString(`: `)
|
|
302
|
+
body.WriteString(esc(strings.ReplaceAll(ex.Suffix, "_", " ")))
|
|
303
|
+
}
|
|
304
|
+
body.WriteString(`</summary>`)
|
|
305
|
+
if ex.Doc != "" {
|
|
306
|
+
body.WriteString(`<div class="doc">`)
|
|
307
|
+
body.WriteString(renderDoc(ex.Doc))
|
|
308
|
+
body.WriteString(`</div>`)
|
|
309
|
+
}
|
|
310
|
+
body.WriteString(codeBlock(ex.Code))
|
|
311
|
+
if ex.Output != "" {
|
|
312
|
+
body.WriteString(`<p class="meta">Output:</p>`)
|
|
313
|
+
body.WriteString(codeBlock(ex.Output))
|
|
314
|
+
}
|
|
315
|
+
body.WriteString(`</details>`)
|
|
316
|
+
return body.String()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func page(title string, snap *snapshot, body string) string {
|
|
320
|
+
var nav strings.Builder
|
|
321
|
+
nav.WriteString(`<li><a href="index.html">Overview</a></li>`)
|
|
322
|
+
for _, pkg := range snap.Packages {
|
|
323
|
+
nav.WriteString(`<li><a href="`)
|
|
324
|
+
nav.WriteString(esc(packageFile(pkg, snap.ModulePath)))
|
|
325
|
+
nav.WriteString(`">`)
|
|
326
|
+
nav.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
|
|
327
|
+
nav.WriteString(`</a></li>`)
|
|
328
|
+
}
|
|
329
|
+
return `<!doctype html>
|
|
330
|
+
<html lang="en">
|
|
331
|
+
<head>
|
|
332
|
+
<meta charset="utf-8">
|
|
333
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
334
|
+
<title>` + esc(title) + `</title>
|
|
335
|
+
<link rel="stylesheet" href="sourcey-godoc.css">
|
|
336
|
+
</head>
|
|
337
|
+
<body><div class="shell">
|
|
338
|
+
<aside><p class="brand">sourcey-godoc</p><p class="module"><code>` + esc(snap.ModulePath) + `</code></p><ul class="nav">` + nav.String() + `</ul></aside>
|
|
339
|
+
<main>` + body + `</main>
|
|
340
|
+
</div></body>
|
|
341
|
+
</html>
|
|
342
|
+
`
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
func renderLLMSText(cfg *config, snap *snapshot) string {
|
|
346
|
+
var body strings.Builder
|
|
347
|
+
body.WriteString("# ")
|
|
348
|
+
body.WriteString(siteTitle(cfg, snap))
|
|
349
|
+
body.WriteString("\n\n")
|
|
350
|
+
if snap.ModulePath != "" {
|
|
351
|
+
body.WriteString("Module: `")
|
|
352
|
+
body.WriteString(snap.ModulePath)
|
|
353
|
+
body.WriteString("`\n\n")
|
|
354
|
+
}
|
|
355
|
+
for _, pkg := range snap.Packages {
|
|
356
|
+
body.WriteString("- [")
|
|
357
|
+
body.WriteString(packageTitle(pkg, snap.ModulePath))
|
|
358
|
+
body.WriteString("](")
|
|
359
|
+
body.WriteString(packageFile(pkg, snap.ModulePath))
|
|
360
|
+
body.WriteString(")")
|
|
361
|
+
if pkg.Synopsis != "" {
|
|
362
|
+
body.WriteString(" - ")
|
|
363
|
+
body.WriteString(pkg.Synopsis)
|
|
364
|
+
}
|
|
365
|
+
body.WriteString("\n")
|
|
366
|
+
}
|
|
367
|
+
return body.String()
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func renderLLMSFull(cfg *config, snap *snapshot) string {
|
|
371
|
+
var body strings.Builder
|
|
372
|
+
body.WriteString("# ")
|
|
373
|
+
body.WriteString(siteTitle(cfg, snap))
|
|
374
|
+
body.WriteString("\n\n")
|
|
375
|
+
for _, pkg := range snap.Packages {
|
|
376
|
+
body.WriteString("## ")
|
|
377
|
+
body.WriteString(pkg.ImportPath)
|
|
378
|
+
body.WriteString("\n\n")
|
|
379
|
+
if pkg.Doc != "" {
|
|
380
|
+
body.WriteString(docText(pkg.Doc))
|
|
381
|
+
body.WriteString("\n\n")
|
|
382
|
+
}
|
|
383
|
+
writeDeclarations(&body, "Constants", valueDeclarations(pkg.Consts))
|
|
384
|
+
writeDeclarations(&body, "Variables", valueDeclarations(pkg.Vars))
|
|
385
|
+
writeDeclarations(&body, "Functions", funcDeclarations(pkg.Funcs))
|
|
386
|
+
writeDeclarations(&body, "Types", typeDeclarations(pkg.Types))
|
|
387
|
+
}
|
|
388
|
+
return body.String()
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
func writeDeclarations(body *strings.Builder, heading string, declarations []string) {
|
|
392
|
+
if len(declarations) == 0 {
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
body.WriteString("### ")
|
|
396
|
+
body.WriteString(heading)
|
|
397
|
+
body.WriteString("\n\n```go\n")
|
|
398
|
+
for _, decl := range declarations {
|
|
399
|
+
body.WriteString(decl)
|
|
400
|
+
if !strings.HasSuffix(decl, "\n") {
|
|
401
|
+
body.WriteString("\n")
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
body.WriteString("```\n\n")
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
func valueDeclarations(values []valueOut) []string {
|
|
408
|
+
seen := map[string]bool{}
|
|
409
|
+
out := []string{}
|
|
410
|
+
for _, value := range values {
|
|
411
|
+
if value.Declaration != "" && !seen[value.Declaration] {
|
|
412
|
+
seen[value.Declaration] = true
|
|
413
|
+
out = append(out, value.Declaration)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return out
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
func funcDeclarations(funcs []funcOut) []string {
|
|
420
|
+
out := make([]string, 0, len(funcs))
|
|
421
|
+
for _, fn := range funcs {
|
|
422
|
+
out = append(out, fn.Signature)
|
|
423
|
+
}
|
|
424
|
+
sort.Strings(out)
|
|
425
|
+
return out
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
func typeDeclarations(types []typeOut) []string {
|
|
429
|
+
out := make([]string, 0, len(types))
|
|
430
|
+
for _, typ := range types {
|
|
431
|
+
out = append(out, typ.Declaration)
|
|
432
|
+
}
|
|
433
|
+
sort.Strings(out)
|
|
434
|
+
return out
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
func siteTitle(cfg *config, snap *snapshot) string {
|
|
438
|
+
if cfg.title != "" {
|
|
439
|
+
return cfg.title
|
|
440
|
+
}
|
|
441
|
+
if snap.ModulePath != "" {
|
|
442
|
+
return snap.ModulePath
|
|
443
|
+
}
|
|
444
|
+
return "Go API"
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
func packageTitle(pkg pkgOut, modulePath string) string {
|
|
448
|
+
rel := relativePackage(pkg.ImportPath, modulePath)
|
|
449
|
+
if rel == "." || rel == "" {
|
|
450
|
+
return pkg.ImportPath
|
|
451
|
+
}
|
|
452
|
+
return rel
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
func packageFile(pkg pkgOut, modulePath string) string {
|
|
456
|
+
rel := relativePackage(pkg.ImportPath, modulePath)
|
|
457
|
+
if rel == "." || rel == "" {
|
|
458
|
+
return "package-root.html"
|
|
459
|
+
}
|
|
460
|
+
return "pkg-" + slugCleaner.ReplaceAllString(strings.ReplaceAll(rel, "/", "-"), "") + ".html"
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
func relativePackage(importPath, modulePath string) string {
|
|
464
|
+
if modulePath == "" {
|
|
465
|
+
return importPath
|
|
466
|
+
}
|
|
467
|
+
if importPath == modulePath {
|
|
468
|
+
return "."
|
|
469
|
+
}
|
|
470
|
+
return strings.TrimPrefix(importPath, modulePath+"/")
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
func renderDoc(text string) string {
|
|
474
|
+
var parser comment.Parser
|
|
475
|
+
doc := parser.Parse(text)
|
|
476
|
+
var printer comment.Printer
|
|
477
|
+
return string(printer.HTML(doc))
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
func docText(text string) string {
|
|
481
|
+
var parser comment.Parser
|
|
482
|
+
doc := parser.Parse(text)
|
|
483
|
+
var printer comment.Printer
|
|
484
|
+
return strings.TrimSpace(string(printer.Text(doc)))
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
func codeBlock(text string) string {
|
|
488
|
+
return "<pre><code>" + esc(text) + "</code></pre>"
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
func symbolSlug(name string) string {
|
|
492
|
+
return slugCleaner.ReplaceAllString(name, "_")
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
func esc(text string) string {
|
|
496
|
+
return html.EscapeString(text)
|
|
497
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestWriteSiteGeneratesBrowsableDocsAndAgentSurfaces(t *testing.T) {
|
|
11
|
+
out := t.TempDir()
|
|
12
|
+
snap := &snapshot{
|
|
13
|
+
SchemaVersion: schemaVersion,
|
|
14
|
+
Source: source,
|
|
15
|
+
ModulePath: "example.com/project",
|
|
16
|
+
Packages: []pkgOut{{
|
|
17
|
+
ImportPath: "example.com/project/internal/core",
|
|
18
|
+
Name: "core",
|
|
19
|
+
Synopsis: "Package core owns the contract.",
|
|
20
|
+
Doc: "Package core owns the contract.\n\nIt has a second paragraph.",
|
|
21
|
+
Dir: "internal/core",
|
|
22
|
+
Files: []string{"core.go"},
|
|
23
|
+
Funcs: []funcOut{{
|
|
24
|
+
Name: "Run",
|
|
25
|
+
Doc: "Run executes the plan.",
|
|
26
|
+
Signature: "func Run() error",
|
|
27
|
+
}},
|
|
28
|
+
Types: []typeOut{{
|
|
29
|
+
Name: "Plan",
|
|
30
|
+
Doc: "Plan describes work.",
|
|
31
|
+
Declaration: "type Plan struct {\n\tName string `json:\"name\"`\n}",
|
|
32
|
+
Kind: "struct",
|
|
33
|
+
Fields: []fieldOut{{
|
|
34
|
+
Name: "Name",
|
|
35
|
+
Doc: "Name is the plan name.",
|
|
36
|
+
Type: "string",
|
|
37
|
+
Tag: `json:"name"`,
|
|
38
|
+
}},
|
|
39
|
+
}},
|
|
40
|
+
}},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if err := writeSite(&config{out: out, title: "Project Go API"}, snap); err != nil {
|
|
44
|
+
t.Fatalf("writeSite failed: %v", err)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for _, rel := range []string{
|
|
48
|
+
"index.html",
|
|
49
|
+
"pkg-internal-core.html",
|
|
50
|
+
"sourcey-godoc.css",
|
|
51
|
+
"sourcey-godoc.json",
|
|
52
|
+
"llms.txt",
|
|
53
|
+
"llms-full.txt",
|
|
54
|
+
} {
|
|
55
|
+
if _, err := os.Stat(filepath.Join(out, rel)); err != nil {
|
|
56
|
+
t.Fatalf("expected %s: %v", rel, err)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pkgHTML := readTestFile(t, filepath.Join(out, "pkg-internal-core.html"))
|
|
61
|
+
for _, want := range []string{
|
|
62
|
+
"func Run() error",
|
|
63
|
+
"type Plan",
|
|
64
|
+
"Name is the plan name.",
|
|
65
|
+
"It has a second paragraph.",
|
|
66
|
+
} {
|
|
67
|
+
if !strings.Contains(pkgHTML, want) {
|
|
68
|
+
t.Fatalf("package page missing %q\n%s", want, pkgHTML)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
llms := readTestFile(t, filepath.Join(out, "llms.txt"))
|
|
73
|
+
if !strings.Contains(llms, "pkg-internal-core.html") {
|
|
74
|
+
t.Fatalf("llms.txt does not link package page:\n%s", llms)
|
|
75
|
+
}
|
|
76
|
+
llmsFull := readTestFile(t, filepath.Join(out, "llms-full.txt"))
|
|
77
|
+
if !strings.Contains(llmsFull, "func Run() error") {
|
|
78
|
+
t.Fatalf("llms-full.txt missing declaration:\n%s", llmsFull)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func readTestFile(t *testing.T, path string) string {
|
|
83
|
+
t.Helper()
|
|
84
|
+
data, err := os.ReadFile(path)
|
|
85
|
+
if err != nil {
|
|
86
|
+
t.Fatal(err)
|
|
87
|
+
}
|
|
88
|
+
return string(data)
|
|
89
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Package sourceygodoc documents the Sourcey Go documentation generator module.
|
|
2
|
+
//
|
|
3
|
+
// The module's primary surface is the sourcey-godoc CLI:
|
|
4
|
+
//
|
|
5
|
+
// go install github.com/sourcey/sourcey/go/sourcey-godoc/cmd/sourcey-godoc@latest
|
|
6
|
+
//
|
|
7
|
+
// The CLI reads a Go module through the Go toolchain and generates a static
|
|
8
|
+
// Go documentation site or Sourcey's portable godoc snapshot format. Sourcey's
|
|
9
|
+
// TypeScript CLI embeds this same module for live-mode docs builds, so the
|
|
10
|
+
// standalone CLI and Sourcey runtime share one implementation.
|
|
11
|
+
package sourceygodoc
|
package/dist/dev-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AA+CA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmZ7E"}
|
package/dist/dev-server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { normalizeMcpSpec } from "./core/mcp-normalizer.js";
|
|
|
11
11
|
import { buildNavFromSpec, buildSiteNavigation, withActivePage } from "./core/navigation.js";
|
|
12
12
|
import { loadDocsPage, slugFromPath } from "./core/markdown-loader.js";
|
|
13
13
|
import { loadDoxygenTab } from "./core/doxygen-loader.js";
|
|
14
|
+
import { loadGodocTab } from "./core/godoc-loader.js";
|
|
14
15
|
import { buildSearchIndex } from "./core/search-indexer.js";
|
|
15
16
|
import { createRenderOptions } from "./renderer/html-builder.js";
|
|
16
17
|
import { assembleSite, buildSiteConfig, collectDocsPagesByTab, enforceChangelogDiagnostics, formatChangelogDiagnostic, rebuildMarkdownTabNavigation, resolveInternalLinks, } from "./site-assembly.js";
|
|
@@ -49,6 +50,12 @@ export async function startDevServer(options) {
|
|
|
49
50
|
watchPaths.push(tab.mcp);
|
|
50
51
|
if (tab.doxygen)
|
|
51
52
|
watchPaths.push(tab.doxygen.xml);
|
|
53
|
+
if (tab.godoc) {
|
|
54
|
+
if (tab.godoc.snapshot)
|
|
55
|
+
watchPaths.push(tab.godoc.snapshot);
|
|
56
|
+
// The Go module directory is large; watching every .go file slows the
|
|
57
|
+
// dev server. Restart manually after broad source edits.
|
|
58
|
+
}
|
|
52
59
|
if (tab.groups) {
|
|
53
60
|
for (const group of tab.groups) {
|
|
54
61
|
for (const page of group.pages) {
|
|
@@ -70,6 +77,9 @@ export async function startDevServer(options) {
|
|
|
70
77
|
if (tab.doxygen) {
|
|
71
78
|
map.set(resolve(tab.doxygen.xml), { kind: "doxygen", tabSlug: tab.slug, xmlDir: tab.doxygen.xml });
|
|
72
79
|
}
|
|
80
|
+
if (tab.godoc?.snapshot) {
|
|
81
|
+
map.set(resolve(tab.godoc.snapshot), { kind: "godoc", tabSlug: tab.slug, snapshotPath: tab.godoc.snapshot });
|
|
82
|
+
}
|
|
73
83
|
if (tab.groups) {
|
|
74
84
|
for (const group of tab.groups) {
|
|
75
85
|
for (const page of group.pages) {
|
|
@@ -182,6 +192,38 @@ export async function startDevServer(options) {
|
|
|
182
192
|
else
|
|
183
193
|
data.siteTabs.push(navTab);
|
|
184
194
|
}
|
|
195
|
+
else if (content.kind === "godoc") {
|
|
196
|
+
const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
|
|
197
|
+
if (!tab?.godoc)
|
|
198
|
+
return;
|
|
199
|
+
log(`rebuilding godoc tab "${tab.label}"`);
|
|
200
|
+
const { pages, navTab } = await loadGodocTab(tab.godoc, tab.slug, tab.label, {
|
|
201
|
+
repo: config.repo,
|
|
202
|
+
editBranch: config.editBranch,
|
|
203
|
+
editBasePath: tab.godoc.sourceBasePath,
|
|
204
|
+
});
|
|
205
|
+
if (cache !== snapshot)
|
|
206
|
+
return;
|
|
207
|
+
for (const [key, page] of data.pageMap) {
|
|
208
|
+
if (page.tabSlug === content.tabSlug)
|
|
209
|
+
data.pageMap.delete(key);
|
|
210
|
+
}
|
|
211
|
+
for (const [slug, page] of pages) {
|
|
212
|
+
const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
|
|
213
|
+
data.pageMap.set(outputPath, {
|
|
214
|
+
outputPath,
|
|
215
|
+
spec: data.primarySpec,
|
|
216
|
+
currentPage: { kind: "markdown", markdown: page },
|
|
217
|
+
tabSlug: tab.slug,
|
|
218
|
+
pageSlug: slug,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
|
|
222
|
+
if (idx !== -1)
|
|
223
|
+
data.siteTabs[idx] = navTab;
|
|
224
|
+
else
|
|
225
|
+
data.siteTabs.push(navTab);
|
|
226
|
+
}
|
|
185
227
|
else if (content.kind === "openapi") {
|
|
186
228
|
log(`reparsing spec ${shortPath(content.specPath)}`);
|
|
187
229
|
const loaded = await loadSpec(content.specPath);
|
|
@@ -199,7 +241,7 @@ export async function startDevServer(options) {
|
|
|
199
241
|
}
|
|
200
242
|
const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
|
|
201
243
|
if (tab) {
|
|
202
|
-
const navTab = buildNavFromSpec(spec, tab.slug);
|
|
244
|
+
const navTab = buildNavFromSpec(spec, tab.slug, config.prettyUrls);
|
|
203
245
|
navTab.label = tab.label;
|
|
204
246
|
const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
|
|
205
247
|
if (idx !== -1)
|
|
@@ -222,7 +264,7 @@ export async function startDevServer(options) {
|
|
|
222
264
|
}
|
|
223
265
|
const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
|
|
224
266
|
if (tab) {
|
|
225
|
-
const navTab = buildNavFromSpec(spec, tab.slug);
|
|
267
|
+
const navTab = buildNavFromSpec(spec, tab.slug, config.prettyUrls);
|
|
226
268
|
navTab.label = tab.label;
|
|
227
269
|
const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
|
|
228
270
|
if (idx !== -1)
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface SiteBuildResult {
|
|
|
30
30
|
outputDir: string;
|
|
31
31
|
pageCount: number;
|
|
32
32
|
changelogDiagnostics: ChangelogDiagnostic[];
|
|
33
|
+
godocDiagnostics: import("./core/godoc-loader.js").GodocLoaderDiagnostic[];
|
|
33
34
|
/** @internal specs by tab slug, for buildDocs compat */
|
|
34
35
|
_specs?: Map<string, NormalizedSpec>;
|
|
35
36
|
}
|