1
2
3
4
5
6 package doc
7
8 import (
9 "bytes"
10 "context"
11 "flag"
12 "fmt"
13 "go/build"
14 "go/token"
15 "io"
16 "log"
17 "os"
18 "os/exec"
19 "path"
20 "path/filepath"
21 "strings"
22
23 "cmd/go/internal/base"
24 "cmd/go/internal/cfg"
25 "cmd/go/internal/load"
26 "cmd/go/internal/modload"
27 "cmd/go/internal/search"
28 "cmd/internal/telemetry/counter"
29 )
30
31 var CmdDoc = &base.Command{
32 Run: runDoc,
33 UsageLine: "go doc [doc flags] [package|[package.]symbol[.methodOrField]]",
34 CustomFlags: true,
35 Short: "show documentation for package or symbol",
36 Long: `
37 Doc prints the documentation comments associated with the item identified by its
38 arguments (a package, const, func, type, var, method, or struct field)
39 followed by a one-line summary of each of the first-level items "under"
40 that item (package-level declarations for a package, methods for a type,
41 etc.).
42
43 Doc accepts zero, one, or two arguments.
44
45 Given no arguments, that is, when run as
46
47 go doc
48
49 it prints the package documentation for the package in the current directory.
50 If the package is a command (package main), the exported symbols of the package
51 are elided from the presentation unless the -cmd flag is provided.
52
53 When run with one argument, the argument is treated as a Go-syntax-like
54 representation of the item to be documented. What the argument selects depends
55 on what is installed in GOROOT and GOPATH, as well as the form of the argument,
56 which is schematically one of these:
57
58 go doc <pkg>
59 go doc <sym>[.<methodOrField>]
60 go doc [<pkg>.]<sym>[.<methodOrField>]
61 go doc [<pkg>.][<sym>.]<methodOrField>
62
63 The first item in this list matched by the argument is the one whose documentation
64 is printed. (See the examples below.) However, if the argument starts with a capital
65 letter it is assumed to identify a symbol or method in the current directory.
66
67 For packages, the order of scanning is determined lexically in breadth-first order.
68 That is, the package presented is the one that matches the search and is nearest
69 the root and lexically first at its level of the hierarchy. The GOROOT tree is
70 always scanned in its entirety before GOPATH.
71
72 If there is no package specified or matched, the package in the current
73 directory is selected, so "go doc Foo" shows the documentation for symbol Foo in
74 the current package.
75
76 The package path must be either a qualified path or a proper suffix of a
77 path. The go tool's usual package mechanism does not apply: package path
78 elements like . and ... are not implemented by go doc.
79
80 When run with two arguments, the first is a package path (full path or suffix),
81 and the second is a symbol, or symbol with method or struct field:
82
83 go doc <pkg> <sym>[.<methodOrField>]
84
85 In all forms, when matching symbols, lower-case letters in the argument match
86 either case but upper-case letters match exactly. This means that there may be
87 multiple matches of a lower-case argument in a package if different symbols have
88 different cases. If this occurs, documentation for all matches is printed.
89
90 Examples:
91 go doc
92 Show documentation for current package.
93 go doc -http
94 Serve HTML documentation over HTTP for the current package.
95 go doc Foo
96 Show documentation for Foo in the current package.
97 (Foo starts with a capital letter so it cannot match
98 a package path.)
99 go doc encoding/json
100 Show documentation for the encoding/json package.
101 go doc json
102 Shorthand for encoding/json.
103 go doc json.Number (or go doc json.number)
104 Show documentation and method summary for json.Number.
105 go doc json.Number.Int64 (or go doc json.number.int64)
106 Show documentation for json.Number's Int64 method.
107 go doc cmd/doc
108 Show package docs for the doc command.
109 go doc -cmd cmd/doc
110 Show package docs and exported symbols within the doc command.
111 go doc template.new
112 Show documentation for html/template's New function.
113 (html/template is lexically before text/template)
114 go doc text/template.new # One argument
115 Show documentation for text/template's New function.
116 go doc text/template new # Two arguments
117 Show documentation for text/template's New function.
118
119 At least in the current tree, these invocations all print the
120 documentation for json.Decoder's Decode method:
121
122 go doc json.Decoder.Decode
123 go doc json.decoder.decode
124 go doc json.decode
125 cd go/src/encoding/json; go doc decode
126
127 Flags:
128 -all
129 Show all the documentation for the package.
130 -c
131 Respect case when matching symbols.
132 -cmd
133 Treat a command (package main) like a regular package.
134 Otherwise package main's exported symbols are hidden
135 when showing the package's top-level documentation.
136 -ex
137 Include executable examples.
138 -http
139 Serve HTML docs over HTTP.
140 -short
141 One-line representation for each symbol. Cannot be
142 combined with -all.
143 -src
144 Show the full source code for the symbol. This will
145 display the full Go source of its declaration and
146 definition, such as a function definition (including
147 the body), type declaration or enclosing const
148 block. The output may therefore include unexported
149 details.
150 -u
151 Show documentation for unexported as well as exported
152 symbols, methods, and fields.
153 `,
154 }
155
156 func runDoc(ctx context.Context, cmd *base.Command, args []string) {
157 log.SetFlags(0)
158 log.SetPrefix("doc: ")
159 dirsInit()
160 var flagSet flag.FlagSet
161 err := do(ctx, os.Stdout, &flagSet, args)
162 if err != nil {
163 log.Fatal(err)
164 }
165 }
166
167 var (
168 unexported bool
169 matchCase bool
170 chdir string
171 showAll bool
172 showCmd bool
173 showEx bool
174 showSrc bool
175 short bool
176 serveHTTP bool
177 )
178
179
180 func usage(flagSet *flag.FlagSet) {
181 fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
182 fmt.Fprintf(os.Stderr, "\tgo doc\n")
183 fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
184 fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<methodOrField>]\n")
185 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.]<sym>[.<methodOrField>]\n")
186 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.][<sym>.]<methodOrField>\n")
187 fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<methodOrField>]\n")
188 fmt.Fprintf(os.Stderr, "For more information run\n")
189 fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
190 os.Exit(2)
191 }
192
193
194 func do(ctx context.Context, writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
195 flagSet.Usage = func() { usage(flagSet) }
196 unexported = false
197 matchCase = false
198 flagSet.StringVar(&chdir, "C", "", "change to `dir` before running command")
199 flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
200 flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
201 flagSet.BoolVar(&showAll, "all", false, "show all documentation for package")
202 flagSet.BoolVar(&showEx, "ex", false, "show executable examples for symbol or package")
203 flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
204 flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol")
205 flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol")
206 flagSet.BoolVar(&serveHTTP, "http", false, "serve HTML docs over HTTP")
207 flagSet.Parse(args)
208 counter.CountFlags("doc/flag:", *flag.CommandLine)
209 if chdir != "" {
210 if err := os.Chdir(chdir); err != nil {
211 return err
212 }
213 }
214 if showAll && short {
215 return fmt.Errorf("cannot combine -all and -short")
216 }
217 if serveHTTP {
218
219
220
221 if len(flagSet.Args()) == 0 {
222 mod, err := runCmd(append(os.Environ(), "GOWORK=off"), "go", "list", "-m")
223 if err == nil && mod != "" && mod != "command-line-arguments" {
224
225 return doPkgsite(mod, "")
226 }
227 gowork, err := runCmd(nil, "go", "env", "GOWORK")
228 if err == nil && gowork != "" {
229
230
231 return doPkgsite("", "")
232 }
233
234 return doPkgsite("std", "")
235 }
236
237
238
239
240 writer = io.Discard
241 }
242 var paths []string
243 var symbol, method string
244
245 dirs.Reset()
246 for i := 0; ; i++ {
247 buildPackage, userPath, sym, more := parseArgs(ctx, flagSet, flagSet.Args())
248 if i > 0 && !more {
249 return failMessage(paths, symbol, method)
250 }
251 if buildPackage == nil {
252 return fmt.Errorf("no such package: %s", userPath)
253 }
254
255
256
257 if buildPackage.ImportPath == "builtin" {
258 unexported = true
259 }
260
261 symbol, method = parseSymbol(flagSet, sym)
262 pkg := parsePackage(writer, buildPackage, userPath)
263 paths = append(paths, pkg.prettyPath())
264
265 defer func() {
266 pkg.flush()
267 e := recover()
268 if e == nil {
269 return
270 }
271 pkgError, ok := e.(PackageError)
272 if ok {
273 err = pkgError
274 return
275 }
276 panic(e)
277 }()
278
279 var found bool
280 switch {
281 case symbol == "":
282 pkg.packageDoc()
283 found = true
284 case method == "":
285 if pkg.symbolDoc(symbol) {
286 found = true
287 }
288 case pkg.printMethodDoc(symbol, method):
289 found = true
290 case pkg.printFieldDoc(symbol, method):
291 found = true
292 }
293 if found {
294 if serveHTTP {
295 path, fragment, err := objectPath(userPath, pkg, symbol, method)
296 if err != nil {
297 return err
298 }
299 return doPkgsite(path, fragment)
300 }
301 return nil
302 }
303 }
304 }
305
306 func runCmd(env []string, cmdline ...string) (string, error) {
307 var stdout, stderr strings.Builder
308 cmd := exec.Command(cmdline[0], cmdline[1:]...)
309 cmd.Env = env
310 cmd.Stdout = &stdout
311 cmd.Stderr = &stderr
312 if err := cmd.Run(); err != nil {
313 return "", fmt.Errorf("go doc: %s: %v\n%s\n", strings.Join(cmdline, " "), err, stderr.String())
314 }
315 return strings.TrimSpace(stdout.String()), nil
316 }
317
318
319 func objectPath(userPath string, pkg *Package, symbol, method string) (string, string, error) {
320 var err error
321 path := pkg.build.ImportPath
322 if path == "." {
323
324
325
326 path, err = runCmd(nil, "go", "list", userPath)
327 if err != nil {
328 return "", "", err
329 }
330 }
331
332 object := symbol
333 if symbol != "" && method != "" {
334 object = symbol + "." + method
335 }
336 return path, object, nil
337 }
338
339
340 func failMessage(paths []string, symbol, method string) error {
341 var b bytes.Buffer
342 if len(paths) > 1 {
343 b.WriteString("s")
344 }
345 b.WriteString(" ")
346 for i, path := range paths {
347 if i > 0 {
348 b.WriteString(", ")
349 }
350 b.WriteString(path)
351 }
352 if method == "" {
353 return fmt.Errorf("no symbol %s in package%s", symbol, &b)
354 }
355 return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b)
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369 func parseArgs(ctx context.Context, flagSet *flag.FlagSet, args []string) (pkg *load.Package, path, symbol string, more bool) {
370 wd, err := os.Getwd()
371 if err != nil {
372 log.Fatal(err)
373 }
374 loader := modload.NewState()
375 if testGOPATH {
376 loader = modload.NewDisabledState()
377 }
378 if len(args) > 0 && strings.Index(args[0], "@") >= 0 {
379
380 loader.ForceUseModules = true
381 loader.RootMode = modload.NoRoot
382 modload.Init(loader)
383 } else if loader.WillBeEnabled() {
384 loader.InitWorkfile()
385 modload.Init(loader)
386 modload.LoadModFile(loader, context.TODO())
387 }
388
389 if len(args) == 0 {
390
391 return mustLoadPackage(ctx, loader, wd), "", "", false
392 }
393 arg := args[0]
394
395 var version string
396 if i := strings.Index(arg, "@"); i >= 0 {
397 arg, version = arg[:i], arg[i+1:]
398 }
399
400
401
402
403 if isDotSlash(arg) {
404 arg = filepath.Join(wd, arg)
405 }
406 if version != "" && (build.IsLocalImport(filepath.ToSlash(arg)) || filepath.IsAbs(arg)) {
407 log.Fatal("cannot use @version with local or absolute paths")
408 }
409
410 importPkg := func(p string) (*load.Package, error) {
411 if version != "" {
412 return loadVersioned(ctx, loader, p, version)
413 }
414 return loadPackage(ctx, loader, p)
415 }
416
417 switch len(args) {
418 default:
419 usage(flagSet)
420 case 1:
421
422 case 2:
423
424 pkg, err := importPkg(arg)
425 if err == nil {
426 return pkg, arg, args[1], false
427 }
428 for {
429 importPath, ok := findNextPackage(arg)
430 if !ok {
431 break
432 }
433 if version != "" {
434 if pkg, err = loadVersioned(ctx, loader, importPath, version); err == nil {
435 return pkg, arg, args[1], true
436 }
437 } else {
438 if pkg, err = loadPackage(ctx, loader, importPath); err == nil {
439 return pkg, arg, args[1], true
440 }
441 }
442 }
443 if version != "" {
444 log.Fatal(err)
445 }
446 return nil, arg, args[1], false
447 }
448
449
450
451
452
453
454 var importErr error
455 if filepath.IsAbs(arg) {
456 pkg, importErr = loadPackage(ctx, loader, arg)
457 if importErr == nil {
458 return pkg, arg, "", false
459 }
460 } else {
461 pkg, importErr = importPkg(arg)
462 if importErr == nil {
463 return pkg, arg, "", false
464 }
465 }
466
467
468
469
470 if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) {
471 pkg, err := loadPackage(ctx, loader, ".")
472 if err == nil {
473 return pkg, "", arg, false
474 }
475 }
476
477
478 slash := strings.LastIndex(arg, "/")
479
480
481
482
483
484 var period int
485
486
487 for start := slash + 1; start < len(arg); start = period + 1 {
488 period = strings.Index(arg[start:], ".")
489 symbol := ""
490 if period < 0 {
491 period = len(arg)
492 } else {
493 period += start
494 symbol = arg[period+1:]
495 }
496
497 pkg, err := loadPackage(ctx, loader, arg[0:period])
498 if err == nil {
499 return pkg, arg[0:period], symbol, false
500 }
501
502
503 pkgName := arg[:period]
504 for {
505 importPath, ok := findNextPackage(pkgName)
506 if !ok {
507 break
508 }
509 if version != "" {
510 if pkg, err = loadVersioned(ctx, loader, importPath, version); err == nil {
511 return pkg, arg[0:period], symbol, true
512 }
513 } else if pkg, err = loadPackage(ctx, loader, importPath); err == nil {
514 return pkg, arg[0:period], symbol, true
515 }
516 }
517 dirs.Reset()
518 }
519
520
521 if slash < 0 && !isDotSlash(arg) && !filepath.IsAbs(arg) {
522 if pkgPath, v, ok := inferVersion(arg); ok {
523 if version == "" {
524 version = v
525 }
526 pkg, err := loadVersioned(ctx, loader, pkgPath, version)
527 if err == nil {
528 return pkg, pkgPath, "", false
529 }
530 }
531 }
532
533 if version != "" {
534 if importErr != nil {
535 log.Fatal(importErr)
536 }
537 log.Fatalf("no such package %q at version %q", arg, version)
538 }
539
540
541 if slash >= 0 {
542
543
544
545
546
547
548 importErrStr := importErr.Error()
549 if strings.Contains(importErrStr, arg[:period]) {
550 log.Fatal(importErrStr)
551 } else {
552 log.Fatalf("no such package %s: %s", arg[:period], importErrStr)
553 }
554 }
555
556 return mustLoadPackage(ctx, loader, wd), "", arg, false
557 }
558
559 func loadPackage(ctx context.Context, loader *modload.State, pattern string) (*load.Package, error) {
560 if !search.NewMatch(pattern).IsLiteral() {
561 return nil, fmt.Errorf("pattern %q does not specify a single package", pattern)
562 }
563
564 pkgOpts := load.PackageOpts{
565 IgnoreImports: true,
566 SuppressBuildInfo: true,
567 SuppressEmbedFiles: true,
568 }
569 pkgs := load.PackagesAndErrors(loader, ctx, pkgOpts, []string{pattern})
570
571 if len(pkgs) != 1 {
572 return nil, fmt.Errorf("path %q matched multiple packages", pattern)
573 }
574
575 p := pkgs[0]
576 if p.Error != nil {
577 return nil, p.Error
578 }
579 return p, nil
580 }
581
582 func mustLoadPackage(ctx context.Context, loader *modload.State, dir string) *load.Package {
583 pkg, err := loadPackage(ctx, loader, dir)
584 if err != nil {
585 log.Fatal(err)
586 }
587 return pkg
588 }
589
590
591
592
593
594 var dotPaths = []string{
595 `./`,
596 `../`,
597 `.\`,
598 `..\`,
599 }
600
601
602
603 func isDotSlash(arg string) bool {
604 if arg == "." || arg == ".." {
605 return true
606 }
607 for _, dotPath := range dotPaths {
608 if strings.HasPrefix(arg, dotPath) {
609 return true
610 }
611 }
612 return false
613 }
614
615
616
617
618 func parseSymbol(flagSet *flag.FlagSet, str string) (symbol, method string) {
619 if str == "" {
620 return
621 }
622 elem := strings.Split(str, ".")
623 switch len(elem) {
624 case 1:
625 case 2:
626 method = elem[1]
627 default:
628 log.Printf("too many periods in symbol specification")
629 usage(flagSet)
630 }
631 symbol = elem[0]
632 return
633 }
634
635
636
637
638 func isExported(name string) bool {
639 return unexported || token.IsExported(name)
640 }
641
642
643
644 func findNextPackage(pkg string) (string, bool) {
645 if filepath.IsAbs(pkg) {
646 if dirs.offset == 0 {
647 dirs.offset = -1
648 return pkg, true
649 }
650 return "", false
651 }
652 if pkg == "" || token.IsExported(pkg) {
653 return "", false
654 }
655 pkg = path.Clean(pkg)
656 pkgSuffix := "/" + pkg
657 for {
658 d, ok := dirs.Next()
659 if !ok {
660 return "", false
661 }
662 if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) {
663 return d.importPath, true
664 }
665 }
666 }
667
668
669 func splitGopath() []string {
670 return filepath.SplitList(cfg.BuildContext.GOPATH)
671 }
672
View as plain text