├── .gitignore ├── LICENSE ├── README.md ├── dirs.go ├── doc_test.go ├── go.mod ├── go.sum ├── main.go ├── pkg.go └── preview.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Built binary 2 | gopherdoc 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Go Authors. All rights reserved. 2 | Copyright (c) 2021 Raven Ravener. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GopherDoc 2 | Improved `go doc` with terminal syntax highlighting. 3 | 4 | This is a modification of the original `go doc` command that adds terminal syntax highlighting. 5 | 6 | ![preview](preview.jpg) 7 | 8 | Uses [go-colorable](https://github.com/mattn/go-colorable) so colors will work on Windows as well. 9 | 10 | ## Install 11 | ```sh 12 | $ git clone https://github.com/ravener/gopherdoc 13 | $ cd gopherdoc 14 | $ go install 15 | ``` 16 | It will install into `$GOPATH/bin/gopherdoc` (Use `go env GOPATH` to find your path) hopefully that's probably in your `PATH` already. 17 | 18 | ## Usage 19 | Usage is the same as `go doc` with just a new flag available. 20 | 21 | `-style` allows to change the syntax highlighting style, the style must be a valid [chroma](https://github.com/alecthomas/chroma) style. (e.g `gopherdoc -style monokai encoding/json`) 22 | 23 | You may also set the style via `GDOC_STYLE` environment variable, so you can e.g set that in your `.bashrc` or something. The flag will still take precedence when available. 24 | 25 | The default style is `vim` 26 | 27 | ## License 28 | Because I modified Go's `cmd/doc` I decided to release it under Go's license. See [LICENSE](LICENSE) 29 | -------------------------------------------------------------------------------- /dirs.go: -------------------------------------------------------------------------------- 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 | "log" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "strings" 14 | "sync" 15 | ) 16 | 17 | // A Dir describes a directory holding code by specifying 18 | // the expected import path and the file system directory. 19 | type Dir struct { 20 | importPath string // import path for that dir 21 | dir string // file system directory 22 | } 23 | 24 | // Dirs is a structure for scanning the directory tree. 25 | // Its Next method returns the next Go source directory it finds. 26 | // Although it can be used to scan the tree multiple times, it 27 | // only walks the tree once, caching the data it finds. 28 | type Dirs struct { 29 | scan chan Dir // Directories generated by walk. 30 | hist []Dir // History of reported Dirs. 31 | offset int // Counter for Next. 32 | } 33 | 34 | var dirs Dirs 35 | 36 | // dirsInit starts the scanning of package directories in GOROOT and GOPATH. Any 37 | // extra paths passed to it are included in the channel. 38 | func dirsInit(extra ...Dir) { 39 | dirs.hist = make([]Dir, 0, 1000) 40 | dirs.hist = append(dirs.hist, extra...) 41 | dirs.scan = make(chan Dir) 42 | go dirs.walk(codeRoots()) 43 | } 44 | 45 | // Reset puts the scan back at the beginning. 46 | func (d *Dirs) Reset() { 47 | d.offset = 0 48 | } 49 | 50 | // Next returns the next directory in the scan. The boolean 51 | // is false when the scan is done. 52 | func (d *Dirs) Next() (Dir, bool) { 53 | if d.offset < len(d.hist) { 54 | dir := d.hist[d.offset] 55 | d.offset++ 56 | return dir, true 57 | } 58 | dir, ok := <-d.scan 59 | if !ok { 60 | return Dir{}, false 61 | } 62 | d.hist = append(d.hist, dir) 63 | d.offset++ 64 | return dir, ok 65 | } 66 | 67 | // walk walks the trees in GOROOT and GOPATH. 68 | func (d *Dirs) walk(roots []Dir) { 69 | for _, root := range roots { 70 | d.bfsWalkRoot(root) 71 | } 72 | close(d.scan) 73 | } 74 | 75 | // bfsWalkRoot walks a single directory hierarchy in breadth-first lexical order. 76 | // Each Go source directory it finds is delivered on d.scan. 77 | func (d *Dirs) bfsWalkRoot(root Dir) { 78 | root.dir = filepath.Clean(root.dir) // because filepath.Join will do it anyway 79 | 80 | // this is the queue of directories to examine in this pass. 81 | this := []string{} 82 | // next is the queue of directories to examine in the next pass. 83 | next := []string{root.dir} 84 | 85 | for len(next) > 0 { 86 | this, next = next, this[0:0] 87 | for _, dir := range this { 88 | fd, err := os.Open(dir) 89 | if err != nil { 90 | log.Print(err) 91 | continue 92 | } 93 | entries, err := fd.Readdir(0) 94 | fd.Close() 95 | if err != nil { 96 | log.Print(err) 97 | continue 98 | } 99 | hasGoFiles := false 100 | for _, entry := range entries { 101 | name := entry.Name() 102 | // For plain files, remember if this directory contains any .go 103 | // source files, but ignore them otherwise. 104 | if !entry.IsDir() { 105 | if !hasGoFiles && strings.HasSuffix(name, ".go") { 106 | hasGoFiles = true 107 | } 108 | continue 109 | } 110 | // Entry is a directory. 111 | 112 | // The go tool ignores directories starting with ., _, or named "testdata". 113 | if name[0] == '.' || name[0] == '_' || name == "testdata" { 114 | continue 115 | } 116 | // Ignore vendor when using modules. 117 | if usingModules && name == "vendor" { 118 | continue 119 | } 120 | // Remember this (fully qualified) directory for the next pass. 121 | next = append(next, filepath.Join(dir, name)) 122 | } 123 | if hasGoFiles { 124 | // It's a candidate. 125 | importPath := root.importPath 126 | if len(dir) > len(root.dir) { 127 | if importPath != "" { 128 | importPath += "/" 129 | } 130 | importPath += filepath.ToSlash(dir[len(root.dir)+1:]) 131 | } 132 | d.scan <- Dir{importPath, dir} 133 | } 134 | } 135 | 136 | } 137 | } 138 | 139 | var testGOPATH = false // force GOPATH use for testing 140 | 141 | // codeRoots returns the code roots to search for packages. 142 | // In GOPATH mode this is GOROOT/src and GOPATH/src, with empty import paths. 143 | // In module mode, this is each module root, with an import path set to its module path. 144 | func codeRoots() []Dir { 145 | codeRootsCache.once.Do(func() { 146 | codeRootsCache.roots = findCodeRoots() 147 | }) 148 | return codeRootsCache.roots 149 | } 150 | 151 | var codeRootsCache struct { 152 | once sync.Once 153 | roots []Dir 154 | } 155 | 156 | var usingModules bool 157 | 158 | func findCodeRoots() []Dir { 159 | list := []Dir{{"", filepath.Join(buildCtx.GOROOT, "src")}} 160 | 161 | if !testGOPATH { 162 | // Check for use of modules by 'go env GOMOD', 163 | // which reports a go.mod file path if modules are enabled. 164 | stdout, _ := exec.Command("go", "env", "GOMOD").Output() 165 | usingModules = len(bytes.TrimSpace(stdout)) > 0 166 | } 167 | 168 | if !usingModules { 169 | for _, root := range splitGopath() { 170 | list = append(list, Dir{"", filepath.Join(root, "src")}) 171 | } 172 | return list 173 | } 174 | 175 | // Find module root directories from go list. 176 | // Eventually we want golang.org/x/tools/go/packages 177 | // to handle the entire file system search and become go/packages, 178 | // but for now enumerating the module roots lets us fit modules 179 | // into the current code with as few changes as possible. 180 | cmd := exec.Command("go", "list", "-m", "-f={{.Path}}\t{{.Dir}}", "all") 181 | cmd.Stderr = os.Stderr 182 | out, _ := cmd.Output() 183 | for _, line := range strings.Split(string(out), "\n") { 184 | i := strings.Index(line, "\t") 185 | if i < 0 { 186 | continue 187 | } 188 | path, dir := line[:i], line[i+1:] 189 | if dir != "" { 190 | list = append(list, Dir{path, dir}) 191 | } 192 | } 193 | 194 | return list 195 | } 196 | -------------------------------------------------------------------------------- /doc_test.go: -------------------------------------------------------------------------------- 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 | "flag" 10 | "os" 11 | "path/filepath" 12 | "regexp" 13 | "runtime" 14 | "strings" 15 | "testing" 16 | ) 17 | 18 | func TestMain(m *testing.M) { 19 | // Clear GOPATH so we don't access the user's own packages in the test. 20 | buildCtx.GOPATH = "" 21 | testGOPATH = true // force GOPATH mode; module test is in cmd/go/testdata/script/mod_doc.txt 22 | 23 | // Add $GOROOT/src/cmd/doc/testdata explicitly so we can access its contents in the test. 24 | // Normally testdata directories are ignored, but sending it to dirs.scan directly is 25 | // a hack that works around the check. 26 | testdataDir, err := filepath.Abs("testdata") 27 | if err != nil { 28 | panic(err) 29 | } 30 | dirsInit(Dir{"testdata", testdataDir}, Dir{"testdata/nested", filepath.Join(testdataDir, "nested")}, Dir{"testdata/nested/nested", filepath.Join(testdataDir, "nested", "nested")}) 31 | 32 | os.Exit(m.Run()) 33 | } 34 | 35 | func maybeSkip(t *testing.T) { 36 | if strings.HasPrefix(runtime.GOOS, "nacl") { 37 | t.Skip("nacl does not have a full file tree") 38 | } 39 | if runtime.GOOS == "darwin" && strings.HasPrefix(runtime.GOARCH, "arm") { 40 | t.Skip("darwin/arm does not have a full file tree") 41 | } 42 | } 43 | 44 | type isDotSlashTest struct { 45 | str string 46 | result bool 47 | } 48 | 49 | var isDotSlashTests = []isDotSlashTest{ 50 | {``, false}, 51 | {`x`, false}, 52 | {`...`, false}, 53 | {`.../`, false}, 54 | {`...\`, false}, 55 | 56 | {`.`, true}, 57 | {`./`, true}, 58 | {`.\`, true}, 59 | {`./x`, true}, 60 | {`.\x`, true}, 61 | 62 | {`..`, true}, 63 | {`../`, true}, 64 | {`..\`, true}, 65 | {`../x`, true}, 66 | {`..\x`, true}, 67 | } 68 | 69 | func TestIsDotSlashPath(t *testing.T) { 70 | for _, test := range isDotSlashTests { 71 | if result := isDotSlash(test.str); result != test.result { 72 | t.Errorf("isDotSlash(%q) = %t; expected %t", test.str, result, test.result) 73 | } 74 | } 75 | } 76 | 77 | type test struct { 78 | name string 79 | args []string // Arguments to "[go] doc". 80 | yes []string // Regular expressions that should match. 81 | no []string // Regular expressions that should not match. 82 | } 83 | 84 | const p = "cmd/doc/testdata" 85 | 86 | var tests = []test{ 87 | // Sanity check. 88 | { 89 | "sanity check", 90 | []string{p}, 91 | []string{`type ExportedType struct`}, 92 | nil, 93 | }, 94 | 95 | // Package dump includes import, package statement. 96 | { 97 | "package clause", 98 | []string{p}, 99 | []string{`package pkg.*cmd/doc/testdata`}, 100 | nil, 101 | }, 102 | 103 | // Constants. 104 | // Package dump 105 | { 106 | "full package", 107 | []string{p}, 108 | []string{ 109 | `Package comment`, 110 | `const ExportedConstant = 1`, // Simple constant. 111 | `const ConstOne = 1`, // First entry in constant block. 112 | `const ConstFive ...`, // From block starting with unexported constant. 113 | `var ExportedVariable = 1`, // Simple variable. 114 | `var VarOne = 1`, // First entry in variable block. 115 | `func ExportedFunc\(a int\) bool`, // Function. 116 | `func ReturnUnexported\(\) unexportedType`, // Function with unexported return type. 117 | `type ExportedType struct{ ... }`, // Exported type. 118 | `const ExportedTypedConstant ExportedType = iota`, // Typed constant. 119 | `const ExportedTypedConstant_unexported unexportedType`, // Typed constant, exported for unexported type. 120 | `const ConstLeft2 uint64 ...`, // Typed constant using unexported iota. 121 | `const ConstGroup1 unexportedType = iota ...`, // Typed constant using unexported type. 122 | `const ConstGroup4 ExportedType = ExportedType{}`, // Typed constant using exported type. 123 | `const MultiLineConst = ...`, // Multi line constant. 124 | `var MultiLineVar = map\[struct{ ... }\]struct{ ... }{ ... }`, // Multi line variable. 125 | `func MultiLineFunc\(x interface{ ... }\) \(r struct{ ... }\)`, // Multi line function. 126 | `var LongLine = newLongLine\(("someArgument[1-4]", ){4}...\)`, // Long list of arguments. 127 | `type T1 = T2`, // Type alias 128 | }, 129 | []string{ 130 | `const internalConstant = 2`, // No internal constants. 131 | `var internalVariable = 2`, // No internal variables. 132 | `func internalFunc(a int) bool`, // No internal functions. 133 | `Comment about exported constant`, // No comment for single constant. 134 | `Comment about exported variable`, // No comment for single variable. 135 | `Comment about block of constants`, // No comment for constant block. 136 | `Comment about block of variables`, // No comment for variable block. 137 | `Comment before ConstOne`, // No comment for first entry in constant block. 138 | `Comment before VarOne`, // No comment for first entry in variable block. 139 | `ConstTwo = 2`, // No second entry in constant block. 140 | `VarTwo = 2`, // No second entry in variable block. 141 | `VarFive = 5`, // From block starting with unexported variable. 142 | `type unexportedType`, // No unexported type. 143 | `unexportedTypedConstant`, // No unexported typed constant. 144 | `\bField`, // No fields. 145 | `Method`, // No methods. 146 | `someArgument[5-8]`, // No truncated arguments. 147 | `type T1 T2`, // Type alias does not display as type declaration. 148 | }, 149 | }, 150 | // Package dump -all 151 | { 152 | "full package", 153 | []string{"-all", p}, 154 | []string{ 155 | `package pkg .*import`, 156 | `Package comment`, 157 | `CONSTANTS`, 158 | `Comment before ConstOne`, 159 | `ConstOne = 1`, 160 | `ConstTwo = 2 // Comment on line with ConstTwo`, 161 | `ConstFive`, 162 | `ConstSix`, 163 | `Const block where first entry is unexported`, 164 | `ConstLeft2, constRight2 uint64`, 165 | `constLeft3, ConstRight3`, 166 | `ConstLeft4, ConstRight4`, 167 | `Duplicate = iota`, 168 | `const CaseMatch = 1`, 169 | `const Casematch = 2`, 170 | `const ExportedConstant = 1`, 171 | `const MultiLineConst = `, 172 | `MultiLineString1`, 173 | `VARIABLES`, 174 | `Comment before VarOne`, 175 | `VarOne = 1`, 176 | `Comment about block of variables`, 177 | `VarFive = 5`, 178 | `var ExportedVariable = 1`, 179 | `var LongLine = newLongLine\(`, 180 | `var MultiLineVar = map\[struct {`, 181 | `FUNCTIONS`, 182 | `func ExportedFunc\(a int\) bool`, 183 | `Comment about exported function`, 184 | `func MultiLineFunc\(x interface`, 185 | `func ReturnUnexported\(\) unexportedType`, 186 | `TYPES`, 187 | `type ExportedInterface interface`, 188 | `type ExportedStructOneField struct`, 189 | `type ExportedType struct`, 190 | `Comment about exported type`, 191 | `const ConstGroup4 ExportedType = ExportedType`, 192 | `ExportedTypedConstant ExportedType = iota`, 193 | `Constants tied to ExportedType`, 194 | `func ExportedTypeConstructor\(\) \*ExportedType`, 195 | `Comment about constructor for exported type`, 196 | `func ReturnExported\(\) ExportedType`, 197 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 198 | `Comment about exported method`, 199 | `type T1 = T2`, 200 | `type T2 int`, 201 | }, 202 | []string{ 203 | `constThree`, 204 | `_, _ uint64 = 2 \* iota, 1 << iota`, 205 | `constLeft1, constRight1`, 206 | `duplicate`, 207 | `varFour`, 208 | `func internalFunc`, 209 | `unexportedField`, 210 | `func \(unexportedType\)`, 211 | }, 212 | }, 213 | // Package dump -u 214 | { 215 | "full package with u", 216 | []string{`-u`, p}, 217 | []string{ 218 | `const ExportedConstant = 1`, // Simple constant. 219 | `const internalConstant = 2`, // Internal constants. 220 | `func internalFunc\(a int\) bool`, // Internal functions. 221 | `func ReturnUnexported\(\) unexportedType`, // Function with unexported return type. 222 | }, 223 | []string{ 224 | `Comment about exported constant`, // No comment for simple constant. 225 | `Comment about block of constants`, // No comment for constant block. 226 | `Comment about internal function`, // No comment for internal function. 227 | `MultiLine(String|Method|Field)`, // No data from multi line portions. 228 | }, 229 | }, 230 | // Package dump -u -all 231 | { 232 | "full package", 233 | []string{"-u", "-all", p}, 234 | []string{ 235 | `package pkg .*import`, 236 | `Package comment`, 237 | `CONSTANTS`, 238 | `Comment before ConstOne`, 239 | `ConstOne += 1`, 240 | `ConstTwo += 2 // Comment on line with ConstTwo`, 241 | `constThree = 3 // Comment on line with constThree`, 242 | `ConstFive`, 243 | `const internalConstant += 2`, 244 | `Comment about internal constant`, 245 | `VARIABLES`, 246 | `Comment before VarOne`, 247 | `VarOne += 1`, 248 | `Comment about block of variables`, 249 | `varFour += 4`, 250 | `VarFive += 5`, 251 | `varSix += 6`, 252 | `var ExportedVariable = 1`, 253 | `var LongLine = newLongLine\(`, 254 | `var MultiLineVar = map\[struct {`, 255 | `var internalVariable = 2`, 256 | `Comment about internal variable`, 257 | `FUNCTIONS`, 258 | `func ExportedFunc\(a int\) bool`, 259 | `Comment about exported function`, 260 | `func MultiLineFunc\(x interface`, 261 | `func internalFunc\(a int\) bool`, 262 | `Comment about internal function`, 263 | `func newLongLine\(ss .*string\)`, 264 | `TYPES`, 265 | `type ExportedType struct`, 266 | `type T1 = T2`, 267 | `type T2 int`, 268 | `type unexportedType int`, 269 | `Comment about unexported type`, 270 | `ConstGroup1 unexportedType = iota`, 271 | `ConstGroup2`, 272 | `ConstGroup3`, 273 | `ExportedTypedConstant_unexported unexportedType = iota`, 274 | `Constants tied to unexportedType`, 275 | `const unexportedTypedConstant unexportedType = 1`, 276 | `func ReturnUnexported\(\) unexportedType`, 277 | `func \(unexportedType\) ExportedMethod\(\) bool`, 278 | `func \(unexportedType\) unexportedMethod\(\) bool`, 279 | }, 280 | nil, 281 | }, 282 | 283 | // Single constant. 284 | { 285 | "single constant", 286 | []string{p, `ExportedConstant`}, 287 | []string{ 288 | `Comment about exported constant`, // Include comment. 289 | `const ExportedConstant = 1`, 290 | }, 291 | nil, 292 | }, 293 | // Single constant -u. 294 | { 295 | "single constant with -u", 296 | []string{`-u`, p, `internalConstant`}, 297 | []string{ 298 | `Comment about internal constant`, // Include comment. 299 | `const internalConstant = 2`, 300 | }, 301 | nil, 302 | }, 303 | // Block of constants. 304 | { 305 | "block of constants", 306 | []string{p, `ConstTwo`}, 307 | []string{ 308 | `Comment before ConstOne.\n.*ConstOne = 1`, // First... 309 | `ConstTwo = 2.*Comment on line with ConstTwo`, // And second show up. 310 | `Comment about block of constants`, // Comment does too. 311 | }, 312 | []string{ 313 | `constThree`, // No unexported constant. 314 | }, 315 | }, 316 | // Block of constants -u. 317 | { 318 | "block of constants with -u", 319 | []string{"-u", p, `constThree`}, 320 | []string{ 321 | `constThree = 3.*Comment on line with constThree`, 322 | }, 323 | nil, 324 | }, 325 | // Block of constants -src. 326 | { 327 | "block of constants with -src", 328 | []string{"-src", p, `ConstTwo`}, 329 | []string{ 330 | `Comment about block of constants`, // Top comment. 331 | `ConstOne.*=.*1`, // Each constant seen. 332 | `ConstTwo.*=.*2.*Comment on line with ConstTwo`, 333 | `constThree`, // Even unexported constants. 334 | }, 335 | nil, 336 | }, 337 | // Block of constants with carryover type from unexported field. 338 | { 339 | "block of constants with carryover type", 340 | []string{p, `ConstLeft2`}, 341 | []string{ 342 | `ConstLeft2, constRight2 uint64`, 343 | `constLeft3, ConstRight3`, 344 | `ConstLeft4, ConstRight4`, 345 | }, 346 | nil, 347 | }, 348 | // Block of constants -u with carryover type from unexported field. 349 | { 350 | "block of constants with carryover type", 351 | []string{"-u", p, `ConstLeft2`}, 352 | []string{ 353 | `_, _ uint64 = 2 \* iota, 1 << iota`, 354 | `constLeft1, constRight1`, 355 | `ConstLeft2, constRight2`, 356 | `constLeft3, ConstRight3`, 357 | `ConstLeft4, ConstRight4`, 358 | }, 359 | nil, 360 | }, 361 | 362 | // Single variable. 363 | { 364 | "single variable", 365 | []string{p, `ExportedVariable`}, 366 | []string{ 367 | `ExportedVariable`, // Include comment. 368 | `var ExportedVariable = 1`, 369 | }, 370 | nil, 371 | }, 372 | // Single variable -u. 373 | { 374 | "single variable with -u", 375 | []string{`-u`, p, `internalVariable`}, 376 | []string{ 377 | `Comment about internal variable`, // Include comment. 378 | `var internalVariable = 2`, 379 | }, 380 | nil, 381 | }, 382 | // Block of variables. 383 | { 384 | "block of variables", 385 | []string{p, `VarTwo`}, 386 | []string{ 387 | `Comment before VarOne.\n.*VarOne = 1`, // First... 388 | `VarTwo = 2.*Comment on line with VarTwo`, // And second show up. 389 | `Comment about block of variables`, // Comment does too. 390 | }, 391 | []string{ 392 | `varThree= 3`, // No unexported variable. 393 | }, 394 | }, 395 | // Block of variables -u. 396 | { 397 | "block of variables with -u", 398 | []string{"-u", p, `varThree`}, 399 | []string{ 400 | `varThree = 3.*Comment on line with varThree`, 401 | }, 402 | nil, 403 | }, 404 | 405 | // Function. 406 | { 407 | "function", 408 | []string{p, `ExportedFunc`}, 409 | []string{ 410 | `Comment about exported function`, // Include comment. 411 | `func ExportedFunc\(a int\) bool`, 412 | }, 413 | nil, 414 | }, 415 | // Function -u. 416 | { 417 | "function with -u", 418 | []string{"-u", p, `internalFunc`}, 419 | []string{ 420 | `Comment about internal function`, // Include comment. 421 | `func internalFunc\(a int\) bool`, 422 | }, 423 | nil, 424 | }, 425 | // Function with -src. 426 | { 427 | "function with -src", 428 | []string{"-src", p, `ExportedFunc`}, 429 | []string{ 430 | `Comment about exported function`, // Include comment. 431 | `func ExportedFunc\(a int\) bool`, 432 | `return true != false`, // Include body. 433 | }, 434 | nil, 435 | }, 436 | 437 | // Type. 438 | { 439 | "type", 440 | []string{p, `ExportedType`}, 441 | []string{ 442 | `Comment about exported type`, // Include comment. 443 | `type ExportedType struct`, // Type definition. 444 | `Comment before exported field.*\n.*ExportedField +int` + 445 | `.*Comment on line with exported field`, 446 | `ExportedEmbeddedType.*Comment on line with exported embedded field`, 447 | `Has unexported fields`, 448 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 449 | `const ExportedTypedConstant ExportedType = iota`, // Must include associated constant. 450 | `func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor. 451 | `io.Reader.*Comment on line with embedded Reader`, 452 | }, 453 | []string{ 454 | `unexportedField`, // No unexported field. 455 | `int.*embedded`, // No unexported embedded field. 456 | `Comment about exported method`, // No comment about exported method. 457 | `unexportedMethod`, // No unexported method. 458 | `unexportedTypedConstant`, // No unexported constant. 459 | `error`, // No embedded error. 460 | }, 461 | }, 462 | // Type with -src. Will see unexported fields. 463 | { 464 | "type", 465 | []string{"-src", p, `ExportedType`}, 466 | []string{ 467 | `Comment about exported type`, // Include comment. 468 | `type ExportedType struct`, // Type definition. 469 | `Comment before exported field`, 470 | `ExportedField.*Comment on line with exported field`, 471 | `ExportedEmbeddedType.*Comment on line with exported embedded field`, 472 | `unexportedType.*Comment on line with unexported embedded field`, 473 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 474 | `const ExportedTypedConstant ExportedType = iota`, // Must include associated constant. 475 | `func ExportedTypeConstructor\(\) \*ExportedType`, // Must include constructor. 476 | `io.Reader.*Comment on line with embedded Reader`, 477 | }, 478 | []string{ 479 | `Comment about exported method`, // No comment about exported method. 480 | `unexportedMethod`, // No unexported method. 481 | `unexportedTypedConstant`, // No unexported constant. 482 | }, 483 | }, 484 | // Type -all. 485 | { 486 | "type", 487 | []string{"-all", p, `ExportedType`}, 488 | []string{ 489 | `type ExportedType struct {`, // Type definition as source. 490 | `Comment about exported type`, // Include comment afterwards. 491 | `const ConstGroup4 ExportedType = ExportedType\{\}`, // Related constants. 492 | `ExportedTypedConstant ExportedType = iota`, 493 | `Constants tied to ExportedType`, 494 | `func ExportedTypeConstructor\(\) \*ExportedType`, 495 | `Comment about constructor for exported type.`, 496 | `func ReturnExported\(\) ExportedType`, 497 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 498 | `Comment about exported method.`, 499 | `func \(ExportedType\) Uncommented\(a int\) bool\n\n`, // Ensure line gap after method with no comment 500 | }, 501 | []string{ 502 | `unexportedType`, 503 | }, 504 | }, 505 | // Type T1 dump (alias). 506 | { 507 | "type T1", 508 | []string{p + ".T1"}, 509 | []string{ 510 | `type T1 = T2`, 511 | }, 512 | []string{ 513 | `type T1 T2`, 514 | `type ExportedType`, 515 | }, 516 | }, 517 | // Type -u with unexported fields. 518 | { 519 | "type with unexported fields and -u", 520 | []string{"-u", p, `ExportedType`}, 521 | []string{ 522 | `Comment about exported type`, // Include comment. 523 | `type ExportedType struct`, // Type definition. 524 | `Comment before exported field.*\n.*ExportedField +int`, 525 | `unexportedField.*int.*Comment on line with unexported field`, 526 | `ExportedEmbeddedType.*Comment on line with exported embedded field`, 527 | `\*ExportedEmbeddedType.*Comment on line with exported embedded \*field`, 528 | `\*qualified.ExportedEmbeddedType.*Comment on line with exported embedded \*selector.field`, 529 | `unexportedType.*Comment on line with unexported embedded field`, 530 | `\*unexportedType.*Comment on line with unexported embedded \*field`, 531 | `io.Reader.*Comment on line with embedded Reader`, 532 | `error.*Comment on line with embedded error`, 533 | `func \(ExportedType\) unexportedMethod\(a int\) bool`, 534 | `unexportedTypedConstant`, 535 | }, 536 | []string{ 537 | `Has unexported fields`, 538 | }, 539 | }, 540 | // Unexported type with -u. 541 | { 542 | "unexported type with -u", 543 | []string{"-u", p, `unexportedType`}, 544 | []string{ 545 | `Comment about unexported type`, // Include comment. 546 | `type unexportedType int`, // Type definition. 547 | `func \(unexportedType\) ExportedMethod\(\) bool`, 548 | `func \(unexportedType\) unexportedMethod\(\) bool`, 549 | `ExportedTypedConstant_unexported unexportedType = iota`, 550 | `const unexportedTypedConstant unexportedType = 1`, 551 | }, 552 | nil, 553 | }, 554 | 555 | // Interface. 556 | { 557 | "interface type", 558 | []string{p, `ExportedInterface`}, 559 | []string{ 560 | `Comment about exported interface`, // Include comment. 561 | `type ExportedInterface interface`, // Interface definition. 562 | `Comment before exported method.*\n.*ExportedMethod\(\)` + 563 | `.*Comment on line with exported method`, 564 | `io.Reader.*Comment on line with embedded Reader`, 565 | `error.*Comment on line with embedded error`, 566 | `Has unexported methods`, 567 | }, 568 | []string{ 569 | `unexportedField`, // No unexported field. 570 | `Comment about exported method`, // No comment about exported method. 571 | `unexportedMethod`, // No unexported method. 572 | `unexportedTypedConstant`, // No unexported constant. 573 | }, 574 | }, 575 | // Interface -u with unexported methods. 576 | { 577 | "interface type with unexported methods and -u", 578 | []string{"-u", p, `ExportedInterface`}, 579 | []string{ 580 | `Comment about exported interface`, // Include comment. 581 | `type ExportedInterface interface`, // Interface definition. 582 | `Comment before exported method.*\n.*ExportedMethod\(\)` + 583 | `.*Comment on line with exported method`, 584 | `unexportedMethod\(\).*Comment on line with unexported method`, 585 | `io.Reader.*Comment on line with embedded Reader`, 586 | `error.*Comment on line with embedded error`, 587 | }, 588 | []string{ 589 | `Has unexported methods`, 590 | }, 591 | }, 592 | 593 | // Interface method. 594 | { 595 | "interface method", 596 | []string{p, `ExportedInterface.ExportedMethod`}, 597 | []string{ 598 | `Comment before exported method.*\n.*ExportedMethod\(\)` + 599 | `.*Comment on line with exported method`, 600 | }, 601 | []string{ 602 | `Comment about exported interface`, 603 | }, 604 | }, 605 | // Interface method at package level. 606 | { 607 | "interface method at package level", 608 | []string{p, `ExportedMethod`}, 609 | []string{ 610 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 611 | `Comment about exported method`, 612 | }, 613 | []string{ 614 | `Comment before exported method.*\n.*ExportedMethod\(\)` + 615 | `.*Comment on line with exported method`, 616 | }, 617 | }, 618 | 619 | // Method. 620 | { 621 | "method", 622 | []string{p, `ExportedType.ExportedMethod`}, 623 | []string{ 624 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 625 | `Comment about exported method`, 626 | }, 627 | nil, 628 | }, 629 | // Method with -u. 630 | { 631 | "method with -u", 632 | []string{"-u", p, `ExportedType.unexportedMethod`}, 633 | []string{ 634 | `func \(ExportedType\) unexportedMethod\(a int\) bool`, 635 | `Comment about unexported method`, 636 | }, 637 | nil, 638 | }, 639 | // Method with -src. 640 | { 641 | "method with -src", 642 | []string{"-src", p, `ExportedType.ExportedMethod`}, 643 | []string{ 644 | `func \(ExportedType\) ExportedMethod\(a int\) bool`, 645 | `Comment about exported method`, 646 | `return true != true`, 647 | }, 648 | nil, 649 | }, 650 | 651 | // Field. 652 | { 653 | "field", 654 | []string{p, `ExportedType.ExportedField`}, 655 | []string{ 656 | `type ExportedType struct`, 657 | `ExportedField int`, 658 | `Comment before exported field`, 659 | `Comment on line with exported field`, 660 | `other fields elided`, 661 | }, 662 | nil, 663 | }, 664 | 665 | // Field with -u. 666 | { 667 | "method with -u", 668 | []string{"-u", p, `ExportedType.unexportedField`}, 669 | []string{ 670 | `unexportedField int`, 671 | `Comment on line with unexported field`, 672 | }, 673 | nil, 674 | }, 675 | 676 | // Field of struct with only one field. 677 | { 678 | "single-field struct", 679 | []string{p, `ExportedStructOneField.OnlyField`}, 680 | []string{`the only field`}, 681 | []string{`other fields elided`}, 682 | }, 683 | 684 | // Case matching off. 685 | { 686 | "case matching off", 687 | []string{p, `casematch`}, 688 | []string{ 689 | `CaseMatch`, 690 | `Casematch`, 691 | }, 692 | nil, 693 | }, 694 | 695 | // Case matching on. 696 | { 697 | "case matching on", 698 | []string{"-c", p, `Casematch`}, 699 | []string{ 700 | `Casematch`, 701 | }, 702 | []string{ 703 | `CaseMatch`, 704 | }, 705 | }, 706 | 707 | // No dups with -u. Issue 21797. 708 | { 709 | "case matching on, no dups", 710 | []string{"-u", p, `duplicate`}, 711 | []string{ 712 | `Duplicate`, 713 | `duplicate`, 714 | }, 715 | []string{ 716 | "\\)\n+const", // This will appear if the const decl appears twice. 717 | }, 718 | }, 719 | { 720 | "non-imported: pkg.sym", 721 | []string{"nested.Foo"}, 722 | []string{"Foo struct"}, 723 | nil, 724 | }, 725 | { 726 | "non-imported: pkg only", 727 | []string{"nested"}, 728 | []string{"Foo struct"}, 729 | nil, 730 | }, 731 | { 732 | "non-imported: pkg sym", 733 | []string{"nested", "Foo"}, 734 | []string{"Foo struct"}, 735 | nil, 736 | }, 737 | { 738 | "formatted doc on function", 739 | []string{p, "ExportedFormattedDoc"}, 740 | []string{ 741 | `func ExportedFormattedDoc\(a int\) bool`, 742 | ` Comment about exported function with formatting\. 743 | 744 | Example 745 | 746 | fmt\.Println\(FormattedDoc\(\)\) 747 | 748 | Text after pre-formatted block\.`, 749 | }, 750 | nil, 751 | }, 752 | { 753 | "formatted doc on type field", 754 | []string{p, "ExportedFormattedType.ExportedField"}, 755 | []string{ 756 | `type ExportedFormattedType struct`, 757 | ` // Comment before exported field with formatting\. 758 | //[ ] 759 | // Example 760 | //[ ] 761 | // a\.ExportedField = 123 762 | //[ ] 763 | // Text after pre-formatted block\.`, 764 | `ExportedField int`, 765 | }, 766 | nil, 767 | }, 768 | } 769 | 770 | func TestDoc(t *testing.T) { 771 | maybeSkip(t) 772 | for _, test := range tests { 773 | var b bytes.Buffer 774 | var flagSet flag.FlagSet 775 | err := do(&b, &flagSet, test.args) 776 | if err != nil { 777 | t.Fatalf("%s %v: %s\n", test.name, test.args, err) 778 | } 779 | output := b.Bytes() 780 | failed := false 781 | for j, yes := range test.yes { 782 | re, err := regexp.Compile(yes) 783 | if err != nil { 784 | t.Fatalf("%s.%d: compiling %#q: %s", test.name, j, yes, err) 785 | } 786 | if !re.Match(output) { 787 | t.Errorf("%s.%d: no match for %s %#q", test.name, j, test.args, yes) 788 | failed = true 789 | } 790 | } 791 | for j, no := range test.no { 792 | re, err := regexp.Compile(no) 793 | if err != nil { 794 | t.Fatalf("%s.%d: compiling %#q: %s", test.name, j, no, err) 795 | } 796 | if re.Match(output) { 797 | t.Errorf("%s.%d: incorrect match for %s %#q", test.name, j, test.args, no) 798 | failed = true 799 | } 800 | } 801 | if bytes.Count(output, []byte("TYPES\n")) > 1 { 802 | t.Fatalf("%s: repeating headers", test.name) 803 | } 804 | if failed { 805 | t.Logf("\n%s", output) 806 | } 807 | } 808 | } 809 | 810 | // Test the code to try multiple packages. Our test case is 811 | // go doc rand.Float64 812 | // This needs to find math/rand.Float64; however crypto/rand, which doesn't 813 | // have the symbol, usually appears first in the directory listing. 814 | func TestMultiplePackages(t *testing.T) { 815 | if testing.Short() { 816 | t.Skip("scanning file system takes too long") 817 | } 818 | maybeSkip(t) 819 | var b bytes.Buffer // We don't care about the output. 820 | // Make sure crypto/rand does not have the symbol. 821 | { 822 | var flagSet flag.FlagSet 823 | err := do(&b, &flagSet, []string{"crypto/rand.float64"}) 824 | if err == nil { 825 | t.Errorf("expected error from crypto/rand.float64") 826 | } else if !strings.Contains(err.Error(), "no symbol float64") { 827 | t.Errorf("unexpected error %q from crypto/rand.float64", err) 828 | } 829 | } 830 | // Make sure math/rand does have the symbol. 831 | { 832 | var flagSet flag.FlagSet 833 | err := do(&b, &flagSet, []string{"math/rand.float64"}) 834 | if err != nil { 835 | t.Errorf("unexpected error %q from math/rand.float64", err) 836 | } 837 | } 838 | // Try the shorthand. 839 | { 840 | var flagSet flag.FlagSet 841 | err := do(&b, &flagSet, []string{"rand.float64"}) 842 | if err != nil { 843 | t.Errorf("unexpected error %q from rand.float64", err) 844 | } 845 | } 846 | // Now try a missing symbol. We should see both packages in the error. 847 | { 848 | var flagSet flag.FlagSet 849 | err := do(&b, &flagSet, []string{"rand.doesnotexit"}) 850 | if err == nil { 851 | t.Errorf("expected error from rand.doesnotexit") 852 | } else { 853 | errStr := err.Error() 854 | if !strings.Contains(errStr, "no symbol") { 855 | t.Errorf("error %q should contain 'no symbol", errStr) 856 | } 857 | if !strings.Contains(errStr, "crypto/rand") { 858 | t.Errorf("error %q should contain crypto/rand", errStr) 859 | } 860 | if !strings.Contains(errStr, "math/rand") { 861 | t.Errorf("error %q should contain math/rand", errStr) 862 | } 863 | } 864 | } 865 | } 866 | 867 | // Test the code to look up packages when given two args. First test case is 868 | // go doc binary BigEndian 869 | // This needs to find encoding/binary.BigEndian, which means 870 | // finding the package encoding/binary given only "binary". 871 | // Second case is 872 | // go doc rand Float64 873 | // which again needs to find math/rand and not give up after crypto/rand, 874 | // which has no such function. 875 | func TestTwoArgLookup(t *testing.T) { 876 | if testing.Short() { 877 | t.Skip("scanning file system takes too long") 878 | } 879 | maybeSkip(t) 880 | var b bytes.Buffer // We don't care about the output. 881 | { 882 | var flagSet flag.FlagSet 883 | err := do(&b, &flagSet, []string{"binary", "BigEndian"}) 884 | if err != nil { 885 | t.Errorf("unexpected error %q from binary BigEndian", err) 886 | } 887 | } 888 | { 889 | var flagSet flag.FlagSet 890 | err := do(&b, &flagSet, []string{"rand", "Float64"}) 891 | if err != nil { 892 | t.Errorf("unexpected error %q from rand Float64", err) 893 | } 894 | } 895 | { 896 | var flagSet flag.FlagSet 897 | err := do(&b, &flagSet, []string{"bytes", "Foo"}) 898 | if err == nil { 899 | t.Errorf("expected error from bytes Foo") 900 | } else if !strings.Contains(err.Error(), "no symbol Foo") { 901 | t.Errorf("unexpected error %q from bytes Foo", err) 902 | } 903 | } 904 | { 905 | var flagSet flag.FlagSet 906 | err := do(&b, &flagSet, []string{"nosuchpackage", "Foo"}) 907 | if err == nil { 908 | // actually present in the user's filesystem 909 | } else if !strings.Contains(err.Error(), "no such package") { 910 | t.Errorf("unexpected error %q from nosuchpackage Foo", err) 911 | } 912 | } 913 | } 914 | 915 | // Test the code to look up packages when the first argument starts with "./". 916 | // Our test case is in effect "cd src/text; doc ./template". This should get 917 | // text/template but before Issue 23383 was fixed would give html/template. 918 | func TestDotSlashLookup(t *testing.T) { 919 | if testing.Short() { 920 | t.Skip("scanning file system takes too long") 921 | } 922 | maybeSkip(t) 923 | where, err := os.Getwd() 924 | if err != nil { 925 | t.Fatal(err) 926 | } 927 | defer func() { 928 | if err := os.Chdir(where); err != nil { 929 | t.Fatal(err) 930 | } 931 | }() 932 | if err := os.Chdir(filepath.Join(buildCtx.GOROOT, "src", "text")); err != nil { 933 | t.Fatal(err) 934 | } 935 | var b bytes.Buffer 936 | var flagSet flag.FlagSet 937 | err = do(&b, &flagSet, []string{"./template"}) 938 | if err != nil { 939 | t.Errorf("unexpected error %q from ./template", err) 940 | } 941 | // The output should contain information about the text/template package. 942 | const want = `package template // import "text/template"` 943 | output := b.String() 944 | if !strings.HasPrefix(output, want) { 945 | t.Fatalf("wrong package: %.*q...", len(want), output) 946 | } 947 | } 948 | 949 | type trimTest struct { 950 | path string 951 | prefix string 952 | result string 953 | ok bool 954 | } 955 | 956 | var trimTests = []trimTest{ 957 | {"", "", "", true}, 958 | {"/usr/gopher", "/usr/gopher", "/usr/gopher", true}, 959 | {"/usr/gopher/bar", "/usr/gopher", "bar", true}, 960 | {"/usr/gopherflakes", "/usr/gopher", "/usr/gopherflakes", false}, 961 | {"/usr/gopher/bar", "/usr/zot", "/usr/gopher/bar", false}, 962 | } 963 | 964 | func TestTrim(t *testing.T) { 965 | for _, test := range trimTests { 966 | result, ok := trim(test.path, test.prefix) 967 | if ok != test.ok { 968 | t.Errorf("%s %s expected %t got %t", test.path, test.prefix, test.ok, ok) 969 | continue 970 | } 971 | if result != test.result { 972 | t.Errorf("%s %s expected %q got %q", test.path, test.prefix, test.result, result) 973 | continue 974 | } 975 | } 976 | } 977 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ravener/gopherdoc 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alecthomas/chroma v0.8.2 7 | github.com/mattn/go-colorable v0.1.8 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 2 | github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg= 3 | github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= 4 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 5 | github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= 6 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 7 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= 8 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= 12 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 13 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 14 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 15 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 16 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 17 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 18 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 19 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 24 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 25 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 26 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= 28 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 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 13 | // go doc [.] 14 | // go doc [.][.] 15 | // go doc [.][.] 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 [.] 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 | "github.com/mattn/go-colorable" 45 | "go/build" 46 | "go/token" 47 | "io" 48 | "log" 49 | "os" 50 | "path" 51 | "path/filepath" 52 | "strings" 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 | style string // -style flag 62 | ) 63 | 64 | // usage is a replacement usage function for the flags package. 65 | func usage() { 66 | fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n") 67 | fmt.Fprintf(os.Stderr, "\tgo doc\n") 68 | fmt.Fprintf(os.Stderr, "\tgo doc \n") 69 | fmt.Fprintf(os.Stderr, "\tgo doc [.]\n") 70 | fmt.Fprintf(os.Stderr, "\tgo doc [].[.]\n") 71 | fmt.Fprintf(os.Stderr, "\tgo doc [.]\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(colorable.NewColorableStdout(), 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.StringVar(&style, "style", "", "change chroma's style (can also set GDOC_STYLE environment variable, default is 'vim')") 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 a package path. 232 | // First, is it a complete package path as it is? If so, we are done. 233 | // This avoids confusion over package paths that have other 234 | // package paths as their prefix. 235 | pkg, err = build.Import(arg, wd, build.ImportComment) 236 | if err == nil { 237 | return pkg, arg, "", false 238 | } 239 | // Another disambiguator: If the symbol starts with an upper 240 | // case letter, it can only be a symbol in the current directory. 241 | // Kills the problem caused by case-insensitive file systems 242 | // matching an upper case name as a package name. 243 | if token.IsExported(arg) { 244 | pkg, err := build.ImportDir(".", build.ImportComment) 245 | if err == nil { 246 | return pkg, "", arg, false 247 | } 248 | } 249 | // If it has a slash, it must be a package path but there is a symbol. 250 | // It's the last package path we care about. 251 | slash := strings.LastIndex(arg, "/") 252 | // There may be periods in the package path before or after the slash 253 | // and between a symbol and method. 254 | // Split the string at various periods to see what we find. 255 | // In general there may be ambiguities but this should almost always 256 | // work. 257 | var period int 258 | // slash+1: if there's no slash, the value is -1 and start is 0; otherwise 259 | // start is the byte after the slash. 260 | for start := slash + 1; start < len(arg); start = period + 1 { 261 | period = strings.Index(arg[start:], ".") 262 | symbol := "" 263 | if period < 0 { 264 | period = len(arg) 265 | } else { 266 | period += start 267 | symbol = arg[period+1:] 268 | } 269 | // Have we identified a package already? 270 | pkg, err := build.Import(arg[0:period], wd, build.ImportComment) 271 | if err == nil { 272 | return pkg, arg[0:period], symbol, false 273 | } 274 | // See if we have the basename or tail of a package, as in json for encoding/json 275 | // or ivy/value for robpike.io/ivy/value. 276 | pkgName := arg[:period] 277 | for { 278 | path, ok := findNextPackage(pkgName) 279 | if !ok { 280 | break 281 | } 282 | if pkg, err = build.ImportDir(path, build.ImportComment); err == nil { 283 | return pkg, arg[0:period], symbol, true 284 | } 285 | } 286 | dirs.Reset() // Next iteration of for loop must scan all the directories again. 287 | } 288 | // If it has a slash, we've failed. 289 | if slash >= 0 { 290 | log.Fatalf("no such package %s", arg[0:period]) 291 | } 292 | // Guess it's a symbol in the current directory. 293 | return importDir(wd), "", arg, false 294 | } 295 | 296 | // dotPaths lists all the dotted paths legal on Unix-like and 297 | // Windows-like file systems. We check them all, as the chance 298 | // of error is minute and even on Windows people will use ./ 299 | // sometimes. 300 | var dotPaths = []string{ 301 | `./`, 302 | `../`, 303 | `.\`, 304 | `..\`, 305 | } 306 | 307 | // isDotSlash reports whether the path begins with a reference 308 | // to the local . or .. directory. 309 | func isDotSlash(arg string) bool { 310 | if arg == "." || arg == ".." { 311 | return true 312 | } 313 | for _, dotPath := range dotPaths { 314 | if strings.HasPrefix(arg, dotPath) { 315 | return true 316 | } 317 | } 318 | return false 319 | } 320 | 321 | // importDir is just an error-catching wrapper for build.ImportDir. 322 | func importDir(dir string) *build.Package { 323 | pkg, err := build.ImportDir(dir, build.ImportComment) 324 | if err != nil { 325 | log.Fatal(err) 326 | } 327 | return pkg 328 | } 329 | 330 | // parseSymbol breaks str apart into a symbol and method. 331 | // Both may be missing or the method may be missing. 332 | // If present, each must be a valid Go identifier. 333 | func parseSymbol(str string) (symbol, method string) { 334 | if str == "" { 335 | return 336 | } 337 | elem := strings.Split(str, ".") 338 | switch len(elem) { 339 | case 1: 340 | case 2: 341 | method = elem[1] 342 | if !token.IsIdentifier(method) { 343 | log.Fatalf("invalid identifier %q", method) 344 | } 345 | default: 346 | log.Printf("too many periods in symbol specification") 347 | usage() 348 | } 349 | symbol = elem[0] 350 | if !token.IsIdentifier(symbol) { 351 | log.Fatalf("invalid identifier %q", symbol) 352 | } 353 | return 354 | } 355 | 356 | // isExported reports whether the name is an exported identifier. 357 | // If the unexported flag (-u) is true, isExported returns true because 358 | // it means that we treat the name as if it is exported. 359 | func isExported(name string) bool { 360 | return unexported || token.IsExported(name) 361 | } 362 | 363 | // findNextPackage returns the next full file name path that matches the 364 | // (perhaps partial) package path pkg. The boolean reports if any match was found. 365 | func findNextPackage(pkg string) (string, bool) { 366 | if pkg == "" || token.IsExported(pkg) { // Upper case symbol cannot be a package name. 367 | return "", false 368 | } 369 | if filepath.IsAbs(pkg) { 370 | if dirs.offset == 0 { 371 | dirs.offset = -1 372 | return pkg, true 373 | } 374 | return "", false 375 | } 376 | pkg = path.Clean(pkg) 377 | pkgSuffix := "/" + pkg 378 | for { 379 | d, ok := dirs.Next() 380 | if !ok { 381 | return "", false 382 | } 383 | if d.importPath == pkg || strings.HasSuffix(d.importPath, pkgSuffix) { 384 | return d.dir, true 385 | } 386 | } 387 | } 388 | 389 | var buildCtx = build.Default 390 | 391 | // splitGopath splits $GOPATH into a list of roots. 392 | func splitGopath() []string { 393 | return filepath.SplitList(buildCtx.GOPATH) 394 | } 395 | -------------------------------------------------------------------------------- /pkg.go: -------------------------------------------------------------------------------- 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 | "bufio" 9 | "bytes" 10 | "fmt" 11 | "github.com/alecthomas/chroma/quick" 12 | "go/ast" 13 | "go/build" 14 | "go/doc" 15 | "go/format" 16 | "go/parser" 17 | "go/printer" 18 | "go/token" 19 | "io" 20 | "log" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | "unicode" 25 | "unicode/utf8" 26 | ) 27 | 28 | const ( 29 | punchedCardWidth = 80 // These things just won't leave us alone. 30 | indentedWidth = punchedCardWidth - len(indent) 31 | indent = " " 32 | ) 33 | 34 | type Package struct { 35 | writer io.Writer // Destination for output. 36 | name string // Package name, json for encoding/json. 37 | userPath string // String the user used to find this package. 38 | pkg *ast.Package // Parsed package. 39 | file *ast.File // Merged from all files in the package 40 | doc *doc.Package 41 | build *build.Package 42 | typedValue map[*doc.Value]bool // Consts and vars related to types. 43 | constructor map[*doc.Func]bool // Constructors. 44 | fs *token.FileSet // Needed for printing. 45 | buf pkgBuffer 46 | } 47 | 48 | // pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the 49 | // first time Write is called. 50 | type pkgBuffer struct { 51 | pkg *Package 52 | printed bool // Prevent repeated package clauses. 53 | bytes.Buffer 54 | } 55 | 56 | func (pb *pkgBuffer) Write(p []byte) (int, error) { 57 | if !pb.printed && len(p) > 0 { 58 | pb.printed = true 59 | // Only show package clause for commands if requested explicitly. 60 | if pb.pkg.pkg.Name != "main" || showCmd { 61 | pb.pkg.packageClause() 62 | } 63 | } 64 | return pb.Buffer.Write(p) 65 | } 66 | 67 | type PackageError string // type returned by pkg.Fatalf. 68 | 69 | func (p PackageError) Error() string { 70 | return string(p) 71 | } 72 | 73 | // prettyPath returns a version of the package path that is suitable for an 74 | // error message. It obeys the import comment if present. Also, since 75 | // pkg.build.ImportPath is sometimes the unhelpful "" or ".", it looks for a 76 | // directory name in GOROOT or GOPATH if that happens. 77 | func (pkg *Package) prettyPath() string { 78 | path := pkg.build.ImportComment 79 | if path == "" { 80 | path = pkg.build.ImportPath 81 | } 82 | if path != "." && path != "" { 83 | return path 84 | } 85 | // Convert the source directory into a more useful path. 86 | // Also convert everything to slash-separated paths for uniform handling. 87 | path = filepath.Clean(filepath.ToSlash(pkg.build.Dir)) 88 | // Can we find a decent prefix? 89 | goroot := filepath.Join(buildCtx.GOROOT, "src") 90 | if p, ok := trim(path, filepath.ToSlash(goroot)); ok { 91 | return p 92 | } 93 | for _, gopath := range splitGopath() { 94 | if p, ok := trim(path, filepath.ToSlash(gopath)); ok { 95 | return p 96 | } 97 | } 98 | return path 99 | } 100 | 101 | // trim trims the directory prefix from the path, paying attention 102 | // to the path separator. If they are the same string or the prefix 103 | // is not present the original is returned. The boolean reports whether 104 | // the prefix is present. That path and prefix have slashes for separators. 105 | func trim(path, prefix string) (string, bool) { 106 | if !strings.HasPrefix(path, prefix) { 107 | return path, false 108 | } 109 | if path == prefix { 110 | return path, true 111 | } 112 | if path[len(prefix)] == '/' { 113 | return path[len(prefix)+1:], true 114 | } 115 | return path, false // Textual prefix but not a path prefix. 116 | } 117 | 118 | // pkg.Fatalf is like log.Fatalf, but panics so it can be recovered in the 119 | // main do function, so it doesn't cause an exit. Allows testing to work 120 | // without running a subprocess. The log prefix will be added when 121 | // logged in main; it is not added here. 122 | func (pkg *Package) Fatalf(format string, args ...interface{}) { 123 | panic(PackageError(fmt.Sprintf(format, args...))) 124 | } 125 | 126 | // parsePackage turns the build package we found into a parsed package 127 | // we can then use to generate documentation. 128 | func parsePackage(writer io.Writer, pkg *build.Package, userPath string) *Package { 129 | fs := token.NewFileSet() 130 | // include tells parser.ParseDir which files to include. 131 | // That means the file must be in the build package's GoFiles or CgoFiles 132 | // list only (no tag-ignored files, tests, swig or other non-Go files). 133 | include := func(info os.FileInfo) bool { 134 | for _, name := range pkg.GoFiles { 135 | if name == info.Name() { 136 | return true 137 | } 138 | } 139 | for _, name := range pkg.CgoFiles { 140 | if name == info.Name() { 141 | return true 142 | } 143 | } 144 | return false 145 | } 146 | pkgs, err := parser.ParseDir(fs, pkg.Dir, include, parser.ParseComments) 147 | if err != nil { 148 | log.Fatal(err) 149 | } 150 | // Make sure they are all in one package. 151 | if len(pkgs) == 0 { 152 | log.Fatalf("no source-code package in directory %s", pkg.Dir) 153 | } 154 | if len(pkgs) > 1 { 155 | log.Fatalf("multiple packages in directory %s", pkg.Dir) 156 | } 157 | astPkg := pkgs[pkg.Name] 158 | 159 | // TODO: go/doc does not include typed constants in the constants 160 | // list, which is what we want. For instance, time.Sunday is of type 161 | // time.Weekday, so it is defined in the type but not in the 162 | // Consts list for the package. This prevents 163 | // go doc time.Sunday 164 | // from finding the symbol. Work around this for now, but we 165 | // should fix it in go/doc. 166 | // A similar story applies to factory functions. 167 | mode := doc.AllDecls 168 | if showSrc { 169 | mode |= doc.PreserveAST // See comment for Package.emit. 170 | } 171 | docPkg := doc.New(astPkg, pkg.ImportPath, mode) 172 | typedValue := make(map[*doc.Value]bool) 173 | constructor := make(map[*doc.Func]bool) 174 | for _, typ := range docPkg.Types { 175 | docPkg.Consts = append(docPkg.Consts, typ.Consts...) 176 | for _, value := range typ.Consts { 177 | typedValue[value] = true 178 | } 179 | docPkg.Vars = append(docPkg.Vars, typ.Vars...) 180 | for _, value := range typ.Vars { 181 | typedValue[value] = true 182 | } 183 | docPkg.Funcs = append(docPkg.Funcs, typ.Funcs...) 184 | for _, fun := range typ.Funcs { 185 | // We don't count it as a constructor bound to the type 186 | // if the type itself is not exported. 187 | if isExported(typ.Name) { 188 | constructor[fun] = true 189 | } 190 | } 191 | } 192 | 193 | p := &Package{ 194 | writer: writer, 195 | name: pkg.Name, 196 | userPath: userPath, 197 | pkg: astPkg, 198 | file: ast.MergePackageFiles(astPkg, 0), 199 | doc: docPkg, 200 | typedValue: typedValue, 201 | constructor: constructor, 202 | build: pkg, 203 | fs: fs, 204 | } 205 | p.buf.pkg = p 206 | return p 207 | } 208 | 209 | func (pkg *Package) Printf(format string, args ...interface{}) { 210 | theme := os.Getenv("GDOC_STYLE") 211 | if theme == "" { 212 | theme = "vim" 213 | } 214 | 215 | if style != "" { 216 | theme = style 217 | } 218 | 219 | quick.Highlight(&pkg.buf, fmt.Sprintf(format, args...), "go", "terminal256", theme) 220 | } 221 | 222 | func (pkg *Package) ToText(text string, indent, preIndent string, width int) { 223 | buf := &bytes.Buffer{} 224 | doc.ToText(buf, text, indent, preIndent, width) 225 | pkg.Printf(buf.String()) 226 | } 227 | 228 | func (pkg *Package) flush() { 229 | _, err := pkg.writer.Write(pkg.buf.Bytes()) 230 | if err != nil { 231 | log.Fatal(err) 232 | } 233 | pkg.buf.Reset() // Not needed, but it's a flush. 234 | } 235 | 236 | var newlineBytes = []byte("\n\n") // We never ask for more than 2. 237 | 238 | // newlines guarantees there are n newlines at the end of the buffer. 239 | func (pkg *Package) newlines(n int) { 240 | for !bytes.HasSuffix(pkg.buf.Bytes(), newlineBytes[:n]) { 241 | pkg.buf.WriteRune('\n') 242 | } 243 | } 244 | 245 | // emit prints the node. If showSrc is true, it ignores the provided comment, 246 | // assuming the comment is in the node itself. Otherwise, the go/doc package 247 | // clears the stuff we don't want to print anyway. It's a bit of a magic trick. 248 | func (pkg *Package) emit(comment string, node ast.Node) { 249 | if node != nil { 250 | var arg interface{} = node 251 | if showSrc { 252 | // Need an extra little dance to get internal comments to appear. 253 | arg = &printer.CommentedNode{ 254 | Node: node, 255 | Comments: pkg.file.Comments, 256 | } 257 | } 258 | buf := &bytes.Buffer{} 259 | err := format.Node(buf, pkg.fs, arg) 260 | pkg.Printf(buf.String()) 261 | if err != nil { 262 | log.Fatal(err) 263 | } 264 | if comment != "" && !showSrc { 265 | pkg.newlines(1) 266 | doc.ToText(&pkg.buf, comment, indent, indent+indent, indentedWidth) 267 | pkg.newlines(2) // Blank line after comment to separate from next item. 268 | } else { 269 | pkg.newlines(1) 270 | } 271 | } 272 | } 273 | 274 | // oneLineNode returns a one-line summary of the given input node. 275 | func (pkg *Package) oneLineNode(node ast.Node) string { 276 | const maxDepth = 10 277 | return pkg.oneLineNodeDepth(node, maxDepth) 278 | } 279 | 280 | // oneLineNodeDepth returns a one-line summary of the given input node. 281 | // The depth specifies the maximum depth when traversing the AST. 282 | func (pkg *Package) oneLineNodeDepth(node ast.Node, depth int) string { 283 | const dotDotDot = "..." 284 | if depth == 0 { 285 | return dotDotDot 286 | } 287 | depth-- 288 | 289 | switch n := node.(type) { 290 | case nil: 291 | return "" 292 | 293 | case *ast.GenDecl: 294 | // Formats const and var declarations. 295 | trailer := "" 296 | if len(n.Specs) > 1 { 297 | trailer = " " + dotDotDot 298 | } 299 | 300 | // Find the first relevant spec. 301 | typ := "" 302 | for i, spec := range n.Specs { 303 | valueSpec := spec.(*ast.ValueSpec) // Must succeed; we can't mix types in one GenDecl. 304 | 305 | // The type name may carry over from a previous specification in the 306 | // case of constants and iota. 307 | if valueSpec.Type != nil { 308 | typ = fmt.Sprintf(" %s", pkg.oneLineNodeDepth(valueSpec.Type, depth)) 309 | } else if len(valueSpec.Values) > 0 { 310 | typ = "" 311 | } 312 | 313 | if !isExported(valueSpec.Names[0].Name) { 314 | continue 315 | } 316 | val := "" 317 | if i < len(valueSpec.Values) && valueSpec.Values[i] != nil { 318 | val = fmt.Sprintf(" = %s", pkg.oneLineNodeDepth(valueSpec.Values[i], depth)) 319 | } 320 | return fmt.Sprintf("%s %s%s%s%s", n.Tok, valueSpec.Names[0], typ, val, trailer) 321 | } 322 | return "" 323 | 324 | case *ast.FuncDecl: 325 | // Formats func declarations. 326 | name := n.Name.Name 327 | recv := pkg.oneLineNodeDepth(n.Recv, depth) 328 | if len(recv) > 0 { 329 | recv = "(" + recv + ") " 330 | } 331 | fnc := pkg.oneLineNodeDepth(n.Type, depth) 332 | if strings.Index(fnc, "func") == 0 { 333 | fnc = fnc[4:] 334 | } 335 | return fmt.Sprintf("func %s%s%s", recv, name, fnc) 336 | 337 | case *ast.TypeSpec: 338 | sep := " " 339 | if n.Assign.IsValid() { 340 | sep = " = " 341 | } 342 | return fmt.Sprintf("type %s%s%s", n.Name.Name, sep, pkg.oneLineNodeDepth(n.Type, depth)) 343 | 344 | case *ast.FuncType: 345 | var params []string 346 | if n.Params != nil { 347 | for _, field := range n.Params.List { 348 | params = append(params, pkg.oneLineField(field, depth)) 349 | } 350 | } 351 | needParens := false 352 | var results []string 353 | if n.Results != nil { 354 | needParens = needParens || len(n.Results.List) > 1 355 | for _, field := range n.Results.List { 356 | needParens = needParens || len(field.Names) > 0 357 | results = append(results, pkg.oneLineField(field, depth)) 358 | } 359 | } 360 | 361 | param := joinStrings(params) 362 | if len(results) == 0 { 363 | return fmt.Sprintf("func(%s)", param) 364 | } 365 | result := joinStrings(results) 366 | if !needParens { 367 | return fmt.Sprintf("func(%s) %s", param, result) 368 | } 369 | return fmt.Sprintf("func(%s) (%s)", param, result) 370 | 371 | case *ast.StructType: 372 | if n.Fields == nil || len(n.Fields.List) == 0 { 373 | return "struct{}" 374 | } 375 | return "struct{ ... }" 376 | 377 | case *ast.InterfaceType: 378 | if n.Methods == nil || len(n.Methods.List) == 0 { 379 | return "interface{}" 380 | } 381 | return "interface{ ... }" 382 | 383 | case *ast.FieldList: 384 | if n == nil || len(n.List) == 0 { 385 | return "" 386 | } 387 | if len(n.List) == 1 { 388 | return pkg.oneLineField(n.List[0], depth) 389 | } 390 | return dotDotDot 391 | 392 | case *ast.FuncLit: 393 | return pkg.oneLineNodeDepth(n.Type, depth) + " { ... }" 394 | 395 | case *ast.CompositeLit: 396 | typ := pkg.oneLineNodeDepth(n.Type, depth) 397 | if len(n.Elts) == 0 { 398 | return fmt.Sprintf("%s{}", typ) 399 | } 400 | return fmt.Sprintf("%s{ %s }", typ, dotDotDot) 401 | 402 | case *ast.ArrayType: 403 | length := pkg.oneLineNodeDepth(n.Len, depth) 404 | element := pkg.oneLineNodeDepth(n.Elt, depth) 405 | return fmt.Sprintf("[%s]%s", length, element) 406 | 407 | case *ast.MapType: 408 | key := pkg.oneLineNodeDepth(n.Key, depth) 409 | value := pkg.oneLineNodeDepth(n.Value, depth) 410 | return fmt.Sprintf("map[%s]%s", key, value) 411 | 412 | case *ast.CallExpr: 413 | fnc := pkg.oneLineNodeDepth(n.Fun, depth) 414 | var args []string 415 | for _, arg := range n.Args { 416 | args = append(args, pkg.oneLineNodeDepth(arg, depth)) 417 | } 418 | return fmt.Sprintf("%s(%s)", fnc, joinStrings(args)) 419 | 420 | case *ast.UnaryExpr: 421 | return fmt.Sprintf("%s%s", n.Op, pkg.oneLineNodeDepth(n.X, depth)) 422 | 423 | case *ast.Ident: 424 | return n.Name 425 | 426 | default: 427 | // As a fallback, use default formatter for all unknown node types. 428 | buf := new(bytes.Buffer) 429 | format.Node(buf, pkg.fs, node) 430 | s := buf.String() 431 | if strings.Contains(s, "\n") { 432 | return dotDotDot 433 | } 434 | return s 435 | } 436 | } 437 | 438 | // oneLineField returns a one-line summary of the field. 439 | func (pkg *Package) oneLineField(field *ast.Field, depth int) string { 440 | var names []string 441 | for _, name := range field.Names { 442 | names = append(names, name.Name) 443 | } 444 | if len(names) == 0 { 445 | return pkg.oneLineNodeDepth(field.Type, depth) 446 | } 447 | return joinStrings(names) + " " + pkg.oneLineNodeDepth(field.Type, depth) 448 | } 449 | 450 | // joinStrings formats the input as a comma-separated list, 451 | // but truncates the list at some reasonable length if necessary. 452 | func joinStrings(ss []string) string { 453 | var n int 454 | for i, s := range ss { 455 | n += len(s) + len(", ") 456 | if n > punchedCardWidth { 457 | ss = append(ss[:i:i], "...") 458 | break 459 | } 460 | } 461 | return strings.Join(ss, ", ") 462 | } 463 | 464 | // allDoc prints all the docs for the package. 465 | func (pkg *Package) allDoc() { 466 | defer pkg.flush() 467 | 468 | doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) 469 | pkg.newlines(1) 470 | 471 | printed := make(map[*ast.GenDecl]bool) 472 | 473 | hdr := "" 474 | printHdr := func(s string) { 475 | if hdr != s { 476 | pkg.Printf("\n%s\n\n", s) 477 | hdr = s 478 | } 479 | } 480 | 481 | // Constants. 482 | for _, value := range pkg.doc.Consts { 483 | // Constants and variables come in groups, and valueDoc prints 484 | // all the items in the group. We only need to find one exported symbol. 485 | for _, name := range value.Names { 486 | if isExported(name) && !pkg.typedValue[value] { 487 | printHdr("CONSTANTS") 488 | pkg.valueDoc(value, printed) 489 | break 490 | } 491 | } 492 | } 493 | 494 | // Variables. 495 | for _, value := range pkg.doc.Vars { 496 | // Constants and variables come in groups, and valueDoc prints 497 | // all the items in the group. We only need to find one exported symbol. 498 | for _, name := range value.Names { 499 | if isExported(name) && !pkg.typedValue[value] { 500 | printHdr("VARIABLES") 501 | pkg.valueDoc(value, printed) 502 | break 503 | } 504 | } 505 | } 506 | 507 | // Functions. 508 | for _, fun := range pkg.doc.Funcs { 509 | if isExported(fun.Name) && !pkg.constructor[fun] { 510 | printHdr("FUNCTIONS") 511 | pkg.emit(fun.Doc, fun.Decl) 512 | } 513 | } 514 | 515 | // Types. 516 | for _, typ := range pkg.doc.Types { 517 | if isExported(typ.Name) { 518 | printHdr("TYPES") 519 | pkg.typeDoc(typ) 520 | } 521 | } 522 | } 523 | 524 | // packageDoc prints the docs for the package (package doc plus one-liners of the rest). 525 | func (pkg *Package) packageDoc() { 526 | defer pkg.flush() 527 | 528 | doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) 529 | pkg.newlines(1) 530 | 531 | if pkg.pkg.Name == "main" && !showCmd { 532 | // Show only package docs for commands. 533 | return 534 | } 535 | 536 | pkg.newlines(2) // Guarantee blank line before the components. 537 | pkg.valueSummary(pkg.doc.Consts, false) 538 | pkg.valueSummary(pkg.doc.Vars, false) 539 | pkg.funcSummary(pkg.doc.Funcs, false) 540 | pkg.typeSummary() 541 | pkg.bugs() 542 | } 543 | 544 | // packageClause prints the package clause. 545 | func (pkg *Package) packageClause() { 546 | importPath := pkg.build.ImportComment 547 | if importPath == "" { 548 | importPath = pkg.build.ImportPath 549 | } 550 | 551 | // If we're using modules, the import path derived from module code locations wins. 552 | // If we did a file system scan, we knew the import path when we found the directory. 553 | // But if we started with a directory name, we never knew the import path. 554 | // Either way, we don't know it now, and it's cheap to (re)compute it. 555 | if usingModules { 556 | for _, root := range codeRoots() { 557 | if pkg.build.Dir == root.dir { 558 | importPath = root.importPath 559 | break 560 | } 561 | if strings.HasPrefix(pkg.build.Dir, root.dir+string(filepath.Separator)) { 562 | suffix := filepath.ToSlash(pkg.build.Dir[len(root.dir)+1:]) 563 | if root.importPath == "" { 564 | importPath = suffix 565 | } else { 566 | importPath = root.importPath + "/" + suffix 567 | } 568 | break 569 | } 570 | } 571 | } 572 | 573 | pkg.Printf("package %s // import %q\n\n", pkg.name, importPath) 574 | if !usingModules && importPath != pkg.build.ImportPath { 575 | pkg.Printf("WARNING: package source is installed in %q\n", pkg.build.ImportPath) 576 | } 577 | } 578 | 579 | // valueSummary prints a one-line summary for each set of values and constants. 580 | // If all the types in a constant or variable declaration belong to the same 581 | // type they can be printed by typeSummary, and so can be suppressed here. 582 | func (pkg *Package) valueSummary(values []*doc.Value, showGrouped bool) { 583 | var isGrouped map[*doc.Value]bool 584 | if !showGrouped { 585 | isGrouped = make(map[*doc.Value]bool) 586 | for _, typ := range pkg.doc.Types { 587 | if !isExported(typ.Name) { 588 | continue 589 | } 590 | for _, c := range typ.Consts { 591 | isGrouped[c] = true 592 | } 593 | for _, v := range typ.Vars { 594 | isGrouped[v] = true 595 | } 596 | } 597 | } 598 | 599 | for _, value := range values { 600 | if !isGrouped[value] { 601 | if decl := pkg.oneLineNode(value.Decl); decl != "" { 602 | pkg.Printf("%s\n", decl) 603 | } 604 | } 605 | } 606 | } 607 | 608 | // funcSummary prints a one-line summary for each function. Constructors 609 | // are printed by typeSummary, below, and so can be suppressed here. 610 | func (pkg *Package) funcSummary(funcs []*doc.Func, showConstructors bool) { 611 | for _, fun := range funcs { 612 | // Exported functions only. The go/doc package does not include methods here. 613 | if isExported(fun.Name) { 614 | if showConstructors || !pkg.constructor[fun] { 615 | pkg.Printf("%s\n", pkg.oneLineNode(fun.Decl)) 616 | } 617 | } 618 | } 619 | } 620 | 621 | // typeSummary prints a one-line summary for each type, followed by its constructors. 622 | func (pkg *Package) typeSummary() { 623 | for _, typ := range pkg.doc.Types { 624 | for _, spec := range typ.Decl.Specs { 625 | typeSpec := spec.(*ast.TypeSpec) // Must succeed. 626 | if isExported(typeSpec.Name.Name) { 627 | pkg.Printf("%s\n", pkg.oneLineNode(typeSpec)) 628 | // Now print the consts, vars, and constructors. 629 | for _, c := range typ.Consts { 630 | if decl := pkg.oneLineNode(c.Decl); decl != "" { 631 | pkg.Printf(indent+"%s\n", decl) 632 | } 633 | } 634 | for _, v := range typ.Vars { 635 | if decl := pkg.oneLineNode(v.Decl); decl != "" { 636 | pkg.Printf(indent+"%s\n", decl) 637 | } 638 | } 639 | for _, constructor := range typ.Funcs { 640 | if isExported(constructor.Name) { 641 | pkg.Printf(indent+"%s\n", pkg.oneLineNode(constructor.Decl)) 642 | } 643 | } 644 | } 645 | } 646 | } 647 | } 648 | 649 | // bugs prints the BUGS information for the package. 650 | // TODO: Provide access to TODOs and NOTEs as well (very noisy so off by default)? 651 | func (pkg *Package) bugs() { 652 | if pkg.doc.Notes["BUG"] == nil { 653 | return 654 | } 655 | pkg.Printf("\n") 656 | for _, note := range pkg.doc.Notes["BUG"] { 657 | pkg.Printf("%s: %v\n", "BUG", note.Body) 658 | } 659 | } 660 | 661 | // findValues finds the doc.Values that describe the symbol. 662 | func (pkg *Package) findValues(symbol string, docValues []*doc.Value) (values []*doc.Value) { 663 | for _, value := range docValues { 664 | for _, name := range value.Names { 665 | if match(symbol, name) { 666 | values = append(values, value) 667 | } 668 | } 669 | } 670 | return 671 | } 672 | 673 | // findFuncs finds the doc.Funcs that describes the symbol. 674 | func (pkg *Package) findFuncs(symbol string) (funcs []*doc.Func) { 675 | for _, fun := range pkg.doc.Funcs { 676 | if match(symbol, fun.Name) { 677 | funcs = append(funcs, fun) 678 | } 679 | } 680 | return 681 | } 682 | 683 | // findTypes finds the doc.Types that describes the symbol. 684 | // If symbol is empty, it finds all exported types. 685 | func (pkg *Package) findTypes(symbol string) (types []*doc.Type) { 686 | for _, typ := range pkg.doc.Types { 687 | if symbol == "" && isExported(typ.Name) || match(symbol, typ.Name) { 688 | types = append(types, typ) 689 | } 690 | } 691 | return 692 | } 693 | 694 | // findTypeSpec returns the ast.TypeSpec within the declaration that defines the symbol. 695 | // The name must match exactly. 696 | func (pkg *Package) findTypeSpec(decl *ast.GenDecl, symbol string) *ast.TypeSpec { 697 | for _, spec := range decl.Specs { 698 | typeSpec := spec.(*ast.TypeSpec) // Must succeed. 699 | if symbol == typeSpec.Name.Name { 700 | return typeSpec 701 | } 702 | } 703 | return nil 704 | } 705 | 706 | // symbolDoc prints the docs for symbol. There may be multiple matches. 707 | // If symbol matches a type, output includes its methods factories and associated constants. 708 | // If there is no top-level symbol, symbolDoc looks for methods that match. 709 | func (pkg *Package) symbolDoc(symbol string) bool { 710 | defer pkg.flush() 711 | found := false 712 | // Functions. 713 | for _, fun := range pkg.findFuncs(symbol) { 714 | // Symbol is a function. 715 | decl := fun.Decl 716 | pkg.emit(fun.Doc, decl) 717 | found = true 718 | } 719 | // Constants and variables behave the same. 720 | values := pkg.findValues(symbol, pkg.doc.Consts) 721 | values = append(values, pkg.findValues(symbol, pkg.doc.Vars)...) 722 | // A declaration like 723 | // const ( c = 1; C = 2 ) 724 | // could be printed twice if the -u flag is set, as it matches twice. 725 | // So we remember which declarations we've printed to avoid duplication. 726 | printed := make(map[*ast.GenDecl]bool) 727 | for _, value := range values { 728 | pkg.valueDoc(value, printed) 729 | found = true 730 | } 731 | // Types. 732 | for _, typ := range pkg.findTypes(symbol) { 733 | pkg.typeDoc(typ) 734 | found = true 735 | } 736 | if !found { 737 | // See if there are methods. 738 | if !pkg.printMethodDoc("", symbol) { 739 | return false 740 | } 741 | } 742 | return true 743 | } 744 | 745 | // valueDoc prints the docs for a constant or variable. 746 | func (pkg *Package) valueDoc(value *doc.Value, printed map[*ast.GenDecl]bool) { 747 | if printed[value.Decl] { 748 | return 749 | } 750 | // Print each spec only if there is at least one exported symbol in it. 751 | // (See issue 11008.) 752 | // TODO: Should we elide unexported symbols from a single spec? 753 | // It's an unlikely scenario, probably not worth the trouble. 754 | // TODO: Would be nice if go/doc did this for us. 755 | specs := make([]ast.Spec, 0, len(value.Decl.Specs)) 756 | var typ ast.Expr 757 | for _, spec := range value.Decl.Specs { 758 | vspec := spec.(*ast.ValueSpec) 759 | 760 | // The type name may carry over from a previous specification in the 761 | // case of constants and iota. 762 | if vspec.Type != nil { 763 | typ = vspec.Type 764 | } 765 | 766 | for _, ident := range vspec.Names { 767 | if showSrc || isExported(ident.Name) { 768 | if vspec.Type == nil && vspec.Values == nil && typ != nil { 769 | // This a standalone identifier, as in the case of iota usage. 770 | // Thus, assume the type comes from the previous type. 771 | vspec.Type = &ast.Ident{ 772 | Name: pkg.oneLineNode(typ), 773 | NamePos: vspec.End() - 1, 774 | } 775 | } 776 | 777 | specs = append(specs, vspec) 778 | typ = nil // Only inject type on first exported identifier 779 | break 780 | } 781 | } 782 | } 783 | if len(specs) == 0 { 784 | return 785 | } 786 | value.Decl.Specs = specs 787 | pkg.emit(value.Doc, value.Decl) 788 | printed[value.Decl] = true 789 | } 790 | 791 | // typeDoc prints the docs for a type, including constructors and other items 792 | // related to it. 793 | func (pkg *Package) typeDoc(typ *doc.Type) { 794 | decl := typ.Decl 795 | spec := pkg.findTypeSpec(decl, typ.Name) 796 | trimUnexportedElems(spec) 797 | // If there are multiple types defined, reduce to just this one. 798 | if len(decl.Specs) > 1 { 799 | decl.Specs = []ast.Spec{spec} 800 | } 801 | pkg.emit(typ.Doc, decl) 802 | pkg.newlines(2) 803 | // Show associated methods, constants, etc. 804 | if showAll { 805 | printed := make(map[*ast.GenDecl]bool) 806 | // We can use append here to print consts, then vars. Ditto for funcs and methods. 807 | values := typ.Consts 808 | values = append(values, typ.Vars...) 809 | for _, value := range values { 810 | for _, name := range value.Names { 811 | if isExported(name) { 812 | pkg.valueDoc(value, printed) 813 | break 814 | } 815 | } 816 | } 817 | funcs := typ.Funcs 818 | funcs = append(funcs, typ.Methods...) 819 | for _, fun := range funcs { 820 | if isExported(fun.Name) { 821 | pkg.emit(fun.Doc, fun.Decl) 822 | if fun.Doc == "" { 823 | pkg.newlines(2) 824 | } 825 | } 826 | } 827 | } else { 828 | pkg.valueSummary(typ.Consts, true) 829 | pkg.valueSummary(typ.Vars, true) 830 | pkg.funcSummary(typ.Funcs, true) 831 | pkg.funcSummary(typ.Methods, true) 832 | } 833 | } 834 | 835 | // trimUnexportedElems modifies spec in place to elide unexported fields from 836 | // structs and methods from interfaces (unless the unexported flag is set or we 837 | // are asked to show the original source). 838 | func trimUnexportedElems(spec *ast.TypeSpec) { 839 | if unexported || showSrc { 840 | return 841 | } 842 | switch typ := spec.Type.(type) { 843 | case *ast.StructType: 844 | typ.Fields = trimUnexportedFields(typ.Fields, false) 845 | case *ast.InterfaceType: 846 | typ.Methods = trimUnexportedFields(typ.Methods, true) 847 | } 848 | } 849 | 850 | // trimUnexportedFields returns the field list trimmed of unexported fields. 851 | func trimUnexportedFields(fields *ast.FieldList, isInterface bool) *ast.FieldList { 852 | what := "methods" 853 | if !isInterface { 854 | what = "fields" 855 | } 856 | 857 | trimmed := false 858 | list := make([]*ast.Field, 0, len(fields.List)) 859 | for _, field := range fields.List { 860 | names := field.Names 861 | if len(names) == 0 { 862 | // Embedded type. Use the name of the type. It must be of the form ident or 863 | // pkg.ident (for structs and interfaces), or *ident or *pkg.ident (structs only). 864 | // Nothing else is allowed. 865 | ty := field.Type 866 | if se, ok := field.Type.(*ast.StarExpr); !isInterface && ok { 867 | // The form *ident or *pkg.ident is only valid on 868 | // embedded types in structs. 869 | ty = se.X 870 | } 871 | switch ident := ty.(type) { 872 | case *ast.Ident: 873 | if isInterface && ident.Name == "error" && ident.Obj == nil { 874 | // For documentation purposes, we consider the builtin error 875 | // type special when embedded in an interface, such that it 876 | // always gets shown publicly. 877 | list = append(list, field) 878 | continue 879 | } 880 | names = []*ast.Ident{ident} 881 | case *ast.SelectorExpr: 882 | // An embedded type may refer to a type in another package. 883 | names = []*ast.Ident{ident.Sel} 884 | } 885 | if names == nil { 886 | // Can only happen if AST is incorrect. Safe to continue with a nil list. 887 | log.Print("invalid program: unexpected type for embedded field") 888 | } 889 | } 890 | // Trims if any is unexported. Good enough in practice. 891 | ok := true 892 | for _, name := range names { 893 | if !isExported(name.Name) { 894 | trimmed = true 895 | ok = false 896 | break 897 | } 898 | } 899 | if ok { 900 | list = append(list, field) 901 | } 902 | } 903 | if !trimmed { 904 | return fields 905 | } 906 | unexportedField := &ast.Field{ 907 | Type: &ast.Ident{ 908 | // Hack: printer will treat this as a field with a named type. 909 | // Setting Name and NamePos to ("", fields.Closing-1) ensures that 910 | // when Pos and End are called on this field, they return the 911 | // position right before closing '}' character. 912 | Name: "", 913 | NamePos: fields.Closing - 1, 914 | }, 915 | Comment: &ast.CommentGroup{ 916 | List: []*ast.Comment{{Text: fmt.Sprintf("// Has unexported %s.\n", what)}}, 917 | }, 918 | } 919 | return &ast.FieldList{ 920 | Opening: fields.Opening, 921 | List: append(list, unexportedField), 922 | Closing: fields.Closing, 923 | } 924 | } 925 | 926 | // printMethodDoc prints the docs for matches of symbol.method. 927 | // If symbol is empty, it prints all methods for any concrete type 928 | // that match the name. It reports whether it found any methods. 929 | func (pkg *Package) printMethodDoc(symbol, method string) bool { 930 | defer pkg.flush() 931 | types := pkg.findTypes(symbol) 932 | if types == nil { 933 | if symbol == "" { 934 | return false 935 | } 936 | pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 937 | } 938 | found := false 939 | for _, typ := range types { 940 | if len(typ.Methods) > 0 { 941 | for _, meth := range typ.Methods { 942 | if match(method, meth.Name) { 943 | decl := meth.Decl 944 | pkg.emit(meth.Doc, decl) 945 | found = true 946 | } 947 | } 948 | continue 949 | } 950 | if symbol == "" { 951 | continue 952 | } 953 | // Type may be an interface. The go/doc package does not attach 954 | // an interface's methods to the doc.Type. We need to dig around. 955 | spec := pkg.findTypeSpec(typ.Decl, typ.Name) 956 | inter, ok := spec.Type.(*ast.InterfaceType) 957 | if !ok { 958 | // Not an interface type. 959 | continue 960 | } 961 | for _, iMethod := range inter.Methods.List { 962 | // This is an interface, so there can be only one name. 963 | // TODO: Anonymous methods (embedding) 964 | if len(iMethod.Names) == 0 { 965 | continue 966 | } 967 | name := iMethod.Names[0].Name 968 | if match(method, name) { 969 | if iMethod.Doc != nil { 970 | for _, comment := range iMethod.Doc.List { 971 | doc.ToText(&pkg.buf, comment.Text, "", indent, indentedWidth) 972 | } 973 | } 974 | s := pkg.oneLineNode(iMethod.Type) 975 | // Hack: s starts "func" but there is no name present. 976 | // We could instead build a FuncDecl but it's not worthwhile. 977 | lineComment := "" 978 | if iMethod.Comment != nil { 979 | lineComment = fmt.Sprintf(" %s", iMethod.Comment.List[0].Text) 980 | } 981 | pkg.Printf("func %s%s%s\n", name, s[4:], lineComment) 982 | found = true 983 | } 984 | } 985 | } 986 | return found 987 | } 988 | 989 | // printFieldDoc prints the docs for matches of symbol.fieldName. 990 | // It reports whether it found any field. 991 | // Both symbol and fieldName must be non-empty or it returns false. 992 | func (pkg *Package) printFieldDoc(symbol, fieldName string) bool { 993 | if symbol == "" || fieldName == "" { 994 | return false 995 | } 996 | defer pkg.flush() 997 | types := pkg.findTypes(symbol) 998 | if types == nil { 999 | pkg.Fatalf("symbol %s is not a type in package %s installed in %q", symbol, pkg.name, pkg.build.ImportPath) 1000 | } 1001 | found := false 1002 | numUnmatched := 0 1003 | for _, typ := range types { 1004 | // Type must be a struct. 1005 | spec := pkg.findTypeSpec(typ.Decl, typ.Name) 1006 | structType, ok := spec.Type.(*ast.StructType) 1007 | if !ok { 1008 | // Not a struct type. 1009 | continue 1010 | } 1011 | for _, field := range structType.Fields.List { 1012 | // TODO: Anonymous fields. 1013 | for _, name := range field.Names { 1014 | if !match(fieldName, name.Name) { 1015 | numUnmatched++ 1016 | continue 1017 | } 1018 | if !found { 1019 | pkg.Printf("type %s struct {\n", typ.Name) 1020 | } 1021 | if field.Doc != nil { 1022 | // To present indented blocks in comments correctly, process the comment as 1023 | // a unit before adding the leading // to each line. 1024 | docBuf := bytes.Buffer{} 1025 | doc.ToText(&docBuf, field.Doc.Text(), "", indent, indentedWidth) 1026 | scanner := bufio.NewScanner(&docBuf) 1027 | for scanner.Scan() { 1028 | /*fmt.Fprintf*/ pkg.Printf("%s// %s\n", indent, scanner.Bytes()) 1029 | } 1030 | } 1031 | s := pkg.oneLineNode(field.Type) 1032 | lineComment := "" 1033 | if field.Comment != nil { 1034 | lineComment = fmt.Sprintf(" %s", field.Comment.List[0].Text) 1035 | } 1036 | pkg.Printf("%s%s %s%s\n", indent, name, s, lineComment) 1037 | found = true 1038 | } 1039 | } 1040 | } 1041 | if found { 1042 | if numUnmatched > 0 { 1043 | pkg.Printf("\n // ... other fields elided ...\n") 1044 | } 1045 | pkg.Printf("}\n") 1046 | } 1047 | return found 1048 | } 1049 | 1050 | // methodDoc prints the docs for matches of symbol.method. 1051 | func (pkg *Package) methodDoc(symbol, method string) bool { 1052 | defer pkg.flush() 1053 | return pkg.printMethodDoc(symbol, method) 1054 | } 1055 | 1056 | // fieldDoc prints the docs for matches of symbol.field. 1057 | func (pkg *Package) fieldDoc(symbol, field string) bool { 1058 | defer pkg.flush() 1059 | return pkg.printFieldDoc(symbol, field) 1060 | } 1061 | 1062 | // match reports whether the user's symbol matches the program's. 1063 | // A lower-case character in the user's string matches either case in the program's. 1064 | // The program string must be exported. 1065 | func match(user, program string) bool { 1066 | if !isExported(program) { 1067 | return false 1068 | } 1069 | if matchCase { 1070 | return user == program 1071 | } 1072 | for _, u := range user { 1073 | p, w := utf8.DecodeRuneInString(program) 1074 | program = program[w:] 1075 | if u == p { 1076 | continue 1077 | } 1078 | if unicode.IsLower(u) && simpleFold(u) == simpleFold(p) { 1079 | continue 1080 | } 1081 | return false 1082 | } 1083 | return program == "" 1084 | } 1085 | 1086 | // simpleFold returns the minimum rune equivalent to r 1087 | // under Unicode-defined simple case folding. 1088 | func simpleFold(r rune) rune { 1089 | for { 1090 | r1 := unicode.SimpleFold(r) 1091 | if r1 <= r { 1092 | return r1 // wrapped around, found min 1093 | } 1094 | r = r1 1095 | } 1096 | } 1097 | -------------------------------------------------------------------------------- /preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravener/gopherdoc/aa0636034a6df040c1466242a4e6fd876fbc651b/preview.jpg --------------------------------------------------------------------------------