@@ -36,8 +36,8 @@ func Get(n ipld.Node, p ipld.Path) (ipld.Node, error) {
36
36
// It cannot cross links automatically (since this requires configuration).
37
37
// Use the equivalent FocusedTransform function on the Progress structure
38
38
// for more advanced and configurable walks.
39
- func FocusedTransform (n ipld.Node , p ipld.Path , fn TransformFn ) (ipld.Node , error ) {
40
- return Progress {}.FocusedTransform (n , p , fn )
39
+ func FocusedTransform (n ipld.Node , p ipld.Path , fn TransformFn , createParents bool ) (ipld.Node , error ) {
40
+ return Progress {}.FocusedTransform (n , p , fn , createParents )
41
41
}
42
42
43
43
// Focus traverses a Node graph according to a path, reaches a single Node,
@@ -171,13 +171,200 @@ func (prog *Progress) get(n ipld.Node, p ipld.Path, trackProgress bool) (ipld.No
171
171
// using more TransformFn calls as desired to produce the replacement elements
172
172
// if it so happens that those replacement elements are easiest to construct
173
173
// by regarding them as incremental updates to the previous values.
174
+ // (This approach can also be used when doing other modifications like insertion
175
+ // or reordering -- which would otherwise be tricky to define, since
176
+ // each operation could change the meaning of subsequently used indexes.)
177
+ //
178
+ // As a special case, list appending is supported by using the path segment "-".
179
+ // (This is determined by the node it applies to -- if that path segment
180
+ // is applied to a map, it's just a regular map key of the string of dash.)
174
181
//
175
182
// Note that anything you can do with the Transform function, you can also
176
183
// do with regular Node and NodeBuilder usage directly. Transform just
177
184
// does a large amount of the intermediate bookkeeping that's useful when
178
185
// creating new values which are partial updates to existing values.
179
186
//
180
- // This feature is not yet implemented.
181
- func (prog Progress ) FocusedTransform (n ipld.Node , p ipld.Path , fn TransformFn ) (ipld.Node , error ) {
182
- panic ("TODO" ) // TODO surprisingly different from Focus -- need to store nodes we traversed, and able do building.
187
+ func (prog Progress ) FocusedTransform (n ipld.Node , p ipld.Path , fn TransformFn , createParents bool ) (ipld.Node , error ) {
188
+ prog .init ()
189
+ nb := n .Prototype ().NewBuilder ()
190
+ if err := prog .focusedTransform (n , nb , p , fn , createParents ); err != nil {
191
+ return nil , err
192
+ }
193
+ return nb .Build (), nil
194
+ }
195
+
196
+ // focusedTransform assumes that an update will actually happen, and as it recurses deeper,
197
+ // begins building an updated node tree.
198
+ //
199
+ // As implemented, this is not actually efficient if the update will be a no-op; it won't notice until it gets there.
200
+ func (prog Progress ) focusedTransform (n ipld.Node , na ipld.NodeAssembler , p ipld.Path , fn TransformFn , createParents bool ) error {
201
+ if p .Len () == 0 {
202
+ n2 , err := fn (prog , n )
203
+ if err != nil {
204
+ return err
205
+ }
206
+ return na .ConvertFrom (n2 )
207
+ }
208
+ seg , p2 := p .Shift ()
209
+ // Special branch for if we've entered createParent mode in an earlier step.
210
+ // This needs slightly different logic because there's no prior node to reference
211
+ // (and we wouldn't want to waste time creating a dummy one).
212
+ if n == nil {
213
+ ma , err := na .BeginMap (1 )
214
+ if err != nil {
215
+ return err
216
+ }
217
+ prog .Path = prog .Path .AppendSegment (seg )
218
+ if err := ma .AssembleKey ().AssignString (seg .String ()); err != nil {
219
+ return err
220
+ }
221
+ if err := prog .focusedTransform (nil , ma .AssembleValue (), p2 , fn , createParents ); err != nil {
222
+ return err
223
+ }
224
+ return ma .Finish ()
225
+ }
226
+ // Handle node based on kind.
227
+ // If it's a recursive kind (map or list), we'll be recursing on it.
228
+ // If it's a link, load it! And recurse on it.
229
+ // If it's a scalar kind (any of the rest), we'll... be erroring, actually;
230
+ // if we're at the end, it was already handled at the top of the function,
231
+ // so we only get to this case if we were expecting to go deeper.
232
+ switch n .Kind () {
233
+ case ipld .Kind_Map :
234
+ ma , err := na .BeginMap (n .Length ())
235
+ if err != nil {
236
+ return err
237
+ }
238
+ // Copy children over. Replace the target (preserving its current position!) while doing this, if found.
239
+ // Note that we don't recurse into copying children (assuming ConvertFrom doesn't); this is as shallow/COW as the ConvertFrom implementation permits.
240
+ var replaced bool
241
+ for itr := n .MapIterator (); ! itr .Done (); {
242
+ k , v , err := itr .Next ()
243
+ if err != nil {
244
+ return err
245
+ }
246
+ if err := ma .AssembleKey ().ConvertFrom (k ); err != nil {
247
+ return err
248
+ }
249
+ if asPathSegment (k ).Equals (seg ) {
250
+ prog .Path = prog .Path .AppendSegment (seg )
251
+ if err := prog .focusedTransform (v , ma .AssembleValue (), p2 , fn , createParents ); err != nil {
252
+ return err
253
+ }
254
+ replaced = true
255
+ } else {
256
+ if err := ma .AssembleValue ().ConvertFrom (v ); err != nil {
257
+ return err
258
+ }
259
+ }
260
+ }
261
+ if replaced {
262
+ return ma .Finish ()
263
+ }
264
+ // If we didn't find the target yet: append it.
265
+ // If we're at the end, always do this;
266
+ // if we're in the middle, only do this if createParents mode is enabled.
267
+ prog .Path = prog .Path .AppendSegment (seg )
268
+ if p .Len () > 1 && ! createParents {
269
+ return fmt .Errorf ("transform: parent position at %q did not exist (and createParents was false)" , prog .Path )
270
+ }
271
+ if err := ma .AssembleKey ().AssignString (seg .String ()); err != nil {
272
+ return err
273
+ }
274
+ if err := prog .focusedTransform (nil , ma .AssembleValue (), p2 , fn , createParents ); err != nil {
275
+ return err
276
+ }
277
+ return ma .Finish ()
278
+ case ipld .Kind_List :
279
+ la , err := na .BeginList (n .Length ())
280
+ if err != nil {
281
+ return err
282
+ }
283
+ // First figure out if this path segment can apply to a list sanely at all.
284
+ // Simultaneously, get it in numeric format, so subsequent operations are cheaper.
285
+ ti , err := seg .Index ()
286
+ if err != nil {
287
+ if seg .String () == "-" {
288
+ ti = - 1
289
+ } else {
290
+ return fmt .Errorf ("transform: cannot navigate path segment %q at %q because a list is here" , seg , prog .Path )
291
+ }
292
+ }
293
+ // Copy children over. Replace the target (preserving its current position!) while doing this, if found.
294
+ // Note that we don't recurse into copying children (assuming ConvertFrom doesn't); this is as shallow/COW as the ConvertFrom implementation permits.
295
+ var replaced bool
296
+ for itr := n .ListIterator (); ! itr .Done (); {
297
+ i , v , err := itr .Next ()
298
+ if err != nil {
299
+ return err
300
+ }
301
+ if ti == i {
302
+ prog .Path = prog .Path .AppendSegment (seg )
303
+ if err := prog .focusedTransform (v , la .AssembleValue (), p2 , fn , createParents ); err != nil {
304
+ return err
305
+ }
306
+ replaced = true
307
+ } else {
308
+ if err := la .AssembleValue ().ConvertFrom (v ); err != nil {
309
+ return err
310
+ }
311
+ }
312
+ }
313
+ if replaced {
314
+ return la .Finish ()
315
+ }
316
+ // If we didn't find the target yet: hopefully this was an append operation;
317
+ // if it wasn't, then it's index out of bounds. We don't arbitrarily extend lists with filler.
318
+ if ti >= 0 {
319
+ return fmt .Errorf ("transform: cannot navigate path segment %q at %q because it is beyond the list bounds" , seg , prog .Path )
320
+ }
321
+ prog .Path = prog .Path .AppendSegment (ipld .PathSegmentOfInt (n .Length ()))
322
+ if err := prog .focusedTransform (nil , la .AssembleValue (), p2 , fn , createParents ); err != nil {
323
+ return err
324
+ }
325
+ return la .Finish ()
326
+ case ipld .Kind_Link :
327
+ lnkCtx := ipld.LinkContext {
328
+ LinkPath : prog .Path ,
329
+ LinkNode : n ,
330
+ ParentNode : nil , // TODO inconvenient that we don't have this. maybe this whole case should be a helper function.
331
+ }
332
+ lnk , _ := n .AsLink ()
333
+ // Pick what in-memory format we will build.
334
+ np , err := prog .Cfg .LinkTargetNodePrototypeChooser (lnk , lnkCtx )
335
+ if err != nil {
336
+ return fmt .Errorf ("transform: error traversing node at %q: could not load link %q: %s" , prog .Path , lnk , err )
337
+ }
338
+ nb := np .NewBuilder ()
339
+ // Load link!
340
+ err = lnk .Load (
341
+ prog .Cfg .Ctx ,
342
+ lnkCtx ,
343
+ nb ,
344
+ prog .Cfg .LinkLoader ,
345
+ )
346
+ if err != nil {
347
+ return fmt .Errorf ("transform: error traversing node at %q: could not load link %q: %s" , prog .Path , lnk , err )
348
+ }
349
+ prog .LastBlock .Path = prog .Path
350
+ prog .LastBlock .Link = lnk
351
+ n = nb .Build ()
352
+ // Recurse.
353
+ // Start a new builder for this, using the same prototype we just used for loading the link.
354
+ // (Or more specifically: this is an opportunity for just resetting a builder and reusing memory!)
355
+ // When we come back... we'll have to engage serialization and storage on the new node!
356
+ // Path isn't updated here (neither progress nor to-go).
357
+ nb .Reset ()
358
+ if err := prog .focusedTransform (n , nb , p , fn , createParents ); err != nil {
359
+ return err
360
+ }
361
+ n = nb .Build ()
362
+ lnk , err = lnk .LinkBuilder ().Build (prog .Cfg .Ctx , lnkCtx , n , prog .Cfg .LinkStorer )
363
+ if err != nil {
364
+ return fmt .Errorf ("transform: error storing transformed node at %q: %s" , prog .Path , err )
365
+ }
366
+ return na .AssignLink (lnk )
367
+ default :
368
+ return fmt .Errorf ("transform: parent position at %q was a scalar, cannot go deeper" , prog .Path )
369
+ }
183
370
}
0 commit comments