@@ -23,67 +23,87 @@ import (
23
23
"path"
24
24
)
25
25
26
- // ImportedData represents imported data and where it came from.
27
- type ImportedData struct {
28
- FoundHere string
29
- Content string
30
- }
31
-
32
26
// An Importer imports data from a path.
27
+ // TODO(sbarzowski) caching of errors (may require breaking changes)
33
28
type Importer interface {
34
- Import (codeDir string , importedPath string ) (* ImportedData , error )
29
+ // Import fetches data from a given path. It may be relative
30
+ // to the file where we do the import. What "relative path"
31
+ // means depends on the importer.
32
+ //
33
+ // It is required that:
34
+ // a) for given (importedFrom, importedPath) the same
35
+ // (contents, foundAt) are returned on subsequent calls.
36
+ // b) for given foundAt, the contents are always the same
37
+ //
38
+ // It is recommended that if there are multiple locations that
39
+ // need to be probed (e.g. relative + multiple library paths)
40
+ // then all results of all attempts will be cached separately,
41
+ // both nonexistence and contents of existing ones.
42
+ // FileImporter may serve as an example.
43
+ Import (importedFrom , importedPath string ) (contents Contents , foundAt string , err error )
35
44
}
36
45
37
- // ImportCacheValue represents a value in an imported-data cache.
38
- type ImportCacheValue struct {
39
- // nil if we got an error
40
- data * ImportedData
41
-
42
- // nil if we got an error or have only imported it via importstr
43
- asCode potentialValue
44
-
45
- // Errors can occur during import, we have to cache these too.
46
- err error
46
+ // Contents is a representation of imported data. It is a simple
47
+ // string wrapper, which makes it easier to enforce the caching policy.
48
+ type Contents struct {
49
+ data * string
47
50
}
48
51
49
- type importCacheKey struct {
50
- dir string
51
- importedPath string
52
+ func (c Contents ) String () string {
53
+ return * c .data
52
54
}
53
55
54
- type importCacheMap map [importCacheKey ]* ImportCacheValue
56
+ // MakeContents creates Contents from a string.
57
+ func MakeContents (s string ) Contents {
58
+ return Contents {
59
+ data : & s ,
60
+ }
61
+ }
55
62
56
63
// ImportCache represents a cache of imported data.
64
+ //
65
+ // While the user-defined Importer implementations
66
+ // are required to cache file content, this cache
67
+ // is an additional layer of optimization that caches values
68
+ // (i.e. the result of executing the file content).
69
+ // It also verifies that the content pointer is the same for two foundAt values.
57
70
type ImportCache struct {
58
- cache importCacheMap
59
- importer Importer
71
+ foundAtVerification map [string ]Contents
72
+ codeCache map [string ]potentialValue
73
+ importer Importer
60
74
}
61
75
62
- // MakeImportCache creates and ImportCache using an importer .
76
+ // MakeImportCache creates an ImportCache using an Importer .
63
77
func MakeImportCache (importer Importer ) * ImportCache {
64
- return & ImportCache {importer : importer , cache : make (importCacheMap )}
78
+ return & ImportCache {
79
+ importer : importer ,
80
+ foundAtVerification : make (map [string ]Contents ),
81
+ codeCache : make (map [string ]potentialValue ),
82
+ }
65
83
}
66
84
67
- func (cache * ImportCache ) importData (key importCacheKey ) * ImportCacheValue {
68
- if cached , ok := cache .cache [key ]; ok {
69
- return cached
85
+ func (cache * ImportCache ) importData (importedFrom , importedPath string ) (contents Contents , foundAt string , err error ) {
86
+ contents , foundAt , err = cache .importer .Import (importedFrom , importedPath )
87
+ if err != nil {
88
+ return Contents {}, "" , err
70
89
}
71
- data , err := cache .importer .Import (key .dir , key .importedPath )
72
- cached := & ImportCacheValue {
73
- data : data ,
74
- err : err ,
90
+ if cached , importedBefore := cache .foundAtVerification [foundAt ]; importedBefore {
91
+ if cached != contents {
92
+ panic (fmt .Sprintf ("importer problem: a different instance of Contents returned when importing %#v again" , foundAt ))
93
+ }
94
+ } else {
95
+ cache .foundAtVerification [foundAt ] = contents
75
96
}
76
- cache .cache [key ] = cached
77
- return cached
97
+ return
78
98
}
79
99
80
100
// ImportString imports a string, caches it and then returns it.
81
- func (cache * ImportCache ) ImportString (codeDir , importedPath string , e * evaluator ) (* valueString , error ) {
82
- cached := cache .importData (importCacheKey { codeDir , importedPath } )
83
- if cached . err != nil {
84
- return nil , e .Error (cached . err .Error ())
101
+ func (cache * ImportCache ) ImportString (importedFrom , importedPath string , e * evaluator ) (* valueString , error ) {
102
+ data , _ , err := cache .importData (importedFrom , importedPath )
103
+ if err != nil {
104
+ return nil , e .Error (err .Error ())
85
105
}
86
- return makeValueString (cached . data .Content ), nil
106
+ return makeValueString (data .String () ), nil
87
107
}
88
108
89
109
func codeToPV (e * evaluator , filename string , code string ) potentialValue {
@@ -100,69 +120,101 @@ func codeToPV(e *evaluator, filename string, code string) potentialValue {
100
120
}
101
121
102
122
// ImportCode imports code from a path.
103
- func (cache * ImportCache ) ImportCode (codeDir , importedPath string , e * evaluator ) (value , error ) {
104
- cached := cache .importData (importCacheKey { codeDir , importedPath } )
105
- if cached . err != nil {
106
- return nil , e .Error (cached . err .Error ())
123
+ func (cache * ImportCache ) ImportCode (importedFrom , importedPath string , e * evaluator ) (value , error ) {
124
+ contents , foundAt , err := cache .importData (importedFrom , importedPath )
125
+ if err != nil {
126
+ return nil , e .Error (err .Error ())
107
127
}
108
- if cached .asCode == nil {
109
- // File hasn't been parsed before, update the cache record.
110
- cached .asCode = codeToPV (e , cached .data .FoundHere , cached .data .Content )
128
+ var pv potentialValue
129
+ if cachedPV , isCached := cache .codeCache [foundAt ]; ! isCached {
130
+ // File hasn't been parsed and analyzed before, update the cache record.
131
+ pv = codeToPV (e , foundAt , contents .String ())
132
+ cache .codeCache [foundAt ] = pv
133
+ } else {
134
+ pv = cachedPV
111
135
}
112
- return e .evaluate (cached . asCode )
136
+ return e .evaluate (pv )
113
137
}
114
138
115
139
// Concrete importers
116
140
// -------------------------------------
117
141
118
- // FileImporter imports data from files .
142
+ // FileImporter imports data from the filesystem .
119
143
type FileImporter struct {
120
- JPaths []string
144
+ JPaths []string
145
+ fsCache map [string ]* fsCacheEntry
146
+ }
147
+
148
+ type fsCacheEntry struct {
149
+ exists bool
150
+ contents Contents
121
151
}
122
152
123
- func tryPath (dir , importedPath string ) (found bool , content []byte , foundHere string , err error ) {
153
+ func (importer * FileImporter ) tryPath (dir , importedPath string ) (found bool , contents Contents , foundHere string , err error ) {
154
+ if importer .fsCache == nil {
155
+ importer .fsCache = make (map [string ]* fsCacheEntry )
156
+ }
124
157
var absPath string
125
158
if path .IsAbs (importedPath ) {
126
159
absPath = importedPath
127
160
} else {
128
161
absPath = path .Join (dir , importedPath )
129
162
}
130
- content , err = ioutil .ReadFile (absPath )
131
- if os .IsNotExist (err ) {
132
- return false , nil , "" , nil
163
+ var entry * fsCacheEntry
164
+ if cacheEntry , isCached := importer .fsCache [absPath ]; isCached {
165
+ entry = cacheEntry
166
+ } else {
167
+ contentBytes , err := ioutil .ReadFile (absPath )
168
+ if err != nil {
169
+ if os .IsNotExist (err ) {
170
+ entry = & fsCacheEntry {
171
+ exists : false ,
172
+ }
173
+ } else {
174
+ return false , Contents {}, "" , err
175
+ }
176
+ } else {
177
+ entry = & fsCacheEntry {
178
+ exists : true ,
179
+ contents : MakeContents (string (contentBytes )),
180
+ }
181
+ }
182
+ importer .fsCache [absPath ] = entry
133
183
}
134
- return true , content , absPath , err
184
+ return entry . exists , entry . contents , absPath , nil
135
185
}
136
186
137
- // Import imports a file.
138
- func (importer * FileImporter ) Import (dir , importedPath string ) (* ImportedData , error ) {
139
- found , content , foundHere , err := tryPath (dir , importedPath )
187
+ // Import imports file from the filesystem.
188
+ func (importer * FileImporter ) Import (importedFrom , importedPath string ) (contents Contents , foundAt string , err error ) {
189
+ dir , _ := path .Split (importedFrom )
190
+ found , content , foundHere , err := importer .tryPath (dir , importedPath )
140
191
if err != nil {
141
- return nil , err
192
+ return Contents {}, "" , err
142
193
}
143
194
144
195
for i := len (importer .JPaths ) - 1 ; ! found && i >= 0 ; i -- {
145
- found , content , foundHere , err = tryPath (importer .JPaths [i ], importedPath )
196
+ found , content , foundHere , err = importer . tryPath (importer .JPaths [i ], importedPath )
146
197
if err != nil {
147
- return nil , err
198
+ return Contents {}, "" , err
148
199
}
149
200
}
150
201
151
202
if ! found {
152
- return nil , fmt .Errorf ("couldn't open import %#v: no match locally or in the Jsonnet library paths" , importedPath )
203
+ return Contents {}, "" , fmt .Errorf ("couldn't open import %#v: no match locally or in the Jsonnet library paths" , importedPath )
153
204
}
154
- return & ImportedData { Content : string ( content ), FoundHere : foundHere } , nil
205
+ return content , foundHere , nil
155
206
}
156
207
157
208
// MemoryImporter "imports" data from an in-memory map.
158
209
type MemoryImporter struct {
159
- Data map [string ]string
210
+ Data map [string ]Contents
160
211
}
161
212
162
- // Import imports a map entry.
163
- func (importer * MemoryImporter ) Import (dir , importedPath string ) (* ImportedData , error ) {
213
+ // Import fetches data from a map entry.
214
+ // All paths are treated as absolute keys.
215
+ func (importer * MemoryImporter ) Import (importedFrom , importedPath string ) (contents Contents , foundAt string , err error ) {
164
216
if content , ok := importer .Data [importedPath ]; ok {
165
- return & ImportedData { Content : content , FoundHere : importedPath } , nil
217
+ return content , importedPath , nil
166
218
}
167
- return nil , fmt .Errorf ("import not available %v" , importedPath )
219
+ return Contents {}, "" , fmt .Errorf ("import not available %v" , importedPath )
168
220
}
0 commit comments