diff --git a/lib/iterator/index.go b/lib/iterator/index.go new file mode 100644 index 0000000..dd66005 --- /dev/null +++ b/lib/iterator/index.go @@ -0,0 +1,51 @@ +package iterator + +// IndexProvider implements a subset of the Interator interface +type IndexProvider interface { + StartIndex() int + EndIndex() int +} + +// FixBounds will force the index i to either its lower- or upper-bound value +// if out-of-bound +func FixBounds(i, lower, upper int) int { + switch { + case i > upper: + i = upper + case i < lower: + i = lower + } + return i +} + +// WrapBounds will wrap the index i around its upper- or lower-bound if +// out-of-bound +func WrapBounds(i, lower, upper int) int { + switch { + case i > upper: + i = lower + (i-upper-1)%upper + case i < lower: + i = upper - (lower-i-1)%upper + } + return i +} + +type BoundsCheckFunc func(int, int, int) int + +// MoveIndex moves the index variable idx forward by delta steps and ensures +// that the boundary policy as defined by the CheckBoundsFunc is enforced. +// +// If CheckBoundsFunc is nil, fix boundary checks are performed. +func MoveIndex(idx, delta int, indexer IndexProvider, cb BoundsCheckFunc) int { + lower, upper := indexer.StartIndex(), indexer.EndIndex() + sign := 1 + if upper < lower { + lower, upper = upper, lower + sign = -1 + } + result := idx + sign*delta + if cb == nil { + return FixBounds(result, lower, upper) + } + return cb(result, lower, upper) +} diff --git a/lib/iterator/index_test.go b/lib/iterator/index_test.go new file mode 100644 index 0000000..c9b7930 --- /dev/null +++ b/lib/iterator/index_test.go @@ -0,0 +1,133 @@ +package iterator_test + +import ( + "testing" + + "git.sr.ht/~rjarry/aerc/lib/iterator" +) + +type indexer struct { + start int + end int +} + +func (ip *indexer) StartIndex() int { + return ip.start +} + +func (ip *indexer) EndIndex() int { + return ip.end +} + +func TestMoveIndex(t *testing.T) { + tests := []struct { + idx int + delta int + start int + end int + cb iterator.BoundsCheckFunc + expected int + }{ + { + idx: 0, + delta: 1, + start: 0, + end: 2, + cb: iterator.FixBounds, + expected: 1, + }, + { + idx: 0, + delta: 5, + start: 0, + end: 2, + cb: iterator.FixBounds, + expected: 2, + }, + { + idx: 0, + delta: -1, + start: 0, + end: 2, + cb: iterator.FixBounds, + expected: 0, + }, + { + idx: 0, + delta: 2, + start: 0, + end: 2, + cb: iterator.WrapBounds, + expected: 2, + }, + { + idx: 0, + delta: 3, + start: 0, + end: 2, + cb: iterator.WrapBounds, + expected: 0, + }, + { + idx: 0, + delta: -1, + start: 0, + end: 2, + cb: iterator.WrapBounds, + expected: 2, + }, + { + idx: 2, + delta: 2, + start: 0, + end: 2, + cb: iterator.WrapBounds, + expected: 1, + }, + { + idx: 0, + delta: -2, + start: 0, + end: 2, + cb: iterator.WrapBounds, + expected: 1, + }, + { + idx: 1, + delta: 1, + start: 2, + end: 0, + cb: iterator.FixBounds, + expected: 0, + }, + { + idx: 0, + delta: 1, + start: 2, + end: 0, + cb: iterator.FixBounds, + expected: 0, + }, + { + idx: 0, + delta: 1, + start: 2, + end: 0, + cb: iterator.WrapBounds, + expected: 2, + }, + } + + for i, test := range tests { + idx := iterator.MoveIndex( + test.idx, + test.delta, + &indexer{test.start, test.end}, + test.cb, + ) + if idx != test.expected { + t.Errorf("test %d [%#v] failed: got %d but expected %d", + i, test, idx, test.expected) + } + } +}