summaryrefslogtreecommitdiff
path: root/src/pkg/go/doc/example.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/go/doc/example.go')
-rw-r--r--src/pkg/go/doc/example.go117
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 }