-
Notifications
You must be signed in to change notification settings - Fork 172
/
Copy pathimports.go
130 lines (106 loc) · 2.82 KB
/
imports.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
130
package jsonnet
import (
"io/ioutil"
"path/filepath"
"sort"
jsonnet "github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
"github.com/google/go-jsonnet/toolutils"
"github.com/pkg/errors"
"github.com/grafana/tanka/pkg/jsonnet/jpath"
"github.com/grafana/tanka/pkg/jsonnet/native"
)
// TransitiveImports returns all recursive imports of an environment
func TransitiveImports(dir string) ([]string, error) {
dir, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
mainFile := filepath.Join(dir, "main.jsonnet")
sonnet, err := ioutil.ReadFile(mainFile)
if err != nil {
return nil, errors.Wrap(err, "opening file")
}
jpath, _, rootDir, err := jpath.Resolve(dir)
if err != nil {
return nil, errors.Wrap(err, "resolving JPATH")
}
vm := jsonnet.MakeVM()
vm.Importer(NewExtendedImporter(jpath))
for _, nf := range native.Funcs() {
vm.NativeFunction(nf)
}
node, err := jsonnet.SnippetToAST("main.jsonnet", string(sonnet))
if err != nil {
return nil, errors.Wrap(err, "creating Jsonnet AST")
}
imports := make(map[string]bool)
if err = importRecursive(imports, vm, node, "main.jsonnet"); err != nil {
return nil, err
}
paths := make([]string, 0, len(imports)+1)
for k := range imports {
paths = append(paths, k)
}
paths = append(paths, mainFile)
for i := range paths {
paths[i], _ = filepath.Rel(rootDir, paths[i])
}
sort.Strings(paths)
return paths, nil
}
// importRecursive takes a Jsonnet VM and recursively imports the AST. Every
// found import is added to the `list` string slice, which will ultimately
// contain all recursive imports
func importRecursive(list map[string]bool, vm *jsonnet.VM, node ast.Node, currentPath string) error {
switch node := node.(type) {
// we have an `import`
case *ast.Import:
p := node.File.Value
contents, foundAt, err := vm.ImportAST(currentPath, p)
if err != nil {
return errors.Wrap(err, "importing jsonnet")
}
abs, _ := filepath.Abs(foundAt)
if list[abs] {
return nil
}
list[abs] = true
if err := importRecursive(list, vm, contents, foundAt); err != nil {
return err
}
// we have an `importstr`
case *ast.ImportStr:
p := node.File.Value
foundAt, err := vm.ResolveImport(currentPath, p)
if err != nil {
return errors.Wrap(err, "importing string")
}
abs, _ := filepath.Abs(foundAt)
if list[abs] {
return nil
}
list[abs] = true
// neither `import` nor `importstr`, probably object or similar: try children
default:
for _, child := range toolutils.Children(node) {
if err := importRecursive(list, vm, child, currentPath); err != nil {
return err
}
}
}
return nil
}
func uniqueStringSlice(s []string) []string {
seen := make(map[string]struct{}, len(s))
j := 0
for _, v := range s {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
s[j] = v
j++
}
return s[:j]
}