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