internal/godoc: factor out example, ast support funcs
Moved with
rf '
mv \
Presentation.example_htmlFunc \
replaceLeadingIndentation \
exampleOutputRx \
filterOutBuildAnnotations \
Presentation.example_nameFunc \
Presentation.example_suffixFunc \
splitExampleName \
stripExampleSuffix \
startsWithUppercase \
examplefuncs.go
mv \
slashSlash \
Presentation.nodeFunc \
Presentation.node_htmlFunc \
Presentation.writeNode \
firstIdent \
comment_htmlFunc \
sanitizeFunc \
astfuncs.go
rm Presentation.WriteNode
'
plus deleting the doc comments and import path comments,
which didn't move well.
Change-Id: I71b1dd58527938cafccfc901a0dadcc6dff34664
Reviewed-on: https://go-review.googlesource.com/c/website/+/296375
Trust: Russ Cox <rsc@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/godoc/astfuncs.go b/internal/godoc/astfuncs.go
new file mode 100644
index 0000000..48ab123
--- /dev/null
+++ b/internal/godoc/astfuncs.go
@@ -0,0 +1,190 @@
+// Copyright 2013 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.
+
+//go:build go1.16
+// +build go1.16
+
+package godoc
+
+import (
+ "bufio"
+ "bytes"
+ "go/ast"
+ "go/doc"
+ "go/printer"
+ "go/token"
+ "io"
+ "log"
+ "unicode"
+
+ "golang.org/x/website/internal/texthtml"
+)
+
+var slashSlash = []byte("//")
+
+func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
+ var buf bytes.Buffer
+ p.writeNode(&buf, info, info.FSet, node)
+ return buf.String()
+}
+
+func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
+ var buf1 bytes.Buffer
+ p.writeNode(&buf1, info, info.FSet, node)
+
+ var buf2 bytes.Buffer
+ var n ast.Node
+ if linkify && p.DeclLinks {
+ n, _ = node.(ast.Node)
+ }
+ buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
+ AST: n,
+ GoComments: true,
+ }))
+ return buf2.String()
+}
+
+// writeNode writes the AST node x to w.
+//
+// The provided fset must be non-nil. The pageInfo is optional. If
+// present, the pageInfo is used to add comments to struct fields to
+// say which version of Go introduced them.
+func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
+ // convert trailing tabs into spaces using a tconv filter
+ // to ensure a good outcome in most browsers (there may still
+ // be tabs in comments and strings, but converting those into
+ // the right number of spaces is much harder)
+ //
+ // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
+ // with an another printer mode (which is more efficiently
+ // implemented in the printer than here with another layer)
+
+ var pkgName, structName string
+ var apiInfo pkgAPIVersions
+ if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
+ p.Corpus != nil &&
+ gd.Tok == token.TYPE && len(gd.Specs) != 0 {
+ pkgName = pageInfo.PDoc.ImportPath
+ if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
+ if _, ok := ts.Type.(*ast.StructType); ok {
+ structName = ts.Name.Name
+ }
+ }
+ apiInfo = p.Corpus.pkgAPIInfo[pkgName]
+ }
+
+ var out = w
+ var buf bytes.Buffer
+ if structName != "" {
+ out = &buf
+ }
+
+ mode := printer.TabIndent | printer.UseSpaces
+ err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(TabSpacer(out, p.TabWidth), fset, x)
+ if err != nil {
+ log.Print(err)
+ }
+
+ // Add comments to struct fields saying which Go version introduced them.
+ if structName != "" {
+ fieldSince := apiInfo.fieldSince[structName]
+ typeSince := apiInfo.typeSince[structName]
+ // Add/rewrite comments on struct fields to note which Go version added them.
+ var buf2 bytes.Buffer
+ buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
+ bs := bufio.NewScanner(&buf)
+ for bs.Scan() {
+ line := bs.Bytes()
+ field := firstIdent(line)
+ var since string
+ if field != "" {
+ since = fieldSince[field]
+ if since != "" && since == typeSince {
+ // Don't highlight field versions if they were the
+ // same as the struct itself.
+ since = ""
+ }
+ }
+ if since == "" {
+ buf2.Write(line)
+ } else {
+ if bytes.Contains(line, slashSlash) {
+ line = bytes.TrimRight(line, " \t.")
+ buf2.Write(line)
+ buf2.WriteString("; added in Go ")
+ } else {
+ buf2.Write(line)
+ buf2.WriteString(" // Go ")
+ }
+ buf2.WriteString(since)
+ }
+ buf2.WriteByte('\n')
+ }
+ w.Write(buf2.Bytes())
+ }
+}
+
+// firstIdent returns the first identifier in x.
+// This actually parses "identifiers" that begin with numbers too, but we
+// never feed it such input, so it's fine.
+func firstIdent(x []byte) string {
+ x = bytes.TrimSpace(x)
+ i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
+ if i == -1 {
+ return string(x)
+ }
+ return string(x[:i])
+}
+
+func comment_htmlFunc(comment string) string {
+ var buf bytes.Buffer
+ // TODO(gri) Provide list of words (e.g. function parameters)
+ // to be emphasized by ToHTML.
+ doc.ToHTML(&buf, comment, nil) // does html-escaping
+ return buf.String()
+}
+
+// sanitizeFunc sanitizes the argument src by replacing newlines with
+// blanks, removing extra blanks, and by removing trailing whitespace
+// and commas before closing parentheses.
+func sanitizeFunc(src string) string {
+ buf := make([]byte, len(src))
+ j := 0 // buf index
+ comma := -1 // comma index if >= 0
+ for i := 0; i < len(src); i++ {
+ ch := src[i]
+ switch ch {
+ case '\t', '\n', ' ':
+ // ignore whitespace at the beginning, after a blank, or after opening parentheses
+ if j == 0 {
+ continue
+ }
+ if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
+ continue
+ }
+ // replace all whitespace with blanks
+ ch = ' '
+ case ',':
+ comma = j
+ case ')', '}', ']':
+ // remove any trailing comma
+ if comma >= 0 {
+ j = comma
+ }
+ // remove any trailing whitespace
+ if j > 0 && buf[j-1] == ' ' {
+ j--
+ }
+ default:
+ comma = -1
+ }
+ buf[j] = ch
+ j++
+ }
+ // remove trailing blank, if any
+ if j > 0 && buf[j-1] == ' ' {
+ j--
+ }
+ return string(buf[:j])
+}
diff --git a/internal/godoc/examplefuncs.go b/internal/godoc/examplefuncs.go
new file mode 100644
index 0000000..a1048bb
--- /dev/null
+++ b/internal/godoc/examplefuncs.go
@@ -0,0 +1,235 @@
+// Copyright 2013 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.
+
+//go:build go1.16
+// +build go1.16
+
+package godoc
+
+import (
+ "bytes"
+ "go/ast"
+ "go/format"
+ "go/printer"
+ "log"
+ "regexp"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
+ var buf bytes.Buffer
+ for _, eg := range info.Examples {
+ name := stripExampleSuffix(eg.Name)
+
+ if name != funcName {
+ continue
+ }
+
+ // print code
+ cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
+ code := p.node_htmlFunc(info, cnode, true)
+ out := eg.Output
+ wholeFile := true
+
+ // Additional formatting if this is a function body.
+ if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
+ wholeFile = false
+ // remove surrounding braces
+ code = code[1 : n-1]
+ // unindent
+ code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
+ // remove output comment
+ if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
+ code = strings.TrimSpace(code[:loc[0]])
+ }
+ }
+
+ // Write out the playground code in standard Go style
+ // (use tabs, no comment highlight, etc).
+ play := ""
+ if eg.Play != nil && p.ShowPlayground {
+ var buf bytes.Buffer
+ eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
+ if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
+ log.Print(err)
+ } else {
+ play = buf.String()
+ }
+ }
+
+ // Drop output, as the output comment will appear in the code.
+ if wholeFile && play == "" {
+ out = ""
+ }
+
+ if p.ExampleHTML == nil {
+ out = ""
+ return ""
+ }
+
+ err := p.ExampleHTML.Execute(&buf, struct {
+ Name, Doc, Code, Play, Output string
+ GoogleCN bool
+ }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
+ if err != nil {
+ log.Print(err)
+ }
+ }
+ return buf.String()
+}
+
+// replaceLeadingIndentation replaces oldIndent at the beginning of each line
+// with newIndent. This is used for formatting examples. Raw strings that
+// span multiple lines are handled specially: oldIndent is not removed (since
+// go/printer will not add any indentation there), but newIndent is added
+// (since we may still want leading indentation).
+func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
+ // Handle indent at the beginning of the first line. After this, we handle
+ // indentation only after a newline.
+ var buf bytes.Buffer
+ if strings.HasPrefix(body, oldIndent) {
+ buf.WriteString(newIndent)
+ body = body[len(oldIndent):]
+ }
+
+ // Use a state machine to keep track of whether we're in a string or
+ // rune literal while we process the rest of the code.
+ const (
+ codeState = iota
+ runeState
+ interpretedStringState
+ rawStringState
+ )
+ searchChars := []string{
+ "'\"`\n", // codeState
+ `\'`, // runeState
+ `\"`, // interpretedStringState
+ "`\n", // rawStringState
+ // newlineState does not need to search
+ }
+ state := codeState
+ for {
+ i := strings.IndexAny(body, searchChars[state])
+ if i < 0 {
+ buf.WriteString(body)
+ break
+ }
+ c := body[i]
+ buf.WriteString(body[:i+1])
+ body = body[i+1:]
+ switch state {
+ case codeState:
+ switch c {
+ case '\'':
+ state = runeState
+ case '"':
+ state = interpretedStringState
+ case '`':
+ state = rawStringState
+ case '\n':
+ if strings.HasPrefix(body, oldIndent) {
+ buf.WriteString(newIndent)
+ body = body[len(oldIndent):]
+ }
+ }
+
+ case runeState:
+ switch c {
+ case '\\':
+ r, size := utf8.DecodeRuneInString(body)
+ buf.WriteRune(r)
+ body = body[size:]
+ case '\'':
+ state = codeState
+ }
+
+ case interpretedStringState:
+ switch c {
+ case '\\':
+ r, size := utf8.DecodeRuneInString(body)
+ buf.WriteRune(r)
+ body = body[size:]
+ case '"':
+ state = codeState
+ }
+
+ case rawStringState:
+ switch c {
+ case '`':
+ state = codeState
+ case '\n':
+ buf.WriteString(newIndent)
+ }
+ }
+ }
+ return buf.String()
+}
+
+var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
+
+func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
+ if len(cg) == 0 {
+ return cg
+ }
+
+ for i := range cg {
+ if !strings.HasPrefix(cg[i].Text(), "+build ") {
+ // Found the first non-build tag, return from here until the end
+ // of the slice.
+ return cg[i:]
+ }
+ }
+
+ // There weren't any non-build tags, return an empty slice.
+ return []*ast.CommentGroup{}
+}
+
+// example_nameFunc takes an example function name and returns its display
+// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
+func (p *Presentation) example_nameFunc(s string) string {
+ name, suffix := splitExampleName(s)
+ // replace _ with . for method names
+ name = strings.Replace(name, "_", ".", 1)
+ // use "Package" if no name provided
+ if name == "" {
+ name = "Package"
+ }
+ return name + suffix
+}
+
+// example_suffixFunc takes an example function name and returns its suffix in
+// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
+func (p *Presentation) example_suffixFunc(name string) string {
+ _, suffix := splitExampleName(name)
+ return suffix
+}
+
+func splitExampleName(s string) (name, suffix string) {
+ i := strings.LastIndex(s, "_")
+ if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
+ name = s[:i]
+ suffix = " (" + strings.Title(s[i+1:]) + ")"
+ return
+ }
+ name = s
+ return
+}
+
+// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
+// while keeping uppercase Braz in Foo_Braz.
+func stripExampleSuffix(name string) string {
+ if i := strings.LastIndex(name, "_"); i != -1 {
+ if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
+ name = name[:i]
+ }
+ }
+ return name
+}
+
+func startsWithUppercase(s string) bool {
+ r, _ := utf8.DecodeRuneInString(s)
+ return unicode.IsUpper(r)
+}
diff --git a/internal/godoc/godoc.go b/internal/godoc/godoc.go
index 47c2b0f..4b80021 100644
--- a/internal/godoc/godoc.go
+++ b/internal/godoc/godoc.go
@@ -5,34 +5,19 @@
//go:build go1.16
// +build go1.16
-// Package godoc is a work-in-progress (2013-07-17) package to
-// begin splitting up the godoc binary into multiple pieces.
-//
-// This package comment will evolve over time as this package splits
-// into smaller pieces.
-package godoc // import "golang.org/x/website/internal/godoc"
+package godoc
import (
- "bufio"
"bytes"
"fmt"
"go/ast"
"go/doc"
- "go/format"
- "go/printer"
"go/token"
- "io"
- "log"
pathpkg "path"
- "regexp"
"strconv"
"strings"
"text/template"
"time"
- "unicode"
- "unicode/utf8"
-
- "golang.org/x/website/internal/texthtml"
)
// Fake relative package path for built-ins. Documentation for all globals
@@ -118,80 +103,6 @@
return localname
}
-func (p *Presentation) nodeFunc(info *PageInfo, node interface{}) string {
- var buf bytes.Buffer
- p.writeNode(&buf, info, info.FSet, node)
- return buf.String()
-}
-
-func (p *Presentation) node_htmlFunc(info *PageInfo, node interface{}, linkify bool) string {
- var buf1 bytes.Buffer
- p.writeNode(&buf1, info, info.FSet, node)
-
- var buf2 bytes.Buffer
- var n ast.Node
- if linkify && p.DeclLinks {
- n, _ = node.(ast.Node)
- }
- buf2.Write(texthtml.Format(buf1.Bytes(), texthtml.Config{
- AST: n,
- GoComments: true,
- }))
- return buf2.String()
-}
-
-func comment_htmlFunc(comment string) string {
- var buf bytes.Buffer
- // TODO(gri) Provide list of words (e.g. function parameters)
- // to be emphasized by ToHTML.
- doc.ToHTML(&buf, comment, nil) // does html-escaping
- return buf.String()
-}
-
-// sanitizeFunc sanitizes the argument src by replacing newlines with
-// blanks, removing extra blanks, and by removing trailing whitespace
-// and commas before closing parentheses.
-func sanitizeFunc(src string) string {
- buf := make([]byte, len(src))
- j := 0 // buf index
- comma := -1 // comma index if >= 0
- for i := 0; i < len(src); i++ {
- ch := src[i]
- switch ch {
- case '\t', '\n', ' ':
- // ignore whitespace at the beginning, after a blank, or after opening parentheses
- if j == 0 {
- continue
- }
- if p := buf[j-1]; p == ' ' || p == '(' || p == '{' || p == '[' {
- continue
- }
- // replace all whitespace with blanks
- ch = ' '
- case ',':
- comma = j
- case ')', '}', ']':
- // remove any trailing comma
- if comma >= 0 {
- j = comma
- }
- // remove any trailing whitespace
- if j > 0 && buf[j-1] == ' ' {
- j--
- }
- default:
- comma = -1
- }
- buf[j] = ch
- j++
- }
- // remove trailing blank, if any
- if j > 0 && buf[j-1] == ' ' {
- j--
- }
- return string(buf[:j])
-}
-
type PageInfo struct {
Dirname string // directory containing the package
Err error // error or nil
@@ -350,321 +261,6 @@
return pathpkg.Clean("/pkg/"+s) + "/#" + ident
}
-func (p *Presentation) example_htmlFunc(info *PageInfo, funcName string) string {
- var buf bytes.Buffer
- for _, eg := range info.Examples {
- name := stripExampleSuffix(eg.Name)
-
- if name != funcName {
- continue
- }
-
- // print code
- cnode := &printer.CommentedNode{Node: eg.Code, Comments: eg.Comments}
- code := p.node_htmlFunc(info, cnode, true)
- out := eg.Output
- wholeFile := true
-
- // Additional formatting if this is a function body.
- if n := len(code); n >= 2 && code[0] == '{' && code[n-1] == '}' {
- wholeFile = false
- // remove surrounding braces
- code = code[1 : n-1]
- // unindent
- code = replaceLeadingIndentation(code, strings.Repeat(" ", p.TabWidth), "")
- // remove output comment
- if loc := exampleOutputRx.FindStringIndex(code); loc != nil {
- code = strings.TrimSpace(code[:loc[0]])
- }
- }
-
- // Write out the playground code in standard Go style
- // (use tabs, no comment highlight, etc).
- play := ""
- if eg.Play != nil && p.ShowPlayground {
- var buf bytes.Buffer
- eg.Play.Comments = filterOutBuildAnnotations(eg.Play.Comments)
- if err := format.Node(&buf, info.FSet, eg.Play); err != nil {
- log.Print(err)
- } else {
- play = buf.String()
- }
- }
-
- // Drop output, as the output comment will appear in the code.
- if wholeFile && play == "" {
- out = ""
- }
-
- if p.ExampleHTML == nil {
- out = ""
- return ""
- }
-
- err := p.ExampleHTML.Execute(&buf, struct {
- Name, Doc, Code, Play, Output string
- GoogleCN bool
- }{eg.Name, eg.Doc, code, play, out, info.GoogleCN})
- if err != nil {
- log.Print(err)
- }
- }
- return buf.String()
-}
-
-func filterOutBuildAnnotations(cg []*ast.CommentGroup) []*ast.CommentGroup {
- if len(cg) == 0 {
- return cg
- }
-
- for i := range cg {
- if !strings.HasPrefix(cg[i].Text(), "+build ") {
- // Found the first non-build tag, return from here until the end
- // of the slice.
- return cg[i:]
- }
- }
-
- // There weren't any non-build tags, return an empty slice.
- return []*ast.CommentGroup{}
-}
-
-// example_nameFunc takes an example function name and returns its display
-// name. For example, "Foo_Bar_quux" becomes "Foo.Bar (Quux)".
-func (p *Presentation) example_nameFunc(s string) string {
- name, suffix := splitExampleName(s)
- // replace _ with . for method names
- name = strings.Replace(name, "_", ".", 1)
- // use "Package" if no name provided
- if name == "" {
- name = "Package"
- }
- return name + suffix
-}
-
-// example_suffixFunc takes an example function name and returns its suffix in
-// parenthesized form. For example, "Foo_Bar_quux" becomes " (Quux)".
-func (p *Presentation) example_suffixFunc(name string) string {
- _, suffix := splitExampleName(name)
- return suffix
-}
-
func noteTitle(note string) string {
return strings.Title(strings.ToLower(note))
}
-
-func startsWithUppercase(s string) bool {
- r, _ := utf8.DecodeRuneInString(s)
- return unicode.IsUpper(r)
-}
-
-var exampleOutputRx = regexp.MustCompile(`(?i)//[[:space:]]*(unordered )?output:`)
-
-// stripExampleSuffix strips lowercase braz in Foo_braz or Foo_Bar_braz from name
-// while keeping uppercase Braz in Foo_Braz.
-func stripExampleSuffix(name string) string {
- if i := strings.LastIndex(name, "_"); i != -1 {
- if i < len(name)-1 && !startsWithUppercase(name[i+1:]) {
- name = name[:i]
- }
- }
- return name
-}
-
-func splitExampleName(s string) (name, suffix string) {
- i := strings.LastIndex(s, "_")
- if 0 <= i && i < len(s)-1 && !startsWithUppercase(s[i+1:]) {
- name = s[:i]
- suffix = " (" + strings.Title(s[i+1:]) + ")"
- return
- }
- name = s
- return
-}
-
-// replaceLeadingIndentation replaces oldIndent at the beginning of each line
-// with newIndent. This is used for formatting examples. Raw strings that
-// span multiple lines are handled specially: oldIndent is not removed (since
-// go/printer will not add any indentation there), but newIndent is added
-// (since we may still want leading indentation).
-func replaceLeadingIndentation(body, oldIndent, newIndent string) string {
- // Handle indent at the beginning of the first line. After this, we handle
- // indentation only after a newline.
- var buf bytes.Buffer
- if strings.HasPrefix(body, oldIndent) {
- buf.WriteString(newIndent)
- body = body[len(oldIndent):]
- }
-
- // Use a state machine to keep track of whether we're in a string or
- // rune literal while we process the rest of the code.
- const (
- codeState = iota
- runeState
- interpretedStringState
- rawStringState
- )
- searchChars := []string{
- "'\"`\n", // codeState
- `\'`, // runeState
- `\"`, // interpretedStringState
- "`\n", // rawStringState
- // newlineState does not need to search
- }
- state := codeState
- for {
- i := strings.IndexAny(body, searchChars[state])
- if i < 0 {
- buf.WriteString(body)
- break
- }
- c := body[i]
- buf.WriteString(body[:i+1])
- body = body[i+1:]
- switch state {
- case codeState:
- switch c {
- case '\'':
- state = runeState
- case '"':
- state = interpretedStringState
- case '`':
- state = rawStringState
- case '\n':
- if strings.HasPrefix(body, oldIndent) {
- buf.WriteString(newIndent)
- body = body[len(oldIndent):]
- }
- }
-
- case runeState:
- switch c {
- case '\\':
- r, size := utf8.DecodeRuneInString(body)
- buf.WriteRune(r)
- body = body[size:]
- case '\'':
- state = codeState
- }
-
- case interpretedStringState:
- switch c {
- case '\\':
- r, size := utf8.DecodeRuneInString(body)
- buf.WriteRune(r)
- body = body[size:]
- case '"':
- state = codeState
- }
-
- case rawStringState:
- switch c {
- case '`':
- state = codeState
- case '\n':
- buf.WriteString(newIndent)
- }
- }
- }
- return buf.String()
-}
-
-// writeNode writes the AST node x to w.
-//
-// The provided fset must be non-nil. The pageInfo is optional. If
-// present, the pageInfo is used to add comments to struct fields to
-// say which version of Go introduced them.
-func (p *Presentation) writeNode(w io.Writer, pageInfo *PageInfo, fset *token.FileSet, x interface{}) {
- // convert trailing tabs into spaces using a tconv filter
- // to ensure a good outcome in most browsers (there may still
- // be tabs in comments and strings, but converting those into
- // the right number of spaces is much harder)
- //
- // TODO(gri) rethink printer flags - perhaps tconv can be eliminated
- // with an another printer mode (which is more efficiently
- // implemented in the printer than here with another layer)
-
- var pkgName, structName string
- var apiInfo pkgAPIVersions
- if gd, ok := x.(*ast.GenDecl); ok && pageInfo != nil && pageInfo.PDoc != nil &&
- p.Corpus != nil &&
- gd.Tok == token.TYPE && len(gd.Specs) != 0 {
- pkgName = pageInfo.PDoc.ImportPath
- if ts, ok := gd.Specs[0].(*ast.TypeSpec); ok {
- if _, ok := ts.Type.(*ast.StructType); ok {
- structName = ts.Name.Name
- }
- }
- apiInfo = p.Corpus.pkgAPIInfo[pkgName]
- }
-
- var out = w
- var buf bytes.Buffer
- if structName != "" {
- out = &buf
- }
-
- mode := printer.TabIndent | printer.UseSpaces
- err := (&printer.Config{Mode: mode, Tabwidth: p.TabWidth}).Fprint(TabSpacer(out, p.TabWidth), fset, x)
- if err != nil {
- log.Print(err)
- }
-
- // Add comments to struct fields saying which Go version introduced them.
- if structName != "" {
- fieldSince := apiInfo.fieldSince[structName]
- typeSince := apiInfo.typeSince[structName]
- // Add/rewrite comments on struct fields to note which Go version added them.
- var buf2 bytes.Buffer
- buf2.Grow(buf.Len() + len(" // Added in Go 1.n")*10)
- bs := bufio.NewScanner(&buf)
- for bs.Scan() {
- line := bs.Bytes()
- field := firstIdent(line)
- var since string
- if field != "" {
- since = fieldSince[field]
- if since != "" && since == typeSince {
- // Don't highlight field versions if they were the
- // same as the struct itself.
- since = ""
- }
- }
- if since == "" {
- buf2.Write(line)
- } else {
- if bytes.Contains(line, slashSlash) {
- line = bytes.TrimRight(line, " \t.")
- buf2.Write(line)
- buf2.WriteString("; added in Go ")
- } else {
- buf2.Write(line)
- buf2.WriteString(" // Go ")
- }
- buf2.WriteString(since)
- }
- buf2.WriteByte('\n')
- }
- w.Write(buf2.Bytes())
- }
-}
-
-var slashSlash = []byte("//")
-
-// WriteNode writes x to w.
-// TODO(bgarcia) Is this method needed? It's just a wrapper for p.writeNode.
-func (p *Presentation) WriteNode(w io.Writer, fset *token.FileSet, x interface{}) {
- p.writeNode(w, nil, fset, x)
-}
-
-// firstIdent returns the first identifier in x.
-// This actually parses "identifiers" that begin with numbers too, but we
-// never feed it such input, so it's fine.
-func firstIdent(x []byte) string {
- x = bytes.TrimSpace(x)
- i := bytes.IndexFunc(x, func(r rune) bool { return !unicode.IsLetter(r) && !unicode.IsNumber(r) })
- if i == -1 {
- return string(x)
- }
- return string(x[:i])
-}