whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
/*
|
||||
Godoc extracts and generates documentation for Go programs.
|
||||
|
||||
It runs as a web server and presents the documentation as a
|
||||
web page.
|
||||
|
||||
godoc -http=:6060
|
||||
|
||||
Usage:
|
||||
|
||||
godoc [flag]
|
||||
|
||||
The flags are:
|
||||
|
||||
-v
|
||||
verbose mode
|
||||
-timestamps=true
|
||||
show timestamps with directory listings
|
||||
-index
|
||||
enable identifier and full text search index
|
||||
(no search box is shown if -index is not set)
|
||||
-index_files=""
|
||||
glob pattern specifying index files; if not empty,
|
||||
the index is read from these files in sorted order
|
||||
-index_throttle=0.75
|
||||
index throttle value; a value of 0 means no time is allocated
|
||||
to the indexer (the indexer will never finish), a value of 1.0
|
||||
means that index creation is running at full throttle (other
|
||||
goroutines may get no time while the index is built)
|
||||
-index_interval=0
|
||||
interval of indexing; a value of 0 sets it to 5 minutes, a
|
||||
negative value indexes only once at startup
|
||||
-play=false
|
||||
enable playground
|
||||
-links=true
|
||||
link identifiers to their declarations
|
||||
-write_index=false
|
||||
write index to a file; the file name must be specified with
|
||||
-index_files
|
||||
-maxresults=10000
|
||||
maximum number of full text search results shown
|
||||
(no full text index is built if maxresults <= 0)
|
||||
-notes="BUG"
|
||||
regular expression matching note markers to show
|
||||
(e.g., "BUG|TODO", ".*")
|
||||
-goroot=$GOROOT
|
||||
Go root directory
|
||||
-http=addr
|
||||
HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
|
||||
-templates=""
|
||||
directory containing alternate template files; if set,
|
||||
the directory may provide alternative template files
|
||||
for the files in $GOROOT/lib/godoc
|
||||
-url=path
|
||||
print to standard output the data that would be served by
|
||||
an HTTP request for path
|
||||
-zip=""
|
||||
zip file providing the file system to serve; disabled if empty
|
||||
|
||||
By default, godoc looks at the packages it finds via $GOROOT and $GOPATH (if set).
|
||||
This behavior can be altered by providing an alternative $GOROOT with the -goroot
|
||||
flag.
|
||||
|
||||
When the -index flag is set, a search index is maintained.
|
||||
The index is created at startup.
|
||||
|
||||
The index contains both identifier and full text search information (searchable
|
||||
via regular expressions). The maximum number of full text search results shown
|
||||
can be set with the -maxresults flag; if set to 0, no full text results are
|
||||
shown, and only an identifier index but no full text search index is created.
|
||||
|
||||
By default, godoc uses the system's GOOS/GOARCH. You can provide the URL parameters
|
||||
"GOOS" and "GOARCH" to set the output on the web page for the target system.
|
||||
|
||||
The presentation mode of web pages served by godoc can be controlled with the
|
||||
"m" URL parameter; it accepts a comma-separated list of flag names as value:
|
||||
|
||||
all show documentation for all declarations, not just the exported ones
|
||||
methods show all embedded methods, not just those of unexported anonymous fields
|
||||
src show the original source code rather than the extracted documentation
|
||||
flat present flat (not indented) directory listings using full paths
|
||||
|
||||
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation
|
||||
for all (not just the exported) declarations of package big.
|
||||
|
||||
By default, godoc serves files from the file system of the underlying OS.
|
||||
Instead, a .zip file may be provided via the -zip flag, which contains
|
||||
the file system to serve. The file paths stored in the .zip file must use
|
||||
slash ('/') as path separator; and they must be unrooted. $GOROOT (or -goroot)
|
||||
must be set to the .zip file directory path containing the Go root directory.
|
||||
For instance, for a .zip file created by the command:
|
||||
|
||||
zip -r go.zip $HOME/go
|
||||
|
||||
one may run godoc as follows:
|
||||
|
||||
godoc -http=:6060 -zip=go.zip -goroot=$HOME/go
|
||||
|
||||
Godoc documentation is converted to HTML or to text using the go/doc package;
|
||||
see https://golang.org/pkg/go/doc/#ToHTML for the exact rules.
|
||||
Godoc also shows example code that is runnable by the testing package;
|
||||
see https://golang.org/pkg/testing/#hdr-Examples for the conventions.
|
||||
See "Godoc: documenting Go code" for how to write good comments for godoc:
|
||||
https://golang.org/doc/articles/godoc_documenting_go_code.html
|
||||
|
||||
Deprecated: godoc cannot select what version of a package is displayed.
|
||||
Instead, use golang.org/x/pkgsite/cmd/pkgsite.
|
||||
*/
|
||||
package main // import "golang.org/x/tools/cmd/godoc"
|
||||
@@ -0,0 +1,464 @@
|
||||
// Copyright 2013 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 main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if os.Getenv("GODOC_TEST_IS_GODOC") != "" {
|
||||
main()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Inform subprocesses that they should run the cmd/godoc main instead of
|
||||
// running tests. It's a close approximation to building and running the real
|
||||
// command, and much less complicated and expensive to build and clean up.
|
||||
os.Setenv("GODOC_TEST_IS_GODOC", "1")
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
var exe struct {
|
||||
path string
|
||||
err error
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func godocPath(t *testing.T) string {
|
||||
if !testenv.HasExec() {
|
||||
t.Skipf("skipping test: exec not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
exe.once.Do(func() {
|
||||
exe.path, exe.err = os.Executable()
|
||||
})
|
||||
if exe.err != nil {
|
||||
t.Fatal(exe.err)
|
||||
}
|
||||
return exe.path
|
||||
}
|
||||
|
||||
func serverAddress(t *testing.T) string {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
ln, err = net.Listen("tcp6", "[::1]:0")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
return ln.Addr().String()
|
||||
}
|
||||
|
||||
func waitForServerReady(t *testing.T, ctx context.Context, cmd *exec.Cmd, addr string) {
|
||||
waitForServer(t, ctx,
|
||||
fmt.Sprintf("http://%v/", addr),
|
||||
"Go Documentation Server",
|
||||
false)
|
||||
}
|
||||
|
||||
func waitForSearchReady(t *testing.T, ctx context.Context, cmd *exec.Cmd, addr string) {
|
||||
waitForServer(t, ctx,
|
||||
fmt.Sprintf("http://%v/search?q=FALLTHROUGH", addr),
|
||||
"The list of tokens.",
|
||||
false)
|
||||
}
|
||||
|
||||
func waitUntilScanComplete(t *testing.T, ctx context.Context, addr string) {
|
||||
waitForServer(t, ctx,
|
||||
fmt.Sprintf("http://%v/pkg", addr),
|
||||
"Scan is not yet complete",
|
||||
// setting reverse as true, which means this waits
|
||||
// until the string is not returned in the response anymore
|
||||
true)
|
||||
}
|
||||
|
||||
const pollInterval = 50 * time.Millisecond
|
||||
|
||||
// waitForServer waits for server to meet the required condition,
|
||||
// failing the test if ctx is canceled before that occurs.
|
||||
func waitForServer(t *testing.T, ctx context.Context, url, match string, reverse bool) {
|
||||
start := time.Now()
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
t.Helper()
|
||||
t.Fatalf("server failed to respond in %v", time.Since(start))
|
||||
}
|
||||
|
||||
time.Sleep(pollInterval)
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil || res.StatusCode != http.StatusOK {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case !reverse && bytes.Contains(body, []byte(match)),
|
||||
reverse && !bytes.Contains(body, []byte(match)):
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// hasTag checks whether a given release tag is contained in the current version
|
||||
// of the go binary.
|
||||
func hasTag(t string) bool {
|
||||
for _, v := range build.Default.ReleaseTags {
|
||||
if t == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("skipping on plan9; fails to start up quickly enough")
|
||||
}
|
||||
bin := godocPath(t)
|
||||
|
||||
testcase := func(url string, contents string) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
||||
|
||||
args := []string{fmt.Sprintf("-url=%s", url)}
|
||||
cmd := testenv.Command(t, bin, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
cmd.Args[0] = "godoc"
|
||||
|
||||
// Set GOPATH variable to a non-existing absolute path
|
||||
// and GOPROXY=off to disable module fetches.
|
||||
// We cannot just unset GOPATH variable because godoc would default it to ~/go.
|
||||
// (We don't want the indexer looking at the local workspace during tests.)
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOPATH=/does_not_exist",
|
||||
"GOPROXY=off",
|
||||
"GO111MODULE=off")
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Fatalf("failed to run godoc -url=%q: %s\nstderr:\n%s", url, err, stderr)
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout.String(), contents) {
|
||||
t.Errorf("did not find substring %q in output of godoc -url=%q:\n%s", contents, url, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("index", testcase("/", "These packages are part of the Go Project but outside the main Go tree."))
|
||||
t.Run("fmt", testcase("/pkg/fmt", "Package fmt implements formatted I/O"))
|
||||
}
|
||||
|
||||
// Basic integration test for godoc HTTP interface.
|
||||
func TestWeb(t *testing.T) {
|
||||
bin := godocPath(t)
|
||||
|
||||
for _, x := range packagestest.All {
|
||||
t.Run(x.Name(), func(t *testing.T) {
|
||||
testWeb(t, x, bin, false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Basic integration test for godoc HTTP interface.
|
||||
func TestWebIndex(t *testing.T) {
|
||||
t.Skip("slow test of to-be-deleted code (golang/go#59056)")
|
||||
if testing.Short() {
|
||||
t.Skip("skipping slow test in -short mode")
|
||||
}
|
||||
bin := godocPath(t)
|
||||
testWeb(t, packagestest.GOPATH, bin, true)
|
||||
}
|
||||
|
||||
// Basic integration test for godoc HTTP interface.
|
||||
func testWeb(t *testing.T, x packagestest.Exporter, bin string, withIndex bool) {
|
||||
switch runtime.GOOS {
|
||||
case "plan9":
|
||||
t.Skip("skipping on plan9: fails to start up quickly enough")
|
||||
case "android", "ios":
|
||||
t.Skip("skipping on mobile: lacks GOROOT/api in test environment")
|
||||
}
|
||||
|
||||
// Write a fake GOROOT/GOPATH with some third party packages.
|
||||
e := packagestest.Export(t, x, []packagestest.Module{
|
||||
{
|
||||
Name: "godoc.test/repo1",
|
||||
Files: map[string]interface{}{
|
||||
"a/a.go": `// Package a is a package in godoc.test/repo1.
|
||||
package a; import _ "godoc.test/repo2/a"; const Name = "repo1a"`,
|
||||
"b/b.go": `package b; const Name = "repo1b"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "godoc.test/repo2",
|
||||
Files: map[string]interface{}{
|
||||
"a/a.go": `package a; const Name = "repo2a"`,
|
||||
"b/b.go": `package b; const Name = "repo2b"`,
|
||||
},
|
||||
},
|
||||
})
|
||||
defer e.Cleanup()
|
||||
|
||||
// Start the server.
|
||||
addr := serverAddress(t)
|
||||
args := []string{fmt.Sprintf("-http=%s", addr)}
|
||||
if withIndex {
|
||||
args = append(args, "-index", "-index_interval=-1s")
|
||||
}
|
||||
cmd := testenv.Command(t, bin, args...)
|
||||
cmd.Dir = e.Config.Dir
|
||||
cmd.Env = e.Config.Env
|
||||
cmdOut := new(strings.Builder)
|
||||
cmd.Stdout = cmdOut
|
||||
cmd.Stderr = cmdOut
|
||||
cmd.Args[0] = "godoc"
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("failed to start godoc: %s", err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
t.Logf("%v: %v", cmd, err)
|
||||
cancel()
|
||||
}()
|
||||
defer func() {
|
||||
// Shut down the server cleanly if possible.
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd.Process.Kill() // Windows doesn't support os.Interrupt.
|
||||
} else {
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
}
|
||||
<-ctx.Done()
|
||||
t.Logf("server output:\n%s", cmdOut)
|
||||
}()
|
||||
|
||||
if withIndex {
|
||||
waitForSearchReady(t, ctx, cmd, addr)
|
||||
} else {
|
||||
waitForServerReady(t, ctx, cmd, addr)
|
||||
waitUntilScanComplete(t, ctx, addr)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
contains []string // substring
|
||||
match []string // regexp
|
||||
notContains []string
|
||||
needIndex bool
|
||||
releaseTag string // optional release tag that must be in go/build.ReleaseTags
|
||||
}{
|
||||
{
|
||||
path: "/",
|
||||
contains: []string{
|
||||
"Go Documentation Server",
|
||||
"Standard library",
|
||||
"These packages are part of the Go Project but outside the main Go tree.",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/fmt/",
|
||||
contains: []string{"Package fmt implements formatted I/O"},
|
||||
},
|
||||
{
|
||||
path: "/src/fmt/",
|
||||
contains: []string{"scan_test.go"},
|
||||
},
|
||||
{
|
||||
path: "/src/fmt/print.go",
|
||||
contains: []string{"// Println formats using"},
|
||||
},
|
||||
{
|
||||
path: "/pkg",
|
||||
contains: []string{
|
||||
"Standard library",
|
||||
"Package fmt implements formatted I/O",
|
||||
"Third party",
|
||||
"Package a is a package in godoc.test/repo1.",
|
||||
},
|
||||
notContains: []string{
|
||||
"internal/syscall",
|
||||
"cmd/gc",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/?m=all",
|
||||
contains: []string{
|
||||
"Standard library",
|
||||
"Package fmt implements formatted I/O",
|
||||
"internal/syscall/?m=all",
|
||||
},
|
||||
notContains: []string{
|
||||
"cmd/gc",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/search?q=ListenAndServe",
|
||||
contains: []string{
|
||||
"/src",
|
||||
},
|
||||
notContains: []string{
|
||||
"/pkg/bootstrap",
|
||||
},
|
||||
needIndex: true,
|
||||
},
|
||||
{
|
||||
path: "/pkg/strings/",
|
||||
contains: []string{
|
||||
`href="/src/strings/strings.go"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/cmd/compile/internal/amd64/",
|
||||
contains: []string{
|
||||
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/math/bits/",
|
||||
contains: []string{
|
||||
`Added in Go 1.9`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/net/",
|
||||
contains: []string{
|
||||
`// IPv6 scoped addressing zone; added in Go 1.1`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/net/http/httptrace/",
|
||||
match: []string{
|
||||
`Got1xxResponse.*// Go 1\.11`,
|
||||
},
|
||||
releaseTag: "go1.11",
|
||||
},
|
||||
// Verify we don't add version info to a struct field added the same time
|
||||
// as the struct itself:
|
||||
{
|
||||
path: "/pkg/net/http/httptrace/",
|
||||
match: []string{
|
||||
`(?m)GotFirstResponseByte func\(\)\s*$`,
|
||||
},
|
||||
},
|
||||
// Remove trailing periods before adding semicolons:
|
||||
{
|
||||
path: "/pkg/database/sql/",
|
||||
contains: []string{
|
||||
"The number of connections currently in use; added in Go 1.11",
|
||||
"The number of idle connections; added in Go 1.11",
|
||||
},
|
||||
releaseTag: "go1.11",
|
||||
},
|
||||
|
||||
// Third party packages.
|
||||
{
|
||||
path: "/pkg/godoc.test/repo1/a",
|
||||
contains: []string{`const <span id="Name">Name</span> = "repo1a"`},
|
||||
},
|
||||
{
|
||||
path: "/pkg/godoc.test/repo2/b",
|
||||
contains: []string{`const <span id="Name">Name</span> = "repo2b"`},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if test.needIndex && !withIndex {
|
||||
continue
|
||||
}
|
||||
url := fmt.Sprintf("http://%s%s", addr, test.path)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Errorf("GET %s failed: %s", url, err)
|
||||
continue
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
strBody := string(body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Errorf("GET %s: failed to read body: %s (response: %v)", url, err, resp)
|
||||
}
|
||||
isErr := false
|
||||
for _, substr := range test.contains {
|
||||
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
||||
continue
|
||||
}
|
||||
if !bytes.Contains(body, []byte(substr)) {
|
||||
t.Errorf("GET %s: wanted substring %q in body", url, substr)
|
||||
isErr = true
|
||||
}
|
||||
}
|
||||
for _, re := range test.match {
|
||||
if test.releaseTag != "" && !hasTag(test.releaseTag) {
|
||||
continue
|
||||
}
|
||||
if ok, err := regexp.MatchString(re, strBody); !ok || err != nil {
|
||||
if err != nil {
|
||||
t.Fatalf("Bad regexp %q: %v", re, err)
|
||||
}
|
||||
t.Errorf("GET %s: wanted to match %s in body", url, re)
|
||||
isErr = true
|
||||
}
|
||||
}
|
||||
for _, substr := range test.notContains {
|
||||
if bytes.Contains(body, []byte(substr)) {
|
||||
t.Errorf("GET %s: didn't want substring %q in body", url, substr)
|
||||
isErr = true
|
||||
}
|
||||
}
|
||||
if isErr {
|
||||
t.Errorf("GET %s: got:\n%s", url, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test for golang.org/issue/35476.
|
||||
func TestNoMainModule(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in -short mode")
|
||||
}
|
||||
if runtime.GOOS == "plan9" {
|
||||
t.Skip("skipping on plan9; for consistency with other tests that build godoc binary")
|
||||
}
|
||||
bin := godocPath(t)
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Run godoc in an empty directory with module mode explicitly on,
|
||||
// so that 'go env GOMOD' reports os.DevNull.
|
||||
cmd := testenv.Command(t, bin, "-url=/")
|
||||
cmd.Dir = tempDir
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
t.Fatalf("godoc command failed: %v\nstderr=%q", err, stderr.String())
|
||||
}
|
||||
if strings.Contains(stderr.String(), "go mod download") {
|
||||
t.Errorf("stderr contains 'go mod download', is that intentional?\nstderr=%q", stderr.String())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 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 main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func findGOROOT() string {
|
||||
if env := os.Getenv("GOROOT"); env != "" {
|
||||
return filepath.Clean(env)
|
||||
}
|
||||
def := filepath.Clean(runtime.GOROOT())
|
||||
if runtime.Compiler == "gccgo" {
|
||||
// gccgo has no real GOROOT, and it certainly doesn't
|
||||
// depend on the executable's location.
|
||||
return def
|
||||
}
|
||||
out, err := exec.Command("go", "env", "GOROOT").Output()
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2010 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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"go/format"
|
||||
"log"
|
||||
"net/http"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/godoc"
|
||||
"golang.org/x/tools/godoc/redirect"
|
||||
"golang.org/x/tools/godoc/vfs"
|
||||
|
||||
_ "golang.org/x/tools/playground" // register "/compile" playground redirect
|
||||
)
|
||||
|
||||
var (
|
||||
pres *godoc.Presentation
|
||||
fs = vfs.NameSpace{}
|
||||
)
|
||||
|
||||
func registerHandlers(pres *godoc.Presentation) {
|
||||
if pres == nil {
|
||||
panic("nil Presentation")
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path == "/" {
|
||||
http.Redirect(w, req, "/pkg/", http.StatusFound)
|
||||
return
|
||||
}
|
||||
pres.ServeHTTP(w, req)
|
||||
})
|
||||
mux.Handle("/pkg/C/", redirect.Handler("/cmd/cgo/"))
|
||||
mux.HandleFunc("/fmt", fmtHandler)
|
||||
redirect.Register(mux)
|
||||
|
||||
http.Handle("/", mux)
|
||||
}
|
||||
|
||||
func readTemplate(name string) *template.Template {
|
||||
if pres == nil {
|
||||
panic("no global Presentation set yet")
|
||||
}
|
||||
path := "lib/godoc/" + name
|
||||
|
||||
// use underlying file system fs to read the template file
|
||||
// (cannot use template ParseFile functions directly)
|
||||
data, err := vfs.ReadFile(fs, path)
|
||||
if err != nil {
|
||||
log.Fatal("readTemplate: ", err)
|
||||
}
|
||||
// be explicit with errors (for app engine use)
|
||||
t, err := template.New(name).Funcs(pres.FuncMap()).Parse(string(data))
|
||||
if err != nil {
|
||||
log.Fatal("readTemplate: ", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func readTemplates(p *godoc.Presentation) {
|
||||
p.CallGraphHTML = readTemplate("callgraph.html")
|
||||
p.DirlistHTML = readTemplate("dirlist.html")
|
||||
p.ErrorHTML = readTemplate("error.html")
|
||||
p.ExampleHTML = readTemplate("example.html")
|
||||
p.GodocHTML = readTemplate("godoc.html")
|
||||
p.ImplementsHTML = readTemplate("implements.html")
|
||||
p.MethodSetHTML = readTemplate("methodset.html")
|
||||
p.PackageHTML = readTemplate("package.html")
|
||||
p.PackageRootHTML = readTemplate("packageroot.html")
|
||||
p.SearchHTML = readTemplate("search.html")
|
||||
p.SearchDocHTML = readTemplate("searchdoc.html")
|
||||
p.SearchCodeHTML = readTemplate("searchcode.html")
|
||||
p.SearchTxtHTML = readTemplate("searchtxt.html")
|
||||
}
|
||||
|
||||
type fmtResponse struct {
|
||||
Body string
|
||||
Error string
|
||||
}
|
||||
|
||||
// fmtHandler takes a Go program in its "body" form value, formats it with
|
||||
// standard gofmt formatting, and writes a fmtResponse as a JSON object.
|
||||
func fmtHandler(w http.ResponseWriter, r *http.Request) {
|
||||
resp := new(fmtResponse)
|
||||
body, err := format.Source([]byte(r.FormValue("body")))
|
||||
if err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Body = string(body)
|
||||
}
|
||||
w.Header().Set("Content-type", "application/json; charset=utf-8")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// Copyright 2009 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.
|
||||
|
||||
// godoc: Go Documentation Server
|
||||
|
||||
// Web server tree:
|
||||
//
|
||||
// http://godoc/ redirect to /pkg/
|
||||
// http://godoc/src/ serve files from $GOROOT/src; .go gets pretty-printed
|
||||
// http://godoc/cmd/ serve documentation about commands
|
||||
// http://godoc/pkg/ serve documentation about packages
|
||||
// (idea is if you say import "compress/zlib", you go to
|
||||
// http://godoc/pkg/compress/zlib)
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
_ "expvar" // to serve /debug/vars
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof" // to serve /debug/pprof/*
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/godoc"
|
||||
"golang.org/x/tools/godoc/static"
|
||||
"golang.org/x/tools/godoc/vfs"
|
||||
"golang.org/x/tools/godoc/vfs/gatefs"
|
||||
"golang.org/x/tools/godoc/vfs/mapfs"
|
||||
"golang.org/x/tools/godoc/vfs/zipfs"
|
||||
"golang.org/x/tools/internal/gocommand"
|
||||
)
|
||||
|
||||
const defaultAddr = "localhost:6060" // default webserver address
|
||||
|
||||
var (
|
||||
// file system to serve
|
||||
// (with e.g.: zip -r go.zip $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i favicon.ico)
|
||||
zipfile = flag.String("zip", "", "zip file providing the file system to serve; disabled if empty")
|
||||
|
||||
// file-based index
|
||||
writeIndex = flag.Bool("write_index", false, "write index to a file; the file name must be specified with -index_files")
|
||||
|
||||
// network
|
||||
httpAddr = flag.String("http", defaultAddr, "HTTP service address")
|
||||
|
||||
// layout control
|
||||
urlFlag = flag.String("url", "", "print HTML for named URL")
|
||||
|
||||
verbose = flag.Bool("v", false, "verbose mode")
|
||||
|
||||
// file system roots
|
||||
// TODO(gri) consider the invariant that goroot always end in '/'
|
||||
goroot = flag.String("goroot", findGOROOT(), "Go root directory")
|
||||
|
||||
// layout control
|
||||
showTimestamps = flag.Bool("timestamps", false, "show timestamps with directory listings")
|
||||
templateDir = flag.String("templates", "", "load templates/JS/CSS from disk in this directory")
|
||||
showPlayground = flag.Bool("play", false, "enable playground")
|
||||
declLinks = flag.Bool("links", true, "link identifiers to their declarations")
|
||||
|
||||
// search index
|
||||
indexEnabled = flag.Bool("index", false, "enable search index")
|
||||
indexFiles = flag.String("index_files", "", "glob pattern specifying index files; if not empty, the index is read from these files in sorted order")
|
||||
indexInterval = flag.Duration("index_interval", 0, "interval of indexing; 0 for default (5m), negative to only index once at startup")
|
||||
maxResults = flag.Int("maxresults", 10000, "maximum number of full text search results shown")
|
||||
indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle")
|
||||
|
||||
// source code notes
|
||||
notesRx = flag.String("notes", "BUG", "regular expression matching note markers to show")
|
||||
)
|
||||
|
||||
// An httpResponseRecorder is an http.ResponseWriter
|
||||
type httpResponseRecorder struct {
|
||||
body *bytes.Buffer
|
||||
header http.Header
|
||||
code int
|
||||
}
|
||||
|
||||
func (w *httpResponseRecorder) Header() http.Header { return w.header }
|
||||
func (w *httpResponseRecorder) Write(b []byte) (int, error) { return w.body.Write(b) }
|
||||
func (w *httpResponseRecorder) WriteHeader(code int) { w.code = code }
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, "usage: godoc -http="+defaultAddr+"\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func loggingHandler(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
log.Printf("%s\t%s", req.RemoteAddr, req.URL)
|
||||
h.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
func handleURLFlag() {
|
||||
// Try up to 10 fetches, following redirects.
|
||||
urlstr := *urlFlag
|
||||
for i := 0; i < 10; i++ {
|
||||
// Prepare request.
|
||||
u, err := url.Parse(urlstr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
req := &http.Request{
|
||||
URL: u,
|
||||
}
|
||||
|
||||
// Invoke default HTTP handler to serve request
|
||||
// to our buffering httpWriter.
|
||||
w := &httpResponseRecorder{code: 200, header: make(http.Header), body: new(bytes.Buffer)}
|
||||
http.DefaultServeMux.ServeHTTP(w, req)
|
||||
|
||||
// Return data, error, or follow redirect.
|
||||
switch w.code {
|
||||
case 200: // ok
|
||||
os.Stdout.Write(w.body.Bytes())
|
||||
return
|
||||
case 301, 302, 303, 307: // redirect
|
||||
redirect := w.header.Get("Location")
|
||||
if redirect == "" {
|
||||
log.Fatalf("HTTP %d without Location header", w.code)
|
||||
}
|
||||
urlstr = redirect
|
||||
default:
|
||||
log.Fatalf("HTTP error %d", w.code)
|
||||
}
|
||||
}
|
||||
log.Fatalf("too many redirects")
|
||||
}
|
||||
|
||||
func initCorpus(corpus *godoc.Corpus) {
|
||||
err := corpus.Init()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
// Check usage.
|
||||
if flag.NArg() > 0 {
|
||||
fmt.Fprintln(os.Stderr, `Unexpected arguments. Use "go doc" for command-line help output instead. For example, "go doc fmt.Printf".`)
|
||||
usage()
|
||||
}
|
||||
if *httpAddr == "" && *urlFlag == "" && !*writeIndex {
|
||||
fmt.Fprintln(os.Stderr, "At least one of -http, -url, or -write_index must be set to a non-zero value.")
|
||||
usage()
|
||||
}
|
||||
|
||||
// Set the resolved goroot.
|
||||
vfs.GOROOT = *goroot
|
||||
|
||||
fsGate := make(chan bool, 20)
|
||||
|
||||
// Determine file system to use.
|
||||
if *zipfile == "" {
|
||||
// use file system of underlying OS
|
||||
rootfs := gatefs.New(vfs.OS(*goroot), fsGate)
|
||||
fs.Bind("/", rootfs, "/", vfs.BindReplace)
|
||||
} else {
|
||||
// use file system specified via .zip file (path separator must be '/')
|
||||
rc, err := zip.OpenReader(*zipfile)
|
||||
if err != nil {
|
||||
log.Fatalf("%s: %s\n", *zipfile, err)
|
||||
}
|
||||
defer rc.Close() // be nice (e.g., -writeIndex mode)
|
||||
fs.Bind("/", zipfs.New(rc, *zipfile), *goroot, vfs.BindReplace)
|
||||
}
|
||||
if *templateDir != "" {
|
||||
fs.Bind("/lib/godoc", vfs.OS(*templateDir), "/", vfs.BindBefore)
|
||||
fs.Bind("/favicon.ico", vfs.OS(*templateDir), "/favicon.ico", vfs.BindReplace)
|
||||
} else {
|
||||
fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)
|
||||
fs.Bind("/favicon.ico", mapfs.New(static.Files), "/favicon.ico", vfs.BindReplace)
|
||||
}
|
||||
|
||||
// Get the GOMOD value, use it to determine if godoc is being invoked in module mode.
|
||||
goModFile, err := goMod()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to determine go env GOMOD value: %v", err)
|
||||
goModFile = "" // Fall back to GOPATH mode.
|
||||
}
|
||||
|
||||
if goModFile != "" {
|
||||
fmt.Printf("using module mode; GOMOD=%s\n", goModFile)
|
||||
|
||||
// Detect whether to use vendor mode or not.
|
||||
vendorEnabled, mainModVendor, err := gocommand.VendorEnabled(context.Background(), gocommand.Invocation{}, &gocommand.Runner{})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to determine if vendoring is enabled: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if vendorEnabled {
|
||||
// Bind the root directory of the main module.
|
||||
fs.Bind(path.Join("/src", mainModVendor.Path), gatefs.New(vfs.OS(mainModVendor.Dir), fsGate), "/", vfs.BindAfter)
|
||||
|
||||
// Bind the vendor directory.
|
||||
//
|
||||
// Note that in module mode, vendor directories in locations
|
||||
// other than the main module's root directory are ignored.
|
||||
// See https://golang.org/ref/mod#vendoring.
|
||||
vendorDir := filepath.Join(mainModVendor.Dir, "vendor")
|
||||
fs.Bind("/src", gatefs.New(vfs.OS(vendorDir), fsGate), "/", vfs.BindAfter)
|
||||
|
||||
} else {
|
||||
// Try to download dependencies that are not in the module cache in order to
|
||||
// show their documentation.
|
||||
// This may fail if module downloading is disallowed (GOPROXY=off) or due to
|
||||
// limited connectivity, in which case we print errors to stderr and show
|
||||
// documentation only for packages that are available.
|
||||
fillModuleCache(os.Stderr, goModFile)
|
||||
|
||||
// Determine modules in the build list.
|
||||
mods, err := buildList(goModFile)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to determine the build list of the main module: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Bind module trees into Go root.
|
||||
for _, m := range mods {
|
||||
if m.Dir == "" {
|
||||
// Module is not available in the module cache, skip it.
|
||||
continue
|
||||
}
|
||||
dst := path.Join("/src", m.Path)
|
||||
fs.Bind(dst, gatefs.New(vfs.OS(m.Dir), fsGate), "/", vfs.BindAfter)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("using GOPATH mode")
|
||||
|
||||
// Bind $GOPATH trees into Go root.
|
||||
for _, p := range filepath.SplitList(build.Default.GOPATH) {
|
||||
fs.Bind("/src", gatefs.New(vfs.OS(p), fsGate), "/src", vfs.BindAfter)
|
||||
}
|
||||
}
|
||||
|
||||
var corpus *godoc.Corpus
|
||||
if goModFile != "" {
|
||||
corpus = godoc.NewCorpus(moduleFS{fs})
|
||||
} else {
|
||||
corpus = godoc.NewCorpus(fs)
|
||||
}
|
||||
corpus.Verbose = *verbose
|
||||
corpus.MaxResults = *maxResults
|
||||
corpus.IndexEnabled = *indexEnabled
|
||||
if *maxResults == 0 {
|
||||
corpus.IndexFullText = false
|
||||
}
|
||||
corpus.IndexFiles = *indexFiles
|
||||
corpus.IndexDirectory = func(dir string) bool {
|
||||
return dir != "/pkg" && !strings.HasPrefix(dir, "/pkg/")
|
||||
}
|
||||
corpus.IndexThrottle = *indexThrottle
|
||||
corpus.IndexInterval = *indexInterval
|
||||
if *writeIndex || *urlFlag != "" {
|
||||
corpus.IndexThrottle = 1.0
|
||||
corpus.IndexEnabled = true
|
||||
initCorpus(corpus)
|
||||
} else {
|
||||
go initCorpus(corpus)
|
||||
}
|
||||
|
||||
// Initialize the version info before readTemplates, which saves
|
||||
// the map value in a method value.
|
||||
corpus.InitVersionInfo()
|
||||
|
||||
pres = godoc.NewPresentation(corpus)
|
||||
pres.ShowTimestamps = *showTimestamps
|
||||
pres.ShowPlayground = *showPlayground
|
||||
pres.DeclLinks = *declLinks
|
||||
if *notesRx != "" {
|
||||
pres.NotesRx = regexp.MustCompile(*notesRx)
|
||||
}
|
||||
|
||||
readTemplates(pres)
|
||||
registerHandlers(pres)
|
||||
|
||||
if *writeIndex {
|
||||
// Write search index and exit.
|
||||
if *indexFiles == "" {
|
||||
log.Fatal("no index file specified")
|
||||
}
|
||||
|
||||
log.Println("initialize file systems")
|
||||
*verbose = true // want to see what happens
|
||||
|
||||
corpus.UpdateIndex()
|
||||
|
||||
log.Println("writing index file", *indexFiles)
|
||||
f, err := os.Create(*indexFiles)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
index, _ := corpus.CurrentIndex()
|
||||
_, err = index.WriteTo(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("done")
|
||||
return
|
||||
}
|
||||
|
||||
// Print content that would be served at the URL *urlFlag.
|
||||
if *urlFlag != "" {
|
||||
handleURLFlag()
|
||||
return
|
||||
}
|
||||
|
||||
var handler http.Handler = http.DefaultServeMux
|
||||
if *verbose {
|
||||
log.Printf("Go Documentation Server")
|
||||
log.Printf("version = %s", runtime.Version())
|
||||
log.Printf("address = %s", *httpAddr)
|
||||
log.Printf("goroot = %s", *goroot)
|
||||
switch {
|
||||
case !*indexEnabled:
|
||||
log.Print("search index disabled")
|
||||
case *maxResults > 0:
|
||||
log.Printf("full text index enabled (maxresults = %d)", *maxResults)
|
||||
default:
|
||||
log.Print("identifier search index enabled")
|
||||
}
|
||||
fs.Fprint(os.Stderr)
|
||||
handler = loggingHandler(handler)
|
||||
}
|
||||
|
||||
// Initialize search index.
|
||||
if *indexEnabled {
|
||||
go corpus.RunIndexer()
|
||||
}
|
||||
|
||||
// Start http server.
|
||||
if *verbose {
|
||||
log.Println("starting HTTP server")
|
||||
}
|
||||
if err := http.ListenAndServe(*httpAddr, handler); err != nil {
|
||||
log.Fatalf("ListenAndServe %s: %v", *httpAddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// goMod returns the go env GOMOD value in the current directory
|
||||
// by invoking the go command.
|
||||
//
|
||||
// GOMOD is documented at https://golang.org/cmd/go/#hdr-Environment_variables:
|
||||
//
|
||||
// The absolute path to the go.mod of the main module,
|
||||
// or the empty string if not using modules.
|
||||
func goMod() (string, error) {
|
||||
out, err := exec.Command("go", "env", "-json", "GOMOD").Output()
|
||||
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
|
||||
return "", fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var env struct {
|
||||
GoMod string
|
||||
}
|
||||
err = json.Unmarshal(out, &env)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return env.GoMod, nil
|
||||
}
|
||||
|
||||
// fillModuleCache does a best-effort attempt to fill the module cache
|
||||
// with all dependencies of the main module in the current directory
|
||||
// by invoking the go command. Module download logs are streamed to w.
|
||||
// If there are any problems encountered, they are also written to w.
|
||||
// It should only be used in module mode, when vendor mode isn't on.
|
||||
//
|
||||
// See https://golang.org/cmd/go/#hdr-Download_modules_to_local_cache.
|
||||
func fillModuleCache(w io.Writer, goMod string) {
|
||||
if goMod == os.DevNull {
|
||||
// No module requirements, nothing to do.
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "mod", "download", "-json")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = w
|
||||
err := cmd.Run()
|
||||
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) && ee.ExitCode() == 1 {
|
||||
// Exit code 1 from this command means there were some
|
||||
// non-empty Error values in the output. Print them to w.
|
||||
fmt.Fprintf(w, "documentation for some packages is not shown:\n")
|
||||
for dec := json.NewDecoder(&out); ; {
|
||||
var m struct {
|
||||
Path string // Module path.
|
||||
Version string // Module version.
|
||||
Error string // Error loading module.
|
||||
}
|
||||
err := dec.Decode(&m)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(w, "error decoding JSON object from go mod download -json: %v\n", err)
|
||||
continue
|
||||
}
|
||||
if m.Error == "" {
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "\tmodule %s@%s is not in the module cache and there was a problem downloading it: %s\n", m.Path, m.Version, m.Error)
|
||||
}
|
||||
} else if err != nil {
|
||||
fmt.Fprintf(w, "there was a problem filling module cache: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
type mod struct {
|
||||
Path string // Module path.
|
||||
Dir string // Directory holding files for this module, if any.
|
||||
}
|
||||
|
||||
// buildList determines the build list in the current directory
|
||||
// by invoking the go command. It should only be used in module mode,
|
||||
// when vendor mode isn't on.
|
||||
//
|
||||
// See https://golang.org/cmd/go/#hdr-The_main_module_and_the_build_list.
|
||||
func buildList(goMod string) ([]mod, error) {
|
||||
if goMod == os.DevNull {
|
||||
// Empty build list.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
out, err := exec.Command("go", "list", "-m", "-json", "all").Output()
|
||||
if ee := (*exec.ExitError)(nil); errors.As(err, &ee) {
|
||||
return nil, fmt.Errorf("go command exited unsuccessfully: %v\n%s", ee.ProcessState.String(), ee.Stderr)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var mods []mod
|
||||
for dec := json.NewDecoder(bytes.NewReader(out)); ; {
|
||||
var m mod
|
||||
err := dec.Decode(&m)
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mods = append(mods, m)
|
||||
}
|
||||
return mods, nil
|
||||
}
|
||||
|
||||
// moduleFS is a vfs.FileSystem wrapper used when godoc is running
|
||||
// in module mode. It's needed so that packages inside modules are
|
||||
// considered to be third party.
|
||||
//
|
||||
// It overrides the RootType method of the underlying filesystem
|
||||
// and implements it using a heuristic based on the import path.
|
||||
// If the first element of the import path does not contain a dot,
|
||||
// that package is considered to be inside GOROOT. If it contains
|
||||
// a dot, then that package is considered to be third party.
|
||||
//
|
||||
// TODO(dmitshur): The RootType abstraction works well when GOPATH
|
||||
// workspaces are bound at their roots, but scales poorly in the
|
||||
// general case. It should be replaced by a more direct solution
|
||||
// for determining whether a package is third party or not.
|
||||
type moduleFS struct{ vfs.FileSystem }
|
||||
|
||||
func (moduleFS) RootType(path string) vfs.RootType {
|
||||
if !strings.HasPrefix(path, "/src/") {
|
||||
return ""
|
||||
}
|
||||
domain := path[len("/src/"):]
|
||||
if i := strings.Index(domain, "/"); i >= 0 {
|
||||
domain = domain[:i]
|
||||
}
|
||||
if !strings.Contains(domain, ".") {
|
||||
// No dot in the first element of import path
|
||||
// suggests this is a package in GOROOT.
|
||||
return vfs.RootTypeGoRoot
|
||||
} else {
|
||||
// A dot in the first element of import path
|
||||
// suggests this is a third party package.
|
||||
return vfs.RootTypeGoPath
|
||||
}
|
||||
}
|
||||
func (fs moduleFS) String() string { return "module(" + fs.FileSystem.String() + ")" }
|
||||
Reference in New Issue
Block a user