55package verror
66
77import (
8- "fmt"
9- "io/ioutil"
10- "os"
118 "path"
129 "path/filepath"
10+ "reflect"
1311 "runtime"
1412 "strings"
1513 "sync"
16-
17- "golang.org/x/mod/modfile"
1814)
1915
2016type pathCache struct {
2117 sync.Mutex
2218 paths map [string ]string
2319}
2420
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-
3921var pkgPathCache = pathCache {
4022 paths : make (map [string ]string ),
4123}
@@ -53,40 +35,94 @@ func (pc *pathCache) set(dir, pkg string) {
5335 pc .paths [dir ] = pkg
5436}
5537
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
7367 }
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+ )
7494
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 ""
78109 }
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
81114}
82115
83116func ensurePackagePath (id ID ) ID {
117+ sid := string (id )
118+ if strings .Contains (sid , "." ) && sid [0 ] != '.' {
119+ return id
120+ }
84121 _ , 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
88125 }
89- sid := string (id )
90126 if strings .HasPrefix (sid , pkg ) {
91127 return id
92128 }
0 commit comments