pax_global_header00006660000000000000000000000064146425220710014515gustar00rootroot0000000000000052 comment=90028e73f89a9c7aa6d49ff2d7605a11de249211 golang-github-lestrrat-go-iter-1.0.2/000077500000000000000000000000001464252207100174465ustar00rootroot00000000000000golang-github-lestrrat-go-iter-1.0.2/.github/000077500000000000000000000000001464252207100210065ustar00rootroot00000000000000golang-github-lestrrat-go-iter-1.0.2/.github/workflows/000077500000000000000000000000001464252207100230435ustar00rootroot00000000000000golang-github-lestrrat-go-iter-1.0.2/.github/workflows/ci.yml000066400000000000000000000017151464252207100241650ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: go: [ '1.15', '1.16' ] name: Go ${{ matrix.go }} test steps: - name: Checkout repository uses: actions/checkout@v2 - name: Install Go stable version if: matrix.go != 'tip' uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Install Go tip if: matrix.go == 'tip' run: | git clone --depth=1 https://go.googlesource.com/go $HOME/gotip cd $HOME/gotip/src ./make.bash echo "::set-env name=GOROOT::$HOME/gotip" echo "::add-path::$HOME/gotip/bin" echo "::add-path::$(go env GOPATH)/bin" - name: Test run: go test -v -race ./... - name: Upload code coverage to codecov if: matrix.go == '1.16' uses: codecov/codecov-action@v1 with: file: ./coverage.out golang-github-lestrrat-go-iter-1.0.2/.github/workflows/lint.yml000066400000000000000000000003451464252207100245360ustar00rootroot00000000000000name: lint on: [push, pull_request] jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: golangci/golangci-lint-action@v2 with: version: v1.36.0 golang-github-lestrrat-go-iter-1.0.2/.gitignore000066400000000000000000000004151464252207100214360ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ golang-github-lestrrat-go-iter-1.0.2/Changes000066400000000000000000000003711464252207100207420ustar00rootroot00000000000000Changes ======= v1.0.2 29 Mar, 2022 * Use fmt.Errorf instead of github.com/pkg/errors * Update github.com/stretchr/testify to 1.7.1 v1.0.1 28 Mar, 2021 * Work with nil values for map[string]interface{} types (#1) v1.0.0 * Initial release golang-github-lestrrat-go-iter-1.0.2/LICENSE000066400000000000000000000020541464252207100204540ustar00rootroot00000000000000MIT License Copyright (c) 2020 lestrrat-go Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-lestrrat-go-iter-1.0.2/README.md000066400000000000000000000033701464252207100207300ustar00rootroot00000000000000# iter Simple tools for container iteration # DESCRIPTION `iter` and its sub-packages provide a set of utilities to make it easy for providers of objects that are iteratable. For example, if your object is map-like and you want a way for users to iterate through all or specific keys in your object, all you need to do is to provide a function that iterates through the pairs that you want, and send them to a channel. Then you create an iterator from the channel, and pass the iterator to the user. The user can then safely iterate through all elements ## Channel-based iterator Iterators pass its values via channels. Your implementation must provide a "source" that writes to this channel. ```go func iterate(ch chan *mapiter.Pair) { ch <- &mapiter.Pair{Key: "key1", Value: ...} ch <- &mapiter.Pair{Key: "key2", Value: ...} ... // DO NOT forget to close the channel close(ch) } ch := make(chan *mapiter.Pair) go iterate(ch) for iter := mapiter.New(ch); i.Next(ctx); { pair := i.Pair() ... } ``` ## Convenience functions As long as an object implements the appropriate method, you can use the convenience functions ```go fn := func(k string, v interface{}) error { ... } mapiter.Walk(ctx, source, mapiter.VisitorFunc(fn)) ``` There are also functions to convert map-like objects to a map, and array-like objects to an array/slice ```go var l []string err := arrayiter.AsArray(ctx, obj, &l) var m map[string]int err := mapiter.AsMap(ctx, obj, &m) ``` ## Iterate over native containers (map/array) ```go m := make(map[...]...) // key and value may be any type for iter := mapiter.Iterate(ctx, m); iter.Next(ctx); { ... } ``` ```go s := make([]...) // element may be any type for iter := arrayiter.Iterate(ctx, s); iter.Next(ctx); { ... } ``` golang-github-lestrrat-go-iter-1.0.2/arrayiter/000077500000000000000000000000001464252207100214505ustar00rootroot00000000000000golang-github-lestrrat-go-iter-1.0.2/arrayiter/arrayiter.go000066400000000000000000000076721464252207100240150ustar00rootroot00000000000000package arrayiter import ( "context" "fmt" "reflect" "sync" ) func Iterate(ctx context.Context, a interface{}) (Iterator, error) { arv := reflect.ValueOf(a) switch arv.Kind() { case reflect.Array, reflect.Slice: default: return nil, fmt.Errorf(`argument must be an array/slice (%s)`, arv.Type()) } ch := make(chan *Pair) go func(ctx context.Context, ch chan *Pair, arv reflect.Value) { defer close(ch) for i := 0; i < arv.Len(); i++ { value := arv.Index(i) pair := &Pair{ Index: i, Value: value.Interface(), } select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, arv) return New(ch), nil } // Source represents a array that knows how to create an iterator type Source interface { Iterate(context.Context) Iterator } // Pair represents a single pair of key and value from a array type Pair struct { Index int Value interface{} } // Iterator iterates through keys and values of a array type Iterator interface { Next(context.Context) bool Pair() *Pair } type iter struct { ch chan *Pair mu sync.RWMutex next *Pair } // Visitor represents an object that handles each pair in a array type Visitor interface { Visit(int, interface{}) error } // VisitorFunc is a type of Visitor based on a function type VisitorFunc func(int, interface{}) error func (fn VisitorFunc) Visit(s int, v interface{}) error { return fn(s, v) } func New(ch chan *Pair) Iterator { return &iter{ ch: ch, } } // Next returns true if there are more items to read from the iterator func (i *iter) Next(ctx context.Context) bool { i.mu.RLock() if i.ch == nil { i.mu.RUnlock() return false } i.mu.RUnlock() i.mu.Lock() defer i.mu.Unlock() select { case <-ctx.Done(): i.ch = nil return false case v, ok := <-i.ch: if !ok { i.ch = nil return false } i.next = v return true } //nolint:govet return false // never reached } // Pair returns the currently buffered Pair. Calling Next() will reset its value func (i *iter) Pair() *Pair { i.mu.RLock() defer i.mu.RUnlock() return i.next } // Walk walks through each element in the array func Walk(ctx context.Context, s Source, v Visitor) error { for i := s.Iterate(ctx); i.Next(ctx); { pair := i.Pair() if err := v.Visit(pair.Index, pair.Value); err != nil { return fmt.Errorf(`failed to visit index %d: %w`, pair.Index, err) } } return nil } func AsArray(ctx context.Context, s interface{}, v interface{}) error { var iter Iterator switch reflect.ValueOf(s).Kind() { case reflect.Array, reflect.Slice: x, err := Iterate(ctx, s) if err != nil { return fmt.Errorf(`failed to iterate over array/slice type: %w`, err) } iter = x default: ssrc, ok := s.(Source) if !ok { return fmt.Errorf(`cannot iterate over %T: not a arrayiter.Source type`, s) } iter = ssrc.Iterate(ctx) } dst := reflect.ValueOf(v) // dst MUST be a pointer to a array type if kind := dst.Kind(); kind != reflect.Ptr { return fmt.Errorf(`dst must be a pointer to a array (%s)`, dst.Type()) } dst = dst.Elem() switch dst.Kind() { case reflect.Array, reflect.Slice: default: return fmt.Errorf(`dst must be a pointer to an array or slice (%s)`, dst.Type()) } var pairs []*Pair for iter.Next(ctx) { pair := iter.Pair() pairs = append(pairs, pair) } switch dst.Kind() { case reflect.Array: if len(pairs) < dst.Len() { return fmt.Errorf(`dst array does not have enough space for elements (%d, want %d)`, dst.Len(), len(pairs)) } case reflect.Slice: if dst.IsNil() { dst.Set(reflect.MakeSlice(dst.Type(), len(pairs), len(pairs))) } } // dst must be assignable if !dst.CanSet() { return fmt.Errorf(`dst is not writeable`) } elemtyp := dst.Type().Elem() for _, pair := range pairs { rvvalue := reflect.ValueOf(pair.Value) if !rvvalue.Type().AssignableTo(elemtyp) { return fmt.Errorf(`cannot assign key of type %s to map key of type %s`, rvvalue.Type(), elemtyp) } dst.Index(pair.Index).Set(rvvalue) } return nil } golang-github-lestrrat-go-iter-1.0.2/arrayiter/arrayiter_test.go000066400000000000000000000055231464252207100250450ustar00rootroot00000000000000package arrayiter_test import ( "context" "fmt" "reflect" "testing" "time" "github.com/lestrrat-go/iter/arrayiter" "github.com/stretchr/testify/assert" ) func TestIterator(t *testing.T) { chSize := 2 ch := make(chan *arrayiter.Pair, chSize) ch <- &arrayiter.Pair{Index: 1, Value: 2} ch <- &arrayiter.Pair{Index: 2, Value: 4} close(ch) i := arrayiter.New(ch) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var loopCount int for i.Next(ctx) { loopCount++ p := i.Pair() if !assert.Equal(t, p.Value, 2*loopCount, "expected values to match") { return } } if !assert.Equal(t, chSize, loopCount, "expected to loop for %d times", chSize) { return } } type ArrayLike struct { Values []string } func (m *ArrayLike) Iterate(ctx context.Context) arrayiter.Iterator { ch := make(chan *arrayiter.Pair) go m.iterate(ctx, ch) return arrayiter.New(ch) } func (m *ArrayLike) iterate(ctx context.Context, ch chan *arrayiter.Pair) { defer close(ch) for k, v := range m.Values { ch <- &arrayiter.Pair{Index: k, Value: v} } } func TestAsArray(t *testing.T) { t.Run("slice", func(t *testing.T) { inputs := []interface{}{ []string{ "foo", "bar", "baz", }, } for _, x := range inputs { input := x t.Run(fmt.Sprintf("%T", input), func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() dst := reflect.New(reflect.TypeOf(input)) if !assert.NoError(t, arrayiter.AsArray(ctx, input, dst.Interface()), `arrayiter.AsArray should succeed`) { return } if !assert.Equal(t, input, dst.Elem().Interface(), `slices should be the same`) { return } }) } }) t.Run("Array-like object", func(t *testing.T) { src := &ArrayLike{ Values: []string{ "one", "two", "three", "four", "five", }, } t.Run("dst is nil (slice)", func(t *testing.T) { var m []string if !assert.NoError(t, arrayiter.AsArray(context.Background(), src, &m), `AsArray against uninitialized array should succeed`) { return } if !assert.Equal(t, src.Values, m, "slices should match") { return } }) t.Run("dst is nil (array)", func(t *testing.T) { var m [5]string if !assert.NoError(t, arrayiter.AsArray(context.Background(), src, &m), `AsArray against uninitialized array should succeed`) { return } var expected [5]string for i, v := range src.Values { expected[i] = v } if !assert.Equal(t, expected, m, "arrays should match") { return } }) t.Run("dst is not nil", func(t *testing.T) { m := make([]string, len(src.Values)) if !assert.NoError(t, arrayiter.AsArray(context.Background(), src, &m), `AsArray against nil map should succeed`) { return } if !assert.Equal(t, src.Values, m, "maps should match") { return } }) }) } golang-github-lestrrat-go-iter-1.0.2/go.mod000066400000000000000000000001301464252207100205460ustar00rootroot00000000000000module github.com/lestrrat-go/iter go 1.13 require github.com/stretchr/testify v1.7.1 golang-github-lestrrat-go-iter-1.0.2/go.sum000066400000000000000000000020001464252207100205710ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-lestrrat-go-iter-1.0.2/mapiter/000077500000000000000000000000001464252207100211075ustar00rootroot00000000000000golang-github-lestrrat-go-iter-1.0.2/mapiter/mapiter.go000066400000000000000000000103401464252207100230750ustar00rootroot00000000000000package mapiter import ( "context" "fmt" "reflect" "sync" ) // Iterate creates an iterator from arbitrary map types. This is not // the most efficient tool, but it's the quickest way to create an // iterator for maps. // Also, note that you cannot make any assumptions on the order of // pairs being returned. func Iterate(ctx context.Context, m interface{}) (Iterator, error) { mrv := reflect.ValueOf(m) if mrv.Kind() != reflect.Map { return nil, fmt.Errorf(`argument must be a map (%s)`, mrv.Type()) } ch := make(chan *Pair) go func(ctx context.Context, ch chan *Pair, mrv reflect.Value) { defer close(ch) for _, key := range mrv.MapKeys() { value := mrv.MapIndex(key) pair := &Pair{ Key: key.Interface(), Value: value.Interface(), } select { case <-ctx.Done(): return case ch <- pair: } } }(ctx, ch, mrv) return New(ch), nil } // Source represents a map that knows how to create an iterator type Source interface { Iterate(context.Context) Iterator } // Pair represents a single pair of key and value from a map type Pair struct { Key interface{} Value interface{} } // Iterator iterates through keys and values of a map type Iterator interface { Next(context.Context) bool Pair() *Pair } type iter struct { ch chan *Pair mu sync.RWMutex next *Pair } // Visitor represents an object that handles each pair in a map type Visitor interface { Visit(interface{}, interface{}) error } // VisitorFunc is a type of Visitor based on a function type VisitorFunc func(interface{}, interface{}) error func (fn VisitorFunc) Visit(s interface{}, v interface{}) error { return fn(s, v) } func New(ch chan *Pair) Iterator { return &iter{ ch: ch, } } // Next returns true if there are more items to read from the iterator func (i *iter) Next(ctx context.Context) bool { i.mu.RLock() if i.ch == nil { i.mu.RUnlock() return false } i.mu.RUnlock() i.mu.Lock() defer i.mu.Unlock() select { case <-ctx.Done(): i.ch = nil return false case v, ok := <-i.ch: if !ok { i.ch = nil return false } i.next = v return true } //nolint:govet return false // never reached } // Pair returns the currently buffered Pair. Calling Next() will reset its value func (i *iter) Pair() *Pair { i.mu.RLock() defer i.mu.RUnlock() return i.next } // Walk walks through each element in the map func Walk(ctx context.Context, s Source, v Visitor) error { for i := s.Iterate(ctx); i.Next(ctx); { pair := i.Pair() if err := v.Visit(pair.Key, pair.Value); err != nil { return fmt.Errorf(`failed to visit key %s: %w`, pair.Key, err) } } return nil } // AsMap returns the values obtained from the source as a map func AsMap(ctx context.Context, s interface{}, v interface{}) error { var iter Iterator switch reflect.ValueOf(s).Kind() { case reflect.Map: x, err := Iterate(ctx, s) if err != nil { return fmt.Errorf(`failed to iterate over map type: %w`, err) } iter = x default: ssrc, ok := s.(Source) if !ok { return fmt.Errorf(`cannot iterate over %T: not a mapiter.Source type`, s) } iter = ssrc.Iterate(ctx) } dst := reflect.ValueOf(v) // dst MUST be a pointer to a map type if kind := dst.Kind(); kind != reflect.Ptr { return fmt.Errorf(`dst must be a pointer to a map (%s)`, dst.Type()) } dst = dst.Elem() if dst.Kind() != reflect.Map { return fmt.Errorf(`dst must be a pointer to a map (%s)`, dst.Type()) } if dst.IsNil() { dst.Set(reflect.MakeMap(dst.Type())) } // dst must be assignable if !dst.CanSet() { return fmt.Errorf(`dst is not writeable`) } keytyp := dst.Type().Key() valtyp := dst.Type().Elem() for iter.Next(ctx) { pair := iter.Pair() rvkey := reflect.ValueOf(pair.Key) rvvalue := reflect.ValueOf(pair.Value) if !rvkey.Type().AssignableTo(keytyp) { return fmt.Errorf(`cannot assign key of type %s to map key of type %s`, rvkey.Type(), keytyp) } switch rvvalue.Kind() { // we can only check if we can assign to rvvalue to valtyp if it's non-nil case reflect.Invalid: rvvalue = reflect.New(valtyp).Elem() default: if !rvvalue.Type().AssignableTo(valtyp) { return fmt.Errorf(`cannot assign value of type %s to map value of type %s`, rvvalue.Type(), valtyp) } } dst.SetMapIndex(rvkey, rvvalue) } return nil } golang-github-lestrrat-go-iter-1.0.2/mapiter/mapiter_test.go000066400000000000000000000066551464252207100241520ustar00rootroot00000000000000package mapiter_test import ( "context" "fmt" "reflect" "testing" "time" "github.com/lestrrat-go/iter/mapiter" "github.com/stretchr/testify/assert" ) func TestIterator(t *testing.T) { chSize := 2 ch := make(chan *mapiter.Pair, chSize) ch <- &mapiter.Pair{Key: "one", Value: 1} ch <- &mapiter.Pair{Key: "two", Value: 2} close(ch) i := mapiter.New(ch) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var loopCount int for i.Next(ctx) { loopCount++ p := i.Pair() if !assert.Equal(t, p.Value, loopCount, "expected values to match") { return } } if !assert.Equal(t, chSize, loopCount, "expected to loop for %d times", chSize) { return } } type MapLike struct { Values map[string]int } func (m *MapLike) Iterate(ctx context.Context) mapiter.Iterator { ch := make(chan *mapiter.Pair) go m.iterate(ctx, ch) return mapiter.New(ch) } func (m *MapLike) iterate(ctx context.Context, ch chan *mapiter.Pair) { defer close(ch) for k, v := range m.Values { ch <- &mapiter.Pair{Key: k, Value: v} } } func TestAsMap(t *testing.T) { t.Run("maps", func(t *testing.T) { inputs := []interface{}{ map[string]string{ "foo": "one", "bar": "two", "baz": "three", }, } for _, x := range inputs { input := x t.Run(fmt.Sprintf("%T", input), func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() dst := reflect.New(reflect.TypeOf(input)) dst.Elem().Set(reflect.MakeMap(reflect.TypeOf(input))) if !assert.NoError(t, mapiter.AsMap(ctx, input, dst.Interface()), `mapiter.AsMap should succeed`) { return } if !assert.Equal(t, input, dst.Elem().Interface(), `maps should be the same`) { return } }) } }) t.Run("Map-like object", func(t *testing.T) { src := &MapLike{ Values: map[string]int{ "one": 1, "two": 2, "three": 3, "four": 4, "five": 5, }, } t.Run("dst is nil", func(t *testing.T) { var m map[string]int if !assert.NoError(t, mapiter.AsMap(context.Background(), src, &m), `AsMap against nil map should succeed`) { return } if !assert.Equal(t, src.Values, m, "maps should match") { return } }) t.Run("dst is nil (elem type does not match)", func(t *testing.T) { var m map[string]string if assert.Error(t, mapiter.AsMap(context.Background(), src, &m), `AsMap against nil map should fail`) { return } }) t.Run("dst is not nil", func(t *testing.T) { m := make(map[string]int) if !assert.NoError(t, mapiter.AsMap(context.Background(), src, &m), `AsMap against nil map should succeed`) { return } if !assert.Equal(t, src.Values, m, "maps should match") { return } }) }) } func TestGH1(t *testing.T) { t.Run("maps", func(t *testing.T) { inputs := []interface{}{ map[string]interface{}{ "foo": "one", "bar": "two", "baz": nil, }, } for _, x := range inputs { input := x t.Run(fmt.Sprintf("%T", input), func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() dst := reflect.New(reflect.TypeOf(input)) dst.Elem().Set(reflect.MakeMap(reflect.TypeOf(input))) if !assert.NoError(t, mapiter.AsMap(ctx, input, dst.Interface()), `mapiter.AsMap should succeed`) { return } if !assert.Equal(t, input, dst.Elem().Interface(), `maps should be the same`) { return } }) } }) }