github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/src/cmd/doc/pkg.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 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/ast" 11 "go/build" 12 "go/doc" 13 "go/format" 14 "go/parser" 15 "go/token" 16 "io" 17 "log" 18 "os" 19 "path/filepath" 20 "strings" 21 "unicode" 22 "unicode/utf8" 23 ) 24 25 const ( 26 punchedCardWidth = 80 // These things just won't leave us alone. 27 indentedWidth = punchedCardWidth - len(indent) 28 indent = " " 29 ) 30 31 type Package struct { 32 writer io.Writer // Destination for output. 33 name string // Package name, json for encoding/json. 34 userPath string // String the user used to find this package. 35 unexported bool 36 matchCase bool 37 pkg *ast.Package // Parsed package. 38 file *ast.File // Merged from all files in the package 39 doc *doc.Package 40 build *build.Package 41 fs *token.FileSet // Needed for printing. 42 buf bytes.Buffer 43 } 44 45 type PackageError string // type returned by pkg.Fatalf. 46 47 func (p PackageError) Error() string { 48 return string(p) 49 } 50 51 // prettyPath returns a version of the package path that is suitable for an 52 // error message. It obeys the import comment if present. Also, since 53 // pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a 54 // directory name in GOROOT or GOPATH if that happens. 55 func (pkg *Package) prettyPath() string { 56 path := pkg.build.ImportComment 57 if path == "" { 58 path = pkg.build.ImportPath 59 } 60 if path != "." && path != "" { 61 return path 62 } 63 // Convert the source directory into a more useful path. 64 // Also convert everything to slash-separated paths for uniform handling. 65 path = filepath.Clean(filepath.ToSlash(pkg.build.Dir)) 66 // Can we find a decent prefix? 67 goroot := filepath.Join(build.Default.GOROOT, "src") 68 if p, ok := trim(path, filepath.ToSlash(goroot)); ok { 69 return p 70 } 71 for _, gopath := range splitGopath() { 72 if p, ok := trim(path, filepath.ToSlash(gopath)); ok { 73 return p 74 } 75 } 76 return path 77 } 78 79 // trim trims the directory prefix from the path, paying attention 80 // to the path separator. If they are the same string or the prefix 81 // is not present the original is returned. The boolean reports whether 82 // the prefix is present. That path and prefix have slashes for separators. 83 func trim(path, prefix string) (string, bool) { 84 if !strings.HasPrefix(path, prefix) { 85 return path, false 86 } 87 if path == prefix { 88 return path, true 89 } 90 if path[len(prefix)] == '/' { 91 return path[len(prefix)+1:], true 92 } 93 return path, false // Textual prefix but not a path prefix. 94 } 95 96 // pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the 97 // main do function, so it doesn't cause an exit. Allows testing to work 98 // without running a subprocess. The log prefix will be added when 99 // logged in main; it is not added here. 100 func (pkg *Package) Fatalf(format string, args ...interface{}) { 101 panic(PackageError(fmt.Sprintf(format, args...))) 102 } 103 104 // parsePackage turns the build package we found into a parsed package 105 // we can then use to generate documentation. 106 func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package { 107 fs := token.NewFileSet() 108 // include tells parser.ParseDir which files to include. 109 // That means the file must be in the build package's GoFiles or CgoFiles 110 // list only (no tag-ignored files, tests, swig or other non-Go files). 111 include := func(info os.FileInfo) bool { 112 for _, name := range pkg.GoFiles { 113 if name == info.Name() { 114 return true 115 } 116 } 117 for _, name := range pkg.CgoFiles { 118 if name == info.Name() { 119 return true 120 } 121 } 122 return false 123 } 124 pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments) 125 if err != nil { 126 log.Fatal(err) 127 } 128 // Make sure they are all in one package. 129 if len(pkgs) != 1 { 130 log.Fatalf("multiple packages in directory %s", pkg.Dir) 131 } 132 astPkg := pkgs[pkg.Name] 133 134 // TODO: go/doc does not include typed constants in the constants 135 // list, which is what we want. For instance, time.Sunday is of type 136 // time.Weekday, so it is defined in the type but not in the 137 // Consts list for the package. This prevents 138 // go doc time.Sunday 139 // from finding the symbol. Work around this for now, but we 140 // should fix it in go/doc. 141 // A similar story applies to factory functions. 142 docPkg := doc.New(astPkg, pkg.ImportPath, doc.AllDecls) 143 for _, typ := range docPkg.Types { 144 docPkg.Consts = append(docPkg.Consts, typ.Consts...) 145 docPkg.Vars = append(docPkg.Vars, typ.Vars...) 146 docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...) 147 } 148 149 return &Package{ 150 writer: writer, 151 name: pkg.Name, 152 userPath: userPath, 153 pkg: astPkg, 154 file: ast.MergePackageFiles(astPkg, 0), 155 doc: docPkg, 156 build: pkg, 157 fs: fs, 158 } 159 } 160 161 func (pkg *Package) Printf(format string, args ...interface{}) { 162 fmt.Fprintf(&pkg.buf, format, args...) 163 } 164 165 func (pkg *Package) flush() { 166 _, err := pkg.writer.Write(pkg.buf.Bytes()) 167 if err != nil { 168 log.Fatal(err) 169 } 170 pkg.buf.Reset() // Not needed, but it's a flush. 171 } 172 173 var newlineBytes = []byte("\n\n") // We never ask for more than 2. 174 175 // newlines guarantees there are n newlines at the end of the buffer. 176 func (pkg *Package) newlines(n int) { 177 for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) { 178 pkg.buf.WriteRune('\n') 179 } 180 } 181 182 // emit prints the node. 183 func (pkg *Package) emit(comment string, node ast.Node) { 184 if node != nil { 185 err := format.Node(&pkg.buf, pkg.fs, node) 186 if err != nil { 187 log.Fatal(err) 188 } 189 if comment != "" { 190 pkg.newlines(1) 191 doc.ToText(&pkg.buf, comment, " ", indent, indentedWidth) 192 pkg.newlines(2) // Blank line after comment to separate from next item. 193 } else { 194 pkg.newlines(1) 195 } 196 } 197 } 198 199 var formatBuf bytes.Buffer // Reusable to avoid allocation. 200 201 // formatNode is a helper function for printing. 202 func (pkg *Package) formatNode(node ast.Node) []byte { 203 formatBuf.Reset() 204 format.Node(&formatBuf, pkg.fs, node) 205 return formatBuf.Bytes() 206 } 207 208 // oneLineFunc prints a function declaration as a single line. 209 func (pkg *Package) oneLineFunc(decl *ast.FuncDecl) { 210 decl.Doc = nil 211 decl.Body = nil 212 pkg.emit("", decl) 213 } 214 215 // oneLineValueGenDecl prints a var or const declaration as a single line. 216 func (pkg *Package) oneLineValueGenDecl(decl *ast.GenDecl) { 217 decl.Doc = nil 218 dotDotDot := "" 219 if len(decl.Specs) > 1 { 220 dotDotDot = " ..." 221 } 222 // Find the first relevant spec. 223 for i, spec := range decl.Specs { 224 valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one genDecl. 225 if !isExported(valueSpec.Names[0].Name) { 226 continue 227 } 228 typ := "" 229 if valueSpec.Type != nil { 230 typ = fmt.Sprintf(" %s", pkg.formatNode(valueSpec.Type)) 231 } 232 val := "" 233 if i < len(valueSpec.Values) && valueSpec.Values[i] != nil { 234 val = fmt.Sprintf(" = %s", pkg.formatNode(valueSpec.Values[i])) 235 } 236 pkg.Printf("%s %s%s%s%s\n", decl.Tok, valueSpec.Names[0], typ, val, dotDotDot) 237 break 238 } 239 } 240 241 // oneLineTypeDecl prints a type declaration as a single line. 242 func (pkg *Package) oneLineTypeDecl(spec *ast.TypeSpec) { 243 spec.Doc = nil 244 spec.Comment = nil 245 switch spec.Type.(type) { 246 case *ast.InterfaceType: 247 pkg.Printf("type %s interface { ... }\n", spec.Name) 248 case *ast.StructType: 249 pkg.Printf("type %s struct { ... }\n", spec.Name) 250 default: 251 pkg.Printf("type %s %s\n", spec.Name, pkg.formatNode(spec.Type)) 252 } 253 } 254 255 // packageDoc prints the docs for the package (package doc plus one-liners of the rest). 256 func (pkg *Package) packageDoc() { 257 defer pkg.flush() 258 if pkg.showInternals() { 259 pkg.packageClause(false) 260 } 261 262 doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) 263 pkg.newlines(1) 264 265 if !pkg.showInternals() { 266 // Show only package docs for commands. 267 return 268 } 269 270 pkg.newlines(2) // Guarantee blank line before the components. 271 pkg.valueSummary(pkg.doc.Consts) 272 pkg.valueSummary(pkg.doc.Vars) 273 pkg.funcSummary(pkg.doc.Funcs) 274 pkg.typeSummary() 275 pkg.bugs() 276 } 277 278 // showInternals reports whether we should show the internals 279 // of a package as opposed to just the package docs. 280 // Used to decide whether to suppress internals for commands. 281 // Called only by Package.packageDoc. 282 func (pkg *Package) showInternals() bool { 283 return pkg.pkg.Name != "main" || showCmd 284 } 285 286 // packageClause prints the package clause. 287 // The argument boolean, if true, suppresses the output if the 288 // user's argument is identical to the actual package path or 289 // is empty, meaning it's the current directory. 290 func (pkg *Package) packageClause(checkUserPath bool) { 291 if checkUserPath { 292 if pkg.userPath == "" || pkg.userPath == pkg.build.ImportPath { 293 return 294 } 295 } 296 importPath := pkg.build.ImportComment 297 if importPath == "" { 298 importPath = pkg.build.ImportPath 299 } 300 pkg.Printf("package %s // import %q\n\n", pkg.name, importPath) 301 if importPath != pkg.build.ImportPath { 302 pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath) 303 } 304 } 305 306 // valueSummary prints a one-line summary for each set of values and constants. 307 func (pkg *Package) valueSummary(values []*doc.Value) { 308 for _, value := range values { 309 pkg.oneLineValueGenDecl(value.Decl) 310 } 311 } 312 313 // funcSummary prints a one-line summary for each function. 314 func (pkg *Package) funcSummary(funcs []*doc.Func) { 315 for _, fun := range funcs { 316 decl := fun.Decl 317 // Exported functions only. The go/doc package does not include methods here. 318 if isExported(fun.Name) { 319 pkg.oneLineFunc(decl) 320 } 321 } 322 } 323 324 // typeSummary prints a one-line summary for each type. 325 func (pkg *Package) typeSummary() { 326 for _, typ := range pkg.doc.Types { 327 for _, spec := range typ.Decl.Specs { 328 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 329 if isExported(typeSpec.Name.Name) { 330 pkg.oneLineTypeDecl(typeSpec) 331 } 332 } 333 } 334 } 335 336 // bugs prints the BUGS information for the package. 337 // TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)? 338 func (pkg *Package) bugs() { 339 if pkg.doc.Notes["BUG"] == nil { 340 return 341 } 342 pkg.Printf("\n") 343 for _, note := range pkg.doc.Notes["BUG"] { 344 pkg.Printf("%s: %v\n", "BUG", note.Body) 345 } 346 } 347 348 // findValues finds the doc.Values that describe the symbol. 349 func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) { 350 for _, value := range docValues { 351 for _, name := range value.Names { 352 if match(symbol, name) { 353 values = append(values, value) 354 } 355 } 356 } 357 return 358 } 359 360 // findFuncs finds the doc.Funcs that describes the symbol. 361 func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) { 362 for _, fun := range pkg.doc.Funcs { 363 if match(symbol, fun.Name) { 364 funcs = append(funcs, fun) 365 } 366 } 367 return 368 } 369 370 // findTypes finds the doc.Types that describes the symbol. 371 // If symbol is empty, it finds all exported types. 372 func (pkg *Package) findTypes(symbol string) (types []*doc.Type) { 373 for _, typ := range pkg.doc.Types { 374 if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) { 375 types = append(types, typ) 376 } 377 } 378 return 379 } 380 381 // findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol. 382 // The name must match exactly. 383 func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec { 384 for _, spec := range decl.Specs { 385 typeSpec := spec.(*ast.TypeSpec) // Must succeed. 386 if symbol == typeSpec.Name.Name { 387 return typeSpec 388 } 389 } 390 return nil 391 } 392 393 // symbolDoc prints the docs for symbol. There may be multiple matches. 394 // If symbol matches a type, output includes its methods factories and associated constants. 395 // If there is no top-level symbol, symbolDoc looks for methods that match. 396 func (pkg *Package) symbolDoc(symbol string) bool { 397 defer pkg.flush() 398 found := false 399 // Functions. 400 for _, fun := range pkg.findFuncs(symbol) { 401 if !found { 402 pkg.packageClause(true) 403 } 404 // Symbol is a function. 405 decl := fun.Decl 406 decl.Body = nil 407 pkg.emit(fun.Doc, decl) 408 found = true 409 } 410 // Constants and variables behave the same. 411 values := pkg.findValues(symbol, pkg.doc.Consts) 412 values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...) 413 for _, value := range values { 414 // Print each spec only if there is at least one exported symbol in it. 415 // (See issue 11008.) 416 // TODO: Should we elide unexported symbols from a single spec? 417 // It's an unlikely scenario, probably not worth the trouble. 418 // TODO: Would be nice if go/doc did this for us. 419 specs := make([]ast.Spec, 0, len(value.Decl.Specs)) 420 for _, spec := range value.Decl.Specs { 421 vspec := spec.(*ast.ValueSpec) 422 for _, ident := range vspec.Names { 423 if isExported(ident.Name) { 424 specs = append(specs, vspec) 425 break 426 } 427 } 428 } 429 if len(specs) == 0 { 430 continue 431 } 432 value.Decl.Specs = specs 433 if !found { 434 pkg.packageClause(true) 435 } 436 pkg.emit(value.Doc, value.Decl) 437 found = true 438 } 439 // Types. 440 for _, typ := range pkg.findTypes(symbol) { 441 if !found { 442 pkg.packageClause(true) 443 } 444 decl := typ.Decl 445 spec := pkg.findTypeSpec(decl, typ.Name) 446 trimUnexportedElems(spec) 447 // If there are multiple types defined, reduce to just this one. 448 if len(decl.Specs) > 1 { 449 decl.Specs = []ast.Spec{spec} 450 } 451 pkg.emit(typ.Doc, decl) 452 // Show associated methods, constants, etc. 453 if len(typ.Consts) > 0 || len(typ.Vars) > 0 || len(typ.Funcs) > 0 || len(typ.Methods) > 0 { 454 pkg.Printf("\n") 455 } 456 pkg.valueSummary(typ.Consts) 457 pkg.valueSummary(typ.Vars) 458 pkg.funcSummary(typ.Funcs) 459 pkg.funcSummary(typ.Methods) 460 found = true 461 } 462 if !found { 463 // See if there are methods. 464 if !pkg.printMethodDoc("", symbol) { 465 return false 466 } 467 } 468 return true 469 } 470 471 // trimUnexportedElems modifies spec in place to elide unexported fields from 472 // structs and methods from interfaces (unless the unexported flag is set). 473 func trimUnexportedElems(spec *ast.TypeSpec) { 474 if unexported { 475 return 476 } 477 switch typ := spec.Type.(type) { 478 case *ast.StructType: 479 typ.Fields = trimUnexportedFields(typ.Fields, "fields") 480 case *ast.InterfaceType: 481 typ.Methods = trimUnexportedFields(typ.Methods, "methods") 482 } 483 } 484 485 // trimUnexportedFields returns the field list trimmed of unexported fields. 486 func trimUnexportedFields(fields *ast.FieldList, what string) *ast.FieldList { 487 trimmed := false 488 list := make([]*ast.Field, 0, len(fields.List)) 489 for _, field := range fields.List { 490 // Trims if any is unexported. Good enough in practice. 491 ok := true 492 for _, name := range field.Names { 493 if !isExported(name.Name) { 494 trimmed = true 495 ok = false 496 break 497 } 498 } 499 if ok { 500 list = append(list, field) 501 } 502 } 503 if !trimmed { 504 return fields 505 } 506 unexportedField := &ast.Field{ 507 Type: ast.NewIdent(""), // Hack: printer will treat this as a field with a named type. 508 Comment: &ast.CommentGroup{ 509 List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}}, 510 }, 511 } 512 return &ast.FieldList{ 513 Opening: fields.Opening, 514 List: append(list, unexportedField), 515 Closing: fields.Closing, 516 } 517 } 518 519 // printMethodDoc prints the docs for matches of symbol.method. 520 // If symbol is empty, it prints all methods that match the name. 521 // It reports whether it found any methods. 522 func (pkg *Package) printMethodDoc(symbol, method string) bool { 523 defer pkg.flush() 524 types := pkg.findTypes(symbol) 525 if types == nil { 526 if symbol == "" { 527 return false 528 } 529 pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 530 } 531 found := false 532 for _, typ := range types { 533 for _, meth := range typ.Methods { 534 if match(method, meth.Name) { 535 decl := meth.Decl 536 decl.Body = nil 537 pkg.emit(meth.Doc, decl) 538 found = true 539 } 540 } 541 } 542 return found 543 } 544 545 // methodDoc prints the docs for matches of symbol.method. 546 func (pkg *Package) methodDoc(symbol, method string) bool { 547 defer pkg.flush() 548 return pkg.printMethodDoc(symbol, method) 549 } 550 551 // match reports whether the user's symbol matches the program's. 552 // A lower-case character in the user's string matches either case in the program's. 553 // The program string must be exported. 554 func match(user, program string) bool { 555 if !isExported(program) { 556 return false 557 } 558 if matchCase { 559 return user == program 560 } 561 for _, u := range user { 562 p, w := utf8.DecodeRuneInString(program) 563 program = program[w:] 564 if u == p { 565 continue 566 } 567 if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) { 568 continue 569 } 570 return false 571 } 572 return program == "" 573 } 574 575 // simpleFold returns the minimum rune equivalent to r 576 // under Unicode-defined simple case folding. 577 func simpleFold(r rune) rune { 578 for { 579 r1 := unicode.SimpleFold(r) 580 if r1 <= r { 581 return r1 // wrapped around, found min 582 } 583 r = r1 584 } 585 }