From 2bdbb87a682d799cf5e262a61a3ef1faf41151af Mon Sep 17 00:00:00 2001
From: Tyler Bui-Palsulich <26876514+tbpg@users.noreply.github.com>
Date: Wed, 27 Jan 2021 08:34:02 -0500
Subject: [PATCH] feat(internal/godocfx): xref function declarations (#3615)
Type declarations are handled differently. So, I'll handle them in a
future PR. There are some TODOs at the top of parse.go with additional
ways we can improve.
I chose to define linkify in parse.go rather than pkgsite because (1) we
need the package/import name logic and (2) I want to keep changes to a
minimum in the third_party code.
---
internal/godocfx/parse.go | 137 ++++++++++-
internal/godocfx/testdata/golden/index.yml | 270 +++++++++++++--------
third_party/pkgsite/synopsis.go | 65 +++--
3 files changed, 329 insertions(+), 143 deletions(-)
diff --git a/internal/godocfx/parse.go b/internal/godocfx/parse.go
index 64628591e0d..33932edd7f9 100644
--- a/internal/godocfx/parse.go
+++ b/internal/godocfx/parse.go
@@ -14,6 +14,13 @@
// +build go1.15
+// TODO:
+// pkgsite.PrintType doesn't linkify.
+// IDs for const/var groups have every name, not just the one to link to.
+// Preserve IDs when sanitizing then use the right ID for linking.
+// Link to different domains by pattern (e.g. for cloud.google.com/go).
+// Make sure dot imports work (those identifiers aren't in the current package).
+
package main
import (
@@ -148,7 +155,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
// Once the files are grouped by package, process each package
// independently.
for _, pi := range pkgInfos {
-
+ link := newLinker(pi.pkg.Imports, pi.importRenames)
pkgItem := &item{
UID: pi.doc.ImportPath,
Name: pi.doc.ImportPath,
@@ -255,7 +262,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
Type: "function",
Summary: fn.Doc,
Langs: onlyGo,
- Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
+ Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
Examples: processExamples(fn.Examples, pi.fset),
})
}
@@ -270,7 +277,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
Type: "method",
Summary: fn.Doc,
Langs: onlyGo,
- Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
+ Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
Examples: processExamples(fn.Examples, pi.fset),
})
}
@@ -286,7 +293,7 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
Type: "function",
Summary: fn.Doc,
Langs: onlyGo,
- Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl)},
+ Syntax: syntax{Content: pkgsite.Synopsis(pi.fset, fn.Decl, link.linkify)},
Examples: processExamples(fn.Examples, pi.fset),
})
}
@@ -300,6 +307,66 @@ func parse(glob string, workingDir string, optionalExtraFiles []string) (*result
}, nil
}
+type linker struct {
+ // imports is a map from local package name to import path.
+ // Behavior is undefined when a single import has different names in
+ // different files.
+ imports map[string]string
+}
+
+func newLinker(rawImports map[string]*packages.Package, importSyntax map[string]string) *linker {
+ imports := map[string]string{}
+ for path, pkg := range rawImports {
+ name := pkg.Name
+ if rename := importSyntax[path]; rename != "" {
+ name = rename
+ }
+ imports[name] = path
+ }
+ return &linker{imports: imports}
+}
+
+func (l *linker) linkify(s string) string {
+ prefix := ""
+ if strings.HasPrefix(s, "...") {
+ s = s[3:]
+ prefix = "..."
+ }
+ if s[0] == '*' {
+ s = s[1:]
+ prefix += "*"
+ }
+
+ // If s does not have a dot, it's in this package.
+ if !strings.Contains(s, ".") {
+ // If s is not exported, it's probably a builtin.
+ if !token.IsExported(s) {
+ if builtins[s] {
+ return fmt.Sprintf(`%s%s`, prefix, strings.ToLower(s), s)
+ }
+ return fmt.Sprintf("%s%s", prefix, s)
+ }
+ return fmt.Sprintf(`%s%s`, prefix, strings.ToLower(s), s)
+ }
+ // Otherwise, it's in another package.
+ split := strings.Split(s, ".")
+ if len(split) != 2 {
+ // Don't know how to link this.
+ return fmt.Sprintf("%s%s", prefix, s)
+ }
+
+ pkg := split[0]
+ pkgPath, ok := l.imports[pkg]
+ if !ok {
+ // Don't know how to link this.
+ return fmt.Sprintf("%s%s", prefix, s)
+ }
+ name := split[1]
+ pkgLink := fmt.Sprintf("http://pkg.go.dev/%s", pkgPath)
+ link := fmt.Sprintf("%s#%s", pkgLink, name)
+ return fmt.Sprintf("%s%s.%s", prefix, pkgLink, pkg, link, name)
+}
+
// processExamples converts the examples to []example.
//
// Surrounding braces and indentation is removed.
@@ -400,11 +467,13 @@ type pkgInfo struct {
pkg *packages.Package
doc *doc.Package
fset *token.FileSet
+ // importRenames is a map from package path to local name or "".
+ importRenames map[string]string
}
func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
config := &packages.Config{
- Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule,
+ Mode: packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule | packages.NeedImports,
Tests: true,
Dir: workingDir,
}
@@ -494,12 +563,64 @@ func loadPackages(glob, workingDir string) ([]pkgInfo, error) {
continue
}
+ imports := map[string]string{}
+ for _, f := range parsedFiles {
+ for _, i := range f.Imports {
+ name := ""
+ // i.Name is nil for imports that aren't renamed.
+ if i.Name != nil {
+ name = i.Name.Name
+ }
+ iPath := strings.Trim(i.Path.Value, `"`)
+ imports[iPath] = name
+ }
+ }
+
result = append(result, pkgInfo{
- pkg: idToPkg[pkgPath],
- doc: docPkg,
- fset: fset,
+ pkg: idToPkg[pkgPath],
+ doc: docPkg,
+ fset: fset,
+ importRenames: imports,
})
}
return result, nil
}
+
+var builtins = map[string]bool{
+ "append": true,
+ "cap": true,
+ "close": true,
+ "complex": true,
+ "copy": true,
+ "delete": true,
+ "imag": true,
+ "len": true,
+ "make": true,
+ "new": true,
+ "panic": true,
+ "print": true,
+ "println": true,
+ "real": true,
+ "recover": true,
+ "bool": true,
+ "byte": true,
+ "complex128": true,
+ "complex64": true,
+ "error": true,
+ "float32": true,
+ "float64": true,
+ "int": true,
+ "int16": true,
+ "int32": true,
+ "int64": true,
+ "int8": true,
+ "rune": true,
+ "string": true,
+ "uint": true,
+ "uint16": true,
+ "uint32": true,
+ "uint64": true,
+ "uint8": true,
+ "uintptr": true,
+}
diff --git a/internal/godocfx/testdata/golden/index.yml b/internal/godocfx/testdata/golden/index.yml
index 018b540eaad..73c6c15cf0f 100644
--- a/internal/godocfx/testdata/golden/index.yml
+++ b/internal/godocfx/testdata/golden/index.yml
@@ -348,8 +348,9 @@ items:
langs:
- go
syntax:
- content: func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity) (err
- error)
+ content: func (a *ACLHandle) Delete(ctx context.Context, entity ACLEntity)
+ (err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -367,7 +368,9 @@ items:
langs:
- go
syntax:
- content: func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule, err error)
+ content: func (a *ACLHandle) List(ctx context.Context) (rules []ACLRule,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -385,8 +388,9 @@ items:
langs:
- go
syntax:
- content: func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity, role ACLRole)
- (err error)
+ content: func (a *ACLHandle) Set(ctx context.Context, entity ACLEntity,
+ role ACLRole) (err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -540,7 +544,8 @@ items:
langs:
- go
syntax:
- content: func (ua *BucketAttrsToUpdate) DeleteLabel(name string)
+ content: func (ua *BucketAttrsToUpdate) DeleteLabel(name
+ string)
- uid: cloud.google.com/go/storage.BucketAttrsToUpdate.SetLabel
name: |
func (*BucketAttrsToUpdate) SetLabel
@@ -553,7 +558,8 @@ items:
langs:
- go
syntax:
- content: func (ua *BucketAttrsToUpdate) SetLabel(name, value string)
+ content: func (ua *BucketAttrsToUpdate) SetLabel(name,
+ value string)
- uid: cloud.google.com/go/storage.BucketConditions
name: BucketConditions
id: BucketConditions
@@ -619,7 +625,7 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) ACL() *ACLHandle
+ content: func (b *BucketHandle) ACL() *ACLHandle
- uid: cloud.google.com/go/storage.BucketHandle.AddNotification
name: |
func (*BucketHandle) AddNotification
@@ -633,8 +639,10 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) AddNotification(ctx context.Context, n *Notification)
- (ret *Notification, err error)
+ content: func (b *BucketHandle) AddNotification(ctx
+ context.Context,
+ n *Notification) (ret *Notification,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -653,8 +661,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs,
- err error)
+ content: func (b *BucketHandle) Attrs(ctx context.Context) (attrs *BucketAttrs,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -672,8 +681,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Create(ctx context.Context, projectID string,
- attrs *BucketAttrs) (err error)
+ content: func (b *BucketHandle) Create(ctx context.Context, projectID string,
+ attrs *BucketAttrs) (err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -692,7 +702,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) DefaultObjectACL() *ACLHandle
+ content: func (b *BucketHandle) DefaultObjectACL()
+ *ACLHandle
- uid: cloud.google.com/go/storage.BucketHandle.Delete
name: |
func (*BucketHandle) Delete
@@ -704,7 +715,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Delete(ctx context.Context) (err error)
+ content: func (b *BucketHandle) Delete(ctx context.Context) (err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -721,8 +733,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) DeleteNotification(ctx context.Context, id string)
- (err error)
+ content: func (b *BucketHandle) DeleteNotification(ctx
+ context.Context,
+ id string) (err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nvar
notificationID string\n\nfunc main() {\n\tctx := context.Background()\n\tclient,
@@ -741,7 +754,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) IAM() *iam.Handle
+ content: func (b *BucketHandle) IAM() *iam.Handle
- uid: cloud.google.com/go/storage.BucketHandle.If
name: |
func (*BucketHandle) If
@@ -757,7 +771,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) If(conds BucketConditions) *BucketHandle
+ content: func (b *BucketHandle) If(conds BucketConditions)
+ *BucketHandle
- uid: cloud.google.com/go/storage.BucketHandle.LockRetentionPolicy
name: |
func (*BucketHandle) LockRetentionPolicy
@@ -777,7 +792,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) LockRetentionPolicy(ctx context.Context) error
+ content: func (b *BucketHandle) LockRetentionPolicy(ctx
+ context.Context)
+ error
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -799,8 +816,10 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Notifications(ctx context.Context) (n map[string]*Notification,
- err error)
+ content: func (b *BucketHandle) Notifications(ctx
+ context.Context)
+ (n map[string]*Notification,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -823,7 +842,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Object(name string) *ObjectHandle
+ content: func (b *BucketHandle) Object(name string)
+ *ObjectHandle
- uid: cloud.google.com/go/storage.BucketHandle.Objects
name: |
func (*BucketHandle) Objects
@@ -838,7 +858,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Objects(ctx context.Context, q *Query) *ObjectIterator
+ content: func (b *BucketHandle) Objects(ctx context.Context, q *Query)
+ *ObjectIterator
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -855,8 +877,9 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate)
- (attrs *BucketAttrs, err error)
+ content: func (b *BucketHandle) Update(ctx context.Context, uattrs BucketAttrsToUpdate)
+ (attrs *BucketAttrs, err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -887,7 +910,8 @@ items:
langs:
- go
syntax:
- content: func (b *BucketHandle) UserProject(projectID string) *BucketHandle
+ content: func (b *BucketHandle) UserProject(projectID
+ string) *BucketHandle
- uid: cloud.google.com/go/storage.BucketIterator
name: BucketIterator
id: BucketIterator
@@ -918,7 +942,8 @@ items:
langs:
- go
syntax:
- content: func (it *BucketIterator) Next() (*BucketAttrs, error)
+ content: func (it *BucketIterator) Next() (*BucketAttrs,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -938,7 +963,8 @@ items:
langs:
- go
syntax:
- content: func (it *BucketIterator) PageInfo() *iterator.PageInfo
+ content: func (it *BucketIterator) PageInfo() *iterator.PageInfo
- uid: cloud.google.com/go/storage.BucketLogging
name: BucketLogging
id: BucketLogging
@@ -1039,8 +1065,10 @@ items:
langs:
- go
syntax:
- content: func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client,
- error)
+ content: func NewClient(ctx context.Context, opts ...option.ClientOption)
+ (*Client, error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\t// Use Google Application Default
@@ -1072,7 +1100,8 @@ items:
langs:
- go
syntax:
- content: func (c *Client) Bucket(name string) *BucketHandle
+ content: func (c *Client) Bucket(name string)
+ *BucketHandle
- uid: cloud.google.com/go/storage.Client.Buckets
name: |
func (*Client) Buckets
@@ -1089,7 +1118,9 @@ items:
langs:
- go
syntax:
- content: func (c *Client) Buckets(ctx context.Context, projectID string) *BucketIterator
+ content: func (c *Client) Buckets(ctx context.Context, projectID string)
+ *BucketIterator
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1108,7 +1139,7 @@ items:
langs:
- go
syntax:
- content: func (c *Client) Close() error
+ content: func (c *Client) Close() error
- uid: cloud.google.com/go/storage.Client.CreateHMACKey
name: |
func (*Client) CreateHMACKey
@@ -1122,8 +1153,10 @@ items:
langs:
- go
syntax:
- content: func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail
- string, ...) (*HMACKey, error)
+ content: func (c *Client) CreateHMACKey(ctx context.Context, projectID, serviceAccountEmail
+ string, opts ...HMACKeyOption)
+ (*HMACKey, error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1143,7 +1176,8 @@ items:
langs:
- go
syntax:
- content: func (c *Client) HMACKeyHandle(projectID, accessID string) *HMACKeyHandle
+ content: func (c *Client) HMACKeyHandle(projectID, accessID
+ string) *HMACKeyHandle
- uid: cloud.google.com/go/storage.Client.ListHMACKeys
name: |
func (*Client) ListHMACKeys
@@ -1159,8 +1193,9 @@ items:
langs:
- go
syntax:
- content: func (c *Client) ListHMACKeys(ctx context.Context, projectID string,
- opts ...HMACKeyOption) *HMACKeysIterator
+ content: func (c *Client) ListHMACKeys(ctx context.Context, projectID string,
+ opts ...HMACKeyOption) *HMACKeysIterator
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1194,8 +1229,9 @@ items:
langs:
- go
syntax:
- content: func (c *Client) ServiceAccount(ctx context.Context, projectID string)
- (string, error)
+ content: func (c *Client) ServiceAccount(ctx context.Context, projectID string)
+ (string, error)
- uid: cloud.google.com/go/storage.Composer
name: Composer
id: Composer
@@ -1228,8 +1264,9 @@ items:
langs:
- go
syntax:
- content: func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs, err
- error)
+ content: func (c *Composer) Run(ctx context.Context) (attrs *ObjectAttrs,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1313,7 +1350,9 @@ items:
langs:
- go
syntax:
- content: func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs, err error)
+ content: func (c *Copier) Run(ctx context.Context) (attrs *ObjectAttrs,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1401,8 +1440,9 @@ items:
langs:
- go
syntax:
- content: func (hkh *HMACKeyHandle) Delete(ctx context.Context, opts ...HMACKeyOption)
- error
+ content: func (hkh *HMACKeyHandle) Delete(ctx context.Context,
+ opts ...HMACKeyOption) error
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1427,8 +1467,9 @@ items:
langs:
- go
syntax:
- content: func (hkh *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption)
- (*HMACKey, error)
+ content: func (hkh *HMACKeyHandle) Get(ctx context.Context, opts ...HMACKeyOption)
+ (*HMACKey, error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1448,8 +1489,10 @@ items:
langs:
- go
syntax:
- content: func (h *HMACKeyHandle) Update(ctx context.Context, au HMACKeyAttrsToUpdate,
- opts ...HMACKeyOption) (*HMACKey, error)
+ content: func (h *HMACKeyHandle) Update(ctx context.Context, au HMACKeyAttrsToUpdate,
+ opts ...HMACKeyOption) (*HMACKey,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1488,7 +1531,8 @@ items:
langs:
- go
syntax:
- content: func ForHMACKeyServiceAccountEmail(serviceAccountEmail string) HMACKeyOption
+ content: func ForHMACKeyServiceAccountEmail(serviceAccountEmail string)
+ HMACKeyOption
- uid: cloud.google.com/go/storage.HMACKeyOption.ShowDeletedHMACKeys
name: |
func ShowDeletedHMACKeys
@@ -1502,7 +1546,7 @@ items:
langs:
- go
syntax:
- content: func ShowDeletedHMACKeys() HMACKeyOption
+ content: func ShowDeletedHMACKeys() HMACKeyOption
- uid: cloud.google.com/go/storage.HMACKeyOption.UserProjectForHMACKeys
name: |
func UserProjectForHMACKeys
@@ -1519,7 +1563,8 @@ items:
langs:
- go
syntax:
- content: func UserProjectForHMACKeys(userProjectID string) HMACKeyOption
+ content: func UserProjectForHMACKeys(userProjectID string)
+ HMACKeyOption
- uid: cloud.google.com/go/storage.HMACKeysIterator
name: HMACKeysIterator
id: HMACKeysIterator
@@ -1553,7 +1598,8 @@ items:
langs:
- go
syntax:
- content: func (it *HMACKeysIterator) Next() (*HMACKey, error)
+ content: func (it *HMACKeysIterator) Next() (*HMACKey, error)
- uid: cloud.google.com/go/storage.HMACKeysIterator.PageInfo
name: |
func (*HMACKeysIterator) PageInfo
@@ -1569,7 +1615,9 @@ items:
langs:
- go
syntax:
- content: func (it *HMACKeysIterator) PageInfo() *iterator.PageInfo
+ content: func (it *HMACKeysIterator) PageInfo()
+ *iterator.PageInfo
- uid: cloud.google.com/go/storage.HMACState
name: HMACState
id: HMACState
@@ -1865,7 +1913,7 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) ACL() *ACLHandle
+ content: func (o *ObjectHandle) ACL() *ACLHandle
- uid: cloud.google.com/go/storage.ObjectHandle.Attrs
name: |
func (*ObjectHandle) Attrs
@@ -1878,8 +1926,9 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs,
- err error)
+ content: func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs,
+ err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1905,7 +1954,7 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) BucketName() string
+ content: func (o *ObjectHandle) BucketName() string
- uid: cloud.google.com/go/storage.ObjectHandle.ComposerFrom
name: |
func (*ObjectHandle) ComposerFrom
@@ -1923,7 +1972,8 @@ items:
langs:
- go
syntax:
- content: func (dst *ObjectHandle) ComposerFrom(srcs ...*ObjectHandle) *Composer
+ content: func (dst *ObjectHandle) ComposerFrom(srcs
+ ...*ObjectHandle) *Composer
- uid: cloud.google.com/go/storage.ObjectHandle.CopierFrom
name: |
func (*ObjectHandle) CopierFrom
@@ -1940,7 +1990,8 @@ items:
langs:
- go
syntax:
- content: func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier
+ content: func (dst *ObjectHandle) CopierFrom(src *ObjectHandle) *Copier
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nvar
key1, key2 []byte\n\nfunc main() {\n\t// To rotate the encryption key on an
@@ -1961,7 +2012,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) Delete(ctx context.Context) error
+ content: func (o *ObjectHandle) Delete(ctx context.Context) error
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -1990,7 +2042,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) Generation(gen int64) *ObjectHandle
+ content: func (o *ObjectHandle) Generation(gen int64) *ObjectHandle
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n)\n\nvar
gen int64\n\nfunc main() {\n\t// Read an object's contents from generation gen,
@@ -2014,7 +2067,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) If(conds Conditions) *ObjectHandle
+ content: func (o *ObjectHandle) If(conds Conditions)
+ *ObjectHandle
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"google.golang.org/api/googleapi\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nvar
gen int64\n\nfunc main() {\n\t// Read from an object only if the current generation
@@ -2042,7 +2096,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) Key(encryptionKey []byte) *ObjectHandle
+ content: func (o *ObjectHandle) Key(encryptionKey
+ []byte) *ObjectHandle
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nvar
secretKey []byte\n\nfunc main() {\n\tctx := context.Background()\n\tclient,
@@ -2071,8 +2126,10 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length
- int64) (r *Reader, err error)
+ content: func (o *ObjectHandle) NewRangeReader(ctx
+ context.Context,
+ offset, length int64) (r *Reader, err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2112,7 +2169,9 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader, error)
+ content: func (o *ObjectHandle) NewReader(ctx context.Context) (*Reader,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/ioutil\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2145,7 +2204,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer
+ content: func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2162,7 +2222,7 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) ObjectName() string
+ content: func (o *ObjectHandle) ObjectName() string
- uid: cloud.google.com/go/storage.ObjectHandle.ReadCompressed
name: |
func (*ObjectHandle) ReadCompressed
@@ -2174,7 +2234,8 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) ReadCompressed(compressed bool) *ObjectHandle
+ content: func (o *ObjectHandle) ReadCompressed(compressed
+ bool) *ObjectHandle
- uid: cloud.google.com/go/storage.ObjectHandle.Update
name: |
func (*ObjectHandle) Update
@@ -2188,8 +2249,9 @@ items:
langs:
- go
syntax:
- content: func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate)
- (oa *ObjectAttrs, err error)
+ content: func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate)
+ (oa *ObjectAttrs, err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2230,7 +2292,8 @@ items:
langs:
- go
syntax:
- content: func (it *ObjectIterator) Next() (*ObjectAttrs, error)
+ content: func (it *ObjectIterator) Next() (*ObjectAttrs,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n\t\"google.golang.org/api/iterator\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2250,7 +2313,8 @@ items:
langs:
- go
syntax:
- content: func (it *ObjectIterator) PageInfo() *iterator.PageInfo
+ content: func (it *ObjectIterator) PageInfo() *iterator.PageInfo
- uid: cloud.google.com/go/storage.PolicyV4Fields
name: PolicyV4Fields
id: PolicyV4Fields
@@ -2303,8 +2367,9 @@ items:
langs:
- go
syntax:
- content: func GenerateSignedPostPolicyV4(bucket, object string, opts *PostPolicyV4Options)
- (*PostPolicyV4, error)
+ content: func GenerateSignedPostPolicyV4(bucket, object string,
+ opts *PostPolicyV4Options) (*PostPolicyV4,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"bytes\"\n\t\"cloud.google.com/go/storage\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc
main() {\n\tpv4, err := storage.GenerateSignedPostPolicyV4(\"my-bucket\", \"my-object.txt\",
@@ -2354,7 +2419,8 @@ items:
langs:
- go
syntax:
- content: func ConditionContentLengthRange(start, end uint64) PostPolicyV4Condition
+ content: func ConditionContentLengthRange(start, end uint64)
+ PostPolicyV4Condition
- uid: cloud.google.com/go/storage.PostPolicyV4Condition.ConditionStartsWith
name: |
func ConditionStartsWith
@@ -2367,7 +2433,8 @@ items:
langs:
- go
syntax:
- content: func ConditionStartsWith(key, value string) PostPolicyV4Condition
+ content: func ConditionStartsWith(key, value string)
+ PostPolicyV4Condition
- uid: cloud.google.com/go/storage.PostPolicyV4Options
name: PostPolicyV4Options
id: PostPolicyV4Options
@@ -2469,7 +2536,8 @@ items:
langs:
- go
syntax:
- content: func (q *Query) SetAttrSelection(attrs []string) error
+ content: func (q *Query) SetAttrSelection(attrs []string)
+ error
- uid: cloud.google.com/go/storage.Reader
name: Reader
id: Reader
@@ -2500,7 +2568,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) CacheControl() string
+ content: func (r *Reader) CacheControl() string
- uid: cloud.google.com/go/storage.Reader.Close
name: |
func (*Reader) Close
@@ -2512,7 +2580,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) Close() error
+ content: func (r *Reader) Close() error
- uid: cloud.google.com/go/storage.Reader.ContentEncoding
name: |
func (*Reader) ContentEncoding
@@ -2526,7 +2594,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) ContentEncoding() string
+ content: func (r *Reader) ContentEncoding() string
- uid: cloud.google.com/go/storage.Reader.ContentType
name: |
func (*Reader) ContentType
@@ -2540,7 +2608,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) ContentType() string
+ content: func (r *Reader) ContentType() string
- uid: cloud.google.com/go/storage.Reader.LastModified
name: |
func (*Reader) LastModified
@@ -2554,7 +2622,8 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) LastModified() (time.Time, error)
+ content: func (r *Reader) LastModified() (time.Time, error)
- uid: cloud.google.com/go/storage.Reader.Read
name: |
func (*Reader) Read
@@ -2564,7 +2633,8 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) Read(p []byte) (int, error)
+ content: func (r *Reader) Read(p []byte)
+ (int, error)
- uid: cloud.google.com/go/storage.Reader.Remain
name: |
func (*Reader) Remain
@@ -2576,7 +2646,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) Remain() int64
+ content: func (r *Reader) Remain() int64
- uid: cloud.google.com/go/storage.Reader.Size
name: |
func (*Reader) Size
@@ -2592,7 +2662,7 @@ items:
langs:
- go
syntax:
- content: func (r *Reader) Size() int64
+ content: func (r *Reader) Size() int64
- uid: cloud.google.com/go/storage.ReaderObjectAttrs
name: ReaderObjectAttrs
id: ReaderObjectAttrs
@@ -2758,7 +2828,8 @@ items:
langs:
- go
syntax:
- content: func BucketBoundHostname(hostname string) URLStyle
+ content: func BucketBoundHostname(hostname string)
+ URLStyle
- uid: cloud.google.com/go/storage.URLStyle.PathStyle
name: |
func PathStyle
@@ -2771,7 +2842,7 @@ items:
langs:
- go
syntax:
- content: func PathStyle() URLStyle
+ content: func PathStyle() URLStyle
- uid: cloud.google.com/go/storage.URLStyle.VirtualHostedStyle
name: |
func VirtualHostedStyle
@@ -2784,7 +2855,7 @@ items:
langs:
- go
syntax:
- content: func VirtualHostedStyle() URLStyle
+ content: func VirtualHostedStyle() URLStyle
- uid: cloud.google.com/go/storage.UniformBucketLevelAccess
name: UniformBucketLevelAccess
id: UniformBucketLevelAccess
@@ -2852,7 +2923,7 @@ items:
langs:
- go
syntax:
- content: func (w *Writer) Attrs() *ObjectAttrs
+ content: func (w *Writer) Attrs() *ObjectAttrs
- uid: cloud.google.com/go/storage.Writer.Close
name: |
func (*Writer) Close
@@ -2866,7 +2937,7 @@ items:
langs:
- go
syntax:
- content: func (w *Writer) Close() error
+ content: func (w *Writer) Close() error
- uid: cloud.google.com/go/storage.Writer.CloseWithError
name: |
func (*Writer) CloseWithError
@@ -2881,7 +2952,8 @@ items:
langs:
- go
syntax:
- content: func (w *Writer) CloseWithError(err error) error
+ content: func (w *Writer) CloseWithError(err error)
+ error
- uid: cloud.google.com/go/storage.Writer.Write
name: |
func (*Writer) Write
@@ -2901,7 +2973,8 @@ items:
langs:
- go
syntax:
- content: func (w *Writer) Write(p []byte) (n int, err error)
+ content: func (w *Writer) Write(p []byte)
+ (n int, err error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"context\"\n\t\"fmt\"\n)\n\nfunc
main() {\n\tctx := context.Background()\n\tclient, err := storage.NewClient(ctx)\n\tif
@@ -2948,8 +3021,9 @@ items:
langs:
- go
syntax:
- content: func SignedURL(bucket, name string, opts *SignedURLOptions) (string,
- error)
+ content: func SignedURL(bucket, name string,
+ opts *SignedURLOptions) (string,
+ error)
codeexamples:
- content: "package main\n\nimport (\n\t\"cloud.google.com/go/storage\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"time\"\n)\n\nfunc
main() {\n\tpkey, err := ioutil.ReadFile(\"my-private-key.pem\")\n\tif err !=
diff --git a/third_party/pkgsite/synopsis.go b/third_party/pkgsite/synopsis.go
index 84d29d37aca..bfe10cc71d3 100644
--- a/third_party/pkgsite/synopsis.go
+++ b/third_party/pkgsite/synopsis.go
@@ -16,14 +16,14 @@ import (
const maxSynopsisNodeDepth = 10
// Synopsis returns a one-line summary of the given input node.
-func Synopsis(fset *token.FileSet, n ast.Node) string {
- return oneLineNodeDepth(fset, n, 0)
+func Synopsis(fset *token.FileSet, n ast.Node, linkify func(string) string) string {
+ return oneLineNodeDepth(fset, n, 0, linkify)
}
// oneLineNodeDepth returns a one-line summary of the given input node.
// The depth specifies the current depth when traversing the AST and the
// function will stop traversing once depth reaches maxSynopsisNodeDepth.
-func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
+func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int, linkify func(string) string) string {
const dotDotDot = "..."
if depth == maxSynopsisNodeDepth {
return dotDotDot
@@ -52,20 +52,20 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
// The type name may carry over from a previous specification in the
// case of constants and iota.
if valueSpec.Type != nil {
- typ = fmt.Sprintf(" %s", oneLineNodeDepth(fset, valueSpec.Type, depth))
+ typ = fmt.Sprintf(" %s", oneLineNodeDepth(fset, valueSpec.Type, depth, linkify))
} else if len(valueSpec.Values) > 0 {
typ = ""
}
val := ""
if i < len(valueSpec.Values) && valueSpec.Values[i] != nil {
- val = fmt.Sprintf(" = %s", oneLineNodeDepth(fset, valueSpec.Values[i], depth))
+ val = fmt.Sprintf(" = %s", oneLineNodeDepth(fset, valueSpec.Values[i], depth, linkify))
}
return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer)
}
case token.TYPE:
if len(n.Specs) > 0 {
- return oneLineNodeDepth(fset, n.Specs[0], depth) + trailer
+ return oneLineNodeDepth(fset, n.Specs[0], depth, linkify) + trailer
}
case token.IMPORT:
if len(n.Specs) > 0 {
@@ -78,11 +78,11 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
case *ast.FuncDecl:
// Formats func declarations.
name := n.Name.Name
- recv := oneLineNodeDepth(fset, n.Recv, depth)
+ recv := oneLineNodeDepth(fset, n.Recv, depth, linkify)
if len(recv) > 0 {
recv = "(" + recv + ") "
}
- fnc := oneLineNodeDepth(fset, n.Type, depth)
+ fnc := oneLineNodeDepth(fset, n.Type, depth, linkify)
if strings.Index(fnc, "func") == 0 {
fnc = fnc[4:]
}
@@ -93,13 +93,13 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
if n.Assign.IsValid() {
sep = " = "
}
- return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, oneLineNodeDepth(fset, n.Type, depth))
+ return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, oneLineNodeDepth(fset, n.Type, depth, linkify))
case *ast.FuncType:
var params []string
if n.Params != nil {
for _, field := range n.Params.List {
- params = append(params, oneLineField(fset, field, depth))
+ params = append(params, oneLineField(fset, field, depth, linkify))
}
}
needParens := false
@@ -108,7 +108,7 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
needParens = needParens || len(n.Results.List) > 1
for _, field := range n.Results.List {
needParens = needParens || len(field.Names) > 0
- results = append(results, oneLineField(fset, field, depth))
+ results = append(results, oneLineField(fset, field, depth, linkify))
}
}
@@ -139,43 +139,43 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
return ""
}
if len(n.List) == 1 {
- return oneLineField(fset, n.List[0], depth)
+ return oneLineField(fset, n.List[0], depth, linkify)
}
return dotDotDot
case *ast.FuncLit:
- return oneLineNodeDepth(fset, n.Type, depth) + " { ... }"
+ return oneLineNodeDepth(fset, n.Type, depth, linkify) + " { ... }"
case *ast.CompositeLit:
- typ := oneLineNodeDepth(fset, n.Type, depth)
+ typ := oneLineNodeDepth(fset, n.Type, depth, linkify)
if len(n.Elts) == 0 {
return fmt.Sprintf("%s{}", typ)
}
return fmt.Sprintf("%s{ %s }", typ, dotDotDot)
case *ast.ArrayType:
- length := oneLineNodeDepth(fset, n.Len, depth)
- element := oneLineNodeDepth(fset, n.Elt, depth)
+ length := oneLineNodeDepth(fset, n.Len, depth, linkify)
+ element := oneLineNodeDepth(fset, n.Elt, depth, linkify)
return fmt.Sprintf("[%s]%s", length, element)
case *ast.MapType:
- key := oneLineNodeDepth(fset, n.Key, depth)
- value := oneLineNodeDepth(fset, n.Value, depth)
+ key := oneLineNodeDepth(fset, n.Key, depth, linkify)
+ value := oneLineNodeDepth(fset, n.Value, depth, linkify)
return fmt.Sprintf("map[%s]%s", key, value)
case *ast.CallExpr:
- fnc := oneLineNodeDepth(fset, n.Fun, depth)
+ fnc := oneLineNodeDepth(fset, n.Fun, depth, linkify)
var args []string
for _, arg := range n.Args {
- args = append(args, oneLineNodeDepth(fset, arg, depth))
+ args = append(args, oneLineNodeDepth(fset, arg, depth, linkify))
}
return fmt.Sprintf("%s(%s)", fnc, joinStrings(args))
case *ast.UnaryExpr:
- return fmt.Sprintf("%s%s", n.Op, oneLineNodeDepth(fset, n.X, depth))
+ return fmt.Sprintf("%s%s", n.Op, oneLineNodeDepth(fset, n.X, depth, linkify))
case *ast.Ident:
- return n.Name
+ return linkify(n.Name)
default:
// As a fallback, use default formatter for all unknown node types.
@@ -185,33 +185,24 @@ func oneLineNodeDepth(fset *token.FileSet, node ast.Node, depth int) string {
if strings.Contains(s, "\n") {
return dotDotDot
}
- return s
+ return linkify(s)
}
}
// oneLineField returns a one-line summary of the field.
-func oneLineField(fset *token.FileSet, field *ast.Field, depth int) string {
+func oneLineField(fset *token.FileSet, field *ast.Field, depth int, linkify func(string) string) string {
var names []string
for _, name := range field.Names {
names = append(names, name.Name)
}
+ t := oneLineNodeDepth(fset, field.Type, depth, linkify)
if len(names) == 0 {
- return oneLineNodeDepth(fset, field.Type, depth)
+ return t
}
- return joinStrings(names) + " " + oneLineNodeDepth(fset, field.Type, depth)
+ return joinStrings(names) + " " + t
}
-// joinStrings formats the input as a comma-separated list,
-// but truncates the list at some reasonable length if necessary.
+// joinStrings formats the input as a comma-separated list.
func joinStrings(ss []string) string {
- const widthLimit = 80
- var n int
- for i, s := range ss {
- n += len(s) + len(", ")
- if n > widthLimit {
- ss = append(ss[:i:i], "...")
- break
- }
- }
return strings.Join(ss, ", ")
}