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, err := objectPath(userPath, pkg, symbol, method)
286 if err != nil {
287 return err
288 }
289 return doPkgsite(path)
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 func objectPath(userPath string, pkg *Package, symbol, method string) (string, error) {
309 var err error
310 path := pkg.build.ImportPath
311 if path == "." {
312
313
314
315 path, err = runCmd(nil, "go", "list", userPath)
316 if err != nil {
317 return "", err
318 }
319 }
320
321 object := symbol
322 if symbol != "" && method != "" {
323 object = symbol + "." + method
324 }
325 if object != "" {
326 path = path + "#" + object
327 }
328 return path, nil
329 }
330
331
332 func failMessage(paths []string, symbol, method string) error {
333 var b bytes.Buffer
334 if len(paths) > 1 {
335 b.WriteString("s")
336 }
337 b.WriteString(" ")
338 for i, path := range paths {
339 if i > 0 {
340 b.WriteString(", ")
341 }
342 b.WriteString(path)
343 }
344 if method == "" {
345 return fmt.Errorf("no symbol %s in package%s", symbol, &b)
346 }
347 return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b)
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361 func parseArgs(flagSet *flag.FlagSet, args []string) (pkg *build.Package, path, symbol string, more bool) {
362 wd, err := os.Getwd()
363 if err != nil {
364 log.Fatal(err)
365 }
366 if len(args) == 0 {
367
368 return importDir(wd), "", "", false
369 }
370 arg := args[0]
371
372
373
374 if isDotSlash(arg) {
375 arg = filepath.Join(wd, arg)
376 }
377 switch len(args) {
378 default:
379 usage(flagSet)
380 case 1:
381
382 case 2:
383
384 pkg, err := build.Import(args[0], wd, build.ImportComment)
385 if err == nil {
386 return pkg, args[0], args[1], false
387 }
388 for {
389 packagePath, ok := findNextPackage(arg)
390 if !ok {
391 break
392 }
393 if pkg, err := build.ImportDir(packagePath, build.ImportComment); err == nil {
394 return pkg, arg, args[1], true
395 }
396 }
397 return nil, args[0], args[1], false
398 }
399
400
401
402
403
404
405 var importErr error
406 if filepath.IsAbs(arg) {
407 pkg, importErr = build.ImportDir(arg, build.ImportComment)
408 if importErr == nil {
409 return pkg, arg, "", false
410 }
411 } else {
412 pkg, importErr = build.Import(arg, wd, build.ImportComment)
413 if importErr == nil {
414 return pkg, arg, "", false
415 }
416 }
417
418
419
420
421 if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) {
422 pkg, err := build.ImportDir(".", build.ImportComment)
423 if err == nil {
424 return pkg, "", arg, false
425 }
426 }
427
428
429 slash := strings.LastIndex(arg, "/")
430
431
432
433
434
435 var period int
436
437
438 for start := slash + 1; start < len(arg); start = period + 1 {
439 period = strings.Index(arg[start:], ".")
440 symbol := ""
441 if period < 0 {
442 period = len(arg)
443 } else {
444 period += start
445 symbol = arg[period+1:]
446 }
447
448 pkg, err := build.Import(arg[0:period], wd, build.ImportComment)
449 if err == nil {
450 return pkg, arg[0:period], symbol, false
451 }
452
453
454 pkgName := arg[:period]
455 for {
456 path, ok := findNextPackage(pkgName)
457 if !ok {
458 break
459 }
460 if pkg, err = build.ImportDir(path, build.ImportComment); err == nil {
461 return pkg, arg[0:period], symbol, true
462 }
463 }
464 dirs.Reset()
465 }
466
467 if slash >= 0 {
468
469
470
471
472
473
474 importErrStr := importErr.Error()
475 if strings.Contains(importErrStr, arg[:period]) {
476 log.Fatal(importErrStr)
477 } else {
478 log.Fatalf("no such package %s: %s", arg[:period], importErrStr)
479 }
480 }
481
482 return importDir(wd), "", arg, false
483 }
484
485
486
487
488
489 var dotPaths = []string{
490 `./`,
491 `../`,
492 `.\`,
493 `..\`,
494 }
495
496
497
498 func isDotSlash(arg string) bool {
499 if arg == "." || arg == ".." {
500 return true
501 }
502 for _, dotPath := range dotPaths {
503 if strings.HasPrefix(arg, dotPath) {
504 return true
505 }
506 }
507 return false
508 }
509
510
511 func importDir(dir string) *build.Package {
512 pkg, err := build.ImportDir(dir, build.ImportComment)
513 if err != nil {
514 log.Fatal(err)
515 }
516 return pkg
517 }
518
519
520
521
522 func parseSymbol(flagSet *flag.FlagSet, str string) (symbol, method string) {
523 if str == "" {
524 return
525 }
526 elem := strings.Split(str, ".")
527 switch len(elem) {
528 case 1:
529 case 2:
530 method = elem[1]
531 default:
532 log.Printf("too many periods in symbol specification")
533 usage(flagSet)
534 }
535 symbol = elem[0]
536 return
537 }
538
539
540
541
542 func isExported(name string) bool {
543 return unexported || token.IsExported(name)
544 }
545
546
547
548 func findNextPackage(pkg string) (string, bool) {
549 if filepath.IsAbs(pkg) {
550 if dirs.offset == 0 {
551 dirs.offset = -1
552 return pkg, true
553 }
554 return "", false
555 }
556 if pkg == "" || token.IsExported(pkg) {
557 return "", false
558 }
559 pkg = path.Clean(pkg)
560 pkgSuffix := "/" + pkg
561 for {
562 d, ok := dirs.Next()
563 if !ok {
564 return "", false
565 }
566 if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) {
567 return d.dir, true
568 }
569 }
570 }
571
572 var buildCtx = build.Default
573
574
575 func splitGopath() []string {
576 return filepath.SplitList(buildCtx.GOPATH)
577 }
578
View as plain text