diff --git a/mctx/ctx.go b/mctx/ctx.go index e56e12e..b89d0cf 100644 --- a/mctx/ctx.go +++ b/mctx/ctx.go @@ -152,6 +152,24 @@ func ChildOf(ctx Context, name string) Context { return childCtx } +// BreadthFirstVisit visits this Context and all of its children, and their +// children, in a breadth-first order. If the callback returns false then the +// function returns without visiting any more Contexts. +// +// The exact order of visitation is non-deterministic. +func BreadthFirstVisit(ctx Context, callback func(Context) bool) { + queue := []Context{ctx} + for len(queue) > 0 { + if !callback(queue[0]) { + return + } + for _, child := range Children(queue[0]) { + queue = append(queue, child) + } + queue = queue[1:] + } +} + //////////////////////////////////////////////////////////////////////////////// // code related to mutable values diff --git a/mctx/ctx_test.go b/mctx/ctx_test.go index 7cf6e64..9a71f95 100644 --- a/mctx/ctx_test.go +++ b/mctx/ctx_test.go @@ -54,6 +54,44 @@ func TestInheritance(t *T) { )) } +func TestBreadFirstVisit(t *T) { + ctx := New() + ctx1 := ChildOf(ctx, "1") + ctx1a := ChildOf(ctx1, "a") + ctx1b := ChildOf(ctx1, "b") + ctx2 := ChildOf(ctx, "2") + + { + got := make([]Context, 0, 5) + BreadthFirstVisit(ctx, func(ctx Context) bool { + got = append(got, ctx) + return true + }) + // since children are stored in a map the exact order is non-deterministic + massert.Fatal(t, massert.Any( + massert.Equal([]Context{ctx, ctx1, ctx2, ctx1a, ctx1b}, got), + massert.Equal([]Context{ctx, ctx1, ctx2, ctx1b, ctx1a}, got), + massert.Equal([]Context{ctx, ctx2, ctx1, ctx1a, ctx1b}, got), + massert.Equal([]Context{ctx, ctx2, ctx1, ctx1b, ctx1a}, got), + )) + } + + { + got := make([]Context, 0, 3) + BreadthFirstVisit(ctx, func(ctx Context) bool { + if len(Path(ctx)) > 1 { + return false + } + got = append(got, ctx) + return true + }) + massert.Fatal(t, massert.Any( + massert.Equal([]Context{ctx, ctx1, ctx2}, got), + massert.Equal([]Context{ctx, ctx2, ctx1}, got), + )) + } +} + func TestMutableValues(t *T) { fn := func(v interface{}) interface{} { if v == nil {