docs.go

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

Source Files