1
0
mirror of https://github.com/golang/go synced 2024-11-24 23:07:56 -07:00

godoc: make example code more readable with new comment convention

go/doc: move Examples to go/ast
cmd/go: use go/doc to read examples
src/pkg: update examples to use new convention

This is to make whole file examples more readable. When presented as a
complete function, preceding an Example with its output is confusing.
The new convention is to put the expected output in the final comment
of the example, preceded by the string "output:" (case insensitive).

An idiomatic example looks like this:

// This example demonstrates Foo by doing bar and quux.
func ExampleFoo() {
        // example body that does bar and quux

        // Output:
        // example output
}

R=rsc, gri
CC=golang-dev
https://golang.org/cl/5673053
This commit is contained in:
Andrew Gerrand 2012-02-16 11:50:28 +11:00
parent fa0100cf26
commit 11e113db57
14 changed files with 213 additions and 148 deletions

View File

@ -183,9 +183,9 @@ where xxx is a suffix not beginning with an upper case letter.
Here is an example of an example:
// The output of this example function.
func ExamplePrintln() {
Println("The output of this example function.")
// Output: The output of this example function.
}
The entire test file is presented as the example when it contains a single
@ -717,17 +717,16 @@ func (t *testFuncs) load(filename, pkg string, seen *bool) error {
case isTest(name, "Benchmark"):
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""})
*seen = true
case isTest(name, "Example"):
output := n.Doc.Text()
if output == "" {
// Don't run examples with no output.
continue
}
t.Examples = append(t.Examples, testFunc{pkg, name, output})
*seen = true
}
}
for _, e := range ast.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
}

View File

@ -499,7 +499,9 @@ func startsWithUppercase(s string) bool {
return unicode.IsUpper(r)
}
func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string {
var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*output:`)
func example_htmlFunc(funcName string, examples []*ast.Example, fset *token.FileSet) string {
var buf bytes.Buffer
for _, eg := range examples {
name := eg.Name
@ -517,16 +519,28 @@ func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.File
}
// print code
code := node_htmlFunc(eg.Body, fset)
cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
code := node_htmlFunc(cnode, fset)
out := eg.Output
// additional formatting if this is a function body
if len(code) > 0 && code[0] == '{' {
// unindent and remove surrounding braces
// unindent
code = strings.Replace(code, "\n ", "\n", -1)
// remove surrounding braces
code = code[2 : len(code)-2]
// remove output comment
if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
code = strings.TrimSpace(code[:loc[0]])
}
} else {
// drop output, as the output comment will appear in the code
out = ""
}
err := exampleHTML.Execute(&buf, struct {
Name, Code, Output string
}{eg.Name, code, eg.Output})
}{eg.Name, code, out})
if err != nil {
log.Print(err)
}
@ -552,7 +566,6 @@ func example_nameFunc(s string) string {
func example_suffixFunc(name string) string {
_, suffix := splitExampleName(name)
return suffix
}
func splitExampleName(s string) (name, suffix string) {
@ -966,7 +979,7 @@ type PageInfo struct {
FSet *token.FileSet // corresponding file set
PAst *ast.File // nil if no single AST with package exports
PDoc *doc.Package // nil if no single package documentation
Examples []*doc.Example // nil if no example code
Examples []*ast.Example // nil if no example code
Dirs *DirList // nil if no directory information
DirTime time.Time // directory time stamp
DirFlat bool // if set, show directory in a flat (non-indented) manner
@ -1115,7 +1128,7 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
}
// get examples from *_test.go files
var examples []*doc.Example
var examples []*ast.Example
filter = func(d os.FileInfo) bool {
return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go")
}
@ -1123,7 +1136,11 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf
log.Println("parsing test files:", err)
} else {
for _, testpkg := range testpkgs {
examples = append(examples, doc.Examples(testpkg)...)
var files []*ast.File
for _, f := range testpkg.Files {
files = append(files, f)
}
examples = append(examples, ast.Examples(files...)...)
}
}

View File

@ -11,18 +11,18 @@ import (
"os"
)
// Hello world!
func ExampleBuffer() {
var b Buffer // A Buffer needs no initialization.
b.Write([]byte("Hello "))
b.Write([]byte("world!"))
b.WriteTo(os.Stdout)
// Output: Hello world!
}
// Gophers rule!
func ExampleBuffer_reader() {
// A Buffer can turn a string or a []byte into an io.Reader.
buf := NewBufferString("R29waGVycyBydWxlIQ==")
dec := base64.NewDecoder(base64.StdEncoding, buf)
io.Copy(os.Stdout, dec)
// Output: Gophers rule!
}

View File

@ -57,7 +57,25 @@ func (pq *PriorityQueue) Pop() interface{} {
return item
}
// 99:seven 88:five 77:zero 66:nine 55:three 44:two 33:six 22:one 11:four 00:eight
// update is not used by the example but shows how to take the top item from
// the queue, update its priority and value, and put it back.
func (pq *PriorityQueue) update(value string, priority int) {
item := heap.Pop(pq).(*Item)
item.value = value
item.priority = priority
heap.Push(pq, item)
}
// changePriority is not used by the example but shows how to change the
// priority of an arbitrary item.
func (pq *PriorityQueue) changePriority(item *Item, priority int) {
heap.Remove(pq, item.index)
item.priority = priority
heap.Push(pq, item)
}
// This example pushes 10 items into a PriorityQueue and takes them out in
// order of priority.
func Example() {
const nItem = 10
// Random priorities for the items (a permutation of 0..9, times 11)).
@ -82,21 +100,6 @@ func Example() {
item := heap.Pop(&pq).(*Item)
fmt.Printf("%.2d:%s ", item.priority, item.value)
}
}
// update is not used by the example but shows how to take the top item from the queue,
// update its priority and value, and put it back.
func (pq *PriorityQueue) update(value string, priority int) {
item := heap.Pop(pq).(*Item)
item.value = value
item.priority = priority
heap.Push(pq, item)
}
// changePriority is not used by the example but shows how to change the priority of an arbitrary
// item.
func (pq *PriorityQueue) changePriority(item *Item, priority int) {
heap.Remove(pq, item.index)
item.priority = priority
heap.Push(pq, item)
// Output:
// 99:seven 88:five 77:zero 66:nine 55:three 44:two 33:six 22:one 11:four 00:eight
}

View File

@ -11,7 +11,6 @@ import (
"math"
)
// 18 2d 44 54 fb 21 09 40
func ExampleWrite() {
buf := new(bytes.Buffer)
var pi float64 = math.Pi
@ -20,9 +19,9 @@ func ExampleWrite() {
fmt.Println("binary.Write failed:", err)
}
fmt.Printf("% x", buf.Bytes())
// Output: 18 2d 44 54 fb 21 09 40
}
// cafebabe
func ExampleWrite_multi() {
buf := new(bytes.Buffer)
var data = []interface{}{
@ -37,9 +36,9 @@ func ExampleWrite_multi() {
}
}
fmt.Printf("%x", buf.Bytes())
// Output: cafebabe
}
// 3.141592653589793
func ExampleRead() {
var pi float64
b := []byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}
@ -49,4 +48,5 @@ func ExampleRead() {
fmt.Println("binary.Read failed:", err)
}
fmt.Print(pi)
// Output: 3.141592653589793
}

View File

@ -10,7 +10,6 @@ import (
"os"
)
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
func ExampleMarshal() {
type ColorGroup struct {
ID int
@ -27,9 +26,10 @@ func ExampleMarshal() {
fmt.Println("error:", err)
}
os.Stdout.Write(b)
// Output:
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}
}
// [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
func ExampleUnmarshal() {
var jsonBlob = []byte(`[
{"Name": "Platypus", "Order": "Monotremata"},
@ -45,4 +45,6 @@ func ExampleUnmarshal() {
fmt.Println("error:", err)
}
fmt.Printf("%+v", animals)
// Output:
// [{Name:Platypus Order:Monotremata} {Name:Quoll Order:Dasyuromorphia}]
}

View File

@ -2,37 +2,37 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Extract example functions from package ASTs.
// Extract example functions from file ASTs.
package doc
package ast
import (
"go/ast"
"go/printer"
"go/token"
"regexp"
"strings"
"unicode"
"unicode/utf8"
)
type Example struct {
Name string // name of the item being demonstrated
Body *printer.CommentedNode // code
Output string // expected output
Name string // name of the item being exemplified
Code Node
Comments []*CommentGroup
Output string // expected output
}
func Examples(pkg *ast.Package) []*Example {
func Examples(files ...*File) []*Example {
var list []*Example
for _, file := range pkg.Files {
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 {
if g, ok := decl.(*GenDecl); ok && g.Tok != token.IMPORT {
numDecl++
continue
}
f, ok := decl.(*ast.FuncDecl)
f, ok := decl.(*FuncDecl)
if !ok {
continue
}
@ -46,25 +46,47 @@ func Examples(pkg *ast.Package) []*Example {
continue
}
flist = append(flist, &Example{
Name: name[len("Example"):],
Body: &printer.CommentedNode{
Node: f.Body,
Comments: file.Comments,
},
Output: f.Doc.Text(),
Name: name[len("Example"):],
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].Body.Node = file
flist[0].Code = file
}
list = append(list, flist...)
}
return list
}
var outputPrefix = regexp.MustCompile(`(?i)^[[:space:]]*output:`)
func exampleOutput(fun *FuncDecl, comments []*CommentGroup) string {
// find the last comment in the function
var last *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.)

View File

@ -10,21 +10,20 @@ import (
"math/big"
)
// 3.142
func ExampleRat_SetString() {
r := new(big.Rat)
r.SetString("355/113")
fmt.Println(r.FloatString(3))
// Output: 3.142
}
// 420
func ExampleInt_SetString() {
i := new(big.Int)
i.SetString("644", 8) // octal
fmt.Println(i)
// Output: 420
}
// 3/2
func ExampleRat_Scan() {
// The Scan function is rarely used directly;
// the fmt package recognizes it as an implementation of fmt.Scanner.
@ -35,9 +34,9 @@ func ExampleRat_Scan() {
} else {
fmt.Println(r)
}
// Output: 3/2
}
// 18446744073709551617
func ExampleInt_Scan() {
// The Scan function is rarely used directly;
// the fmt package recognizes it as an implementation of fmt.Scanner.
@ -48,4 +47,5 @@ func ExampleInt_Scan() {
} else {
fmt.Println(i)
}
// Output: 18446744073709551617
}

View File

@ -9,17 +9,11 @@ import (
"path"
)
// b
func ExampleBase() {
fmt.Println(path.Base("/a/b"))
// Output: b
}
// Clean("a/c") = "a/c"
// Clean("a//c") = "a/c"
// Clean("a/c/.") = "a/c"
// Clean("a/c/b/..") = "a/c"
// Clean("/../a/c") = "/a/c"
// Clean("/../a/b/../././/c") = "/a/c"
func ExampleClean() {
paths := []string{
"a/c",
@ -33,29 +27,37 @@ func ExampleClean() {
for _, p := range paths {
fmt.Printf("Clean(%q) = %q\n", p, path.Clean(p))
}
// Output:
// Clean("a/c") = "a/c"
// Clean("a//c") = "a/c"
// Clean("a/c/.") = "a/c"
// Clean("a/c/b/..") = "a/c"
// Clean("/../a/c") = "/a/c"
// Clean("/../a/b/../././/c") = "/a/c"
}
// /a/b
func ExampleDir() {
fmt.Println(path.Dir("/a/b/c"))
// Output: /a/b
}
// .css
func ExampleExt() {
fmt.Println(path.Ext("/a/b/c/bar.css"))
// Output: .css
}
// true
func ExampleIsAbs() {
fmt.Println(path.IsAbs("/dev/null"))
// Output: true
}
// a/b/c
func ExampleJoin() {
fmt.Println(path.Join("a", "b", "c"))
// Output: a/b/c
}
// static/ myfile.css
func ExampleSplit() {
fmt.Println(path.Split("static/myfile.css"))
// Output: static/ myfile.css
}

View File

@ -9,9 +9,9 @@ import (
"sort"
)
// [1 2 3 4 5 6]
func ExampleInts() {
s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Ints(s)
fmt.Println(s)
// Output: [1 2 3 4 5 6]
}

View File

@ -9,134 +9,142 @@ import (
"strings"
)
// Fields are: ["foo" "bar" "baz"]
func ExampleFields() {
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
// Output: Fields are: ["foo" "bar" "baz"]
}
// true
// false
// true
// true
func ExampleContains() {
fmt.Println(strings.Contains("seafood", "foo"))
fmt.Println(strings.Contains("seafood", "bar"))
fmt.Println(strings.Contains("seafood", ""))
fmt.Println(strings.Contains("", ""))
// Output:
// true
// false
// true
// true
}
// false
// true
// false
// false
func ExampleContainsAny() {
fmt.Println(strings.ContainsAny("team", "i"))
fmt.Println(strings.ContainsAny("failure", "u & i"))
fmt.Println(strings.ContainsAny("foo", ""))
fmt.Println(strings.ContainsAny("", ""))
// Output:
// false
// true
// false
// false
}
// 3
// 5
func ExampleCount() {
fmt.Println(strings.Count("cheese", "e"))
fmt.Println(strings.Count("five", "")) // before & after each rune
// Output:
// 3
// 5
}
// true
func ExampleEqualFold() {
fmt.Println(strings.EqualFold("Go", "go"))
// Output: true
}
// 4
// -1
func ExampleIndex() {
fmt.Println(strings.Index("chicken", "ken"))
fmt.Println(strings.Index("chicken", "dmr"))
// Output:
// 4
// -1
}
// 4
// -1
func ExampleRune() {
fmt.Println(strings.IndexRune("chicken", 'k'))
fmt.Println(strings.IndexRune("chicken", 'd'))
// Output:
// 4
// -1
}
// 0
// 3
// -1
func ExampleLastIndex() {
fmt.Println(strings.Index("go gopher", "go"))
fmt.Println(strings.LastIndex("go gopher", "go"))
fmt.Println(strings.LastIndex("go gopher", "rodent"))
// Output:
// 0
// 3
// -1
}
// foo, bar, baz
func ExampleJoin() {
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
// Output: foo, bar, baz
}
// banana
func ExampleRepeat() {
fmt.Println("ba" + strings.Repeat("na", 2))
// Output: banana
}
// oinky oinky oink
// moo moo moo
func ExampleReplace() {
fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2))
fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1))
// Output:
// oinky oinky oink
// moo moo moo
}
// ["a" "b" "c"]
// ["" "man " "plan " "canal panama"]
// [" " "x" "y" "z" " "]
// [""]
func ExampleSplit() {
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
// Output:
// ["a" "b" "c"]
// ["" "man " "plan " "canal panama"]
// [" " "x" "y" "z" " "]
// [""]
}
// ["a" "b,c"]
// [] (nil = true)
func ExampleSplitN() {
fmt.Printf("%q\n", strings.SplitN("a,b,c", ",", 2))
z := strings.SplitN("a,b,c", ",", 0)
fmt.Printf("%q (nil = %v)\n", z, z == nil)
// Output:
// ["a" "b,c"]
// [] (nil = true)
}
// ["a," "b," "c"]
func ExampleSplitAfter() {
fmt.Printf("%q\n", strings.SplitAfter("a,b,c", ","))
// Output: ["a," "b," "c"]
}
// ["a," "b,c"]
func ExampleSplitAfterN() {
fmt.Printf("%q\n", strings.SplitAfterN("a,b,c", ",", 2))
// Output: ["a," "b,c"]
}
// Her Royal Highness
func ExampleTitle() {
fmt.Println(strings.Title("her royal highness"))
// Output: Her Royal Highness
}
// LOUD NOISES
// ХЛЕБ
func ExampleToTitle() {
fmt.Println(strings.ToTitle("loud noises"))
fmt.Println(strings.ToTitle("хлеб"))
// Output:
// LOUD NOISES
// ХЛЕБ
}
// [Achtung]
func ExampleTrim() {
fmt.Printf("[%s]", strings.Trim(" !!! Achtung !!! ", "! "))
fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! "))
// Output: ["Achtung"]
}
// 'Gjnf oevyyvt naq gur fyvgul tbcure...
func ExampleMap() {
rot13 := func(r rune) rune {
switch {
@ -148,25 +156,26 @@ func ExampleMap() {
return r
}
fmt.Println(strings.Map(rot13, "'Twas brillig and the slithy gopher..."))
// Output: 'Gjnf oevyyvt naq gur fyvgul tbcure...
}
// a lone gopher
func ExampleTrimSpace() {
fmt.Println(strings.TrimSpace(" \t\n a lone gopher \n\t\r\n"))
// Output: a lone gopher
}
// This is &lt;b&gt;HTML&lt;/b&gt;!
func ExampleNewReplacer() {
r := strings.NewReplacer("<", "&lt;", ">", "&gt;")
fmt.Println(r.Replace("This is <b>HTML</b>!"))
// Output: This is &lt;b&gt;HTML&lt;/b&gt;!
}
// GOPHER
func ExampleToUpper() {
fmt.Println(strings.ToUpper("Gopher"))
// Output: GOPHER
}
// gopher
func ExampleToLower() {
fmt.Println(strings.ToLower("Gopher"))
// Output: gopher
}

View File

@ -38,16 +38,25 @@
// }
// }
//
// The package also runs and verifies example code. Example functions
// include an introductory comment that is compared with the standard output
// of the function when the tests are run, as in this example of an example:
// The package also runs and verifies example code. Example functions may
// include a concluding comment that begins with "Output:" and is compared with
// the standard output of the function when the tests are run, as in these
// examples of an example:
//
// // hello
// func ExampleHello() {
// fmt.Println("hello")
// // Output: hello
// }
//
// Example functions without comments are compiled but not executed.
// func ExampleSalutations() {
// fmt.Println("hello, and")
// fmt.Println("goodbye")
// // Output:
// // hello, and
// // goodbye
// }
//
// Example functions without output comments are compiled but not executed.
//
// The naming convention to declare examples for a function F, a type T and
// method M on type T are:

View File

@ -10,28 +10,6 @@ import (
"text/template"
)
// Dear Aunt Mildred,
//
// It was a pleasure to see you at the wedding.
// Thank you for the lovely bone china tea set.
//
// Best wishes,
// Josie
//
// Dear Uncle John,
//
// It is a shame you couldn't make it to the wedding.
// Thank you for the lovely moleskin pants.
//
// Best wishes,
// Josie
//
// Dear Cousin Rodney,
//
// It is a shame you couldn't make it to the wedding.
//
// Best wishes,
// Josie
func ExampleTemplate() {
// Define a template.
const letter = `
@ -66,4 +44,28 @@ Josie
log.Println("executing template:", err)
}
}
// Output:
// Dear Aunt Mildred,
//
// It was a pleasure to see you at the wedding.
// Thank you for the lovely bone china tea set.
//
// Best wishes,
// Josie
//
// Dear Uncle John,
//
// It is a shame you couldn't make it to the wedding.
// Thank you for the lovely moleskin pants.
//
// Best wishes,
// Josie
//
// Dear Cousin Rodney,
//
// It is a shame you couldn't make it to the wedding.
//
// Best wishes,
// Josie
}

View File

@ -51,8 +51,8 @@ func ExampleMonth() {
}
}
// Go launched at 2009-11-10 15:00:00 -0800 PST
func ExampleDate() {
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
fmt.Printf("Go launched at %s\n", t.Local())
// Output: Go launched at 2009-11-10 15:00:00 -0800 PST
}