blob: 08e014a632491a4f5ac07048c974d342d47d072d [file] [log] [blame]
// Copyright 2019 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 (
"fmt"
"net/http"
"net/url"
"strings"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/licenses"
"golang.org/x/pkgsite/internal/postgres"
)
// TabSettings defines tab-specific metadata.
type TabSettings struct {
// Name is the tab name used in the URL.
Name string
// DisplayName is the formatted tab name.
DisplayName string
// AlwaysShowDetails defines whether the tab content can be shown even if the
// package is not determined to be redistributable.
AlwaysShowDetails bool
// TemplateName is the name of the template used to render the
// corresponding tab, as defined in Server.templates.
TemplateName string
// Disabled indicates whether a tab should be displayed as disabled.
Disabled bool
}
var (
packageTabSettings = []TabSettings{
{
Name: "doc",
DisplayName: "Doc",
TemplateName: "pkg_doc.tmpl",
},
{
Name: "overview",
AlwaysShowDetails: true,
DisplayName: "Overview",
TemplateName: "overview.tmpl",
},
{
Name: "subdirectories",
AlwaysShowDetails: true,
DisplayName: "Subdirectories",
TemplateName: "subdirectories.tmpl",
},
{
Name: "versions",
AlwaysShowDetails: true,
DisplayName: "Versions",
TemplateName: "versions.tmpl",
},
{
Name: "imports",
DisplayName: "Imports",
AlwaysShowDetails: true,
TemplateName: "pkg_imports.tmpl",
},
{
Name: "importedby",
DisplayName: "Imported By",
AlwaysShowDetails: true,
TemplateName: "pkg_importedby.tmpl",
},
{
Name: "licenses",
DisplayName: "Licenses",
TemplateName: "licenses.tmpl",
},
}
packageTabLookup = make(map[string]TabSettings)
directoryTabSettings = make([]TabSettings, len(packageTabSettings))
directoryTabLookup = make(map[string]TabSettings)
moduleTabSettings = []TabSettings{
{
Name: "overview",
AlwaysShowDetails: true,
DisplayName: "Overview",
TemplateName: "overview.tmpl",
},
{
Name: "packages",
AlwaysShowDetails: true,
DisplayName: "Packages",
TemplateName: "subdirectories.tmpl",
},
{
Name: "versions",
AlwaysShowDetails: true,
DisplayName: "Versions",
TemplateName: "versions.tmpl",
},
{
Name: "licenses",
DisplayName: "Licenses",
TemplateName: "licenses.tmpl",
},
}
moduleTabLookup = make(map[string]TabSettings)
)
// validDirectoryTabs indicates if a tab is enabled in the directory view.
var validDirectoryTabs = map[string]bool{
"licenses": true,
"overview": true,
"subdirectories": true,
}
func init() {
for i, ts := range packageTabSettings {
// The directory view uses the same design as the packages view
// for visual consistency, but some tabs don't make sense, so
// we disable them.
if !validDirectoryTabs[ts.Name] {
ts.Disabled = true
}
directoryTabSettings[i] = ts
}
for _, d := range packageTabSettings {
packageTabLookup[d.Name] = d
}
for _, d := range directoryTabSettings {
directoryTabLookup[d.Name] = d
}
for _, d := range moduleTabSettings {
moduleTabLookup[d.Name] = d
}
}
// fetchDetailsForPackage returns tab details by delegating to the correct detail
// handler.
func fetchDetailsForPackage(r *http.Request, tab string, ds internal.DataSource, vdir *internal.VersionedDirectory) (interface{}, error) {
ctx := r.Context()
switch tab {
case "doc":
return fetchDocumentationDetails(vdir.Package.Documentation), nil
case "overview":
return fetchPackageOverviewDetails(ctx, vdir, urlIsVersioned(r.URL))
case "subdirectories":
return fetchDirectoryDetails(ctx, ds, vdir, false)
case "versions":
return fetchVersionsDetails(ctx, ds, vdir.Path, vdir.V1Path, vdir.ModulePath)
case "imports":
return fetchImportsDetails(ctx, ds, vdir.Path, vdir.ModulePath, vdir.Version)
case "importedby":
return fetchImportedByDetails(ctx, ds, vdir.Path, vdir.ModulePath)
case "licenses":
return fetchLicensesDetails(ctx, ds, vdir.Path, vdir.ModulePath, vdir.Version)
}
return nil, fmt.Errorf("BUG: unable to fetch details: unknown tab %q", tab)
}
// fetchDetailsForModule returns tab details by delegating to the correct detail
// handler.
func fetchDetailsForModule(r *http.Request, tab string, ds internal.DataSource, mi *internal.ModuleInfo, licenses []*licenses.License, readme *internal.Readme) (interface{}, error) {
ctx := r.Context()
switch tab {
case "packages":
if isActiveUseDirectories(ctx) {
vdir := &internal.VersionedDirectory{
ModuleInfo: *mi,
Directory: internal.Directory{
DirectoryMeta: internal.DirectoryMeta{
Path: mi.ModulePath,
V1Path: mi.SeriesPath(),
IsRedistributable: mi.IsRedistributable,
Licenses: licensesToMetadatas(licenses),
},
Readme: readme,
},
}
return fetchDirectoryDetails(ctx, ds, vdir, true)
}
return legacyFetchDirectoryDetails(ctx, ds, mi.ModulePath, mi, licensesToMetadatas(licenses), true)
case "licenses":
return &LicensesDetails{Licenses: transformLicenses(mi.ModulePath, mi.Version, licenses)}, nil
case "versions":
return fetchModuleVersionsDetails(ctx, ds, mi.ModulePath)
case "overview":
return constructOverviewDetails(ctx, mi, readme, mi.IsRedistributable, urlIsVersioned(r.URL))
}
return nil, fmt.Errorf("BUG: unable to fetch details: unknown tab %q", tab)
}
// fetchDetailsForDirectory returns tab details by delegating to the correct
// detail handler.
func fetchDetailsForDirectory(r *http.Request, tab string, ds internal.DataSource, vdir *internal.VersionedDirectory) (interface{}, error) {
ctx := r.Context()
switch tab {
case "overview":
return constructOverviewDetails(ctx, &vdir.ModuleInfo, vdir.Readme, vdir.IsRedistributable, urlIsVersioned(r.URL))
case "subdirectories":
return fetchDirectoryDetails(ctx, ds, vdir, false)
case "licenses":
return fetchLicensesDetails(ctx, ds, vdir.Path, vdir.ModulePath, vdir.Version)
}
return nil, fmt.Errorf("BUG: unable to fetch details: unknown tab %q", tab)
}
// legacyFetchDetailsForDirectory returns tab details by delegating to the correct
// detail handler.
func legacyFetchDetailsForDirectory(r *http.Request, tab string, dir *internal.LegacyDirectory, licenses []*licenses.License) (interface{}, error) {
switch tab {
case "overview":
readme := &internal.Readme{Filepath: dir.LegacyReadmeFilePath, Contents: dir.LegacyReadmeContents}
return constructOverviewDetails(r.Context(), &dir.ModuleInfo, readme, dir.LegacyModuleInfo.IsRedistributable, urlIsVersioned(r.URL))
case "subdirectories":
// Ideally we would just use fetchDirectoryDetails here so that it
// follows the same code path as fetchDetailsForModule and
// fetchDetailsForPackage. However, since we already have the directory
// and licenses info, it doesn't make sense to call
// postgres.GetDirectory again.
return legacyCreateDirectory(dir, licensesToMetadatas(licenses), false)
case "licenses":
return &LicensesDetails{Licenses: transformLicenses(dir.ModulePath, dir.Version, licenses)}, nil
}
return nil, fmt.Errorf("BUG: unable to fetch details: unknown tab %q", tab)
}
// legacyFetchDetailsForPackage returns tab details by delegating to the correct detail
// handler.
func legacyFetchDetailsForPackage(r *http.Request, tab string, ds internal.DataSource, pkg *internal.LegacyVersionedPackage) (interface{}, error) {
ctx := r.Context()
switch tab {
case "doc":
return legacyFetchDocumentationDetails(pkg), nil
case "versions":
return legacyFetchPackageVersionsDetails(ctx, ds, pkg.Path, pkg.V1Path, pkg.ModulePath)
case "subdirectories":
return legacyFetchDirectoryDetails(ctx, ds, pkg.Path, &pkg.ModuleInfo, pkg.Licenses, false)
case "imports":
return fetchImportsDetails(ctx, ds, pkg.Path, pkg.ModulePath, pkg.Version)
case "importedby":
db, ok := ds.(*postgres.DB)
if !ok {
// The proxydatasource does not support the imported by page.
return nil, proxydatasourceNotSupportedErr()
}
return fetchImportedByDetails(ctx, db, pkg.Path, pkg.ModulePath)
case "licenses":
return legacyFetchPackageLicensesDetails(ctx, ds, pkg.Path, pkg.ModulePath, pkg.Version)
case "overview":
return legacyFetchPackageOverviewDetails(ctx, pkg, urlIsVersioned(r.URL))
}
return nil, fmt.Errorf("BUG: unable to fetch details: unknown tab %q", tab)
}
func urlIsVersioned(url *url.URL) bool {
return strings.ContainsRune(url.Path, '@')
}