forked from KyleBanks/depth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
depth.go
129 lines (110 loc) · 3.29 KB
/
depth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Package depth provides the ability to traverse and retrieve Go source code dependencies in the form of
// internal and external packages.
//
// For example, the dependencies of the stdlib `strings` package can be resolved like so:
//
// import "github.com/KyleBanks/depth"
//
// var t depth.Tree
// err := t.Resolve("strings")
// if err != nil {
// log.Fatal(err)
// }
//
// // Output: "strings has 4 dependencies."
// log.Printf("%v has %v dependencies.", t.Root.Name, len(t.Root.Deps))
//
// For additional customization, simply set the appropriate flags on the `Tree` before resolving:
//
// import "github.com/KyleBanks/depth"
//
// t := depth.Tree {
// ResolveInternal: true,
// ResolveTest: true,
// MaxDepth: 10,
// }
// err := t.Resolve("strings")
package depth
import (
"errors"
"go/build"
"os"
)
// ErrRootPkgNotResolved is returned when the root Pkg of the Tree cannot be resolved,
// typically because it does not exist.
var ErrRootPkgNotResolved = errors.New("unable to resolve root package")
// Importer defines a type that can import a package and return its details.
type Importer interface {
Import(name, srcDir string, im build.ImportMode) (*build.Package, error)
}
// Tree represents the top level of a Pkg and the configuration used to
// initialize and represent its contents.
type Tree struct {
Root *Pkg
ResolveInternal bool
ResolveTest bool
MaxDepth int
Importer Importer
importCache map[string]struct{}
}
// Resolve recursively finds all dependencies for the root Pkg name provided,
// and the packages it depends on.
func (t *Tree) Resolve(name string) error {
pwd, err := os.Getwd()
if err != nil {
return err
}
t.Root = &Pkg{
Name: name,
Tree: t,
SrcDir: pwd,
Test: false,
}
// Reset the import cache each time to ensure a reused Tree doesn't
// reuse the same cache.
t.importCache = nil
// Allow custom importers, but use build.Default if none is provided.
if t.Importer == nil {
t.Importer = &build.Default
}
t.Root.Resolve(t.Importer)
if !t.Root.Resolved {
return ErrRootPkgNotResolved
}
return nil
}
// shouldResolveInternal determines if internal packages should be further resolved beyond the
// current parent.
//
// For example, if the parent Pkg is `github.com/foo/bar` and true is returned, all the
// internal dependencies it relies on will be resolved. If for example `strings` is one of those
// dependencies, and it is passed as the parent here, false may be returned and its internal
// dependencies will not be resolved.
func (t *Tree) shouldResolveInternal(parent *Pkg) bool {
if t.ResolveInternal {
return true
}
return parent == t.Root
}
// isAtMaxDepth returns true when the depth of the Pkg provided is at or beyond the maximum
// depth allowed by the tree.
//
// If the Tree has a MaxDepth of zero, true is never returned.
func (t *Tree) isAtMaxDepth(p *Pkg) bool {
if t.MaxDepth == 0 {
return false
}
return p.depth() >= t.MaxDepth
}
// hasSeenImport returns true if the import name provided has already been seen within the tree.
// This function only returns false for a name once.
func (t *Tree) hasSeenImport(name string) bool {
if t.importCache == nil {
t.importCache = make(map[string]struct{})
}
if _, ok := t.importCache[name]; ok {
return true
}
t.importCache[name] = struct{}{}
return false
}