src/pkg/go/doc/example.go - The Go Programming Language

Golang

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 }