/
main.go
280 lines (243 loc) · 9.25 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package frontend
import (
"context"
"errors"
"github.com/google/safehtml"
"github.com/google/safehtml/template"
"golang.org/x/mod/semver"
"github.com/khulnasoft-lab/godep/internal"
"github.com/khulnasoft-lab/godep/internal/derrors"
"github.com/khulnasoft-lab/godep/internal/frontend/serrors"
"github.com/khulnasoft-lab/godep/internal/godoc"
"github.com/khulnasoft-lab/godep/internal/godoc/dochtml"
"github.com/khulnasoft-lab/godep/internal/log"
"github.com/khulnasoft-lab/godep/internal/middleware/stats"
"github.com/khulnasoft-lab/godep/internal/version"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
// MainDetails contains data needed to render the unit template.
type MainDetails struct {
// Directories are packages and nested modules relative to the path for the
// unit.
Directories []*Directory
// Licenses contains license metadata used in the header.
Licenses []LicenseMetadata
// NumImports is the number of imports for the package.
NumImports string
// CommitTime is time that this version was published, or the time that
// has elapsed since this version was committed if it was done so recently.
CommitTime string
// Readme is the rendered readme HTML.
Readme safehtml.HTML
// ReadmeOutline is a collection of headings from the readme file
// used to render the readme outline in the sidebar.
ReadmeOutline []*Heading
// ReadmeLinks are from the "Links" section of this unit's readme file, and
// are displayed on the right sidebar.
ReadmeLinks []link
// DocLinks are from the "Links" section of the Go package documentation,
// and are displayed on the right sidebar.
DocLinks []link
// ModuleReadmeLinks are from the "Links" section of this unit's module, if
// the unit is not itself a module. They are displayed on the right sidebar.
// See https://golang.org/issue/42968.
ModuleReadmeLinks []link
// ImportedByCount is the number of packages that import this path.
// When the count is > limit it will read as 'limit+'. This field
// is not supported when using a datasource proxy.
ImportedByCount string
DocBody safehtml.HTML
DocOutline safehtml.HTML
MobileOutline safehtml.HTML
IsPackage bool
// DocSynopsis is used as the content for the <meta name="Description">
// tag on the main unit page.
DocSynopsis string
// GOOS and GOARCH are the build context for the doc.
GOOS, GOARCH string
// BuildContexts holds the values for build contexts available for the doc.
BuildContexts []internal.BuildContext
// SourceFiles contains .go files for the package.
SourceFiles []*File
// RepositoryURL is the URL to the repository containing the package.
RepositoryURL string
// SourceURL is the URL to the source of the package.
SourceURL string
// ExpandReadme is holds the expandable readme state.
ExpandReadme bool
// ModFileURL is an URL to the mod file.
ModFileURL string
// IsTaggedVersion is true if the version is not a psuedorelease.
IsTaggedVersion bool
// IsStableVersion is true if the major version is v1 or greater.
IsStableVersion bool
// IsRedistributable is whether the unit is redistributable.
IsRedistributable bool
}
// File is a source file for a package.
type File struct {
Name string
URL string
}
func fetchMainDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta,
requestedVersion string, expandReadme bool, bc internal.BuildContext) (_ *MainDetails, err error) {
defer stats.Elapsed(ctx, "fetchMainDetails")()
unit, err := ds.GetUnit(ctx, um, internal.WithMain, bc)
if err != nil {
return nil, err
}
subdirectories := getSubdirectories(um, unit.Subdirectories, requestedVersion)
if err != nil {
return nil, err
}
nestedModules, err := getNestedModules(ctx, ds, um, subdirectories)
if err != nil {
return nil, err
}
readme, err := readmeContent(ctx, unit)
if err != nil {
return nil, err
}
var (
docParts = &dochtml.Parts{}
docLinks, modLinks []link
files []*File
synopsis string
goos, goarch string
buildContexts []internal.BuildContext
)
unit.Documentation = cleanDocumentation(unit.Documentation)
// There should be at most one Documentation.
var doc *internal.Documentation
if len(unit.Documentation) > 0 {
doc = unit.Documentation[0]
}
if doc != nil {
synopsis = doc.Synopsis
goos = doc.GOOS
goarch = doc.GOARCH
buildContexts = unit.BuildContexts
end := stats.Elapsed(ctx, "DecodePackage")
docPkg, err := godoc.DecodePackage(doc.Source)
end()
if err != nil {
if errors.Is(err, godoc.ErrInvalidEncodingType) {
// Instead of returning a 500, return a 404 so the user can
// reprocess the documentation.
log.Errorf(ctx, "fetchMainDetails(%q, %q, %q): %v", um.Path, um.ModulePath, um.Version, err)
return nil, serrors.ErrUnitNotFoundWithoutFetch
}
return nil, err
}
docParts, err = getHTML(ctx, unit, docPkg, unit.SymbolHistory, bc)
// If err is ErrTooLarge, then docBody will have an appropriate message.
if err != nil && !errors.Is(err, dochtml.ErrTooLarge) {
return nil, err
}
for _, l := range docParts.Links {
docLinks = append(docLinks, link{Href: l.Href, Body: l.Text})
}
end = stats.Elapsed(ctx, "sourceFiles")
files = sourceFiles(unit, docPkg)
end()
}
// If the unit is not a module, fetch the module readme to extract its
// links.
// In the unlikely event that the module is redistributable but the unit is
// not, we will not show the module links on the unit page.
if unit.Path != unit.ModulePath && unit.IsRedistributable {
modReadme, err := ds.GetModuleReadme(ctx, unit.ModulePath, unit.Version)
if err != nil && !errors.Is(err, derrors.NotFound) {
return nil, err
}
if err == nil {
rm, err := processReadme(ctx, modReadme, um.SourceInfo)
if err != nil {
return nil, err
}
modLinks = rm.Links
}
}
versionType, err := version.ParseType(um.Version)
if err != nil {
return nil, err
}
isTaggedVersion := versionType != version.TypePseudo
isStableVersion := semver.Major(um.Version) != "v0" && versionType == version.TypeRelease
pr := message.NewPrinter(language.English)
return &MainDetails{
ExpandReadme: expandReadme,
Directories: unitDirectories(append(subdirectories, nestedModules...)),
Licenses: transformLicenseMetadata(unit.Licenses),
CommitTime: absoluteTime(um.CommitTime),
Readme: readme.HTML,
ReadmeOutline: readme.Outline,
ReadmeLinks: readme.Links,
DocLinks: docLinks,
ModuleReadmeLinks: modLinks,
DocOutline: docParts.Outline,
DocBody: docParts.Body,
DocSynopsis: synopsis,
GOOS: goos,
GOARCH: goarch,
BuildContexts: buildContexts,
SourceFiles: files,
RepositoryURL: um.SourceInfo.RepoURL(),
SourceURL: um.SourceInfo.DirectoryURL(internal.Suffix(um.Path, um.ModulePath)),
MobileOutline: docParts.MobileOutline,
NumImports: pr.Sprint(unit.NumImports),
ImportedByCount: pr.Sprint(unit.NumImportedBy),
IsPackage: unit.IsPackage(),
ModFileURL: um.SourceInfo.ModuleURL() + "/go.mod",
IsTaggedVersion: isTaggedVersion,
IsStableVersion: isStableVersion,
IsRedistributable: unit.IsRedistributable,
}, nil
}
func cleanDocumentation(docs []*internal.Documentation) []*internal.Documentation {
// If there is more than one row but the first is all/all, ignore the others.
// Should never happen; temporary fix until the DB is cleaned up.
if len(docs) > 1 && docs[0].BuildContext() == internal.BuildContextAll {
return docs[:1]
}
// If there is only one Documentation and it is linux/amd64, then
// make it all/all.
//
// This is temporary, until the next reprocessing. It assumes a unit
// with a single linux/amd64 actually has only one build context,
// and hasn't been reprocessed to have all/all.
//
// The only effect of this is to prevent "GOOS=linux, GOARCH=amd64" from
// appearing at the bottom of the doc. That is wrong in the (rather
// unlikely) case that the package truly only has doc for linux/amd64,
// but the bug is only cosmetic.
if len(docs) == 1 && docs[0].GOOS == "linux" && docs[0].GOARCH == "amd64" {
docs[0].GOOS = internal.All
docs[0].GOARCH = internal.All
}
return docs
}
// readmeContent renders the readme to html and collects the headings
// into an outline.
func readmeContent(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
defer derrors.Wrap(&err, "readmeContent(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
defer stats.Elapsed(ctx, "readmeContent")()
if !u.IsRedistributable {
return &Readme{}, nil
}
return ProcessReadme(ctx, u)
}
const missingDocReplacement = `<p>Documentation is missing.</p>`
func getHTML(ctx context.Context, u *internal.Unit, docPkg *godoc.Package,
nameToVersion map[string]string, bc internal.BuildContext) (_ *dochtml.Parts, err error) {
defer derrors.Wrap(&err, "getHTML(%s)", u.Path)
if len(u.Documentation[0].Source) > 0 {
return renderDocParts(ctx, u, docPkg, nameToVersion, bc)
}
log.Errorf(ctx, "unit %s (%s@%s) missing documentation source", u.Path, u.ModulePath, u.Version)
return &dochtml.Parts{Body: template.MustParseAndExecuteToHTML(missingDocReplacement)}, nil
}