iterator: implement iterators over uid/thread data

Implement an iterator framework for uid and thread data. Iterators
ensure that the underlying data is accessed in the right order and
provide an abstraction of the index handling.

Iterators should be used when the order of the uids/threads is
important.

Signed-off-by: Koni Marti <koni.marti@gmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Koni Marti 2022-10-20 16:43:40 +02:00 committed by Robin Jarry
parent f20933f512
commit c83ffabf38
3 changed files with 255 additions and 0 deletions

125
lib/iterator/impl.go Normal file
View file

@ -0,0 +1,125 @@
package iterator
import (
"errors"
"git.sr.ht/~rjarry/aerc/worker/types"
)
// defaultFactory
type defaultFactory struct{}
func (df *defaultFactory) NewIterator(a interface{}) Iterator {
switch data := a.(type) {
case []uint32:
return &defaultUid{data: data, index: len(data)}
case []*types.Thread:
return &defaultThread{data: data, index: len(data)}
}
panic(errors.New("a iterator for this type is not implemented yet"))
}
// defaultUid
type defaultUid struct {
data []uint32
index int
}
func (du *defaultUid) Next() bool {
du.index--
return du.index >= 0
}
func (du *defaultUid) Value() interface{} {
return du.data[du.index]
}
func (du *defaultUid) StartIndex() int {
return len(du.data) - 1
}
func (du *defaultUid) EndIndex() int {
return 0
}
// defaultThread
type defaultThread struct {
data []*types.Thread
index int
}
func (dt *defaultThread) Next() bool {
dt.index--
return dt.index >= 0
}
func (dt *defaultThread) Value() interface{} {
return dt.data[dt.index]
}
func (dt *defaultThread) StartIndex() int {
return len(dt.data) - 1
}
func (dt *defaultThread) EndIndex() int {
return 0
}
// reverseFactory
type reverseFactory struct{}
func (rf *reverseFactory) NewIterator(a interface{}) Iterator {
switch data := a.(type) {
case []uint32:
return &reverseUid{data: data, index: -1}
case []*types.Thread:
return &reverseThread{data: data, index: -1}
}
panic(errors.New("an iterator for this type is not implemented yet"))
}
// reverseUid
type reverseUid struct {
data []uint32
index int
}
func (ru *reverseUid) Next() bool {
ru.index++
return ru.index < len(ru.data)
}
func (ru *reverseUid) Value() interface{} {
return ru.data[ru.index]
}
func (ru *reverseUid) StartIndex() int {
return 0
}
func (ru *reverseUid) EndIndex() int {
return len(ru.data) - 1
}
// reverseThread
type reverseThread struct {
data []*types.Thread
index int
}
func (rt *reverseThread) Next() bool {
rt.index++
return rt.index < len(rt.data)
}
func (rt *reverseThread) Value() interface{} {
return rt.data[rt.index]
}
func (rt *reverseThread) StartIndex() int {
return 0
}
func (rt *reverseThread) EndIndex() int {
return len(rt.data) - 1
}

35
lib/iterator/iterator.go Normal file
View file

@ -0,0 +1,35 @@
package iterator
// Factory is the interface that wraps the NewIterator method. The
// NewIterator() creates either UID or thread iterators and ensures that both
// types of iterators implement the same iteration direction.
type Factory interface {
NewIterator(a interface{}) Iterator
}
// Iterator implements an interface for iterating over UID or thread data. If
// Next() returns true, the current value of the iterator can be read with
// Value(). The return value of Value() is an interface{} type which needs to
// be cast to the correct type.
//
// The iterators are implemented such that the first returned value always
// represents the top message in the message list. Hence, StartIndex() would
// return the index of the top message whereas EndIndex() returns the index of
// message at the bottom of the list.
type Iterator interface {
Next() bool
Value() interface{}
StartIndex() int
EndIndex() int
}
// NewFactory creates an iterator factory. When reverse is true, the iterators
// are reversed in the sense that the lowest UID messages are displayed at the
// top of the message list. Otherwise, the default order is with the highest
// UID message on top.
func NewFactory(reverse bool) Factory {
if reverse {
return &reverseFactory{}
}
return &defaultFactory{}
}

View file

@ -0,0 +1,95 @@
package iterator_test
import (
"testing"
"git.sr.ht/~rjarry/aerc/lib/iterator"
"git.sr.ht/~rjarry/aerc/worker/types"
)
func toThreads(uids []uint32) []*types.Thread {
threads := make([]*types.Thread, len(uids))
for i, u := range uids {
threads[i] = &types.Thread{Uid: u}
}
return threads
}
func TestIterator_DefaultFactory(t *testing.T) {
input := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9}
want := []uint32{9, 8, 7, 6, 5, 4, 3, 2, 1}
factory := iterator.NewFactory(false)
if factory == nil {
t.Errorf("could not create factory")
}
start, end := len(input)-1, 0
checkUids(t, factory, input, want, start, end)
checkThreads(t, factory, toThreads(input),
toThreads(want), start, end)
}
func TestIterator_ReverseFactory(t *testing.T) {
input := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9}
want := []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9}
factory := iterator.NewFactory(true)
if factory == nil {
t.Errorf("could not create factory")
}
start, end := 0, len(input)-1
checkUids(t, factory, input, want, start, end)
checkThreads(t, factory, toThreads(input),
toThreads(want), start, end)
}
func checkUids(t *testing.T, factory iterator.Factory,
input []uint32, want []uint32, start, end int,
) {
label := "uids"
got := make([]uint32, 0)
iter := factory.NewIterator(input)
for iter.Next() {
got = append(got, iter.Value().(uint32))
}
if len(got) != len(want) {
t.Errorf(label + "number of elements not correct")
}
for i, u := range want {
if got[i] != u {
t.Errorf(label + "order not correct")
}
}
if iter.StartIndex() != start {
t.Errorf(label + "start index not correct")
}
if iter.EndIndex() != end {
t.Errorf(label + "end index not correct")
}
}
func checkThreads(t *testing.T, factory iterator.Factory,
input []*types.Thread, want []*types.Thread, start, end int,
) {
label := "threads"
got := make([]*types.Thread, 0)
iter := factory.NewIterator(input)
for iter.Next() {
got = append(got, iter.Value().(*types.Thread))
}
if len(got) != len(want) {
t.Errorf(label + "number of elements not correct")
}
for i, th := range want {
if got[i].Uid != th.Uid {
t.Errorf(label + "order not correct")
}
}
if iter.StartIndex() != start {
t.Errorf(label + "start index not correct")
}
if iter.EndIndex() != end {
t.Errorf(label + "end index not correct")
}
}