func (p *Package) Examples() []*Example { if p.examples != nil || p.bpkg == nil { return p.examples } fset := token.NewFileSet() var files []*ast.File for _, val := range [][]string{p.bpkg.TestGoFiles, p.bpkg.XTestGoFiles} { for _, v := range val { f, err := parser.ParseFile(fset, p.env.Join(p.Dir(), v), nil, parser.ParseComments) if err == nil { files = append(files, f) } } } for _, v := range doc.Examples(files...) { p.examples = append(p.examples, &Example{ fset: fset, pkg: p, example: v, }) } p.examplesByKey = make(map[string][]*Example) for _, v := range p.examples { k := v.Key() p.examplesByKey[k] = append(p.examplesByKey[k], v) } if p.examples == nil { p.examples = make([]*Example, 0, 1) } return p.examples }
// parseExamples gets examples for packages in pkgs from *_test.go files in dir. func parseExamples(fset *token.FileSet, pkgs map[string]*ast.Package, dir string) ([]*doc.Example, error) { var examples []*doc.Example filter := func(d os.FileInfo) bool { return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") } testpkgs, err := parseDir(fset, dir, filter) if err != nil { return nil, err } globals := globalNames(pkgs) for _, testpkg := range testpkgs { var files []*ast.File for _, f := range testpkg.Files { files = append(files, f) } for _, e := range doc.Examples(files...) { name := stripExampleSuffix(e.Name) if name == "" || globals[name] { examples = append(examples, e) } else { log.Printf("skipping example Example%s: refers to unknown function or type", e.Name) } } } return examples, nil }
func (t *testFuncs) load(filename, pkg string, seen *bool) error { f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) if err != nil { return expandScanner(err) } for _, d := range f.Decls { n, ok := d.(*ast.FuncDecl) if !ok { continue } if n.Recv != nil { continue } name := n.Name.String() switch { case isTest(name, "Test"): t.Tests = append(t.Tests, testFunc{pkg, name, ""}) *seen = true case isTest(name, "Benchmark"): t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""}) *seen = true } } for _, e := range doc.Examples(f) { if e.Output == "" { // Don't run examples with no output. continue } t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output}) *seen = true } return nil }
func TestExamples(t *testing.T) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments) if err != nil { t.Fatal(err) } for i, e := range doc.Examples(file) { c := exampleTestCases[i] if e.Name != c.Name { t.Errorf("got Name == %q, want %q", e.Name, c.Name) } if w := c.Play; w != "" { var g string // hah if e.Play == nil { g = "<nil>" } else { var buf bytes.Buffer if err := format.Node(&buf, fset, e.Play); err != nil { t.Fatal(err) } g = buf.String() } if g != w { t.Errorf("%s: got Play == %q, want %q", c.Name, g, w) } } if g, w := e.Output, c.Output; g != w { t.Errorf("%s: got Output == %q, want %q", c.Name, g, w) } } }
func (ctx *Context) loadPackage(importPath string, flags int) (*Package, error) { bpkg, err := build.Import(importPath, ctx.cwd, 0) if _, ok := err.(*build.NoGoError); ok { return &Package{bpkg: bpkg}, nil } if err != nil { return nil, err } pkg := &Package{ fset: token.NewFileSet(), bpkg: bpkg, } files := make(map[string]*ast.File) for _, name := range append(pkg.bpkg.GoFiles, pkg.bpkg.CgoFiles...) { file, err := pkg.parseFile(name) if err != nil { pkg.errors = append(pkg.errors, err) continue } files[name] = file } pkg.apkg, _ = ast.NewPackage(pkg.fset, files, simpleImporter, nil) if flags&loadDoc != 0 { mode := doc.Mode(0) if pkg.bpkg.ImportPath == "builtin" || flags&loadUnexported != 0 { mode |= doc.AllDecls } pkg.dpkg = doc.New(pkg.apkg, pkg.bpkg.ImportPath, mode) if pkg.bpkg.ImportPath == "builtin" { for _, t := range pkg.dpkg.Types { pkg.dpkg.Funcs = append(pkg.dpkg.Funcs, t.Funcs...) t.Funcs = nil } sort.Sort(byFuncName(pkg.dpkg.Funcs)) } } if flags&loadExamples != 0 { for _, name := range append(pkg.bpkg.TestGoFiles, pkg.bpkg.XTestGoFiles...) { file, err := pkg.parseFile(name) if err != nil { pkg.errors = append(pkg.errors, err) continue } pkg.examples = append(pkg.examples, doc.Examples(file)...) } } return pkg, nil }
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error { f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) if err != nil { return expandScanner(err) } for _, d := range f.Decls { n, ok := d.(*ast.FuncDecl) if !ok { continue } if n.Recv != nil { continue } name := n.Name.String() switch { case name == "TestMain" && isTestFunc(n, "M"): if t.TestMain != nil { return errors.New("multiple definitions of TestMain") } t.TestMain = &testFunc{pkg, name, "", false} *doImport, *seen = true, true case isTest(name, "Test"): err := checkTestFunc(n, "T") if err != nil { return err } t.Tests = append(t.Tests, testFunc{pkg, name, "", false}) *doImport, *seen = true, true case isTest(name, "Benchmark"): err := checkTestFunc(n, "B") if err != nil { return err } t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false}) *doImport, *seen = true, true } } ex := doc.Examples(f) sort.Sort(byOrder(ex)) for _, e := range ex { *doImport = true // import test file whether executed or not if e.Output == "" && !e.EmptyOutput { // Don't run examples with no output. continue } t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered}) *seen = true } return nil }
// collectExamples collects examples for pkg from testfiles. func collectExamples(c *Corpus, pkg *ast.Package, testfiles map[string]*ast.File) []*doc.Example { var files []*ast.File for _, f := range testfiles { files = append(files, f) } var examples []*doc.Example globals := globalNames(pkg) for _, e := range doc.Examples(files...) { name := stripExampleSuffix(e.Name) if name == "" || globals[name] { examples = append(examples, e) } else if c.Verbose { log.Printf("skipping example 'Example%s' because '%s' is not a known function or type", e.Name, e.Name) } } return examples }
func buildDoc(importPath string, files []string) (*Package, error) { b := &builder{ fset: token.NewFileSet(), importPaths: make(map[string]map[string]string), } pkgs := make(map[string]*ast.Package) for _, f := range files { if strings.HasSuffix(f, "_test.go") { continue } if src, err := parser.ParseFile(b.fset, f, nil, parser.ParseComments); err == nil { name := src.Name.Name pkg, found := pkgs[name] if !found { pkg = &ast.Package{Name: name, Files: make(map[string]*ast.File)} pkgs[name] = pkg } pkg.Files[f] = src } } score := 0 for _, pkg := range pkgs { switch { case score < 3 && strings.HasSuffix(importPath, pkg.Name): b.pkg = pkg score = 3 case score < 2 && pkg.Name != "main": b.pkg = pkg score = 2 case score < 1: b.pkg = pkg score = 1 } } if b.pkg == nil { return nil, fmt.Errorf("Package %s not found", importPath) } ast.PackageExports(b.pkg) pdoc := doc.New(b.pkg, importPath, 0) pdoc.Doc = strings.TrimRight(pdoc.Doc, " \t\n\r") // Collect examples. for _, f := range files { if !strings.HasSuffix(f, "_test.go") { continue } src, err := parser.ParseFile(b.fset, f, nil, parser.ParseComments) if err != nil { continue } if src.Name.Name != pdoc.Name && src.Name.Name != pdoc.Name+"_test" { continue } b.examples = append(b.examples, doc.Examples(src)...) } return &Package{ Consts: b.values(pdoc.Consts), Doc: pdoc.Doc, Examples: b.getExamples(""), Files: b.files(pdoc.Filenames), Funcs: b.funcs(pdoc.Funcs), ImportPath: pdoc.ImportPath, Name: pdoc.Name, Types: b.types(pdoc.Types), Updated: time.Now(), Vars: b.values(pdoc.Vars), }, nil }
// getPageInfo returns the PageInfo for a package directory abspath. If the // parameter genAST is set, an AST containing only the package exports is // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) // is extracted from the AST. If there is no corresponding package in the // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- // directories, PageInfo.Dirs is nil. If a directory read error occurred, // PageInfo.Err is set to the respective error but the error is not logged. // func (h *docServer) getPageInfo(abspath, relpath, pkgname string, mode PageInfoMode) PageInfo { var pkgFiles []string // If we're showing the default package, restrict to the ones // that would be used when building the package on this // system. This makes sure that if there are separate // implementations for, say, Windows vs Unix, we don't // jumble them all together. if pkgname == "" { // Note: Uses current binary's GOOS/GOARCH. // To use different pair, such as if we allowed the user // to choose, set ctxt.GOOS and ctxt.GOARCH before // calling ctxt.ScanDir. ctxt := build.Default ctxt.IsAbsPath = pathpkg.IsAbs ctxt.ReadDir = fsReadDir ctxt.OpenFile = fsOpenFile dir, err := ctxt.ImportDir(abspath, 0) if err == nil { pkgFiles = append(dir.GoFiles, dir.CgoFiles...) } } // filter function to select the desired .go files filter := func(d os.FileInfo) bool { // Only Go files. if !isPkgFile(d) { return false } // If we are looking at cmd documentation, only accept // the special fakePkgFile containing the documentation. if !h.isPkg { return d.Name() == fakePkgFile } // Also restrict file list to pkgFiles. return pkgFiles == nil || inList(d.Name(), pkgFiles) } // get package ASTs fset := token.NewFileSet() pkgs, err := parseDir(fset, abspath, filter) if err != nil && pkgs == nil { // only report directory read errors, ignore parse errors // (may be able to extract partial package information) return PageInfo{Dirname: abspath, Err: err} } // select package var pkg *ast.Package // selected package var plist []string // list of other package (names), if any if len(pkgs) == 1 { // Exactly one package - select it. for _, p := range pkgs { pkg = p } } else if len(pkgs) > 1 { // Multiple packages - select the best matching package: The // 1st choice is the package with pkgname, the 2nd choice is // the package with dirname, and the 3rd choice is a package // that is not called "main" if there is exactly one such // package. Otherwise, don't select a package. dirpath, dirname := pathpkg.Split(abspath) // If the dirname is "go" we might be in a sub-directory for // .go files - use the outer directory name instead for better // results. if dirname == "go" { _, dirname = pathpkg.Split(pathpkg.Clean(dirpath)) } var choice3 *ast.Package loop: for _, p := range pkgs { switch { case p.Name == pkgname: pkg = p break loop // 1st choice; we are done case p.Name == dirname: pkg = p // 2nd choice case p.Name != "main": choice3 = p } } if pkg == nil && len(pkgs) == 2 { pkg = choice3 } // Compute the list of other packages // (excluding the selected package, if any). plist = make([]string, len(pkgs)) i := 0 for name := range pkgs { if pkg == nil || name != pkg.Name { plist[i] = name i++ } } plist = plist[0:i] sort.Strings(plist) } // get examples from *_test.go files var examples []*doc.Example filter = func(d os.FileInfo) bool { return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") } if testpkgs, err := parseDir(fset, abspath, filter); err != nil { log.Println("parsing test files:", err) } else { for _, testpkg := range testpkgs { var files []*ast.File for _, f := range testpkg.Files { files = append(files, f) } examples = append(examples, doc.Examples(files...)...) } } // compute package documentation var past *ast.File var pdoc *doc.Package if pkg != nil { if mode&showSource == 0 { // show extracted documentation var m doc.Mode if mode&noFiltering != 0 { m = doc.AllDecls } if mode&allMethods != 0 { m |= doc.AllMethods } pdoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath } else { // show source code // TODO(gri) Consider eliminating export filtering in this mode, // or perhaps eliminating the mode altogether. if mode&noFiltering == 0 { ast.PackageExports(pkg) } past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments) } } // get directory information var dir *Directory var timestamp time.Time if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { // directory tree is present; lookup respective directory // (may still fail if the file system was updated and the // new directory tree has not yet been computed) dir = tree.(*Directory).lookup(abspath) timestamp = ts } if dir == nil { // no directory tree present (too early after startup or // command-line mode); compute one level for this page // note: cannot use path filter here because in general // it doesn't contain the fsTree path dir = newDirectory(abspath, 1) timestamp = time.Now() } return PageInfo{ Dirname: abspath, PList: plist, FSet: fset, PAst: past, PDoc: pdoc, Examples: examples, Dirs: dir.listing(true), DirTime: timestamp, DirFlat: mode&flatDir != 0, IsPkg: h.isPkg, Err: nil, } }
func (b *builder) build(srcs []*source) (*Package, error) { b.pdoc.Updated = time.Now().UTC() references := make(map[string]bool) b.srcs = make(map[string]*source) for _, src := range srcs { if strings.HasSuffix(src.name, ".go") { b.srcs[src.name] = src } else { addReferences(references, src.data) fn := strings.ToLower(src.name) if fn == "readme" || strings.HasPrefix(fn, "readme.") { if b.pdoc.ReadmeFiles == nil { b.pdoc.ReadmeFiles = make(map[string][]byte) } b.pdoc.ReadmeFiles[src.name] = src.data } } } for r := range references { b.pdoc.References = append(b.pdoc.References, r) } if len(b.srcs) == 0 { return b.pdoc, nil } b.fset = token.NewFileSet() // Find the package and associated files. ctxt := build.Context{ GOOS: "linux", GOARCH: "amd64", CgoEnabled: true, ReleaseTags: build.Default.ReleaseTags, JoinPath: path.Join, IsAbsPath: path.IsAbs, SplitPathList: func(list string) []string { return strings.Split(list, ":") }, IsDir: func(path string) bool { panic("unexpected") }, HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") }, ReadDir: func(dir string) (fi []os.FileInfo, err error) { return b.readDir(dir) }, OpenFile: func(path string) (r io.ReadCloser, err error) { return b.openFile(path) }, Compiler: "gc", } var err error var bpkg *build.Package for _, env := range goEnvs { ctxt.GOOS = env.GOOS ctxt.GOARCH = env.GOARCH bpkg, err = ctxt.ImportDir("/", 0) if _, ok := err.(*build.NoGoError); !ok { break } } if err != nil { if _, ok := err.(*build.NoGoError); !ok { b.pdoc.Errors = append(b.pdoc.Errors, err.Error()) } return b.pdoc, nil } // Parse the Go files files := make(map[string]*ast.File) names := append(bpkg.GoFiles, bpkg.CgoFiles...) sort.Strings(names) b.pdoc.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 { b.pdoc.Errors = append(b.pdoc.Errors, err.Error()) continue } src := b.srcs[name] src.index = i b.pdoc.Files[i] = &File{Name: name, URL: src.browseURL} b.pdoc.SourceSize += len(src.data) files[name] = file } apkg, _ := ast.NewPackage(b.fset, files, simpleImporter, nil) // Find examples in the test files. names = append(bpkg.TestGoFiles, bpkg.XTestGoFiles...) sort.Strings(names) b.pdoc.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 { b.pdoc.Errors = append(b.pdoc.Errors, err.Error()) continue } b.pdoc.TestFiles[i] = &File{Name: name, URL: b.srcs[name].browseURL} b.pdoc.TestSourceSize += len(b.srcs[name].data) b.examples = append(b.examples, doc.Examples(file)...) } b.vetPackage(apkg) mode := doc.Mode(0) if b.pdoc.ImportPath == "builtin" { mode |= doc.AllDecls } dpkg := doc.New(apkg, b.pdoc.ImportPath, mode) b.pdoc.Name = dpkg.Name b.pdoc.Doc = strings.TrimRight(dpkg.Doc, " \t\n\r") b.pdoc.Synopsis = synopsis(b.pdoc.Doc) b.pdoc.Examples = b.getExamples("") b.pdoc.IsCmd = bpkg.IsCommand() b.pdoc.GOOS = ctxt.GOOS b.pdoc.GOARCH = ctxt.GOARCH b.pdoc.Consts = b.values(dpkg.Consts) b.pdoc.Funcs = b.funcs(dpkg.Funcs) b.pdoc.Types = b.types(dpkg.Types) b.pdoc.Vars = b.values(dpkg.Vars) b.pdoc.Notes = b.notes(dpkg.Notes) b.pdoc.Imports = bpkg.Imports b.pdoc.TestImports = bpkg.TestImports b.pdoc.XTestImports = bpkg.XTestImports return b.pdoc, nil }
// getPageInfo returns the PageInfo for a package directory abspath. If the // parameter genAST is set, an AST containing only the package exports is // computed (PageInfo.PAst), otherwise package documentation (PageInfo.Doc) // is extracted from the AST. If there is no corresponding package in the // directory, PageInfo.PAst and PageInfo.PDoc are nil. If there are no sub- // directories, PageInfo.Dirs is nil. If a directory read error occurred, // PageInfo.Err is set to the respective error but the error is not logged. // func (h *docServer) getPageInfo(abspath, relpath string, mode PageInfoMode) PageInfo { var pkgFiles []string // Restrict to the package files // that would be used when building the package on this // system. This makes sure that if there are separate // implementations for, say, Windows vs Unix, we don't // jumble them all together. // Note: Uses current binary's GOOS/GOARCH. // To use different pair, such as if we allowed the user // to choose, set ctxt.GOOS and ctxt.GOARCH before // calling ctxt.ScanDir. ctxt := build.Default ctxt.IsAbsPath = pathpkg.IsAbs ctxt.ReadDir = fsReadDir ctxt.OpenFile = fsOpenFile if dir, err := ctxt.ImportDir(abspath, 0); err == nil { pkgFiles = append(dir.GoFiles, dir.CgoFiles...) } // filter function to select the desired .go files filter := func(d os.FileInfo) bool { // Only Go files. if !isPkgFile(d) { return false } // If we are looking at cmd documentation, only accept // the special fakePkgFile containing the documentation. if !h.isPkg { return d.Name() == fakePkgFile } // Also restrict file list to pkgFiles. return pkgFiles == nil || inList(d.Name(), pkgFiles) } // get package ASTs fset := token.NewFileSet() pkgs, err := parseDir(fset, abspath, filter) if err != nil { return PageInfo{Dirname: abspath, Err: err} } // select package var pkg *ast.Package // selected package if len(pkgs) == 1 { // Exactly one package - select it. for _, p := range pkgs { pkg = p } } else if len(pkgs) > 1 { // More than one package - report an error. var buf bytes.Buffer for _, p := range pkgs { if buf.Len() > 0 { fmt.Fprintf(&buf, ", ") } fmt.Fprintf(&buf, p.Name) } return PageInfo{ Dirname: abspath, Err: fmt.Errorf("%s contains more than one package: %s", abspath, buf.Bytes()), } } // get examples from *_test.go files var examples []*doc.Example filter = func(d os.FileInfo) bool { return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") } if testpkgs, err := parseDir(fset, abspath, filter); err != nil { log.Println("parsing test files:", err) } else { for _, testpkg := range testpkgs { var files []*ast.File for _, f := range testpkg.Files { files = append(files, f) } examples = append(examples, doc.Examples(files...)...) } } // compute package documentation var past *ast.File var pdoc *doc.Package if pkg != nil { if mode&showSource == 0 { // show extracted documentation var m doc.Mode if mode&noFiltering != 0 { m = doc.AllDecls } if mode&allMethods != 0 { m |= doc.AllMethods } pdoc = doc.New(pkg, pathpkg.Clean(relpath), m) // no trailing '/' in importpath } else { // show source code // TODO(gri) Consider eliminating export filtering in this mode, // or perhaps eliminating the mode altogether. if mode&noFiltering == 0 { packageExports(fset, pkg) } past = ast.MergePackageFiles(pkg, 0) } } // get directory information var dir *Directory var timestamp time.Time if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { // directory tree is present; lookup respective directory // (may still fail if the file system was updated and the // new directory tree has not yet been computed) dir = tree.(*Directory).lookup(abspath) timestamp = ts } if dir == nil { // no directory tree present (too early after startup or // command-line mode); compute one level for this page // note: cannot use path filter here because in general // it doesn't contain the fsTree path dir = newDirectory(abspath, 1) timestamp = time.Now() } return PageInfo{ Dirname: abspath, FSet: fset, PAst: past, PDoc: pdoc, Examples: examples, Dirs: dir.listing(true), DirTime: timestamp, DirFlat: mode&flatDir != 0, IsPkg: h.isPkg, Err: nil, } }
// loadPackage returns details about the Go package named by the import // path, interpreting local import paths relative to the srcDir directory. func loadPackage(ctx *build.Context, importPath string, srcDir string, flags int) (*pkg, error) { bpkg, err := ctx.Import(importPath, srcDir, build.ImportComment) if _, ok := err.(*build.NoGoError); ok { return &pkg{Build: bpkg}, nil } if err != nil { return nil, err } pkg := &pkg{ FSet: token.NewFileSet(), Build: bpkg, } files := make(map[string]*ast.File) for _, name := range append(pkg.Build.GoFiles, pkg.Build.CgoFiles...) { file, err := pkg.parseFile(ctx, name) if err != nil { pkg.Errors = append(pkg.Errors, err) continue } files[name] = file } vendor := make(map[string]string) pkg.AST, _ = ast.NewPackage(pkg.FSet, files, importer(ctx, bpkg.Dir, vendor), nil) if flags&loadPackageFixVendor != 0 { for _, f := range pkg.AST.Files { for _, i := range f.Imports { if lit := i.Path; lit != nil { if s, err := strconv.Unquote(lit.Value); err != nil { if p, ok := vendor[s]; ok { lit.Value = strconv.Quote(p) } } } } } } if flags&loadPackageDoc != 0 { mode := godoc.Mode(0) if pkg.Build.ImportPath == "builtin" || flags&loadPackageUnexported != 0 { mode |= godoc.AllDecls } pkg.GoDoc = godoc.New(pkg.AST, pkg.Build.ImportPath, mode) if pkg.Build.ImportPath == "builtin" { for _, t := range pkg.GoDoc.Types { pkg.GoDoc.Funcs = append(pkg.GoDoc.Funcs, t.Funcs...) t.Funcs = nil } sort.Sort(byFuncName(pkg.GoDoc.Funcs)) } } if flags&loadPackageExamples != 0 { for _, name := range append(pkg.Build.TestGoFiles, pkg.Build.XTestGoFiles...) { file, err := pkg.parseFile(ctx, name) if err != nil { pkg.Errors = append(pkg.Errors, err) continue } pkg.Examples = append(pkg.Examples, godoc.Examples(file)...) } } return pkg, nil }
func (b *builder) build(srcs []*source) (*Package, error) { b.pkg.Updated = time.Now().UTC() references := make(map[string]bool) b.srcs = make(map[string]*source) for _, src := range srcs { if strings.HasSuffix(src.name, ".go") { b.srcs[src.name] = src } else { addReferences(references, src.data) } } for r := range references { b.pkg.References = append(b.pkg.References, r) } if len(b.srcs) == 0 { return b.pkg, nil } b.fset = token.NewFileSet() b.importPaths = make(map[string]map[string]string) // Find the package and associated files. ctxt := build.Context{ GOOS: "linux", GOARCH: "amd64", CgoEnabled: true, JoinPath: path.Join, IsAbsPath: path.IsAbs, SplitPathList: func(list string) []string { return strings.Split(list, ":") }, IsDir: func(path string) bool { panic("unexpected") }, HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") }, ReadDir: func(dir string) (fi []os.FileInfo, err error) { return b.readDir(dir) }, OpenFile: func(path string) (r io.ReadCloser, err error) { return b.openFile(path) }, Compiler: "gc", } pkg, err := ctxt.ImportDir(b.pkg.ImportPath, 0) if _, ok := err.(*build.NoGoError); ok { return b.pkg, nil } else if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) return b.pkg, nil } // Parse the Go files b.ast = &ast.Package{Name: pkg.Name, Files: make(map[string]*ast.File)} if pkg.IsCommand() && b.srcs["doc.go"] != nil { file, err := parser.ParseFile(b.fset, "doc.go", b.srcs["doc.go"].data, parser.ParseComments) if err == nil && file.Name.Name == "documentation" { b.ast.Files["doc.go"] = file } } if len(b.ast.Files) == 0 { for _, name := range append(pkg.GoFiles, pkg.CgoFiles...) { file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments) if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) continue } b.pkg.Files = append(b.pkg.Files, &File{Name: name, URL: b.srcs[name].browseURL}) b.pkg.SourceSize += len(b.srcs[name].data) b.ast.Files[name] = file } } // Find examples in the test files. for _, name := range append(pkg.TestGoFiles, pkg.XTestGoFiles...) { file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments) if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) continue } b.pkg.TestFiles = append(b.pkg.TestFiles, &File{Name: name, URL: b.srcs[name].browseURL}) b.pkg.TestSourceSize += len(b.srcs[name].data) b.examples = append(b.examples, doc.Examples(file)...) } b.vetPackage() mode := doc.Mode(0) if b.pkg.ImportPath == "builtin" { mode |= doc.AllDecls } pdoc := doc.New(b.ast, b.pkg.ImportPath, mode) b.pkg.Name = pdoc.Name b.pkg.Doc = strings.TrimRight(pdoc.Doc, " \t\n\r") b.pkg.Synopsis = synopsis(b.pkg.Doc) b.pkg.Examples = b.getExamples("") b.pkg.IsCmd = pkg.IsCommand() b.pkg.Consts = b.values(pdoc.Consts) b.pkg.Funcs = b.funcs(pdoc.Funcs) b.pkg.Types = b.types(pdoc.Types) b.pkg.Vars = b.values(pdoc.Vars) b.pkg.Bugs = pdoc.Bugs b.pkg.Imports = pkg.Imports b.pkg.TestImports = pkg.TestImports b.pkg.XTestImports = pkg.XTestImports return b.pkg, nil }
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, DeadEndFork: dir.DeadEndFork, Subdirectories: dir.Subdirectories, } 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 { ctxt.GOOS = env.GOOS ctxt.GOARCH = env.GOARCH // TODO(garyburd): Change second argument to build.ImportComment when // gddo is upgraded to Go 1.4. bpkg, err = dir.Import(&ctxt, 0 /* 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 } /* TODO(garyburd): This block of code uses the import comment feature added in Go 1.4. Uncomment this block when gddo upgraded to Go 1.4. Also, change the second argument to dir.Import above from 0 to build.ImportComment. 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 }
// build generates data from source files. func (w *walker) build(srcs []*source) (*Package, error) { // Set created time. w.pdoc.Created = time.Now().UTC() // Add source files to walker, I skipped references here. w.srcs = make(map[string]*source) for _, src := range srcs { srcName := strings.ToLower(src.name) // For readme comparation. switch { case strings.HasSuffix(src.name, ".go"): w.srcs[src.name] = src case len(w.pdoc.Tag) > 0: continue // Only save latest readme. case strings.HasPrefix(srcName, "readme_zh") || strings.HasPrefix(srcName, "readme_cn"): models.SavePkgDoc(w.pdoc.ImportPath, "zh", src.data) case strings.HasPrefix(srcName, "readme"): models.SavePkgDoc(w.pdoc.ImportPath, "en", src.data) } } w.fset = token.NewFileSet() // Find the package and associated files. ctxt := build.Context{ GOOS: runtime.GOOS, GOARCH: runtime.GOARCH, CgoEnabled: true, JoinPath: path.Join, IsAbsPath: path.IsAbs, SplitPathList: func(list string) []string { return strings.Split(list, ":") }, IsDir: func(path string) bool { panic("unexpected") }, HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") }, ReadDir: func(dir string) (fi []os.FileInfo, err error) { return w.readDir(dir) }, OpenFile: func(path string) (r io.ReadCloser, err error) { return w.openFile(path) }, Compiler: "gc", } bpkg, err := ctxt.ImportDir(w.pdoc.ImportPath, 0) // Continue if there are no Go source files; we still want the directory info. _, nogo := err.(*build.NoGoError) if err != nil { if nogo { err = nil beego.Info("doc.walker.build -> No Go Source file") } else { return w.pdoc, errors.New("doc.walker.build -> " + err.Error()) } } // Parse the Go files files := make(map[string]*ast.File) for _, name := range append(bpkg.GoFiles, bpkg.CgoFiles...) { file, err := parser.ParseFile(w.fset, name, w.srcs[name].data, parser.ParseComments) if err != nil { beego.Error("doc.walker.build -> parse go files:", err) continue } w.pdoc.Files = append(w.pdoc.Files, name) //w.pdoc.SourceSize += len(w.srcs[name].data) files[name] = file } apkg, _ := ast.NewPackage(w.fset, files, simpleImporter, nil) // Find examples in the test files. for _, name := range append(bpkg.TestGoFiles, bpkg.XTestGoFiles...) { file, err := parser.ParseFile(w.fset, name, w.srcs[name].data, parser.ParseComments) if err != nil { beego.Error("doc.walker.build -> find examples:", err) continue } //w.pdoc.TestFiles = append(w.pdoc.TestFiles, &File{Name: name, URL: w.srcs[name].browseURL}) //w.pdoc.TestSourceSize += len(w.srcs[name].data) w.examples = append(w.examples, doc.Examples(file)...) } //w.vetPackage(apkg) mode := doc.Mode(0) if w.pdoc.ImportPath == "builtin" { mode |= doc.AllDecls } pdoc := doc.New(apkg, w.pdoc.ImportPath, mode) w.pdoc.Synopsis = utils.Synopsis(pdoc.Doc) pdoc.Doc = strings.TrimRight(pdoc.Doc, " \t\n\r") var buf bytes.Buffer doc.ToHTML(&buf, pdoc.Doc, nil) w.pdoc.Doc = w.pdoc.Doc + "<br />" + buf.String() w.pdoc.Doc = strings.Replace(w.pdoc.Doc, "<p>", "<p><b>", 1) w.pdoc.Doc = strings.Replace(w.pdoc.Doc, "</p>", "</b></p>", 1) w.pdoc.Doc = base32.StdEncoding.EncodeToString([]byte(w.pdoc.Doc)) w.pdoc.Examples = w.getExamples("") w.pdoc.IsCmd = bpkg.IsCommand() w.srcLines = make(map[string][]string) w.pdoc.Consts = w.values(pdoc.Consts) w.pdoc.Funcs = w.funcs(pdoc.Funcs) w.pdoc.Types = w.types(pdoc.Types) w.pdoc.Vars = w.values(pdoc.Vars) //w.pdoc.Notes = w.notes(pdoc.Notes) w.pdoc.Imports = bpkg.Imports w.pdoc.TestImports = bpkg.TestImports //w.pdoc.XTestImports = bpkg.XTestImports beego.Info("doc.walker.build(", pdoc.ImportPath, "), Goroutine #", runtime.NumGoroutine()) return w.pdoc, err }
func buildDoc(importPath, projectRoot, projectName, projectURL, etag string, lineFmt string, srcs []*source) (*Package, error) { b := &builder{ lineFmt: lineFmt, fset: token.NewFileSet(), importPaths: make(map[string]map[string]string), srcs: make(map[string]*source), pkg: &Package{ ImportPath: importPath, ProjectName: projectName, ProjectRoot: projectRoot, ProjectURL: projectURL, Etag: etag, Updated: time.Now(), }, } if len(srcs) == 0 { return b.pkg, nil } for _, src := range srcs { b.srcs[src.name] = src } // Find the package and associated files. ctxt := build.Context{ GOOS: "linux", GOARCH: "amd64", CgoEnabled: true, JoinPath: path.Join, IsAbsPath: path.IsAbs, SplitPathList: func(list string) []string { return strings.Split(list, ":") }, IsDir: func(path string) bool { panic("unexpected") }, HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") }, ReadDir: func(dir string) (fi []os.FileInfo, err error) { return b.readDir(dir) }, OpenFile: func(path string) (r io.ReadCloser, err error) { return b.openFile(path) }, Compiler: "gc", } pkg, err := ctxt.ImportDir(b.pkg.ImportPath, 0) if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) return b.pkg, nil } // Parse the Go files b.ast = &ast.Package{Name: pkg.Name, Files: make(map[string]*ast.File)} if pkg.IsCommand() && b.srcs["doc.go"] != nil { file, err := parser.ParseFile(b.fset, "doc.go", b.srcs["doc.go"].data, parser.ParseComments) if err == nil && file.Name.Name == "documentation" { b.ast.Files["doc.go"] = file } } if len(b.ast.Files) == 0 { for _, name := range append(pkg.GoFiles, pkg.CgoFiles...) { file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments) if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) continue } b.pkg.Files = append(b.pkg.Files, &File{Name: name, URL: b.srcs[name].browseURL}) b.pkg.SourceSize += len(b.srcs[name].data) b.ast.Files[name] = file } } // Find examples in the test files. for _, name := range append(pkg.TestGoFiles, pkg.XTestGoFiles...) { file, err := parser.ParseFile(b.fset, name, b.srcs[name].data, parser.ParseComments) if err != nil { b.pkg.Errors = append(b.pkg.Errors, err.Error()) continue } b.pkg.TestFiles = append(b.pkg.TestFiles, &File{Name: name, URL: b.srcs[name].browseURL}) b.pkg.TestSourceSize += len(b.srcs[name].data) b.examples = append(b.examples, doc.Examples(file)...) } b.vetPackage() pdoc := doc.New(b.ast, b.pkg.ImportPath, 0) b.pkg.Name = pdoc.Name b.pkg.Doc = strings.TrimRight(pdoc.Doc, " \t\n\r") b.pkg.Synopsis = synopsis(b.pkg.Doc) b.pkg.Examples = b.getExamples("") b.pkg.IsCmd = pkg.IsCommand() b.pkg.Consts = b.values(pdoc.Consts) b.pkg.Funcs = b.funcs(pdoc.Funcs) b.pkg.Types = b.types(pdoc.Types) b.pkg.Vars = b.values(pdoc.Vars) b.pkg.Imports = pkg.Imports b.pkg.TestImports = pkg.TestImports return b.pkg, nil }
// Build generates documentation from given source files through 'WalkType'. func (w *Walker) Build(wr *WalkRes) (*Package, error) { ctxt := build.Context{ CgoEnabled: true, ReleaseTags: build.Default.ReleaseTags, BuildTags: build.Default.BuildTags, Compiler: "gc", } if w.Pdoc.PkgDecl == nil { w.Pdoc.PkgDecl = &PkgDecl{} } // Check 'WalkType'. switch wr.WalkType { case WT_Local: // Check root path. if len(wr.RootPath) == 0 { return nil, errors.New("WT_Local: empty root path") } else if !com.IsDir(wr.RootPath) { return nil, errors.New("WT_Local: cannot find specific directory or it's a file") } w.setLocalContext(&ctxt) return nil, errors.New("Hasn't supported yet!") case WT_Memory: // Convert source files. w.SrcFiles = make(map[string]*Source) w.Pdoc.Readme = make(map[string][]byte) for _, src := range wr.Srcs { srcName := strings.ToLower(src.Name()) // For readme comparation. switch { case strings.HasSuffix(src.Name(), ".go"): w.SrcFiles[src.Name()] = src case len(w.Pdoc.Tag) > 0 || (wr.WalkMode&WM_NoReadme != 0): // This means we are not on the latest version of the code, // so we do not collect the README files. continue case strings.HasPrefix(srcName, "readme_zh") || strings.HasPrefix(srcName, "readme_cn"): w.Pdoc.Readme["zh"] = src.Data() case strings.HasPrefix(srcName, "readme"): w.Pdoc.Readme["en"] = src.Data() } } // Check source files. if w.SrcFiles == nil { return nil, errors.New("WT_Memory: no Go source file") } w.setMemoryContext(&ctxt) default: return nil, errors.New("Hasn't supported yet!") } var err error var bpkg *build.Package for _, env := range goEnvs { ctxt.GOOS = env.GOOS ctxt.GOARCH = env.GOARCH bpkg, err = ctxt.ImportDir(w.Pdoc.ImportPath, 0) // Continue if there are no Go source files; we still want the directory info. _, nogo := err.(*build.NoGoError) if err != nil { if nogo { err = nil } else { return nil, errors.New("hv.Walker.Build -> ImportDir: " + err.Error()) } } } w.Pdoc.IsCmd = bpkg.IsCommand() w.Pdoc.Synopsis = synopsis(bpkg.Doc) w.Pdoc.Imports = bpkg.Imports w.Pdoc.IsCgo = w.isCgo() w.Pdoc.TestImports = bpkg.TestImports // Check depth. if wr.WalkDepth <= WD_Imports { return w.Pdoc, nil } w.Fset = token.NewFileSet() // Parse the Go files files := make(map[string]*ast.File) for _, name := range append(bpkg.GoFiles, bpkg.CgoFiles...) { file, err := parser.ParseFile(w.Fset, name, w.SrcFiles[name].Data(), parser.ParseComments) if err != nil { return nil, errors.New("hv.Walker.Build -> parse Go files: " + err.Error()) continue } w.Pdoc.Files = append(w.Pdoc.Files, w.SrcFiles[name]) w.Pdoc.SourceSize += int64(len(w.SrcFiles[name].Data())) files[name] = file } w.apkg, _ = ast.NewPackage(w.Fset, files, poorMansImporter, nil) // Find examples in the test files. for _, name := range append(bpkg.TestGoFiles, bpkg.XTestGoFiles...) { file, err := parser.ParseFile(w.Fset, name, w.SrcFiles[name].Data(), parser.ParseComments) if err != nil { return nil, errors.New("hv.Walker.Build -> find examples: " + err.Error()) continue } w.Pdoc.TestFiles = append(w.Pdoc.TestFiles, w.SrcFiles[name]) //w.pdoc.TestSourceSize += len(w.srcs[name].data) if wr.WalkMode&WM_NoExample != 0 { continue } w.Examples = append(w.Examples, doc.Examples(file)...) } mode := doc.Mode(0) if w.Pdoc.ImportPath == "builtin" || wr.BuildAll { mode |= doc.AllDecls } pdoc := doc.New(w.apkg, w.Pdoc.ImportPath, mode) // Get doc. pdoc.Doc = strings.TrimRight(pdoc.Doc, " \t\n\r") var buf bytes.Buffer doc.ToHTML(&buf, pdoc.Doc, nil) w.Pdoc.Doc = buf.String() // Highlight first sentence. w.Pdoc.Doc = strings.Replace(w.Pdoc.Doc, "<p>", "<p><b>", 1) w.Pdoc.Doc = strings.Replace(w.Pdoc.Doc, "</p>", "</b></p>", 1) if wr.WalkMode&WM_NoExample == 0 { w.getExamples() } w.SrcLines = make(map[string][]string) w.Pdoc.Consts = w.values(pdoc.Consts) w.Pdoc.Funcs, w.Pdoc.Ifuncs = w.funcs(pdoc.Funcs) w.Pdoc.Types, w.Pdoc.Itypes = w.types(pdoc.Types) w.Pdoc.Vars = w.values(pdoc.Vars) //w.Pdoc.Notes = w.notes(pdoc.Notes) return w.Pdoc, nil }