github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/doc/main.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Doc (usually run as go doc) accepts zero, one or two arguments. 6 // 7 // Zero arguments: 8 // go doc 9 // Show the documentation for the package in the current directory. 10 // 11 // One argument: 12 // go doc <pkg> 13 // go doc <sym>[.<methodOrField>] 14 // go doc [<pkg>.]<sym>[.<methodOrField>] 15 // go doc [<pkg>.][<sym>.]<methodOrField> 16 // The first item in this list that succeeds is the one whose documentation 17 // is printed. If there is a symbol but no package, the package in the current 18 // directory is chosen. However, if the argument begins with a capital 19 // letter it is always assumed to be a symbol in the current directory. 20 // 21 // Two arguments: 22 // go doc <pkg> <sym>[.<methodOrField>] 23 // 24 // Show the documentation for the package, symbol, and method or field. The 25 // first argument must be a full package path. This is similar to the 26 // command-line usage for the godoc command. 27 // 28 // For commands, unless the -cmd flag is present "go doc command" 29 // shows only the package-level docs for the package. 30 // 31 // The -src flag causes doc to print the full source code for the symbol, such 32 // as the body of a struct, function or method. 33 // 34 // The -all flag causes doc to print all documentation for the package and 35 // all its visible symbols. The argument must identify a package. 36 // 37 // For complete documentation, run "go help doc". 38 package main 39 40 import ( 41 "bytes" 42 "flag" 43 "fmt" 44 "go/build" 45 "go/token" 46 "io" 47 "log" 48 "os" 49 "path" 50 "path/filepath" 51 "strings" 52 ) 53 54 var ( 55 unexported bool // -u flag 56 matchCase bool // -c flag 57 showAll bool // -all flag 58 showCmd bool // -cmd flag 59 showSrc bool // -src flag 60 short bool // -short flag 61 ) 62 63 // usage is a replacement usage function for the flags package. 64 func usage() { 65 fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n") 66 fmt.Fprintf(os.Stderr, "\tgo doc\n") 67 fmt.Fprintf(os.Stderr, "\tgo doc <pkg>\n") 68 fmt.Fprintf(os.Stderr, "\tgo doc <sym>[.<methodOrField>]\n") 69 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.]<sym>[.<methodOrField>]\n") 70 fmt.Fprintf(os.Stderr, "\tgo doc [<pkg>.][<sym>.]<methodOrField>\n") 71 fmt.Fprintf(os.Stderr, "\tgo doc <pkg> <sym>[.<methodOrField>]\n") 72 fmt.Fprintf(os.Stderr, "For more information run\n") 73 fmt.Fprintf(os.Stderr, "\tgo help doc\n\n") 74 fmt.Fprintf(os.Stderr, "Flags:\n") 75 flag.PrintDefaults() 76 os.Exit(2) 77 } 78 79 func main() { 80 log.SetFlags(0) 81 log.SetPrefix("doc: ") 82 dirsInit() 83 err := do(os.Stdout, flag.CommandLine, os.Args[1:]) 84 if err != nil { 85 log.Fatal(err) 86 } 87 } 88 89 // do is the workhorse, broken out of main to make testing easier. 90 func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { 91 flagSet.Usage = usage 92 unexported = false 93 matchCase = false 94 flagSet.BoolVar(&unexported, "u", false, "show unexported symbols as well as exported") 95 flagSet.BoolVar(&matchCase, "c", false, "symbol matching honors case (paths not affected)") 96 flagSet.BoolVar(&showAll, "all", false, "show all documentation for package") 97 flagSet.BoolVar(&showCmd, "cmd", false, "show symbols with package docs even if package is a command") 98 flagSet.BoolVar(&showSrc, "src", false, "show source code for symbol") 99 flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol") 100 flagSet.Parse(args) 101 var paths []string 102 var symbol, method string 103 // Loop until something is printed. 104 dirs.Reset() 105 for i := 0; ; i++ { 106 buildPackage, userPath, sym, more := parseArgs(flagSet.Args()) 107 if i > 0 && !more { // Ignore the "more" bit on the first iteration. 108 return failMessage(paths, symbol, method) 109 } 110 if buildPackage == nil { 111 return fmt.Errorf("no such package: %s", userPath) 112 } 113 symbol, method = parseSymbol(sym) 114 pkg := parsePackage(writer, buildPackage, userPath) 115 paths = append(paths, pkg.prettyPath()) 116 117 defer func() { 118 pkg.flush() 119 e := recover() 120 if e == nil { 121 return 122 } 123 pkgError, ok := e.(PackageError) 124 if ok { 125 err = pkgError 126 return 127 } 128 panic(e) 129 }() 130 131 // The builtin package needs special treatment: its symbols are lower 132 // case but we want to see them, always. 133 if pkg.build.ImportPath == "builtin" { 134 unexported = true 135 } 136 137 // We have a package. 138 if showAll && symbol == "" { 139 pkg.allDoc() 140 return 141 } 142 143 switch { 144 case symbol == "": 145 pkg.packageDoc() // The package exists, so we got some output. 146 return 147 case method == "": 148 if pkg.symbolDoc(symbol) { 149 return 150 } 151 default: 152 if pkg.methodDoc(symbol, method) { 153 return 154 } 155 if pkg.fieldDoc(symbol, method) { 156 return 157 } 158 } 159 } 160 } 161 162 // failMessage creates a nicely formatted error message when there is no result to show. 163 func failMessage(paths []string, symbol, method string) error { 164 var b bytes.Buffer 165 if len(paths) > 1 { 166 b.WriteString("s") 167 } 168 b.WriteString(" ") 169 for i, path := range paths { 170 if i > 0 { 171 b.WriteString(", ") 172 } 173 b.WriteString(path) 174 } 175 if method == "" { 176 return fmt.Errorf("no symbol %s in package%s", symbol, &b) 177 } 178 return fmt.Errorf("no method or field %s.%s in package%s", symbol, method, &b) 179 } 180 181 // parseArgs analyzes the arguments (if any) and returns the package 182 // it represents, the part of the argument the user used to identify 183 // the path (or "" if it's the current package) and the symbol 184 // (possibly with a .method) within that package. 185 // parseSymbol is used to analyze the symbol itself. 186 // The boolean final argument reports whether it is possible that 187 // there may be more directories worth looking at. It will only 188 // be true if the package path is a partial match for some directory 189 // and there may be more matches. For example, if the argument 190 // is rand.Float64, we must scan both crypto/rand and math/rand 191 // to find the symbol, and the first call will return crypto/rand, true. 192 func parseArgs(args []string) (pkg *build.Package, path, symbol string, more bool) { 193 wd, err := os.Getwd() 194 if err != nil { 195 log.Fatal(err) 196 } 197 if len(args) == 0 { 198 // Easy: current directory. 199 return importDir(wd), "", "", false 200 } 201 arg := args[0] 202 // We have an argument. If it is a directory name beginning with . or .., 203 // use the absolute path name. This discriminates "./errors" from "errors" 204 // if the current directory contains a non-standard errors package. 205 if isDotSlash(arg) { 206 arg = filepath.Join(wd, arg) 207 } 208 switch len(args) { 209 default: 210 usage() 211 case 1: 212 // Done below. 213 case 2: 214 // Package must be findable and importable. 215 pkg, err := build.Import(args[0], wd, build.ImportComment) 216 if err == nil { 217 return pkg, args[0], args[1], false 218 } 219 for { 220 packagePath, ok := findNextPackage(arg) 221 if !ok { 222 break 223 } 224 if pkg, err := build.ImportDir(packagePath, build.ImportComment); err == nil { 225 return pkg, arg, args[1], true 226 } 227 } 228 return nil, args[0], args[1], false 229 } 230 // Usual case: one argument. 231 // If it contains slashes, it begins with either a package path 232 // or an absolute directory. 233 // First, is it a complete package path as it is? If so, we are done. 234 // This avoids confusion over package paths that have other 235 // package paths as their prefix. 236 var importErr error 237 if filepath.IsAbs(arg) { 238 pkg, importErr = build.ImportDir(arg, build.ImportComment) 239 if importErr == nil { 240 return pkg, arg, "", false 241 } 242 } else { 243 pkg, importErr = build.Import(arg, wd, build.ImportComment) 244 if importErr == nil { 245 return pkg, arg, "", false 246 } 247 } 248 // Another disambiguator: If the argument starts with an upper 249 // case letter, it can only be a symbol in the current directory. 250 // Kills the problem caused by case-insensitive file systems 251 // matching an upper case name as a package name. 252 if !strings.ContainsAny(arg, `/\`) && token.IsExported(arg) { 253 pkg, err := build.ImportDir(".", build.ImportComment) 254 if err == nil { 255 return pkg, "", arg, false 256 } 257 } 258 // If it has a slash, it must be a package path but there is a symbol. 259 // It's the last package path we care about. 260 slash := strings.LastIndex(arg, "/") 261 // There may be periods in the package path before or after the slash 262 // and between a symbol and method. 263 // Split the string at various periods to see what we find. 264 // In general there may be ambiguities but this should almost always 265 // work. 266 var period int 267 // slash+1: if there's no slash, the value is -1 and start is 0; otherwise 268 // start is the byte after the slash. 269 for start := slash + 1; start < len(arg); start = period + 1 { 270 period = strings.Index(arg[start:], ".") 271 symbol := "" 272 if period < 0 { 273 period = len(arg) 274 } else { 275 period += start 276 symbol = arg[period+1:] 277 } 278 // Have we identified a package already? 279 pkg, err := build.Import(arg[0:period], wd, build.ImportComment) 280 if err == nil { 281 return pkg, arg[0:period], symbol, false 282 } 283 // See if we have the basename or tail of a package, as in json for encoding/json 284 // or ivy/value for robpike.io/ivy/value. 285 pkgName := arg[:period] 286 for { 287 path, ok := findNextPackage(pkgName) 288 if !ok { 289 break 290 } 291 if pkg, err = build.ImportDir(path, build.ImportComment); err == nil { 292 return pkg, arg[0:period], symbol, true 293 } 294 } 295 dirs.Reset() // Next iteration of for loop must scan all the directories again. 296 } 297 // If it has a slash, we've failed. 298 if slash >= 0 { 299 // build.Import should always include the path in its error message, 300 // and we should avoid repeating it. Unfortunately, build.Import doesn't 301 // return a structured error. That can't easily be fixed, since it 302 // invokes 'go list' and returns the error text from the loaded package. 303 // TODO(golang.org/issue/34750): load using golang.org/x/tools/go/packages 304 // instead of go/build. 305 importErrStr := importErr.Error() 306 if strings.Contains(importErrStr, arg[:period]) { 307 log.Fatal(importErrStr) 308 } else { 309 log.Fatalf("no such package %s: %s", arg[:period], importErrStr) 310 } 311 } 312 // Guess it's a symbol in the current directory. 313 return importDir(wd), "", arg, false 314 } 315 316 // dotPaths lists all the dotted paths legal on Unix-like and 317 // Windows-like file systems. We check them all, as the chance 318 // of error is minute and even on Windows people will use ./ 319 // sometimes. 320 var dotPaths = []string{ 321 `./`, 322 `../`, 323 `.\`, 324 `..\`, 325 } 326 327 // isDotSlash reports whether the path begins with a reference 328 // to the local . or .. directory. 329 func isDotSlash(arg string) bool { 330 if arg == "." || arg == ".." { 331 return true 332 } 333 for _, dotPath := range dotPaths { 334 if strings.HasPrefix(arg, dotPath) { 335 return true 336 } 337 } 338 return false 339 } 340 341 // importDir is just an error-catching wrapper for build.ImportDir. 342 func importDir(dir string) *build.Package { 343 pkg, err := build.ImportDir(dir, build.ImportComment) 344 if err != nil { 345 log.Fatal(err) 346 } 347 return pkg 348 } 349 350 // parseSymbol breaks str apart into a symbol and method. 351 // Both may be missing or the method may be missing. 352 // If present, each must be a valid Go identifier. 353 func parseSymbol(str string) (symbol, method string) { 354 if str == "" { 355 return 356 } 357 elem := strings.Split(str, ".") 358 switch len(elem) { 359 case 1: 360 case 2: 361 method = elem[1] 362 default: 363 log.Printf("too many periods in symbol specification") 364 usage() 365 } 366 symbol = elem[0] 367 return 368 } 369 370 // isExported reports whether the name is an exported identifier. 371 // If the unexported flag (-u) is true, isExported returns true because 372 // it means that we treat the name as if it is exported. 373 func isExported(name string) bool { 374 return unexported || token.IsExported(name) 375 } 376 377 // findNextPackage returns the next full file name path that matches the 378 // (perhaps partial) package path pkg. The boolean reports if any match was found. 379 func findNextPackage(pkg string) (string, bool) { 380 if filepath.IsAbs(pkg) { 381 if dirs.offset == 0 { 382 dirs.offset = -1 383 return pkg, true 384 } 385 return "", false 386 } 387 if pkg == "" || token.IsExported(pkg) { // Upper case symbol cannot be a package name. 388 return "", false 389 } 390 pkg = path.Clean(pkg) 391 pkgSuffix := "/" + pkg 392 for { 393 d, ok := dirs.Next() 394 if !ok { 395 return "", false 396 } 397 if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) { 398 return d.dir, true 399 } 400 } 401 } 402 403 var buildCtx = build.Default 404 405 // splitGopath splits $GOPATH into a list of roots. 406 func splitGopath() []string { 407 return filepath.SplitList(buildCtx.GOPATH) 408 }