#![no_std]
mod trait_impls_by_crate;
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CollectionCursor<Tape> {
inner: Tape,
pos: usize,
}
impl<Tape> CollectionCursor<Tape> {
pub fn new(inner: Tape) -> Self {
Self {
inner,
pos: Default::default(),
}
}
pub fn position(&self) -> usize {
self.pos
}
pub fn get_ref(&self) -> &Tape {
&self.inner
}
pub fn get_mut(&mut self) -> &mut Tape {
&mut self.inner
}
pub fn into_inner(self) -> Tape {
self.inner
}
}
impl<Tape: IndexableCollection> CollectionCursor<Tape> {
pub fn is_cursor_at_end(&self) -> bool {
self.pos == self.inner.len()
}
pub fn seek(&mut self, pos: SeekFrom) -> Option<usize> {
let collection_len = self.inner.len();
let desired_position = match pos {
SeekFrom::Start(p) => Some(p),
SeekFrom::End(p) => collection_len.checked_add_signed(p),
SeekFrom::Current(p) => self.pos.checked_add_signed(p),
};
desired_position
.filter(|&pos| pos <= collection_len)
.inspect(|&new_pos| self.pos = new_pos)
}
pub fn clamp_to_last_item(&mut self) {
self.pos = self
.pos
.min(self.inner.len().saturating_sub(1));
}
pub fn clamp_to_end(&mut self) {
self.pos = self.pos.min(self.inner.len());
}
pub fn seek_to_start(&mut self) {
self.pos = 0;
}
pub fn seek_backward_one(&mut self) -> bool {
self.seek_relative(-1).is_some()
}
pub fn seek_relative(&mut self, offset: isize) -> Option<usize> {
self.seek(SeekFrom::Current(offset))
}
pub fn seek_forward_one(&mut self) -> bool {
self.seek_relative(1).is_some()
}
pub fn seek_to_last_item(&mut self) {
self.pos = self.inner.len().saturating_sub(1);
}
pub fn seek_to_end(&mut self) {
self.pos = self.inner.len();
}
pub fn get_item_at_cursor(&self) -> Option<&Tape::Item> {
self.inner.get_item(self.pos)
}
}
impl<Tape: IndexableCollectionMut> CollectionCursor<Tape> {
pub fn get_item_at_cursor_mut(&mut self) -> Option<&mut Tape::Item> {
self.inner.get_item_mut(self.pos)
}
pub fn set_item_at_cursor(&mut self, item: Tape::Item) {
self.inner.set_item(self.pos, item);
}
}
impl<Tape: IndexableCollectionResizable> CollectionCursor<Tape> {
pub fn clear(&mut self) {
self.inner.clear();
self.pos = 0;
}
pub fn insert_item_at_cursor(&mut self, item: Tape::Item) {
self.inner.insert_item(self.pos, item);
}
pub fn set_or_insert_item_at_cursor(&mut self, item: Tape::Item) {
if self.is_cursor_at_end() {
self.insert_item_at_cursor(item);
} else {
self.set_item_at_cursor(item);
}
}
pub fn remove_item_at_cursor(&mut self) -> Option<Tape::Item> {
self.inner.remove_item(self.pos)
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SeekFrom {
Start(usize),
End(isize),
Current(isize),
}
#[allow(
clippy::len_without_is_empty,
reason = "While is_empty would normally be useful, we don't have a use for it here"
)]
pub trait IndexableCollection {
type Item;
fn len(&self) -> usize;
fn get_item(&self, index: usize) -> Option<&Self::Item>;
}
pub trait IndexableCollectionMut: IndexableCollection {
fn get_item_mut(&mut self, index: usize) -> Option<&mut Self::Item>;
fn set_item(&mut self, index: usize, element: Self::Item);
}
pub trait IndexableCollectionResizable: IndexableCollectionMut {
fn insert_item(&mut self, index: usize, element: Self::Item);
fn remove_item(&mut self, index: usize) -> Option<Self::Item>;
fn clear(&mut self);
}
#[cfg(test)]
mod collection_cursor_tests {
extern crate alloc;
use super::*;
use alloc::vec::Vec;
type TestCollection = CollectionCursor<TestVec>;
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct TestVec {
inner: Vec<i32>,
}
impl IndexableCollection for TestVec {
type Item = i32;
fn len(&self) -> usize {
self.inner.len()
}
fn get_item(&self, index: usize) -> Option<&Self::Item> {
self.inner.get(index)
}
}
impl IndexableCollectionMut for TestVec {
fn get_item_mut(&mut self, index: usize) -> Option<&mut Self::Item> {
self.inner.get_mut(index)
}
fn set_item(&mut self, index: usize, element: Self::Item) {
self.inner[index] = element;
}
}
impl IndexableCollectionResizable for TestVec {
fn insert_item(&mut self, index: usize, element: Self::Item) {
self.inner.insert(index, element);
}
fn remove_item(&mut self, index: usize) -> Option<Self::Item> {
(index < self.inner.len()).then(|| self.inner.remove(index))
}
fn clear(&mut self) {
self.inner.clear();
}
}
fn test_vec() -> TestVec {
let res = TestVec {
inner: Vec::from([0, 1, 2, 3, 4, 5, 9, 8, 7, 6]),
};
assert_eq!(res.inner.len(), 10);
res
}
fn test_collection() -> TestCollection {
let res = CollectionCursor {
inner: self::test_vec(),
pos: Default::default(),
};
assert_eq!(res.pos, Default::default());
res
}
#[test]
fn new() {
let new_collection = CollectionCursor::new(self::test_vec());
let test_collection = self::test_collection();
assert_eq!(new_collection, test_collection);
}
#[test]
fn position() {
let mut collection = self::test_collection();
assert_eq!(collection.position(), 0);
collection.pos = 5;
assert_eq!(collection.position(), 5);
collection.pos = usize::MAX;
assert_eq!(collection.position(), usize::MAX);
}
#[test]
fn get_ref() {
let collection = self::test_collection();
assert_eq!(collection.get_ref(), &self::test_vec());
}
#[test]
fn get_mut() {
let mut collection = self::test_collection();
assert_eq!(collection.get_mut(), &mut self::test_vec());
}
#[test]
fn into_inner() {
let collection = self::test_collection();
assert_eq!(collection.into_inner(), self::test_vec());
}
#[test]
fn is_cursor_at_end() {
let mut collection = self::test_collection();
assert!(
!collection.is_cursor_at_end(),
"should return false when not at end"
);
collection.pos = collection.inner.len();
assert!(
collection.is_cursor_at_end(),
"should return true when at end"
);
}
#[test]
fn seek() {
fn inner(
collection: &mut TestCollection,
seek_from: SeekFrom,
expected_result: Option<usize>,
expected_pos: usize,
error_message: &'static str,
) {
let new_pos = collection.seek(seek_from);
assert_eq!(new_pos, expected_result, "{error_message}");
assert_eq!(
collection.pos, expected_pos,
"the seek did not place the cursor at the expected position"
);
}
let mut collection = self::test_collection();
inner(
&mut collection,
SeekFrom::Start(3),
Some(3),
3,
"`Start(x)` should move the cursor within the bounds of the collection",
);
inner(
&mut collection,
SeekFrom::Start(0),
Some(0),
0,
"`Start(0)` should move the cursor to the start of the collection",
);
inner(
&mut collection,
SeekFrom::Current(0),
Some(0),
0,
"`Current(0) shouldn't move the cursor",
);
inner(
&mut collection,
SeekFrom::Current(7),
Some(7),
7,
"`Current(x)` should move the cursor forward within the bounds of the collection",
);
inner(
&mut collection,
SeekFrom::Current(-2),
Some(5),
5,
"`Current(-x) should move the cursor backwards within the bounds of the collection",
);
inner(
&mut collection,
SeekFrom::Current(-5),
Some(0),
0,
"`Current(-current_pos) should move the cursor to the start of the collection",
);
inner(
&mut collection,
SeekFrom::End(0),
Some(10),
10,
"`End(0)` should move the cursor to one past the end of the collection",
);
inner(
&mut collection,
SeekFrom::End(-1),
Some(9),
9,
"`End(-1)` should move the cursor to the end of the collection",
);
inner(
&mut collection,
SeekFrom::End(-5),
Some(5),
5,
"`End(-x)` should move the cursor within the bounds of the collection",
);
inner(
&mut collection,
SeekFrom::End(-10),
Some(0),
0,
"`End(-len)` should move the cursor to the start of the collection",
);
inner(
&mut collection,
SeekFrom::Start(7),
Some(7),
7,
"this shouldn't fail",
);
inner(
&mut collection,
SeekFrom::Start(usize::MAX),
None,
7,
"`Start(x)` shouldn't move if doing so would put it past one index past the end of the collection",
);
inner(
&mut collection,
SeekFrom::Current(-isize::MAX),
None,
7,
"`Current(-X)` shouldn't move if doing so would put it past the start of the collection",
);
inner(
&mut collection,
SeekFrom::Current(isize::MAX),
None,
7,
"`Current(x)` shouldn't move if doing so would put it past the end of the collection",
);
inner(
&mut collection,
SeekFrom::End(1),
None,
7,
"`End(1)` shouldn't move if doing so would put it past the end of the collection",
);
inner(
&mut collection,
SeekFrom::End(-isize::MAX),
None,
7,
"`End(-x)` shouldn't move if doing so would put it past the start of the collection",
);
inner(
&mut collection,
SeekFrom::End(isize::MAX),
None,
7,
"`End(x)` shouldn't move if doing so would put it past the end of the collection",
);
}
fn __clamp_to(
clamp_method: fn(&mut TestCollection),
first_test_expected_pos: usize,
first_test_error_message: &'static str,
) {
let mut collection = self::test_collection();
collection.pos = usize::MAX;
clamp_method(&mut collection);
assert_eq!(
collection.pos, first_test_expected_pos,
"{}",
first_test_error_message
);
collection.pos = 2;
clamp_method(&mut collection);
assert_eq!(
collection.pos, 2,
"shouldn't move the cursor when already within the bounds of the collection"
);
collection = CollectionCursor::new(TestVec::default());
collection.pos = 0;
clamp_method(&mut collection);
assert_eq!(
collection.pos, 0,
"should keep the cursor at index `0` when given an empty collection"
);
collection.pos = usize::MAX;
clamp_method(&mut collection);
assert_eq!(
collection.pos, 0,
"should keep the cursor at index `0` when given an empty collection"
);
}
#[test]
fn clamp_to_last_item() {
let collection_len = self::test_collection().inner.len();
__clamp_to(
CollectionCursor::clamp_to_last_item,
collection_len - 1,
"should move the cursor to the last item of the collection",
);
}
#[test]
fn clamp_to_end() {
let collection_len = self::test_collection().inner.len();
__clamp_to(
CollectionCursor::clamp_to_end,
collection_len,
"should move the cursor to the one index past the last item of the collection",
);
}
#[test]
fn seek_backward_one() {
fn inner(
collection: &mut TestCollection,
should_succeed: bool,
expected_new_pos: usize,
error_message: &'static str,
) {
let seek_success = collection.seek_backward_one();
assert_eq!(seek_success, should_succeed, "{error_message}");
assert_eq!(
collection.pos, expected_new_pos,
"cursor was not moved to the expected position"
);
}
let mut collection = self::test_collection();
inner(
&mut collection,
false,
0,
"shouldn't seek past the beginning of the collection",
);
collection.pos = 5;
inner(
&mut collection,
true,
4,
"should seek backward if within the bounds of the collection",
);
collection.pos = usize::MAX;
inner(
&mut collection,
false,
usize::MAX,
"shouldn't seek if outside the bounds of the collection",
);
}
#[test]
fn seek_relative() {
fn inner(
collection: &mut TestCollection,
offset: isize,
expected_result: Option<usize>,
expected_pos: usize,
error_message: &'static str,
) {
let seek_res = collection.seek_relative(offset);
assert_eq!(seek_res, expected_result, "{error_message}");
assert_eq!(
collection.pos, expected_pos,
"the relative seek did not position the cursor as expected"
);
}
let mut collection = self::test_collection();
inner(&mut collection, 0, Some(0), 0, "shouldn't move at all");
collection.pos = 5;
inner(
&mut collection,
-2,
Some(3),
3,
"should move when within the bounds of the collection",
);
inner(
&mut collection,
2,
Some(5),
5,
"should move when within the bounds of the collection",
);
inner(&mut collection, 0, Some(5), 5, "shouldn't move at all");
collection.pos = 5;
inner(
&mut collection,
isize::MAX,
None,
5,
"shouldn't move past the end of the collection",
);
inner(
&mut collection,
-isize::MAX,
None,
5,
"shouldn't move before the beginning of the collection",
);
collection.pos = usize::MAX;
inner(
&mut collection,
5,
None,
usize::MAX,
"shouldn't move when outside the bounds of the collection",
);
inner(
&mut collection,
-5,
None,
usize::MAX,
"shouldn't move when outside the bounds of the collection",
);
}
#[test]
fn seek_forward_one() {
fn inner(
collection: &mut TestCollection,
should_succeed: bool,
expected_new_pos: usize,
error_message: &'static str,
) {
let seek_success = collection.seek_forward_one();
assert_eq!(seek_success, should_succeed, "{error_message}");
assert_eq!(
collection.pos, expected_new_pos,
"cursor was not moved to the expected position"
);
}
let mut collection = self::test_collection();
inner(
&mut collection,
true,
1,
"should seek forward when an item is available",
);
collection.pos = 5;
inner(
&mut collection,
true,
6,
"should seek forward when an item is available",
);
let collection_len = collection.inner.len();
collection.pos = collection_len - 1;
inner(
&mut collection,
true,
collection_len,
"should seek one past the end of the bounds of the collection",
);
collection.pos = collection_len;
inner(
&mut collection,
false,
collection_len,
"shouldn't seek past one index past the end of the bounds of the collection",
);
collection.pos = usize::MAX;
inner(
&mut collection,
false,
usize::MAX,
"shouldn't seek at all past `len()` of the collection",
);
}
fn __seek_to(
seek_method: fn(&mut TestCollection),
initial_pos: usize,
expected_new_pos: usize,
) {
let mut collection = self::test_collection();
collection.pos = initial_pos;
seek_method(&mut collection);
assert_eq!(
collection.pos, expected_new_pos,
"should seek to the expected position"
);
}
#[test]
fn seek_to_start() {
__seek_to(CollectionCursor::seek_to_start, 5, 0);
__seek_to(CollectionCursor::seek_to_start, usize::MAX, 0);
}
#[test]
fn seek_to_last_item() {
let expected_pos = self::test_vec().len() - 1;
__seek_to(CollectionCursor::seek_to_last_item, 5, expected_pos);
__seek_to(
CollectionCursor::seek_to_last_item,
usize::MAX,
expected_pos,
);
}
#[test]
fn seek_to_end() {
let expected_pos = self::test_collection().inner.len();
__seek_to(CollectionCursor::seek_to_end, 5, expected_pos);
__seek_to(CollectionCursor::seek_to_end, usize::MAX, expected_pos);
}
#[test]
fn get_item_at_cursor() {
let test_vec = self::test_vec();
let mut collection = self::test_collection();
for i in 0..=(test_vec.len()) {
collection.pos = i;
let test_vec_get = test_vec.inner.get(i);
let collection_get = collection.get_item_at_cursor();
assert_eq!(
test_vec_get, collection_get,
"should get the same item from the same index (index = `{i}`)"
);
}
}
#[test]
fn clear() {
let mut test_vec = self::test_vec();
let mut collection = self::test_collection();
test_vec.clear();
collection.clear();
assert_eq!(collection.inner, test_vec);
}
#[test]
fn get_item_at_cursor_mut() {
let mut test_vec = self::test_vec();
let mut collection = self::test_collection();
for i in 0..=(test_vec.len()) {
collection.pos = i;
let test_vec_get = test_vec.inner.get_mut(i);
let collection_get = collection.get_item_at_cursor_mut();
assert_eq!(
test_vec_get, collection_get,
"should get the same item from the same index (index = `{i}`)"
);
}
}
#[test]
fn set_item_at_cursor() {
let mut test_vec = self::test_vec();
let mut collection = self::test_collection();
const AT_POS: usize = 5;
const TO_VALUE: i32 = 52345;
test_vec.inner[AT_POS] = TO_VALUE;
collection.pos = AT_POS;
collection.set_item_at_cursor(TO_VALUE);
assert_eq!(
collection.inner.inner.get(AT_POS),
Some(&TO_VALUE),
"should modify the inner collection to have the correct value at the head"
);
assert_eq!(collection.inner, test_vec, "should set only one value");
}
#[test]
fn insert_item_at_cursor() {
let mut test_vec = self::test_vec();
let mut collection = self::test_collection();
const AT_POS: usize = 5;
const TO_VALUE: i32 = 52345;
test_vec.inner.insert(AT_POS, TO_VALUE);
collection.pos = AT_POS;
collection.insert_item_at_cursor(TO_VALUE);
assert_eq!(
collection.inner.inner.get(AT_POS),
Some(&TO_VALUE),
"should modify the inner collection to have the correct value at the head"
);
assert_eq!(collection.inner, test_vec, "should insert only one value");
}
#[test]
fn set_or_insert_item_at_cursor() {
fn should_set(
collection: &mut TestCollection,
test_vec: &mut TestVec,
at_pos: usize,
to_value: i32,
) {
test_vec.inner[at_pos] = to_value;
collection.pos = at_pos;
collection.set_or_insert_item_at_cursor(to_value);
assert_eq!(
collection.inner.inner.get(at_pos),
Some(&to_value),
"should modify the inner collection to have the correct value at the head"
);
assert_eq!(
collection.inner, *test_vec,
"should set the value at the cursor's position, when the cursor is not at the end"
);
}
fn should_insert(
collection: &mut TestCollection,
test_vec: &mut TestVec,
at_pos: usize,
to_value: i32,
) {
test_vec.inner.insert(at_pos, to_value);
collection.pos = at_pos;
collection.set_or_insert_item_at_cursor(to_value);
assert_eq!(
collection.inner.inner.get(at_pos),
Some(&to_value),
"should modify the inner collection to have the correct value at the head"
);
assert_eq!(
collection.inner, *test_vec,
"should insert the value at the end, when the cursor is at the end"
);
}
let mut collection = self::test_collection();
let mut test_vec = self::test_vec();
should_set(&mut collection, &mut test_vec, 5, 52345);
should_insert(&mut collection, &mut test_vec, 10, 67241);
should_insert(&mut collection, &mut test_vec, 11, 34512);
should_set(&mut collection, &mut test_vec, 7, 51263);
}
#[test]
fn remove_item_at_cursor() {
const AT_POS: usize = 5;
let mut test_vec = self::test_vec();
let mut collection = self::test_collection();
let test_vec_res = test_vec.inner.remove(5);
collection.pos = AT_POS;
let collection_res = collection.remove_item_at_cursor();
assert_eq!(
collection_res,
Some(test_vec_res),
"should return the right value"
);
assert_eq!(collection.inner, test_vec, "should remove only one value");
collection.pos = collection.inner.len() * 2;
let collection_res = collection.remove_item_at_cursor();
assert_eq!(
collection_res, None,
"should return `None` if the head was out-of-bounds"
);
}
}