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