diff options
Diffstat (limited to 'src/pkg/go/doc/example.go')
-rw-r--r-- | src/pkg/go/doc/example.go | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/src/pkg/go/doc/example.go b/src/pkg/go/doc/example.go new file mode 100644 index 000000000..a7e0e250a --- /dev/null +++ b/src/pkg/go/doc/example.go @@ -0,0 +1,117 @@ +// Copyright 2011 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. + +// Extract example functions from file ASTs. + +package doc + +import ( + "go/ast" + "go/token" + "regexp" + "sort" + "strings" + "unicode" + "unicode/utf8" +) + +type Example struct { + Name string // name of the item being exemplified + Doc string // example function doc string + Code ast.Node + Comments []*ast.CommentGroup + Output string // expected output +} + +func Examples(files ...*ast.File) []*Example { + var list []*Example + for _, file := range files { + hasTests := false // file contains tests or benchmarks + numDecl := 0 // number of non-import declarations in the file + var flist []*Example + for _, decl := range file.Decls { + if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { + numDecl++ + continue + } + f, ok := decl.(*ast.FuncDecl) + if !ok { + continue + } + numDecl++ + name := f.Name.Name + if isTest(name, "Test") || isTest(name, "Benchmark") { + hasTests = true + continue + } + if !isTest(name, "Example") { + continue + } + var doc string + if f.Doc != nil { + doc = f.Doc.Text() + } + flist = append(flist, &Example{ + Name: name[len("Example"):], + Doc: doc, + Code: f.Body, + Comments: file.Comments, + Output: exampleOutput(f, file.Comments), + }) + } + if !hasTests && numDecl > 1 && len(flist) == 1 { + // If this file only has one example function, some + // other top-level declarations, and no tests or + // benchmarks, use the whole file as the example. + flist[0].Code = file + } + list = append(list, flist...) + } + sort.Sort(exampleByName(list)) + return list +} + +var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`) + +func exampleOutput(fun *ast.FuncDecl, comments []*ast.CommentGroup) string { + // find the last comment in the function + var last *ast.CommentGroup + for _, cg := range comments { + if cg.Pos() < fun.Pos() { + continue + } + if cg.End() > fun.End() { + break + } + last = cg + } + if last != nil { + // test that it begins with the correct prefix + text := last.Text() + if loc := outputPrefix.FindStringIndex(text); loc != nil { + return strings.TrimSpace(text[loc[1]:]) + } + } + return "" // no suitable comment found +} + +// isTest tells whether name looks like a test, example, or benchmark. +// It is a Test (say) if there is a character after Test that is not a +// lower-case letter. (We don't want Testiness.) +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + +type exampleByName []*Example + +func (s exampleByName) Len() int { return len(s) } +func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name } |