Source file
src/cmd/doc/main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package main
44
45 import (
46 "bytes"
47 "flag"
48 "fmt"
49 "go/build"
50 "go/token"
51 "io"
52 "log"
53 "os"
54 "path"
55 "path/filepath"
56 "strings"
57
58 "cmd/internal/telemetry/counter"
59 )
60
61 var (
62 unexported bool
63 matchCase bool
64 chdir string
65 showAll bool
66 showCmd bool
67 showSrc bool
68 short bool
69 )
70
71
72 func usage() {
73 fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n")
74 fmt.Fprintf(os.Stderr, "\tgo doc\n")
75 fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n")
76 fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<methodOrField>]\n")
77 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.]<sym>[.<methodOrField>]\n")
78 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.][<sym>.]<methodOrField>\n")
79 fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<methodOrField>]\n")
80 fmt.Fprintf(os.Stderr, "For more information run\n")
81 fmt.Fprintf(os.Stderr, "\tgo help doc\n\n")
82 fmt.Fprintf(os.Stderr, "Flags:\n")
83 flag.PrintDefaults()
84 os.Exit(2)
85 }
86
87 func main() {
88 log.SetFlags(0)
89 log.SetPrefix("doc: ")
90 counter.Open()
91 dirsInit()
92 err := do(os.Stdout, flag.CommandLine, os.Args[1:])
93 if err != nil {
94 log.Fatal(err)
95 }
96 }
97
98
99 func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) {
100 flagSet.Usage = usage
101 unexported = false
102 matchCase = false
103 flagSet.StringVar(&chdir, "C", "", "change to `dir` before running command")
104 flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported")
105 flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)")
106 flagSet.BoolVar(&showAll, "all", false, "show all documentation for package")
107 flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command")
108 flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol")
109 flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol")
110 flagSet.Parse(args)
111 counter.Inc("doc/invocations")
112 counter.CountFlags("doc/flag:", *flag.CommandLine)
113 if chdir != "" {
114 if err := os.Chdir(chdir); err != nil {
115 return err
116 }
117 }
118 var paths []string
119 var symbol, method string
120
121 dirs.Reset()
122 for i := 0; ; i++ {
123 buildPackage, userPath, sym, more := parseArgs(flagSet.Args())
124 if i > 0 && !more {
125 return failMessage(paths, symbol, method)
126 }
127 if buildPackage == nil {
128 return fmt.Errorf("no such package: %s", userPath)
129 }
130
131
132
133 if buildPackage.ImportPath == "builtin" {
134 unexported = true
135 }
136
137 symbol, method = parseSymbol(sym)
138 pkg := parsePackage(writer, buildPackage, userPath)
139 paths = append(paths, pkg.prettyPath())
140
141 defer func() {
142 pkg.flush()
143 e := recover()
144 if e == nil {
145 return
146 }
147 pkgError, ok := e.(PackageError)
148 if ok {
149 err = pkgError
150 return
151 }
152 panic(e)
153 }()
154
155 switch {
156 case symbol == "":
157 pkg.packageDoc()
158 return
159 case method == "":
160 if pkg.symbolDoc(symbol) {
161 return
162 }
163 case pkg.printMethodDoc(symbol, method):
164 return
165 case pkg.printFieldDoc(symbol, method):
166 return
167 }
168 }
169 }
170
171
172 func failMessage(paths []string, symbol, method string) error {
173 var b bytes.Buffer
174 if len(paths) > 1 {
175 b.WriteString("s")
176 }
177 b.WriteString(" ")
178 for i, path := range paths {
179 if i > 0 {
180 b.WriteString(", ")
181 }
182 b.WriteString(path)
183 }
184 if method == "" {
185 return fmt.Errorf("no symbol %s in package%s", symbol, &b)
186 }
187 return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b)
188 }
189
190
191
192
193
194
195
196
197
198
199
200
201 func parseArgs(args []string) (pkg *build.Package, path, symbol string, more bool) {
202 wd, err := os.Getwd()
203 if err != nil {
204 log.Fatal(err)
205 }
206 if len(args) == 0 {
207
208 return importDir(wd), "", "", false
209 }
210 arg := args[0]
211
212
213
214 if isDotSlash(arg) {
215 arg = filepath.Join(wd, arg)
216 }
217 switch len(args) {
218 default:
219 usage()
220 case 1:
221
222 case 2:
223
224 pkg, err := build.Import(args[0], wd, build.ImportComment)
225 if err == nil {
226 return pkg, args[0], args[1], false
227 }
228 for {
229 packagePath, ok := findNextPackage(arg)
230 if !ok {
231 break
232 }
233 if pkg, err := build.ImportDir(packagePath, build.ImportComment); err == nil {
234 return pkg, arg, args[1], true
235 }
236 }
237 return nil, args[0], args[1], false
238 }
239
240
241
242
243
244
245 var importErr error
246 if filepath.IsAbs(arg) {
247 pkg, importErr = build.ImportDir(arg, build.ImportComment)
248 if importErr == nil {
249 return pkg, arg, "", false
250 }
251 } else {
252 pkg, importErr = build.Import(arg, wd, build.ImportComment)
253 if importErr == nil {
254 return pkg, arg, "", false
255 }
256 }
257
258
259
260
261 if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) {
262 pkg, err := build.ImportDir(".", build.ImportComment)
263 if err == nil {
264 return pkg, "", arg, false
265 }
266 }
267
268
269 slash := strings.LastIndex(arg, "/")
270
271
272
273
274
275 var period int
276
277
278 for start := slash + 1; start < len(arg); start = period + 1 {
279 period = strings.Index(arg[start:], ".")
280 symbol := ""
281 if period < 0 {
282 period = len(arg)
283 } else {
284 period += start
285 symbol = arg[period+1:]
286 }
287
288 pkg, err := build.Import(arg[0:period], wd, build.ImportComment)
289 if err == nil {
290 return pkg, arg[0:period], symbol, false
291 }
292
293
294 pkgName := arg[:period]
295 for {
296 path, ok := findNextPackage(pkgName)
297 if !ok {
298 break
299 }
300 if pkg, err = build.ImportDir(path, build.ImportComment); err == nil {
301 return pkg, arg[0:period], symbol, true
302 }
303 }
304 dirs.Reset()
305 }
306
307 if slash >= 0 {
308
309
310
311
312
313
314 importErrStr := importErr.Error()
315 if strings.Contains(importErrStr, arg[:period]) {
316 log.Fatal(importErrStr)
317 } else {
318 log.Fatalf("no such package %s: %s", arg[:period], importErrStr)
319 }
320 }
321
322 return importDir(wd), "", arg, false
323 }
324
325
326
327
328
329 var dotPaths = []string{
330 `./`,
331 `../`,
332 `.\`,
333 `..\`,
334 }
335
336
337
338 func isDotSlash(arg string) bool {
339 if arg == "." || arg == ".." {
340 return true
341 }
342 for _, dotPath := range dotPaths {
343 if strings.HasPrefix(arg, dotPath) {
344 return true
345 }
346 }
347 return false
348 }
349
350
351 func importDir(dir string) *build.Package {
352 pkg, err := build.ImportDir(dir, build.ImportComment)
353 if err != nil {
354 log.Fatal(err)
355 }
356 return pkg
357 }
358
359
360
361
362 func parseSymbol(str string) (symbol, method string) {
363 if str == "" {
364 return
365 }
366 elem := strings.Split(str, ".")
367 switch len(elem) {
368 case 1:
369 case 2:
370 method = elem[1]
371 default:
372 log.Printf("too many periods in symbol specification")
373 usage()
374 }
375 symbol = elem[0]
376 return
377 }
378
379
380
381
382 func isExported(name string) bool {
383 return unexported || token.IsExported(name)
384 }
385
386
387
388 func findNextPackage(pkg string) (string, bool) {
389 if filepath.IsAbs(pkg) {
390 if dirs.offset == 0 {
391 dirs.offset = -1
392 return pkg, true
393 }
394 return "", false
395 }
396 if pkg == "" || token.IsExported(pkg) {
397 return "", false
398 }
399 pkg = path.Clean(pkg)
400 pkgSuffix := "/" + pkg
401 for {
402 d, ok := dirs.Next()
403 if !ok {
404 return "", false
405 }
406 if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) {
407 return d.dir, true
408 }
409 }
410 }
411
412 var buildCtx = build.Default
413
414
415 func splitGopath() []string {
416 return filepath.SplitList(buildCtx.GOPATH)
417 }
418
View as plain text