Major rewrite
* use dep for vendoring * lets encrypt * moved web to transfer.sh-web repo * single command install * added first tests
This commit is contained in:
656
vendor/github.com/golang/gddo/doc/builder.go
generated
vendored
Normal file
656
vendor/github.com/golang/gddo/doc/builder.go
generated
vendored
Normal file
@@ -0,0 +1,656 @@
|
||||
// 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 or at
|
||||
// https://developers.google.com/open-source/licenses/bsd.
|
||||
|
||||
package doc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/doc"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/golang/gddo/gosrc"
|
||||
)
|
||||
|
||||
func startsWithUppercase(s string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(s)
|
||||
return unicode.IsUpper(r)
|
||||
}
|
||||
|
||||
var badSynopsisPrefixes = []string{
|
||||
"Autogenerated by Thrift Compiler",
|
||||
"Automatically generated ",
|
||||
"Auto-generated by ",
|
||||
"Copyright ",
|
||||
"COPYRIGHT ",
|
||||
`THE SOFTWARE IS PROVIDED "AS IS"`,
|
||||
"TODO: ",
|
||||
"vim:",
|
||||
}
|
||||
|
||||
// synopsis extracts the first sentence from s. All runs of whitespace are
|
||||
// replaced by a single space.
|
||||
func synopsis(s string) string {
|
||||
|
||||
parts := strings.SplitN(s, "\n\n", 2)
|
||||
s = parts[0]
|
||||
|
||||
var buf []byte
|
||||
const (
|
||||
other = iota
|
||||
period
|
||||
space
|
||||
)
|
||||
last := space
|
||||
Loop:
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch b {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
switch last {
|
||||
case period:
|
||||
break Loop
|
||||
case other:
|
||||
buf = append(buf, ' ')
|
||||
last = space
|
||||
}
|
||||
case '.':
|
||||
last = period
|
||||
buf = append(buf, b)
|
||||
default:
|
||||
last = other
|
||||
buf = append(buf, b)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that synopsis fits an App Engine datastore text property.
|
||||
const m = 400
|
||||
if len(buf) > m {
|
||||
buf = buf[:m]
|
||||
if i := bytes.LastIndex(buf, []byte{' '}); i >= 0 {
|
||||
buf = buf[:i]
|
||||
}
|
||||
buf = append(buf, " ..."...)
|
||||
}
|
||||
|
||||
s = string(buf)
|
||||
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if n < 0 || unicode.IsPunct(r) || unicode.IsSymbol(r) {
|
||||
// ignore Markdown headings, editor settings, Go build constraints, and * in poorly formatted block comments.
|
||||
s = ""
|
||||
} else {
|
||||
for _, prefix := range badSynopsisPrefixes {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
s = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
var referencesPats = []*regexp.Regexp{
|
||||
regexp.MustCompile(`"([-a-zA-Z0-9~+_./]+)"`), // quoted path
|
||||
regexp.MustCompile(`https://drone\.io/([-a-zA-Z0-9~+_./]+)/status\.png`),
|
||||
regexp.MustCompile(`\b(?:` + strings.Join([]string{
|
||||
`go\s+get\s+`,
|
||||
`goinstall\s+`,
|
||||
regexp.QuoteMeta("http://godoc.org/"),
|
||||
regexp.QuoteMeta("http://gopkgdoc.appspot.com/pkg/"),
|
||||
regexp.QuoteMeta("http://go.pkgdoc.org/"),
|
||||
regexp.QuoteMeta("http://gowalker.org/"),
|
||||
}, "|") + `)([-a-zA-Z0-9~+_./]+)`),
|
||||
}
|
||||
|
||||
// addReferences adds packages referenced in plain text s.
|
||||
func addReferences(references map[string]bool, s []byte) {
|
||||
for _, pat := range referencesPats {
|
||||
for _, m := range pat.FindAllSubmatch(s, -1) {
|
||||
p := string(m[1])
|
||||
if gosrc.IsValidRemotePath(p) {
|
||||
references[p] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byFuncName []*doc.Func
|
||||
|
||||
func (s byFuncName) Len() int { return len(s) }
|
||||
func (s byFuncName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byFuncName) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
||||
|
||||
func removeAssociations(dpkg *doc.Package) {
|
||||
for _, t := range dpkg.Types {
|
||||
dpkg.Funcs = append(dpkg.Funcs, t.Funcs...)
|
||||
t.Funcs = nil
|
||||
}
|
||||
sort.Sort(byFuncName(dpkg.Funcs))
|
||||
}
|
||||
|
||||
// builder holds the state used when building the documentation.
|
||||
type builder struct {
|
||||
srcs map[string]*source
|
||||
fset *token.FileSet
|
||||
examples []*doc.Example
|
||||
buf []byte // scratch space for printNode method.
|
||||
}
|
||||
|
||||
type Value struct {
|
||||
Decl Code
|
||||
Pos Pos
|
||||
Doc string
|
||||
}
|
||||
|
||||
func (b *builder) values(vdocs []*doc.Value) []*Value {
|
||||
var result []*Value
|
||||
for _, d := range vdocs {
|
||||
result = append(result, &Value{
|
||||
Decl: b.printDecl(d.Decl),
|
||||
Pos: b.position(d.Decl),
|
||||
Doc: d.Doc,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Note struct {
|
||||
Pos Pos
|
||||
UID string
|
||||
Body string
|
||||
}
|
||||
|
||||
type posNode token.Pos
|
||||
|
||||
func (p posNode) Pos() token.Pos { return token.Pos(p) }
|
||||
func (p posNode) End() token.Pos { return token.Pos(p) }
|
||||
|
||||
func (b *builder) notes(gnotes map[string][]*doc.Note) map[string][]*Note {
|
||||
if len(gnotes) == 0 {
|
||||
return nil
|
||||
}
|
||||
notes := make(map[string][]*Note)
|
||||
for tag, gvalues := range gnotes {
|
||||
values := make([]*Note, len(gvalues))
|
||||
for i := range gvalues {
|
||||
values[i] = &Note{
|
||||
Pos: b.position(posNode(gvalues[i].Pos)),
|
||||
UID: gvalues[i].UID,
|
||||
Body: strings.TrimSpace(gvalues[i].Body),
|
||||
}
|
||||
}
|
||||
notes[tag] = values
|
||||
}
|
||||
return notes
|
||||
}
|
||||
|
||||
type Example struct {
|
||||
Name string
|
||||
Doc string
|
||||
Code Code
|
||||
Play string
|
||||
Output string
|
||||
}
|
||||
|
||||
var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
|
||||
|
||||
func (b *builder) getExamples(name string) []*Example {
|
||||
var docs []*Example
|
||||
for _, e := range b.examples {
|
||||
if !strings.HasPrefix(e.Name, name) {
|
||||
continue
|
||||
}
|
||||
n := e.Name[len(name):]
|
||||
if n != "" {
|
||||
if i := strings.LastIndex(n, "_"); i != 0 {
|
||||
continue
|
||||
}
|
||||
n = n[1:]
|
||||
if startsWithUppercase(n) {
|
||||
continue
|
||||
}
|
||||
n = strings.Title(n)
|
||||
}
|
||||
|
||||
code, output := b.printExample(e)
|
||||
|
||||
play := ""
|
||||
if e.Play != nil {
|
||||
b.buf = b.buf[:0]
|
||||
if err := format.Node(sliceWriter{&b.buf}, b.fset, e.Play); err != nil {
|
||||
play = err.Error()
|
||||
} else {
|
||||
play = string(b.buf)
|
||||
}
|
||||
}
|
||||
|
||||
docs = append(docs, &Example{
|
||||
Name: n,
|
||||
Doc: e.Doc,
|
||||
Code: code,
|
||||
Output: output,
|
||||
Play: play})
|
||||
}
|
||||
return docs
|
||||
}
|
||||
|
||||
type Func struct {
|
||||
Decl Code
|
||||
Pos Pos
|
||||
Doc string
|
||||
Name string
|
||||
Recv string // Actual receiver "T" or "*T".
|
||||
Orig string // Original receiver "T" or "*T". This can be different from Recv due to embedding.
|
||||
Examples []*Example
|
||||
}
|
||||
|
||||
func (b *builder) funcs(fdocs []*doc.Func) []*Func {
|
||||
var result []*Func
|
||||
for _, d := range fdocs {
|
||||
var exampleName string
|
||||
switch {
|
||||
case d.Recv == "":
|
||||
exampleName = d.Name
|
||||
case d.Recv[0] == '*':
|
||||
exampleName = d.Recv[1:] + "_" + d.Name
|
||||
default:
|
||||
exampleName = d.Recv + "_" + d.Name
|
||||
}
|
||||
result = append(result, &Func{
|
||||
Decl: b.printDecl(d.Decl),
|
||||
Pos: b.position(d.Decl),
|
||||
Doc: d.Doc,
|
||||
Name: d.Name,
|
||||
Recv: d.Recv,
|
||||
Orig: d.Orig,
|
||||
Examples: b.getExamples(exampleName),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Doc string
|
||||
Name string
|
||||
Decl Code
|
||||
Pos Pos
|
||||
Consts []*Value
|
||||
Vars []*Value
|
||||
Funcs []*Func
|
||||
Methods []*Func
|
||||
Examples []*Example
|
||||
}
|
||||
|
||||
func (b *builder) types(tdocs []*doc.Type) []*Type {
|
||||
var result []*Type
|
||||
for _, d := range tdocs {
|
||||
result = append(result, &Type{
|
||||
Doc: d.Doc,
|
||||
Name: d.Name,
|
||||
Decl: b.printDecl(d.Decl),
|
||||
Pos: b.position(d.Decl),
|
||||
Consts: b.values(d.Consts),
|
||||
Vars: b.values(d.Vars),
|
||||
Funcs: b.funcs(d.Funcs),
|
||||
Methods: b.funcs(d.Methods),
|
||||
Examples: b.getExamples(d.Name),
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var packageNamePats = []*regexp.Regexp{
|
||||
// Last element with .suffix removed.
|
||||
regexp.MustCompile(`/([^-./]+)[-.](?:git|svn|hg|bzr|v\d+)$`),
|
||||
|
||||
// Last element with "go" prefix or suffix removed.
|
||||
regexp.MustCompile(`/([^-./]+)[-.]go$`),
|
||||
regexp.MustCompile(`/go[-.]([^-./]+)$`),
|
||||
|
||||
// Special cases for popular repos.
|
||||
regexp.MustCompile(`^code\.google\.com/p/google-api-go-client/([^/]+)/v[^/]+$`),
|
||||
regexp.MustCompile(`^code\.google\.com/p/biogo\.([^/]+)$`),
|
||||
|
||||
// It's also common for the last element of the path to contain an
|
||||
// extra "go" prefix, but not always. TODO: examine unresolved ids to
|
||||
// detect when trimming the "go" prefix is appropriate.
|
||||
|
||||
// Last component of path.
|
||||
regexp.MustCompile(`([^/]+)$`),
|
||||
}
|
||||
|
||||
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
||||
pkg := imports[path]
|
||||
if pkg != nil {
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
// Guess the package name without importing it.
|
||||
for _, pat := range packageNamePats {
|
||||
m := pat.FindStringSubmatch(path)
|
||||
if m != nil {
|
||||
pkg = ast.NewObj(ast.Pkg, m[1])
|
||||
pkg.Data = ast.NewScope(nil)
|
||||
imports[path] = pkg
|
||||
return pkg, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("package not found")
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
URL string
|
||||
}
|
||||
|
||||
type Pos struct {
|
||||
Line int32 // 0 if not valid.
|
||||
N uint16 // number of lines - 1
|
||||
File int16 // index in Package.Files
|
||||
}
|
||||
|
||||
type source struct {
|
||||
name string
|
||||
browseURL string
|
||||
data []byte
|
||||
index int
|
||||
}
|
||||
|
||||
// PackageVersion is modified when previously stored packages are invalid.
|
||||
const PackageVersion = "8"
|
||||
|
||||
type Package struct {
|
||||
// The import path for this package.
|
||||
ImportPath string
|
||||
|
||||
// Import path prefix for all packages in the project.
|
||||
ProjectRoot string
|
||||
|
||||
// Name of the project.
|
||||
ProjectName string
|
||||
|
||||
// Project home page.
|
||||
ProjectURL string
|
||||
|
||||
// Errors found when fetching or parsing this package.
|
||||
Errors []string
|
||||
|
||||
// Packages referenced in README files.
|
||||
References []string
|
||||
|
||||
// Version control system: git, hg, bzr, ...
|
||||
VCS string
|
||||
|
||||
// Version control: active or suppressed.
|
||||
Status gosrc.DirectoryStatus
|
||||
|
||||
// Whether the package is a fork of another one.
|
||||
Fork bool
|
||||
|
||||
// How many stars (for a GitHub project) or followers (for a BitBucket
|
||||
// project) the repository of this package has.
|
||||
Stars int
|
||||
|
||||
// The time this object was created.
|
||||
Updated time.Time
|
||||
|
||||
// Cache validation tag. This tag is not necessarily an HTTP entity tag.
|
||||
// The tag is "" if there is no meaningful cache validation for the VCS.
|
||||
Etag string
|
||||
|
||||
// Subdirectories, possibly containing Go code.
|
||||
Subdirectories []string
|
||||
|
||||
// Package name or "" if no package for this import path. The proceeding
|
||||
// fields are set even if a package is not found for the import path.
|
||||
Name string
|
||||
|
||||
// Synopsis and full documentation for the package.
|
||||
Synopsis string
|
||||
Doc string
|
||||
|
||||
// Format this package as a command.
|
||||
IsCmd bool
|
||||
|
||||
// True if package documentation is incomplete.
|
||||
Truncated bool
|
||||
|
||||
// Environment
|
||||
GOOS, GOARCH string
|
||||
|
||||
// Top-level declarations.
|
||||
Consts []*Value
|
||||
Funcs []*Func
|
||||
Types []*Type
|
||||
Vars []*Value
|
||||
|
||||
// Package examples
|
||||
Examples []*Example
|
||||
|
||||
Notes map[string][]*Note
|
||||
|
||||
// Source.
|
||||
LineFmt string
|
||||
BrowseURL string
|
||||
Files []*File
|
||||
TestFiles []*File
|
||||
|
||||
// Source size in bytes.
|
||||
SourceSize int
|
||||
TestSourceSize int
|
||||
|
||||
// Imports
|
||||
Imports []string
|
||||
TestImports []string
|
||||
XTestImports []string
|
||||
}
|
||||
|
||||
var goEnvs = []struct{ GOOS, GOARCH string }{
|
||||
{"linux", "amd64"},
|
||||
{"darwin", "amd64"},
|
||||
{"windows", "amd64"},
|
||||
{"linux", "js"},
|
||||
}
|
||||
|
||||
// SetDefaultGOOS sets given GOOS value as default one to use when building
|
||||
// package documents. SetDefaultGOOS has no effect on some windows-only
|
||||
// packages.
|
||||
func SetDefaultGOOS(goos string) {
|
||||
if goos == "" {
|
||||
return
|
||||
}
|
||||
var i int
|
||||
for ; i < len(goEnvs); i++ {
|
||||
if goEnvs[i].GOOS == goos {
|
||||
break
|
||||
}
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
return
|
||||
case len(goEnvs):
|
||||
env := goEnvs[0]
|
||||
env.GOOS = goos
|
||||
goEnvs = append(goEnvs, env)
|
||||
}
|
||||
goEnvs[0], goEnvs[i] = goEnvs[i], goEnvs[0]
|
||||
}
|
||||
|
||||
var windowsOnlyPackages = map[string]bool{
|
||||
"internal/syscall/windows": true,
|
||||
"internal/syscall/windows/registry": true,
|
||||
"golang.org/x/exp/shiny/driver/internal/win32": true,
|
||||
"golang.org/x/exp/shiny/driver/windriver": true,
|
||||
"golang.org/x/sys/windows": true,
|
||||
"golang.org/x/sys/windows/registry": true,
|
||||
}
|
||||
|
||||
func newPackage(dir *gosrc.Directory) (*Package, error) {
|
||||
|
||||
pkg := &Package{
|
||||
Updated: time.Now().UTC(),
|
||||
LineFmt: dir.LineFmt,
|
||||
ImportPath: dir.ImportPath,
|
||||
ProjectRoot: dir.ProjectRoot,
|
||||
ProjectName: dir.ProjectName,
|
||||
ProjectURL: dir.ProjectURL,
|
||||
BrowseURL: dir.BrowseURL,
|
||||
Etag: PackageVersion + "-" + dir.Etag,
|
||||
VCS: dir.VCS,
|
||||
Status: dir.Status,
|
||||
Subdirectories: dir.Subdirectories,
|
||||
Fork: dir.Fork,
|
||||
Stars: dir.Stars,
|
||||
}
|
||||
|
||||
var b builder
|
||||
b.srcs = make(map[string]*source)
|
||||
references := make(map[string]bool)
|
||||
for _, file := range dir.Files {
|
||||
if strings.HasSuffix(file.Name, ".go") {
|
||||
gosrc.OverwriteLineComments(file.Data)
|
||||
b.srcs[file.Name] = &source{name: file.Name, browseURL: file.BrowseURL, data: file.Data}
|
||||
} else {
|
||||
addReferences(references, file.Data)
|
||||
}
|
||||
}
|
||||
|
||||
for r := range references {
|
||||
pkg.References = append(pkg.References, r)
|
||||
}
|
||||
|
||||
if len(b.srcs) == 0 {
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
b.fset = token.NewFileSet()
|
||||
|
||||
// Find the package and associated files.
|
||||
|
||||
ctxt := build.Context{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
CgoEnabled: true,
|
||||
ReleaseTags: build.Default.ReleaseTags,
|
||||
BuildTags: build.Default.BuildTags,
|
||||
Compiler: "gc",
|
||||
}
|
||||
|
||||
var err error
|
||||
var bpkg *build.Package
|
||||
|
||||
for _, env := range goEnvs {
|
||||
// Some packages should be always displayed as GOOS=windows (see issue #16509 for details).
|
||||
// TODO: remove this once issue #16509 is resolved.
|
||||
if windowsOnlyPackages[dir.ImportPath] && env.GOOS != "windows" {
|
||||
continue
|
||||
}
|
||||
|
||||
ctxt.GOOS = env.GOOS
|
||||
ctxt.GOARCH = env.GOARCH
|
||||
bpkg, err = dir.Import(&ctxt, build.ImportComment)
|
||||
if _, ok := err.(*build.NoGoError); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if _, ok := err.(*build.NoGoError); !ok {
|
||||
pkg.Errors = append(pkg.Errors, err.Error())
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
if bpkg.ImportComment != "" && bpkg.ImportComment != dir.ImportPath {
|
||||
return nil, gosrc.NotFoundError{
|
||||
Message: "not at canonical import path",
|
||||
Redirect: bpkg.ImportComment,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the Go files
|
||||
|
||||
files := make(map[string]*ast.File)
|
||||
names := append(bpkg.GoFiles, bpkg.CgoFiles...)
|
||||
sort.Strings(names)
|
||||
pkg.Files = make([]*File, len(names))
|
||||
for i, name := range names {
|
||||
file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
|
||||
if err != nil {
|
||||
pkg.Errors = append(pkg.Errors, err.Error())
|
||||
} else {
|
||||
files[name] = file
|
||||
}
|
||||
src := b.srcs[name]
|
||||
src.index = i
|
||||
pkg.Files[i] = &File{Name: name, URL: src.browseURL}
|
||||
pkg.SourceSize += len(src.data)
|
||||
}
|
||||
|
||||
apkg, _ := ast.NewPackage(b.fset, files, simpleImporter, nil)
|
||||
|
||||
// Find examples in the test files.
|
||||
|
||||
names = append(bpkg.TestGoFiles, bpkg.XTestGoFiles...)
|
||||
sort.Strings(names)
|
||||
pkg.TestFiles = make([]*File, len(names))
|
||||
for i, name := range names {
|
||||
file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments)
|
||||
if err != nil {
|
||||
pkg.Errors = append(pkg.Errors, err.Error())
|
||||
} else {
|
||||
b.examples = append(b.examples, doc.Examples(file)...)
|
||||
}
|
||||
pkg.TestFiles[i] = &File{Name: name, URL: b.srcs[name].browseURL}
|
||||
pkg.TestSourceSize += len(b.srcs[name].data)
|
||||
}
|
||||
|
||||
b.vetPackage(pkg, apkg)
|
||||
|
||||
mode := doc.Mode(0)
|
||||
if pkg.ImportPath == "builtin" {
|
||||
mode |= doc.AllDecls
|
||||
}
|
||||
|
||||
dpkg := doc.New(apkg, pkg.ImportPath, mode)
|
||||
|
||||
if pkg.ImportPath == "builtin" {
|
||||
removeAssociations(dpkg)
|
||||
}
|
||||
|
||||
pkg.Name = dpkg.Name
|
||||
pkg.Doc = strings.TrimRight(dpkg.Doc, " \t\n\r")
|
||||
pkg.Synopsis = synopsis(pkg.Doc)
|
||||
|
||||
pkg.Examples = b.getExamples("")
|
||||
pkg.IsCmd = bpkg.IsCommand()
|
||||
pkg.GOOS = ctxt.GOOS
|
||||
pkg.GOARCH = ctxt.GOARCH
|
||||
|
||||
pkg.Consts = b.values(dpkg.Consts)
|
||||
pkg.Funcs = b.funcs(dpkg.Funcs)
|
||||
pkg.Types = b.types(dpkg.Types)
|
||||
pkg.Vars = b.values(dpkg.Vars)
|
||||
pkg.Notes = b.notes(dpkg.Notes)
|
||||
|
||||
pkg.Imports = bpkg.Imports
|
||||
pkg.TestImports = bpkg.TestImports
|
||||
pkg.XTestImports = bpkg.XTestImports
|
||||
|
||||
return pkg, nil
|
||||
}
|
Reference in New Issue
Block a user