5
5
package verror
6
6
7
7
import (
8
- "fmt"
9
- "io/ioutil"
10
- "os"
11
8
"path"
12
9
"path/filepath"
10
+ "reflect"
13
11
"runtime"
14
12
"strings"
15
13
"sync"
16
-
17
- "golang.org/x/mod/modfile"
18
14
)
19
15
20
16
type pathCache struct {
21
17
sync.Mutex
22
18
paths map [string ]string
23
19
}
24
20
25
- func enclosingGoMod (dir string ) (string , error ) {
26
- for {
27
- gomodfile := filepath .Join (dir , "go.mod" )
28
- if fi , err := os .Stat (gomodfile ); err == nil && ! fi .IsDir () {
29
- return dir , nil
30
- }
31
- d := filepath .Dir (dir )
32
- if d == dir {
33
- return "" , fmt .Errorf ("failed to find enclosing go.mod for dir %v" , dir )
34
- }
35
- dir = d
36
- }
37
- }
38
-
39
21
var pkgPathCache = pathCache {
40
22
paths : make (map [string ]string ),
41
23
}
@@ -53,40 +35,94 @@ func (pc *pathCache) set(dir, pkg string) {
53
35
pc .paths [dir ] = pkg
54
36
}
55
37
56
- func (pc * pathCache ) pkgPath (file string ) (string , error ) {
57
- dir := filepath .Clean (filepath .Dir (file ))
58
- if p , ok := pc .has (dir ); ok {
59
- return p , nil
60
- }
61
- root , err := enclosingGoMod (dir )
62
- if err != nil {
63
- return "" , err
64
- }
65
- gomodfile := filepath .Join (root , "go.mod" )
66
- gomod , err := ioutil .ReadFile (gomodfile )
67
- if err != nil {
68
- return "" , err
69
- }
70
- module := modfile .ModulePath (gomod )
71
- if len (module ) == 0 {
72
- return "" , fmt .Errorf ("failed to read module path from %v" , gomodfile )
38
+ // IDPath returns a string of the form <package-path>.<name>
39
+ // where <package-path> is derived from the type of the supplied
40
+ // value. Typical usage would be except that dummy can be replaced
41
+ // by an existing type defined in the package.
42
+ //
43
+ // type dummy int
44
+ // verror.ID(verror.IDPath(dummy(0), "MyError"))
45
+ //
46
+ func IDPath (val interface {}, id string ) ID {
47
+ return ID (reflect .TypeOf (val ).PkgPath () + "." + id )
48
+ }
49
+
50
+ // longestCommonSuffix for a package path and filename.
51
+ func longestCommonSuffix (pkgPath , filename string ) (string , string ) {
52
+ longestPkg , longestFilePath := "" , ""
53
+ for {
54
+ fl := filepath .Base (filename )
55
+ pl := path .Base (pkgPath )
56
+ if fl == pl {
57
+ longestPkg = path .Join (fl , longestPkg )
58
+ longestFilePath = filepath .Join (fl , longestFilePath )
59
+ filename = filepath .Dir (filename )
60
+ pkgPath = path .Dir (pkgPath )
61
+ if fl == "/" {
62
+ break
63
+ }
64
+ continue
65
+ }
66
+ break
73
67
}
68
+ return longestPkg , longestFilePath
69
+ }
70
+
71
+ type pathState struct {
72
+ pkg string // pkg path for the value passed to init
73
+ dir string // the directory component for the file passed to init
74
+ // The portion of the local file path that is outside of the go module,
75
+ // e.g. for /a/b/c/core/v23/verror it would be /a/b/c/core.
76
+ filePrefix string
77
+ // the portion of the package path that does not appear in the file name,
78
+ // e.g. for /a/b/c/core/v23/verror and v.io/v23/verror it would be v.io.
79
+ pkgPrefix string
80
+ }
81
+
82
+ func (ps * pathState ) init (pkgPath string , file string ) {
83
+ ps .pkg = pkgPath
84
+ ps .dir = filepath .Dir (file )
85
+ pkgLCS , fileLCS := longestCommonSuffix (ps .pkg , ps .dir )
86
+ ps .filePrefix = filepath .Clean (strings .TrimSuffix (ps .dir , fileLCS ))
87
+ ps .pkgPrefix = path .Clean (strings .TrimSuffix (ps .pkg , pkgLCS ))
88
+ }
89
+
90
+ var (
91
+ ps = & pathState {}
92
+ initOnce sync.Once
93
+ )
74
94
75
- pkgPath := strings .TrimPrefix (dir , root )
76
- if ! strings .HasPrefix (pkgPath , module ) {
77
- pkgPath = path .Join (module , pkgPath )
95
+ func convertFileToPkgName (filename string ) string {
96
+ return path .Clean (strings .ReplaceAll (filename , string (filepath .Separator ), "/" ))
97
+ }
98
+
99
+ func (pc * pathCache ) pkgPath (file string ) string {
100
+ initOnce .Do (func () {
101
+ type dummy int
102
+ _ , file , _ , _ := runtime .Caller (0 )
103
+ ps .init (reflect .TypeOf (dummy (0 )).PkgPath (), file )
104
+ })
105
+ pdir := filepath .Dir (file )
106
+ rel := strings .TrimPrefix (pdir , ps .filePrefix )
107
+ if rel == pdir {
108
+ return ""
78
109
}
79
- pc .set (dir , pkgPath )
80
- return pkgPath , nil
110
+ relPkg := convertFileToPkgName (rel )
111
+ pkgPath := path .Join (ps .pkgPrefix , relPkg )
112
+ pc .set (filepath .Dir (file ), pkgPath )
113
+ return pkgPath
81
114
}
82
115
83
116
func ensurePackagePath (id ID ) ID {
117
+ sid := string (id )
118
+ if strings .Contains (sid , "." ) && sid [0 ] != '.' {
119
+ return id
120
+ }
84
121
_ , file , _ , _ := runtime .Caller (2 )
85
- pkg , err := pkgPathCache .pkgPath (file )
86
- if err != nil {
87
- panic ( fmt . Sprintf ( "failed to determine package name for %v: %v" , file , err ))
122
+ pkg := pkgPathCache .pkgPath (file )
123
+ if len ( pkg ) == 0 {
124
+ return id
88
125
}
89
- sid := string (id )
90
126
if strings .HasPrefix (sid , pkg ) {
91
127
return id
92
128
}
0 commit comments