| // 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 ( |
| "context" |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/log" |
| "golang.org/x/pkgsite/internal/postgres" |
| "golang.org/x/pkgsite/internal/stdlib" |
| ) |
| |
| // ImportsDetails contains information for a package's imports. |
| type ImportsDetails struct { |
| ModulePath string |
| |
| // ExternalImports is the collection of package imports that are not in |
| // the Go standard library and are not part of the same module |
| ExternalImports []string |
| |
| // InternalImports is an array of packages representing the package's |
| // imports that are part of the same module. |
| InternalImports []string |
| |
| // StdLib is an array of packages representing the package's imports |
| // that are in the Go standard library. |
| StdLib []string |
| } |
| |
| // fetchImportsDetails fetches imports for the package version specified by |
| // pkgPath, modulePath and version from the database and returns a ImportsDetails. |
| func fetchImportsDetails(ctx context.Context, ds internal.DataSource, pkgPath, modulePath, resolvedVersion string) (_ *ImportsDetails, err error) { |
| u, err := ds.GetUnit(ctx, &internal.UnitMeta{ |
| Path: pkgPath, |
| ModuleInfo: internal.ModuleInfo{ |
| ModulePath: modulePath, |
| Version: resolvedVersion, |
| }, |
| }, internal.WithImports, internal.BuildContext{}) |
| if err != nil { |
| return nil, err |
| } |
| |
| var externalImports, moduleImports, std []string |
| for _, p := range u.Imports { |
| if stdlib.Contains(p) { |
| std = append(std, p) |
| } else if strings.HasPrefix(p+"/", modulePath+"/") { |
| moduleImports = append(moduleImports, p) |
| } else { |
| externalImports = append(externalImports, p) |
| } |
| } |
| |
| return &ImportsDetails{ |
| ModulePath: modulePath, |
| ExternalImports: externalImports, |
| InternalImports: moduleImports, |
| StdLib: std, |
| }, nil |
| } |
| |
| // ImportedByDetails contains information for the collection of packages that |
| // import a given package. |
| type ImportedByDetails struct { |
| // ModulePath is the module path for the package referenced on this page. |
| ModulePath string |
| |
| // ImportedBy is the collection of packages that import the |
| // given package and are not part of the same module. |
| // They are organized into a tree of sections by prefix. |
| ImportedBy []*Section |
| |
| // NumImportedByDisplay is the display text at the top of the imported by |
| // tab section, which shows the imported by count and package limit. |
| NumImportedByDisplay string |
| |
| // Total is the total number of importers. |
| Total int |
| } |
| |
| var ( |
| // importedByLimit is the maximum number of importers displayed on the imported |
| // by page. |
| // Variable for testing. |
| importedByLimit = 20001 |
| ) |
| |
| // fetchImportedByDetails fetches importers for the package version specified by |
| // path and version from the database and returns a ImportedByDetails. |
| func fetchImportedByDetails(ctx context.Context, ds internal.DataSource, pkgPath, modulePath string) (*ImportedByDetails, error) { |
| db, ok := ds.(*postgres.DB) |
| if !ok { |
| // The proxydatasource does not support the imported by page. |
| return nil, proxydatasourceNotSupportedErr() |
| } |
| |
| importedBy, err := db.GetImportedBy(ctx, pkgPath, modulePath, importedByLimit) |
| if err != nil { |
| return nil, err |
| } |
| numImportedBy := len(importedBy) |
| numImportedBySearch, err := db.GetImportedByCount(ctx, pkgPath, modulePath) |
| if err != nil { |
| return nil, err |
| } |
| if numImportedBySearch > numImportedBy { |
| // numImportedBySearch should never be greater than numImportedBy. |
| // |
| // If that happens, log an error so that we can debug, but continue |
| // with generating the page fo the user. |
| log.Errorf(ctx, "search_documents.num_imported_by > numImportedBy from imports unique, which shouldn't happen: %d", numImportedBySearch) |
| } |
| |
| if numImportedBy >= importedByLimit { |
| importedBy = importedBy[:importedByLimit-1] |
| } |
| sections := Sections(importedBy, nextPrefixAccount) |
| |
| // Display the number of importers, taking into account the number we |
| // actually retrieved, the limit on that number, and the imported-by count |
| // in the search_documents table. |
| var ( |
| display string |
| pkgword = "package" |
| ) |
| if numImportedBy > 1 { |
| pkgword = "packages" |
| } |
| switch { |
| // If there are more importers than the limit, and the search number is |
| // greater, use the search number and indicate that we're displaying fewer. |
| case numImportedBy >= importedByLimit && numImportedBySearch > numImportedBy: |
| display = fmt.Sprintf("%d (displaying %d %s)", numImportedBySearch, importedByLimit-1, pkgword) |
| // If we've exceeded the limit but the search number is smaller, we don't |
| // know the true number, so say so. |
| case numImportedBy >= importedByLimit: |
| display = fmt.Sprintf("%d (displaying more than %d %s, including internal and invalid packages)", numImportedBySearch, importedByLimit-1, pkgword) |
| // If we haven't exceeded the limit and we have more than the search number, |
| // then display both numbers so users coming from the search page won't see |
| // a mismatch. |
| case numImportedBy > numImportedBySearch: |
| display = fmt.Sprintf("%d (displaying %d %s, including internal and invalid packages)", numImportedBySearch, numImportedBy, pkgword) |
| // Otherwise, we have all the packages, and the search number is either |
| // wrong (perhaps it hasn't been recomputed yet) or it is the same as the |
| // retrieved number. In that case, just display the retrieved number. |
| default: |
| display = strconv.Itoa(numImportedBy) |
| } |
| return &ImportedByDetails{ |
| ModulePath: modulePath, |
| ImportedBy: sections, |
| NumImportedByDisplay: display, |
| Total: numImportedBy, |
| }, nil |
| } |