Source file src/pkg/go/doc/example.go
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Extract example functions from file ASTs.
6
7 package doc
8
9 import (
10 "go/ast"
11 "go/token"
12 "regexp"
13 "sort"
14 "strings"
15 "unicode"
16 "unicode/utf8"
17 )
18
19 type Example struct {
20 Name string // name of the item being exemplified
21 Doc string // example function doc string
22 Code ast.Node
23 Comments []*ast.CommentGroup
24 Output string // expected output
25 }
26
27 func Examples(files ...*ast.File) []*Example {
28 var list []*Example
29 for _, file := range files {
30 hasTests := false // file contains tests or benchmarks
31 numDecl := 0 // number of non-import declarations in the file
32 var flist []*Example
33 for _, decl := range file.Decls {
34 if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
35 numDecl++
36 continue
37 }
38 f, ok := decl.(*ast.FuncDecl)
39 if !ok {
40 continue
41 }
42 numDecl++
43 name := f.Name.Name
44 if isTest(name, "Test") || isTest(name, "Benchmark") {
45 hasTests = true
46 continue
47 }
48 if !isTest(name, "Example") {
49 continue
50 }
51 var doc string
52 if f.Doc != nil {
53 doc = f.Doc.Text()
54 }
55 flist = append(flist, &Example{
56 Name: name[len("Example"):],
57 Doc: doc,
58 Code: f.Body,
59 Comments: file.Comments,
60 Output: exampleOutput(f, file.Comments),
61 })
62 }
63 if !hasTests && numDecl > 1 && len(flist) == 1 {
64 // If this file only has one example function, some
65 // other top-level declarations, and no tests or
66 // benchmarks, use the whole file as the example.
67 flist[0].Code = file
68 }
69 list = append(list, flist...)
70 }
71 sort.Sort(exampleByName(list))
72 return list
73 }
74
75 var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
76
77 func exampleOutput(fun *ast.FuncDecl, comments []*ast.CommentGroup) string {
78 // find the last comment in the function
79 var last *ast.CommentGroup
80 for _, cg := range comments {
81 if cg.Pos() < fun.Pos() {
82 continue
83 }
84 if cg.End() > fun.End() {
85 break
86 }
87 last = cg
88 }
89 if last != nil {
90 // test that it begins with the correct prefix
91 text := last.Text()
92 if loc := outputPrefix.FindStringIndex(text); loc != nil {
93 return strings.TrimSpace(text[loc[1]:])
94 }
95 }
96 return "" // no suitable comment found
97 }
98
99 // isTest tells whether name looks like a test, example, or benchmark.
100 // It is a Test (say) if there is a character after Test that is not a
101 // lower-case letter. (We don't want Testiness.)
102 func isTest(name, prefix string) bool {
103 if !strings.HasPrefix(name, prefix) {
104 return false
105 }
106 if len(name) == len(prefix) { // "Test" is ok
107 return true
108 }
109 rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
110 return !unicode.IsLower(rune)
111 }
112
113 type exampleByName []*Example
114
115 func (s exampleByName) Len() int { return len(s) }
116 func (s exampleByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
117 func (s exampleByName) Less(i, j int) bool { return s[i].Name < s[j].Name }