pax_global_header00006660000000000000000000000064141372736100014516gustar00rootroot0000000000000052 comment=2b65d175d3d2f5435932c746fb4e4237492967c4 golang-github-jkeiser-iter-0.0~git20200628.c8aa0ae/000077500000000000000000000000001413727361000214265ustar00rootroot00000000000000golang-github-jkeiser-iter-0.0~git20200628.c8aa0ae/LICENSE000066400000000000000000000020541413727361000224340ustar00rootroot00000000000000MIT License Copyright (c) 2020 John Keiser 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-jkeiser-iter-0.0~git20200628.c8aa0ae/channel.go000066400000000000000000000106761413727361000233770ustar00rootroot00000000000000package iter import "log" // A result from a iterator.Go() channel. type ChannelItem struct { Value interface{} Error error } // Runs the iterator in a goroutine, sending to a channel. // // The returned items channel will send ChannelItems to you, which can indicate // either a value or an error. If item.Error is set, iteration is ready // to terminate. Otherwise item.Value is the next value of the iteration. // // When you are done iterating, you must close the done channel. Even if iteration // was terminated early, this will ensure that the goroutine and channel are // properly cleaned up. // // channel, done := iter.Go(iterator) // defer close(done) // if early return or panic happens, this will clean up the goroutine // for item := range channel { // if item.Error != nil { // // Iteration failed; handle the error and exit the loop // ... // } // value := item.Value // ... // } func (iterator Iterator) Go() (items <-chan ChannelItem, done chan<- bool) { itemsChannel := make(chan ChannelItem) doneChannel := make(chan bool) go iterator.IterateToChannel(itemsChannel, doneChannel) return itemsChannel, doneChannel } // Iterates all items and sends them to the given channel. Runs on the current // goroutine (call go iterator.IterateToChannel to set it up on a new goroutine). // This will close the items channel when done. If the done channel is closed, // iteration will terminate. func (iterator Iterator) IterateToChannel(items chan<- ChannelItem, done <-chan bool) { defer close(items) err := iterator.EachWithError(func(result interface{}) error { select { case items <- ChannelItem{Value: result}: return nil case _, _ = <-done: // If we are told we're done early, we finish quietly. return FINISHED } }) if err != nil { items <- ChannelItem{Error: err} } } // Iterate over the channels from a Go(), calling a user-defined function for each value. // This function handles all anomalous conditions including errors, early // termination and safe cleanup of the goroutine and channels. func EachFromChannel(items <-chan ChannelItem, done chan<- bool, processor func(interface{}) error) error { defer close(done) // if early return or panic happens, this will clean up the goroutine for item := range items { if item.Error != nil { return item.Error } err := processor(item.Value) if err != nil { return err } } return nil } // Perform the iteration in the background concurrently with the Each() statement. // Useful when the iterator or iteratee will be doing blocking work. // // The bufferSize parameter lets you set how far ahead the background goroutine can // get. // // iterator.BackgroundEach(100, func(item interface{}) { ... }) func (iterator Iterator) BackgroundEach(bufferSize int, processor func(interface{}) error) error { itemsChannel := make(chan ChannelItem, bufferSize) doneChannel := make(chan bool) go iterator.IterateToChannel(itemsChannel, doneChannel) return EachFromChannel(itemsChannel, doneChannel, processor) } // Iterate to a channel in the background. // // for value := range iter.GoSimple(iterator) { // ... // } // // With this method, two undesirable things can happen: // - if the iteration stops early due to an error, you will not be able to handle // it (the goroutine will log and panic, and the program will exit). // - if callers panic or exit early without retrieving all values from the channel, // the goroutine is blocked forever and leaks. // // The Go() routine allows you to handle both of these issues, at a small cost to // caller complexity. BackgroundEach() provides a simple way to use Go(), as // well. // // That said, if you can make guarantees about no panics or don't care, this // method can make calling code easier to read. func (iterator Iterator) GoSimple() (values <-chan interface{}) { mainChannel := make(chan interface{}) go iterator.IterateToChannelSimple(mainChannel) return mainChannel } // Iterates all items and sends them to the given channel. Runs on the current // goroutine (call go iterator.IterateToChannelSimple() to set it up on a new goroutine). // This will close the values channel when done. See warnings about GoSimple() // vs. Go() in the GoSimple() method. func (iterator Iterator) IterateToChannelSimple(values chan<- interface{}) { defer close(values) err := iterator.Each(func(item interface{}) { values <- item }) if err != nil { log.Fatalf("Iterator returned an error in GoSimple: %v", err) } } golang-github-jkeiser-iter-0.0~git20200628.c8aa0ae/doc.go000066400000000000000000000074671413727361000225400ustar00rootroot00000000000000/* Generic forward-only iterator that is safe and leak-free. This package is intended to support forward-only iteration in a variety of use cases while avoiding the normal errors and leaks that can happen with iterators in Go. It provides mechanisms for map/select filtering, background iteration through a goroutine, and error handling throughout. The type of the iterator is interface{}, so it can store anything, at the cost that you have to cast it back out when you use it. This package can be used as is, or used as an example for creating your own forward-only iterators of more specific types. sum := 0 iterator.Each(func(item interface{}) { sum = sum + item.(int) }) Motivation With the lack of generics and a builtin iterator pattern, iterators have been the topic of much discussion in Go. Here are the discussions that inspired this: http://ewencp.org/blog/golang-iterators/: Ewan Cheslack-Postava's discussion of the major iteration patterns. Herein we have chosen the closure pattern for iterator implementation, and given the choice of callback and channel patterns for iteration callers. http://blog.golang.org/pipelines: A March 2014 discussion of pipelines on the go blog presents some of the pitfalls of channel iteration, suggesting the "done" channel implementation to compensate. Creating Iterators Simple error- and cleanup-free iterators can be easily created: // Create a simple iterator from a function val := 1 iterator := iter.NewSimple(func() interface{} { val = val * 2; return val > 2000 ? val : nil // nil terminates iteration }) Typically you will create iterators in packages ("please iterate over this complicated thing"). You will often handle errors and have cleanup to do. iter supports both of these. You can create a fully-functional iterator thusly: // Create a normal iterator parsing a file, close when done func ParseStream(reader io.ReadCloser) iter.Iterator { return iter.Iterator{ Next: func() (iterator{}, error) { item, err := Parse() if item == nil && err == nil { return nil, iter.FINISHED } return item, err }, Close: func() { reader.Close() } } } Iterating Callback iteration looks like this and handles any bookkeeping automatically: // Iterate over all values err := iterator.Each(func(item interface{}) { fmt.Println(item) }) Sometimes you need to handle errors: // Iterate over all values, terminating if processing has a problem var files []*File err := iterator.EachWithError(func(item interface{}) error { file, err = os.Open(item.(string)) if err == nil { files = append(files, file) } return err }) Raw iteration looks like this: defer iterator.Close() // allow the iterator to clean itself up item, err := iterator.Next() for err == nil { ... // do stuff with value item, err = iterator.Next() } if err != iter.FINISHED { ... // handle error } Background goroutine iteration (using channels) deserves special mention: // Produce the values in a goroutine, cleaning up safely afterwards. // This allows background iteration to continue at its own pace while we // perform blocking operations in the foreground. var responses []http.Response err := iterator.BackgroundEach(1000, func(item interface{}) error { response, err := http.Get(item.(string)) if err == nil { responses = append(list, response) } return err }) Utilities There are several useful functions provided to work with iterators: // Square the ints squaredIterator, err := iterator.Map(func(item interface{}) interface{} { item.int() * 2 }) // Select non-nil values nonNilIterator, err := iterator.Select(func(item interface{}) bool) { item != nil }) // Produce a list list, err := iterator.ToList() */ package iter golang-github-jkeiser-iter-0.0~git20200628.c8aa0ae/iterator.go000066400000000000000000000073311413727361000236120ustar00rootroot00000000000000package iter import "errors" // Error returned from Iterator.Next() when iteration is done. var FINISHED = errors.New("FINISHED") // Iterator letting you specify just two functions defining iteration. All // helper functions utilize this. // // Iterator is designed to avoid common pitfalls and to allow common Go patterns: // - errors can be yielded all the way up the chain to the caller // - safe iteration in background channels // - iterators are nil-clean (you can send nil values through the iterator) // - allows for cleanup when iteration ends early type Iterator struct { // Get the next value (or error). // nil, iter.FINISHED indicates iteration is complete. Next func() (interface{}, error) // Close out resources in case iteration terminates early. Callers are *not* // required to call this if iteration completes cleanly (but they may). Close func() } // Use to create an iterator from a single closure. retun nil to stop iteration. // // iter.NewSimple(func() interface{} { return x*2 }) func NewSimple(f func() interface{}) Iterator { return Iterator{ Next: func() (interface{}, error) { val := f() if val == nil { return nil, FINISHED } else { return f, nil } }, Close: func() {}, } } // Transform values in the input. // iterator.Map(func(item interface{}) interface{} { item.(int) * 2 }) func (iterator Iterator) Map(mapper func(interface{}) interface{}) Iterator { return Iterator{ Next: func() (interface{}, error) { item, err := iterator.Next() if err != nil { return nil, err } return mapper(item), nil }, Close: func() { iterator.Close() }, } } // Filter values from the input. // iterator.Select(func(item interface{}) bool { item.(int) > 10 }) func (iterator Iterator) Select(selector func(interface{}) bool) Iterator { return Iterator{ Next: func() (interface{}, error) { for { item, err := iterator.Next() if err != nil { return nil, err } if selector(item) { return item, nil } } }, Close: func() { iterator.Close() }, } } // Concatenate multiple iterators together in one. // d := iter.Concat(a, b, c) func Concat(iterators ...Iterator) Iterator { i := 0 return Iterator{ Next: func() (interface{}, error) { if i >= len(iterators) { return nil, FINISHED } item, err := iterators[i].Next() if err == FINISHED { i++ return nil, err } else if err != nil { i = len(iterators) return nil, err } return item, nil }, Close: func() { // Close out remaining iterators for ; i < len(iterators); i++ { iterators[i].Close() } }, } } // Iterate over all values, calling a user-defined function for each one. // iterator.Each(func(item interface{}) { fmt.Println(item) }) func (iterator Iterator) Each(processor func(interface{})) error { defer iterator.Close() item, err := iterator.Next() for err == nil { processor(item) item, err = iterator.Next() } return err } // Iterate over all values, calling a user-defined function for each one. // If an error is returned from the function, iteration terminates early // and the EachWithError function returns the error. // iterator.EachWithError(func(item interface{}) error { return http.Get(item.(string)) }) func (iterator Iterator) EachWithError(processor func(interface{}) error) error { defer iterator.Close() item, err := iterator.Next() for err == nil { err = processor(item) if err == nil { item, err = iterator.Next() } } return err } // Convert the iteration directly to a slice. // var list []interface{} // list, err := iterator.ToSlice() func (iterator Iterator) ToSlice() (list []interface{}, err error) { err = iterator.Each(func(item interface{}) { list = append(list, item) }) return list, err }