github.com/goplus/gop@v1.2.6/outline.go (about) 1 /* 2 * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package gop 18 19 import ( 20 "fmt" 21 "go/token" 22 "io/fs" 23 "os" 24 "path" 25 "path/filepath" 26 "strings" 27 28 "github.com/goplus/gop/cl/outline" 29 "github.com/goplus/gop/parser" 30 "github.com/goplus/gop/x/c2go" 31 "github.com/goplus/gop/x/gopenv" 32 "github.com/goplus/mod/gopmod" 33 "github.com/qiniu/x/errors" 34 ) 35 36 // ----------------------------------------------------------------------------- 37 38 func Outline(dir string, conf *Config) (out outline.Package, err error) { 39 if dir, err = filepath.Abs(dir); err != nil { 40 return 41 } 42 43 if conf == nil { 44 conf = new(Config) 45 } 46 47 mod := conf.Mod 48 if mod == nil { 49 if mod, err = LoadMod(dir); err != nil { 50 err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir) 51 return 52 } 53 } 54 55 filterConf := conf.Filter 56 filter := func(fi fs.FileInfo) bool { 57 if filterConf != nil && !filterConf(fi) { 58 return false 59 } 60 fname := fi.Name() 61 if pos := strings.Index(fname, "."); pos > 0 { 62 fname = fname[:pos] 63 } 64 return !strings.HasSuffix(fname, "_test") 65 } 66 fset := conf.Fset 67 if fset == nil { 68 fset = token.NewFileSet() 69 } 70 pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{ 71 ClassKind: mod.ClassKind, 72 Filter: filter, 73 Mode: parser.ParseComments, 74 }) 75 if err != nil { 76 return 77 } 78 if len(pkgs) == 0 { 79 err = ErrNotFound 80 return 81 } 82 83 imp := conf.Importer 84 if imp == nil { 85 gop := conf.Gop 86 if gop == nil { 87 gop = gopenv.Get() 88 } 89 imp = NewImporter(mod, gop, fset) 90 } 91 92 for name, pkg := range pkgs { 93 if out.Valid() { 94 err = fmt.Errorf("%w: %s, %s", ErrMultiPackges, name, out.Pkg().Name()) 95 return 96 } 97 if len(pkg.Files)+len(pkg.GoFiles) == 0 { // no Go/Go+ source files 98 break 99 } 100 relPart, _ := filepath.Rel(mod.Root(), dir) 101 pkgPath := path.Join(mod.Path(), filepath.ToSlash(relPart)) 102 out, err = outline.NewPackage(pkgPath, pkg, &outline.Config{ 103 Fset: fset, 104 Importer: imp, 105 LookupClass: mod.LookupClass, 106 LookupPub: c2go.LookupPub(mod), 107 }) 108 if err != nil { 109 return 110 } 111 } 112 if !out.Valid() { 113 err = ErrNotFound 114 } 115 return 116 } 117 118 // ----------------------------------------------------------------------------- 119 120 func OutlinePkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (out outline.Package, err error) { 121 mod := conf.Mod 122 if mod == nil { 123 if mod, err = LoadMod(workDir); err != nil { 124 err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", workDir) 125 return 126 } 127 } 128 129 if NotFound(err) && allowExtern { 130 remotePkgPathDo(pkgPath, func(pkgDir, modDir string) { 131 modFile := chmodModfile(modDir) 132 defer os.Chmod(modFile, modReadonly) 133 out, err = Outline(pkgDir, conf) 134 }, func(e error) { 135 err = e 136 }) 137 return 138 } else if err != nil { 139 return 140 } 141 142 pkg, err := mod.Lookup(pkgPath) 143 if err != nil { 144 return 145 } 146 if pkg.Type == gopmod.PkgtExtern { 147 modFile := chmodModfile(pkg.ModDir) 148 defer os.Chmod(modFile, modReadonly) 149 } 150 return Outline(pkg.Dir, conf) 151 } 152 153 func chmodModfile(modDir string) string { 154 modFile := modDir + "/go.mod" 155 os.Chmod(modFile, modWritable) 156 return modFile 157 } 158 159 // -----------------------------------------------------------------------------