Skip to content

Commit 082bca2

Browse files
sudo-sturbiajulieqiu
authored andcommitted
internal/localdatasource: implement a local datasource
Create localdatasource package which implements an in-memory internal.DataSource to display documentation locally. Add tests for localdatasource.DataSource. Updates golang/go#40159 Change-Id: Ie18dd68e6108cfa361c4db31030679fc55661d35 Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/260778 Trust: Jonathan Amsterdam <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Julie Qiu <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent 0077d00 commit 082bca2

File tree

2 files changed

+423
-0
lines changed

2 files changed

+423
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package localdatasource implements an in-memory internal.DataSource used to load
6+
// and display documentation for local modules that are not available via a proxy.
7+
// Similar to proxydatasource, search and other tabs are not supported in this mode.
8+
package localdatasource
9+
10+
import (
11+
"context"
12+
"fmt"
13+
"os"
14+
"path"
15+
"path/filepath"
16+
"strings"
17+
"sync"
18+
19+
"golang.org/x/pkgsite/internal"
20+
"golang.org/x/pkgsite/internal/derrors"
21+
"golang.org/x/pkgsite/internal/fetch"
22+
"golang.org/x/pkgsite/internal/source"
23+
)
24+
25+
// DataSource implements an in-memory internal.DataSource used to display documentation
26+
// locally. DataSource is not backed by a database or a proxy instance.
27+
type DataSource struct {
28+
sourceClient *source.Client
29+
30+
mu sync.Mutex
31+
loadedModules map[string]*internal.Module
32+
}
33+
34+
// New creates and returns a new local datasource that bypasses license
35+
// checks by default.
36+
func New() *DataSource {
37+
return &DataSource{
38+
loadedModules: make(map[string]*internal.Module),
39+
}
40+
}
41+
42+
// Load loads a module from the given local path. Loading is required before
43+
// being able to display the module.
44+
func (ds *DataSource) Load(ctx context.Context, localPath string) (err error) {
45+
defer derrors.Wrap(&err, "Load(%q)", localPath)
46+
return ds.fetch(ctx, "", localPath)
47+
}
48+
49+
// LoadFromGOPATH loads a module from GOPATH using the given import path. The full
50+
// path of the module should be GOPATH/src/importPath. If several GOPATHs exist, the
51+
// module is loaded from the first one that contains the import path. Loading is required
52+
// before being able to display the module.
53+
func (ds *DataSource) LoadFromGOPATH(ctx context.Context, importPath string) (err error) {
54+
defer derrors.Wrap(&err, "LoadFromGOPATH(%q)", importPath)
55+
56+
path := getFullPath(importPath)
57+
if path == "" {
58+
return fmt.Errorf("path %s doesn't exist: %w", importPath, derrors.NotFound)
59+
}
60+
61+
return ds.fetch(ctx, importPath, path)
62+
}
63+
64+
// fetch fetches a module using FetchLocalModule and adds it to the datasource.
65+
// If the fetching fails, an error is returned.
66+
func (ds *DataSource) fetch(ctx context.Context, modulePath, localPath string) error {
67+
fr := fetch.FetchLocalModule(ctx, modulePath, localPath, ds.sourceClient)
68+
if fr.Error != nil {
69+
return fr.Error
70+
}
71+
72+
fr.Module.IsRedistributable = true
73+
for _, unit := range fr.Module.Units {
74+
unit.IsRedistributable = true
75+
}
76+
for _, pkg := range fr.Module.LegacyPackages {
77+
pkg.IsRedistributable = true
78+
}
79+
80+
ds.mu.Lock()
81+
defer ds.mu.Unlock()
82+
ds.loadedModules[fr.ModulePath] = fr.Module
83+
return nil
84+
}
85+
86+
// getFullPath takes an import path, tests it relative to each GOPATH, and returns
87+
// a full path to the module. If the given import path doesn't exist in any GOPATH,
88+
// an empty string is returned.
89+
func getFullPath(modulePath string) string {
90+
gopaths := filepath.SplitList(os.Getenv("GOPATH"))
91+
for _, gopath := range gopaths {
92+
path := filepath.Join(gopath, "src", modulePath)
93+
info, err := os.Stat(path)
94+
if err == nil && info.IsDir() {
95+
return path
96+
}
97+
}
98+
return ""
99+
}
100+
101+
// GetUnit returns information about a unit. Both the module path and package
102+
// path must both be known.
103+
func (ds *DataSource) GetUnit(ctx context.Context, pathInfo *internal.UnitMeta, fields internal.FieldSet) (_ *internal.Unit, err error) {
104+
defer derrors.Wrap(&err, "GetUnit(%q, %q)", pathInfo.Path, pathInfo.ModulePath)
105+
106+
modulepath := pathInfo.ModulePath
107+
path := pathInfo.Path
108+
109+
ds.mu.Lock()
110+
defer ds.mu.Unlock()
111+
if ds.loadedModules[modulepath] == nil {
112+
return nil, fmt.Errorf("%s not loaded: %w", modulepath, derrors.NotFound)
113+
}
114+
115+
module := ds.loadedModules[modulepath]
116+
for _, unit := range module.Units {
117+
if unit.Path == path {
118+
return unit, nil
119+
}
120+
}
121+
122+
return nil, fmt.Errorf("%s not found: %w", path, derrors.NotFound)
123+
}
124+
125+
// GetUnitMeta returns information about a path.
126+
func (ds *DataSource) GetUnitMeta(ctx context.Context, path, requestedModulePath, requestedVersion string) (_ *internal.UnitMeta, err error) {
127+
defer derrors.Wrap(&err, "GetUnitMeta(%q, %q, %q)", path, requestedModulePath, requestedVersion)
128+
129+
if requestedModulePath == internal.UnknownModulePath {
130+
requestedModulePath, err = ds.findModule(path)
131+
if err != nil {
132+
return nil, err
133+
}
134+
}
135+
136+
ds.mu.Lock()
137+
module := ds.loadedModules[requestedModulePath]
138+
ds.mu.Unlock()
139+
140+
um := &internal.UnitMeta{
141+
Path: path,
142+
ModulePath: requestedModulePath,
143+
Version: fetch.LocalVersion,
144+
CommitTime: fetch.LocalCommitTime,
145+
}
146+
147+
for _, u := range module.Units {
148+
if u.Path == path {
149+
um.Name = u.Name
150+
um.IsRedistributable = u.IsRedistributable
151+
}
152+
}
153+
154+
return um, nil
155+
}
156+
157+
// findModule finds the longest module path in loadedModules containing the given
158+
// package path. It iteratively checks parent directories to find an import path.
159+
// Returns an error if no module is found.
160+
func (ds *DataSource) findModule(pkgPath string) (_ string, err error) {
161+
defer derrors.Wrap(&err, "findModule(%q)", pkgPath)
162+
163+
pkgPath = strings.TrimLeft(pkgPath, "/")
164+
165+
ds.mu.Lock()
166+
defer ds.mu.Unlock()
167+
for modulePath := pkgPath; modulePath != "" && modulePath != "."; modulePath = path.Dir(modulePath) {
168+
if ds.loadedModules[modulePath] != nil {
169+
return modulePath, nil
170+
}
171+
}
172+
173+
return "", fmt.Errorf("%s not loaded: %w", pkgPath, derrors.NotFound)
174+
}
175+
176+
// GetLatestMajorVersion returns the latest major version of a series path.
177+
// When fetching local modules, version is not accounted for, so an empty
178+
// string is returned.
179+
func (ds *DataSource) GetLatestMajorVersion(ctx context.Context, seriesPath string) (string, error) {
180+
return "", nil
181+
}
182+
183+
// GetNestedModules is not implemented.
184+
func (ds *DataSource) GetNestedModules(ctx context.Context, modulePath string) ([]*internal.ModuleInfo, error) {
185+
return nil, nil
186+
}

0 commit comments

Comments
 (0)