blob: 5a1a421aee110fe584ba940be3b5d5dd600bfccd [file] [log] [blame]
// Copyright 2021 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"
"sort"
"strings"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/stdlib"
"golang.org/x/pkgsite/internal/version"
)
// Directories is the directory listing for all directories in the unit,
// which is listed in the directories section of the main page.
type Directories struct {
// External contains all of the non-internal directories for the unit.
External []*Directory
// Internal contains the top level internal directory for the unit, if any.
Internal *Directory
}
// Directory is either a nested module or subdirectory of a unit, organized in
// a two level tree structure. This content is used in the
// directories section of the unit page.
type Directory struct {
// Prefix is the prefix of the unit path for the subdirectories.
Prefix string
// Root is the package located at prefix, nil for a directory.
Root *DirectoryInfo
// Subdirectories contains subdirectories with prefix trimmed from their suffix.
Subdirectories []*DirectoryInfo
}
// DirectoryInfo contains information about a package or nested module,
// relative to the path of a given unit. This content is used in the
// Directories section of the unit page.
type DirectoryInfo struct {
Suffix string
URL string
Synopsis string
IsModule bool
}
// unitDirectories zips the subdirectories and nested modules together in a two
// level tree hierarchy.
func unitDirectories(directories []*DirectoryInfo) *Directories {
if len(directories) == 0 {
return nil
}
// Organize the subdirectories into a two level tree hierarchy. The first part of
// the unit path suffix for a subdirectory becomes the prefix under which matching
// subdirectories are grouped.
mappedDirs := make(map[string]*Directory)
for _, d := range directories {
prefix, _, _ := strings.Cut(d.Suffix, "/")
// Skip internal directories that are not in the top level internal
// directory. For example, foo/internal and foo/internal/bar should
// be skipped, but internal/foo should be included.
if prefix != "internal" && (strings.HasSuffix(d.Suffix, "/internal") ||
strings.Contains(d.Suffix, "/internal/")) {
continue
}
if _, ok := mappedDirs[prefix]; !ok {
mappedDirs[prefix] = &Directory{Prefix: prefix}
}
d.Suffix = strings.TrimPrefix(d.Suffix, prefix+"/")
if prefix == d.Suffix {
mappedDirs[prefix].Root = d
} else {
mappedDirs[prefix].Subdirectories = append(mappedDirs[prefix].Subdirectories, d)
}
}
section := &Directories{}
for prefix, dir := range mappedDirs {
if prefix == "internal" {
section.Internal = dir
} else {
section.External = append(section.External, dir)
}
}
sort.Slice(section.External, func(i, j int) bool {
return section.External[i].Prefix < section.External[j].Prefix
})
return section
}
func getNestedModules(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta, sds []*DirectoryInfo) ([]*DirectoryInfo, error) {
nestedModules, err := ds.GetNestedModules(ctx, um.ModulePath)
if err != nil {
return nil, err
}
// Build a map of existing suffixes in subdirectories to filter out nested modules
// which have the same suffix.
excludedSuffixes := make(map[string]bool)
for _, dir := range sds {
excludedSuffixes[dir.Suffix] = true
}
var mods []*DirectoryInfo
for _, m := range nestedModules {
if m.SeriesPath() == internal.SeriesPathForModule(um.ModulePath) {
continue
}
if !strings.HasPrefix(m.ModulePath, um.Path+"/") {
continue
}
suffix := internal.Suffix(m.SeriesPath(), um.Path)
if excludedSuffixes[suffix] {
continue
}
mods = append(mods, &DirectoryInfo{
URL: constructUnitURL(m.ModulePath, m.ModulePath, version.Latest),
Suffix: suffix,
IsModule: true,
})
}
return mods, nil
}
func getSubdirectories(um *internal.UnitMeta, pkgs []*internal.PackageMeta, requestedVersion string) []*DirectoryInfo {
var sdirs []*DirectoryInfo
for _, pm := range pkgs {
if um.Path == pm.Path {
continue
}
if um.Path == stdlib.ModulePath && strings.HasPrefix(pm.Path, "cmd/") {
// Omit "cmd" from the directory listing on
// pkg.go.dev/std, since go list std does not
// list them.
continue
}
sdirs = append(sdirs, &DirectoryInfo{
URL: constructUnitURL(pm.Path, um.ModulePath,
linkVersion(um.ModulePath, requestedVersion, um.Version)),
Suffix: internal.Suffix(pm.Path, um.Path),
Synopsis: pm.Synopsis,
})
}
sort.Slice(sdirs, func(i, j int) bool { return sdirs[i].Suffix < sdirs[j].Suffix })
return sdirs
}