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