docs.go

v1.4.0
Doc Versions Source
1
package server
2
3
import (
4
	"log"
5
	"net/http"
6
	"slices"
7
8
	"go.bigb.es/curator/internal/dochtml"
9
	"go.bigb.es/curator/internal/git"
10
	"go.bigb.es/curator/internal/godoc"
11
	"go.bigb.es/curator/internal/metrics"
12
)
13
14
func (s *Server) serveDocs(w http.ResponseWriter, r *http.Request) {
15
	resolver := func(name string) bool {
16
		_, ok := s.Resolver.ResolveModule(name)
17
		return ok
18
	}
19
	modName, version, subpath := ResolveModulePath(r.URL.Path, resolver)
20
21
	if modName == "" {
22
		http.NotFound(w, r)
23
		return
24
	}
25
26
	if !s.CanAccessModule(modName, r) {
27
		http.NotFound(w, r)
28
		return
29
	}
30
31
	mod, ok := s.Resolver.ResolveModule(modName)
32
	if !ok {
33
		http.NotFound(w, r)
34
		return
35
	}
36
37
	// Versions tab doesn't need a git checkout.
38
	if r.URL.Query().Get("tab") == "versions" {
39
		s.serveVersionsPage(w, r, modName, mod.VCS, mod.Repo)
40
		return
41
	}
42
43
	repoPath, err := s.Git.CloneOrFetch(modName, mod.Repo, s.authForModule(mod))
44
	if err != nil {
45
		log.Printf("docs: git error for %s: %v", modName, err)
46
		http.Error(w, "internal error", http.StatusInternalServerError)
47
		return
48
	}
49
50
	// Resolve version.
51
	if version == "" {
52
		tags, err := git.ListTags(repoPath)
53
		if err != nil || len(tags) == 0 {
54
			log.Printf("docs: no tags for %s: %v", modName, err)
55
			http.Error(w, "no versions available", http.StatusNotFound)
56
			return
57
		}
58
		version = tags[len(tags)-1] // latest
59
	}
60
61
	rv, err := git.ResolveVersion(repoPath, version)
62
	if err != nil {
63
		log.Printf("docs: resolve version %s@%s: %v", modName, version, err)
64
		http.NotFound(w, r)
65
		return
66
	}
67
68
	// Source view.
69
	if r.URL.Query().Get("view") == "source" {
70
		s.serveSourcePage(w, r, modName, mod.VCS, mod.Repo, repoPath, rv, subpath)
71
		return
72
	}
73
74
	// Documentation page.
75
	s.serveDocPage(w, r, modName, mod.VCS, mod.Repo, repoPath, rv, subpath)
76
}
77
78
func (s *Server) serveDocPage(w http.ResponseWriter, r *http.Request, modName, vcs, repo, repoPath string, rv *git.ResolvedVersion, subpath string) {
79
	metrics.DocRendersTotal.WithLabelValues("package").Inc()
80
81
	modulePath := s.Cfg.Host + "/" + modName
82
	importPath := modulePath
83
	if subpath != "" {
84
		importPath = modulePath + "/" + subpath
85
	}
86
87
	doc, err := godoc.ParsePackage(s.Git, repoPath, rv.GitRev, subpath, importPath)
88
	if err != nil {
89
		log.Printf("docs: parse %s@%s/%s: %v", modName, rv.Version, subpath, err)
90
		http.Error(w, "internal error", http.StatusInternalServerError)
91
		return
92
	}
93
94
	// Get versions for the selector.
95
	tags, _ := git.ListTags(repoPath)
96
97
	// Get sub-packages (only for root package).
98
	var subPkgs []godoc.SubPkgSummary
99
	if subpath == "" {
100
		subPkgs, _ = godoc.ListSubPackages(s.Git, repoPath, rv.GitRev, modulePath)
101
	}
102
103
	// Reverse versions so newest is first.
104
	versions := make([]string, len(tags))
105
	copy(versions, tags)
106
	slices.Reverse(versions)
107
108
	data := dochtml.BuildModulePageData(s.Cfg.Host, modName, rv.Version, subpath, vcs, repo, doc, versions, subPkgs)
109
110
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
111
	if err := s.DocRenderer.RenderModule(w, data); err != nil {
112
		log.Printf("docs: render %s: %v", modName, err)
113
	}
114
}
115
116
func (s *Server) serveSourcePage(w http.ResponseWriter, r *http.Request, modName, vcs, repo, repoPath string, rv *git.ResolvedVersion, subpath string) {
117
	metrics.DocRendersTotal.WithLabelValues("source").Inc()
118
119
	fileName := r.URL.Query().Get("file")
120
	if fileName == "" {
121
		http.NotFound(w, r)
122
		return
123
	}
124
125
	filePath := fileName
126
	if subpath != "" {
127
		filePath = subpath + "/" + fileName
128
	}
129
130
	src, err := git.ReadFile(repoPath, rv.GitRev, filePath)
131
	if err != nil {
132
		log.Printf("docs: read source %s@%s:%s: %v", modName, rv.Version, filePath, err)
133
		http.NotFound(w, r)
134
		return
135
	}
136
137
	highlighted := godoc.HighlightGo(src)
138
139
	// Get file list for the package.
140
	files, _ := git.ReadDir(repoPath, rv.GitRev, subpath)
141
	var fileNames []string
142
	for name := range files {
143
		fileNames = append(fileNames, name)
144
	}
145
	slices.Sort(fileNames)
146
147
	data := &dochtml.SourcePageData{
148
		Host:              s.Cfg.Host,
149
		ImportPrefix:      s.Cfg.Host + "/" + modName,
150
		ModuleName:        modName,
151
		VCS:               vcs,
152
		Repo:              repo,
153
		Version:           rv.Version,
154
		SubPath:           subpath,
155
		FileName:          fileName,
156
		HighlightedSource: dochtml.TrustedHTML(highlighted),
157
		SourceFiles:       fileNames,
158
	}
159
160
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
161
	if err := s.DocRenderer.RenderSource(w, data); err != nil {
162
		log.Printf("docs: render source %s: %v", fileName, err)
163
	}
164
}
165
166
func (s *Server) serveVersionsPage(w http.ResponseWriter, r *http.Request, modName, vcs, repo string) {
167
	metrics.DocRendersTotal.WithLabelValues("versions").Inc()
168
169
	mod, _ := s.Resolver.ResolveModule(modName)
170
	repoPath, err := s.Git.CloneOrFetch(modName, mod.Repo, s.authForModule(mod))
171
	if err != nil {
172
		log.Printf("docs: git error for %s: %v", modName, err)
173
		http.Error(w, "internal error", http.StatusInternalServerError)
174
		return
175
	}
176
177
	tags, _ := git.ListTags(repoPath)
178
179
	// Reverse so newest is first.
180
	versions := make([]string, len(tags))
181
	copy(versions, tags)
182
	slices.Reverse(versions)
183
184
	currentVersion := ""
185
	if len(versions) > 0 {
186
		currentVersion = versions[0]
187
	}
188
189
	data := &dochtml.VersionsPageData{
190
		Host:           s.Cfg.Host,
191
		ImportPrefix:   s.Cfg.Host + "/" + modName,
192
		ModuleName:     modName,
193
		VCS:            vcs,
194
		Repo:           repo,
195
		Versions:       versions,
196
		CurrentVersion: currentVersion,
197
	}
198
199
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
200
	if err := s.DocRenderer.RenderVersions(w, data); err != nil {
201
		log.Printf("docs: render versions %s: %v", modName, err)
202
	}
203
}
204

Source Files