github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/cmdline.go (about) 1 // Copyright 2013 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 godoc 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/build" 11 "io" 12 "log" 13 "os" 14 pathpkg "path" 15 "path/filepath" 16 "regexp" 17 "strings" 18 19 "golang.org/x/tools/godoc/vfs" 20 ) 21 22 const ( 23 target = "/target" 24 cmdPrefix = "cmd/" 25 srcPrefix = "src/" 26 toolsPath = "golang.org/x/tools/cmd/" 27 ) 28 29 // CommandLine returns godoc results to w. 30 // Note that it may add a /target path to fs. 31 func CommandLine(w io.Writer, fs vfs.NameSpace, pres *Presentation, args []string) error { 32 path := args[0] 33 srcMode := pres.SrcMode 34 cmdMode := strings.HasPrefix(path, cmdPrefix) 35 if strings.HasPrefix(path, srcPrefix) { 36 path = strings.TrimPrefix(path, srcPrefix) 37 srcMode = true 38 } 39 var abspath, relpath string 40 if cmdMode { 41 path = strings.TrimPrefix(path, cmdPrefix) 42 } else { 43 abspath, relpath = paths(fs, pres, path) 44 } 45 46 var mode PageInfoMode 47 if relpath == builtinPkgPath { 48 // the fake built-in package contains unexported identifiers 49 mode = NoFiltering | NoTypeAssoc 50 } 51 if srcMode { 52 // only filter exports if we don't have explicit command-line filter arguments 53 if len(args) > 1 { 54 mode |= NoFiltering 55 } 56 mode |= ShowSource 57 } 58 59 // First, try as package unless forced as command. 60 var info *PageInfo 61 if !cmdMode { 62 info = pres.GetPkgPageInfo(abspath, relpath, mode) 63 } 64 65 // Second, try as command (if the path is not absolute). 66 var cinfo *PageInfo 67 if !filepath.IsAbs(path) { 68 // First try go.tools/cmd. 69 abspath = pathpkg.Join(pres.PkgFSRoot(), toolsPath+path) 70 cinfo = pres.GetCmdPageInfo(abspath, relpath, mode) 71 if cinfo.IsEmpty() { 72 // Then try $GOROOT/cmd. 73 abspath = pathpkg.Join(pres.CmdFSRoot(), path) 74 cinfo = pres.GetCmdPageInfo(abspath, relpath, mode) 75 } 76 } 77 78 // determine what to use 79 if info == nil || info.IsEmpty() { 80 if cinfo != nil && !cinfo.IsEmpty() { 81 // only cinfo exists - switch to cinfo 82 info = cinfo 83 } 84 } else if cinfo != nil && !cinfo.IsEmpty() { 85 // both info and cinfo exist - use cinfo if info 86 // contains only subdirectory information 87 if info.PAst == nil && info.PDoc == nil { 88 info = cinfo 89 } else if relpath != target { 90 // The above check handles the case where an operating system path 91 // is provided (see documentation for paths below). In that case, 92 // relpath is set to "/target" (in anticipation of accessing packages there), 93 // and is therefore not expected to match a command. 94 fmt.Fprintf(w, "use 'godoc %s%s' for documentation on the %s command \n\n", cmdPrefix, relpath, relpath) 95 } 96 } 97 98 if info == nil { 99 return fmt.Errorf("%s: no such directory or package", args[0]) 100 } 101 if info.Err != nil { 102 return info.Err 103 } 104 105 if info.PDoc != nil && info.PDoc.ImportPath == target { 106 // Replace virtual /target with actual argument from command line. 107 info.PDoc.ImportPath = args[0] 108 } 109 110 // If we have more than one argument, use the remaining arguments for filtering. 111 if len(args) > 1 { 112 info.IsFiltered = true 113 filterInfo(args[1:], info) 114 } 115 116 packageText := pres.PackageText 117 if pres.HTMLMode { 118 packageText = pres.PackageHTML 119 } 120 if err := packageText.Execute(w, info); err != nil { 121 return err 122 } 123 return nil 124 } 125 126 // paths determines the paths to use. 127 // 128 // If we are passed an operating system path like . or ./foo or /foo/bar or c:\mysrc, 129 // we need to map that path somewhere in the fs name space so that routines 130 // like getPageInfo will see it. We use the arbitrarily-chosen virtual path "/target" 131 // for this. That is, if we get passed a directory like the above, we map that 132 // directory so that getPageInfo sees it as /target. 133 // Returns the absolute and relative paths. 134 func paths(fs vfs.NameSpace, pres *Presentation, path string) (string, string) { 135 if filepath.IsAbs(path) { 136 fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace) 137 return target, target 138 } 139 if build.IsLocalImport(path) { 140 cwd, _ := os.Getwd() // ignore errors 141 path = filepath.Join(cwd, path) 142 fs.Bind(target, vfs.OS(path), "/", vfs.BindReplace) 143 return target, target 144 } 145 if bp, _ := build.Import(path, "", build.FindOnly); bp.Dir != "" && bp.ImportPath != "" { 146 fs.Bind(target, vfs.OS(bp.Dir), "/", vfs.BindReplace) 147 return target, bp.ImportPath 148 } 149 return pathpkg.Join(pres.PkgFSRoot(), path), path 150 } 151 152 // filterInfo updates info to include only the nodes that match the given 153 // filter args. 154 func filterInfo(args []string, info *PageInfo) { 155 rx, err := makeRx(args) 156 if err != nil { 157 log.Fatalf("illegal regular expression from %v: %v", args, err) 158 } 159 160 filter := func(s string) bool { return rx.MatchString(s) } 161 switch { 162 case info.PAst != nil: 163 newPAst := map[string]*ast.File{} 164 for name, a := range info.PAst { 165 cmap := ast.NewCommentMap(info.FSet, a, a.Comments) 166 a.Comments = []*ast.CommentGroup{} // remove all comments. 167 ast.FilterFile(a, filter) 168 if len(a.Decls) > 0 { 169 newPAst[name] = a 170 } 171 for _, d := range a.Decls { 172 // add back the comments associated with d only 173 comments := cmap.Filter(d).Comments() 174 a.Comments = append(a.Comments, comments...) 175 } 176 } 177 info.PAst = newPAst // add only matching files. 178 case info.PDoc != nil: 179 info.PDoc.Filter(filter) 180 } 181 } 182 183 // Does s look like a regular expression? 184 func isRegexp(s string) bool { 185 return strings.IndexAny(s, ".(|)*+?^$[]") >= 0 186 } 187 188 // Make a regular expression of the form 189 // names[0]|names[1]|...names[len(names)-1]. 190 // Returns an error if the regular expression is illegal. 191 func makeRx(names []string) (*regexp.Regexp, error) { 192 if len(names) == 0 { 193 return nil, fmt.Errorf("no expression provided") 194 } 195 s := "" 196 for i, name := range names { 197 if i > 0 { 198 s += "|" 199 } 200 if isRegexp(name) { 201 s += name 202 } else { 203 s += "^" + name + "$" // must match exactly 204 } 205 } 206 return regexp.Compile(s) 207 }