mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
godoc: show version information for stdlib
This change reads $GOROOT/api/go1.*.txt when godoc starts and caches information about which versions of Go introduce functions, types, and methods. This information is displayed currently only in HTML output. Functions, types, and methods introduced as part of Go 1 are not annotated, as their presence at that version is implied. This change does not address constants or variables, and completely ignores the syscall package. The former are future work, the latter is likely an exercise in futility. In all cases, this is because the story around displaying the version information is not well developed. Fixes golang/go#5778 Change-Id: Ieb3cc0da7b18e195bc9c443f14fd8a82e8b2bbf8 Reviewed-on: https://go-review.googlesource.com/85396 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Devon O'Dell <dhobsd@google.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
d74aaa1f57
commit
57f659e14d
@ -320,6 +320,12 @@ func testWeb(t *testing.T, withIndex bool) {
|
||||
`href="/src/cmd/compile/internal/amd64/ssa.go"`,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/pkg/math/bits/",
|
||||
match: []string{
|
||||
`Added in Go 1.9`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if test.needIndex && !withIndex {
|
||||
|
@ -250,6 +250,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the version info before readTemplates, which saves
|
||||
// the map value in a method value.
|
||||
corpus.InitVersionInfo()
|
||||
|
||||
pres = godoc.NewPresentation(corpus)
|
||||
pres.TabWidth = *tabWidth
|
||||
pres.ShowTimestamps = *showTimestamps
|
||||
|
@ -108,6 +108,10 @@ type Corpus struct {
|
||||
// flag to check whether a corpus is initialized or not
|
||||
initMu sync.RWMutex
|
||||
initDone bool
|
||||
|
||||
// pkgAPIInfo contains the information about which package API
|
||||
// features were added in which version of Go.
|
||||
pkgAPIInfo apiVersions
|
||||
}
|
||||
|
||||
// NewCorpus returns a new Corpus from a filesystem.
|
||||
|
@ -61,6 +61,7 @@ func (p *Presentation) initFuncMap() {
|
||||
// various helpers
|
||||
"filename": filenameFunc,
|
||||
"repeat": strings.Repeat,
|
||||
"since": p.Corpus.pkgAPIInfo.sinceVersionFunc,
|
||||
|
||||
// access to FileInfos (directory listings)
|
||||
"fileInfoName": fileInfoNameFunc,
|
||||
|
@ -168,6 +168,8 @@
|
||||
{{$name_html := html .Name}}
|
||||
<h2 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
|
||||
<a class="permalink" href="#{{$name_html}}">¶</a>
|
||||
{{$since := since "func" "" .Name $.PDoc.ImportPath}}
|
||||
{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
|
||||
</h2>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
@ -180,6 +182,8 @@
|
||||
{{$tname_html := html .Name}}
|
||||
<h2 id="{{$tname_html}}">type <a href="{{posLink_url $ .Decl}}">{{$tname_html}}</a>
|
||||
<a class="permalink" href="#{{$tname_html}}">¶</a>
|
||||
{{$since := since "type" "" .Name $.PDoc.ImportPath}}
|
||||
{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
|
||||
</h2>
|
||||
{{comment_html .Doc}}
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
@ -202,6 +206,8 @@
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$name_html}}">func <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
|
||||
<a class="permalink" href="#{{$name_html}}">¶</a>
|
||||
{{$since := since "func" "" .Name $.PDoc.ImportPath}}
|
||||
{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
|
||||
</h3>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
@ -213,6 +219,8 @@
|
||||
{{$name_html := html .Name}}
|
||||
<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url $ .Decl}}">{{$name_html}}</a>
|
||||
<a class="permalink" href="#{{$tname_html}}.{{$name_html}}">¶</a>
|
||||
{{$since := since "method" .Recv .Name $.PDoc.ImportPath}}
|
||||
{{if $since}}<span title="Added in Go {{$since}}">{{$since}}</span>{{end}}
|
||||
</h3>
|
||||
<pre>{{node_html $ .Decl true}}</pre>
|
||||
{{comment_html .Doc}}
|
||||
|
File diff suppressed because one or more lines are too long
@ -103,12 +103,17 @@ h2 {
|
||||
padding: 0.5rem;
|
||||
line-height: 1.25;
|
||||
font-weight: normal;
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
h2 a {
|
||||
font-weight: bold;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25;
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
h3,
|
||||
h4 {
|
||||
@ -122,6 +127,14 @@ h4 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 > span,
|
||||
h3 > span {
|
||||
float: right;
|
||||
margin: 0 25px 0 0;
|
||||
font-weight: normal;
|
||||
color: #5279C7;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 1.25rem;
|
||||
}
|
||||
|
211
godoc/versions.go
Normal file
211
godoc/versions.go
Normal file
@ -0,0 +1,211 @@
|
||||
// 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.
|
||||
|
||||
// This file caches information about which standard library types, methods,
|
||||
// and functions appeared in what version of Go
|
||||
|
||||
package godoc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"go/build"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// apiVersions is a map of packages to information about those packages'
|
||||
// symbols and when they were added to Go.
|
||||
//
|
||||
// Only things added after Go1 are tracked. Version strings are of the
|
||||
// form "1.1", "1.2", etc.
|
||||
type apiVersions map[string]pkgAPIVersions // keyed by Go package ("net/http")
|
||||
|
||||
// pkgAPIVersions contains information about which version of Go added
|
||||
// certain package symbols.
|
||||
//
|
||||
// Only things added after Go1 are tracked. Version strings are of the
|
||||
// form "1.1", "1.2", etc.
|
||||
type pkgAPIVersions struct {
|
||||
typeSince map[string]string // "Server" -> "1.7"
|
||||
methodSince map[string]map[string]string // "*Server"->"Shutdown"->1.8
|
||||
funcSince map[string]string // "NewServer" -> "1.7"
|
||||
}
|
||||
|
||||
// sinceVersionFunc returns a string (such as "1.7") specifying which Go
|
||||
// version introduced a symbol, unless it was introduced in Go1, in
|
||||
// which case it returns the empty string.
|
||||
//
|
||||
// The kind is one of "type", "method", or "func".
|
||||
//
|
||||
// The receiver is only used for "methods" and specifies the receiver type,
|
||||
// such as "*Server".
|
||||
//
|
||||
// The name is the symbol name ("Server") and the pkg is the package
|
||||
// ("net/http").
|
||||
func (v apiVersions) sinceVersionFunc(kind, receiver, name, pkg string) string {
|
||||
pv := v[pkg]
|
||||
switch kind {
|
||||
case "func":
|
||||
return pv.funcSince[name]
|
||||
case "type":
|
||||
return pv.typeSince[name]
|
||||
case "method":
|
||||
return pv.methodSince[receiver][name]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// versionedRow represents an API feature, a parsed line of a
|
||||
// $GOROOT/api/go.*txt file.
|
||||
type versionedRow struct {
|
||||
pkg string // "net/http"
|
||||
kind string // "type", "func", "method", TODO: "const", "var"
|
||||
recv string // for methods, the receiver type ("Server", "*Server")
|
||||
name string // name of type, func, or method
|
||||
}
|
||||
|
||||
// versionParser parses $GOROOT/api/go*.txt files and stores them in in its rows field.
|
||||
type versionParser struct {
|
||||
res apiVersions // initialized lazily
|
||||
}
|
||||
|
||||
func (vp *versionParser) parseFile(name string) error {
|
||||
base := filepath.Base(name)
|
||||
ver := strings.TrimPrefix(strings.TrimSuffix(base, ".txt"), "go")
|
||||
if ver == "1" {
|
||||
return nil
|
||||
}
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
row, ok := parseRow(sc.Text())
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if vp.res == nil {
|
||||
vp.res = make(apiVersions)
|
||||
}
|
||||
pkgi, ok := vp.res[row.pkg]
|
||||
if !ok {
|
||||
pkgi = pkgAPIVersions{
|
||||
typeSince: make(map[string]string),
|
||||
methodSince: make(map[string]map[string]string),
|
||||
funcSince: make(map[string]string),
|
||||
}
|
||||
vp.res[row.pkg] = pkgi
|
||||
}
|
||||
switch row.kind {
|
||||
case "func":
|
||||
pkgi.funcSince[row.name] = ver
|
||||
case "type":
|
||||
pkgi.typeSince[row.name] = ver
|
||||
case "method":
|
||||
if _, ok := pkgi.methodSince[row.recv]; !ok {
|
||||
pkgi.methodSince[row.recv] = make(map[string]string)
|
||||
}
|
||||
pkgi.methodSince[row.recv][row.name] = ver
|
||||
}
|
||||
}
|
||||
return sc.Err()
|
||||
}
|
||||
|
||||
func parseRow(s string) (vr versionedRow, ok bool) {
|
||||
if !strings.HasPrefix(s, "pkg ") {
|
||||
// Skip comments, blank lines, etc.
|
||||
return
|
||||
}
|
||||
rest := s[len("pkg "):]
|
||||
endPkg := strings.IndexFunc(rest, func(r rune) bool { return !(unicode.IsLetter(r) || r == '/') })
|
||||
if endPkg == -1 {
|
||||
return
|
||||
}
|
||||
vr.pkg, rest = rest[:endPkg], rest[endPkg:]
|
||||
if !strings.HasPrefix(rest, ", ") {
|
||||
// If the part after the pkg name isn't ", ", then it's a OS/ARCH-dependent line of the form:
|
||||
// pkg syscall (darwin-amd64), const ImplementsGetwd = false
|
||||
// We skip those for now.
|
||||
return
|
||||
}
|
||||
rest = rest[len(", "):]
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(rest, "type "):
|
||||
vr.kind = "type"
|
||||
rest = rest[len("type "):]
|
||||
sp := strings.IndexByte(rest, ' ')
|
||||
if sp == -1 {
|
||||
return
|
||||
}
|
||||
vr.name, rest = rest[:sp], rest[sp+1:]
|
||||
if strings.HasPrefix(rest, "struct, ") {
|
||||
// TODO: handle struct fields
|
||||
return
|
||||
}
|
||||
return vr, true
|
||||
case strings.HasPrefix(rest, "func "):
|
||||
vr.kind = "func"
|
||||
rest = rest[len("func "):]
|
||||
if i := strings.IndexByte(rest, '('); i != -1 {
|
||||
vr.name = rest[:i]
|
||||
return vr, true
|
||||
}
|
||||
case strings.HasPrefix(rest, "method "): // "method (*File) SetModTime(time.Time)"
|
||||
vr.kind = "method"
|
||||
rest = rest[len("method "):] // "(*File) SetModTime(time.Time)"
|
||||
sp := strings.IndexByte(rest, ' ')
|
||||
if sp == -1 {
|
||||
return
|
||||
}
|
||||
vr.recv = strings.Trim(rest[:sp], "()") // "*File"
|
||||
rest = rest[sp+1:] // SetMode(os.FileMode)
|
||||
paren := strings.IndexByte(rest, '(')
|
||||
if paren == -1 {
|
||||
return
|
||||
}
|
||||
vr.name = rest[:paren]
|
||||
return vr, true
|
||||
}
|
||||
return // TODO: handle more cases
|
||||
}
|
||||
|
||||
// InitVersionInfo parses the $GOROOT/api/go*.txt API definition files to discover
|
||||
// which API features were added in which Go releases.
|
||||
func (c *Corpus) InitVersionInfo() {
|
||||
var err error
|
||||
c.pkgAPIInfo, err = parsePackageAPIInfo()
|
||||
if err != nil {
|
||||
// TODO: consider making this fatal, after the Go 1.11 cycle.
|
||||
log.Printf("godoc: error parsing API version files: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parsePackageAPIInfo() (apiVersions, error) {
|
||||
var apiGlob string
|
||||
if os.Getenv("GOROOT") == "" {
|
||||
apiGlob = filepath.Join(build.Default.GOROOT, "api", "go*.txt")
|
||||
} else {
|
||||
apiGlob = filepath.Join(os.Getenv("GOROOT"), "api", "go*.txt")
|
||||
}
|
||||
|
||||
files, err := filepath.Glob(apiGlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vp := new(versionParser)
|
||||
for _, f := range files {
|
||||
if err := vp.parseFile(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return vp.res, nil
|
||||
}
|
101
godoc/versions_test.go
Normal file
101
godoc/versions_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
// 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 godoc
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseVersionRow(t *testing.T) {
|
||||
tests := []struct {
|
||||
row string
|
||||
want versionedRow
|
||||
}{
|
||||
{
|
||||
row: "# comment",
|
||||
},
|
||||
{
|
||||
row: "",
|
||||
},
|
||||
{
|
||||
row: "pkg archive/tar, type Writer struct",
|
||||
want: versionedRow{
|
||||
pkg: "archive/tar",
|
||||
kind: "type",
|
||||
name: "Writer",
|
||||
},
|
||||
},
|
||||
{
|
||||
row: "pkg archive/tar, type Header struct, AccessTime time.Time",
|
||||
// TODO: implement; for now we expect nothing
|
||||
},
|
||||
{
|
||||
row: "pkg archive/tar, method (*Reader) Read([]uint8) (int, error)",
|
||||
want: versionedRow{
|
||||
pkg: "archive/tar",
|
||||
kind: "method",
|
||||
name: "Read",
|
||||
recv: "*Reader",
|
||||
},
|
||||
},
|
||||
{
|
||||
row: "pkg archive/zip, func FileInfoHeader(os.FileInfo) (*FileHeader, error)",
|
||||
want: versionedRow{
|
||||
pkg: "archive/zip",
|
||||
kind: "func",
|
||||
name: "FileInfoHeader",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
got, ok := parseRow(tt.row)
|
||||
if !ok {
|
||||
got = versionedRow{}
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("%d. parseRow(%q) = %+v; want %+v", i, tt.row, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIVersion(t *testing.T) {
|
||||
av, err := parsePackageAPIInfo()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
kind string
|
||||
pkg string
|
||||
name string
|
||||
receiver string
|
||||
want string
|
||||
}{
|
||||
// Things that were added post-1.0 should appear
|
||||
{"func", "archive/tar", "FileInfoHeader", "", "1.1"},
|
||||
{"type", "bufio", "Scanner", "", "1.1"},
|
||||
{"method", "bufio", "WriteTo", "*Reader", "1.1"},
|
||||
|
||||
{"func", "bytes", "LastIndexByte", "", "1.5"},
|
||||
{"type", "crypto", "Decrypter", "", "1.5"},
|
||||
{"method", "crypto/rsa", "Decrypt", "*PrivateKey", "1.5"},
|
||||
{"method", "debug/dwarf", "GoString", "Class", "1.5"},
|
||||
|
||||
{"func", "os", "IsTimeout", "", "1.10"},
|
||||
{"type", "strings", "Builder", "", "1.10"},
|
||||
{"method", "strings", "WriteString", "*Builder", "1.10"},
|
||||
|
||||
// Things from package syscall should never appear
|
||||
{"func", "syscall", "FchFlags", "", ""},
|
||||
{"type", "syscall", "Inet4Pktinfo", "", ""},
|
||||
|
||||
// Things added in Go 1 should never appear
|
||||
{"func", "archive/tar", "NewReader", "", ""},
|
||||
{"type", "archive/tar", "Header", "", ""},
|
||||
{"method", "archive/tar", "Next", "*Reader", ""},
|
||||
} {
|
||||
if got := av.sinceVersionFunc(tc.kind, tc.receiver, tc.name, tc.pkg); got != tc.want {
|
||||
t.Errorf(`sinceFunc("%s", "%s", "%s", "%s") = "%s"; want "%s"`, tc.kind, tc.receiver, tc.name, tc.pkg, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user