remove load_utf8, move methods to trait
Some checks failed
Rust / build (pull_request) Failing after 1m7s

This commit is contained in:
Vinzenz Schroeter 2025-07-09 21:40:03 +02:00
parent e45d41e8b1
commit bbd1d643b9
8 changed files with 304 additions and 282 deletions

View file

@ -285,43 +285,23 @@ impl From<Bitmap> for DisplayBitVec {
impl TryFrom<&ValueGrid<bool>> for Bitmap {
type Error = ();
/// Converts a grid of [bool]s into a [Bitmap].
///
/// Returns Err if the width of `value` is not dividable by 8
fn try_from(value: &ValueGrid<bool>) -> Result<Self, Self::Error> {
let mut result = Self::new(value.width(), value.height()).ok_or(())?;
for (mut to, from) in result.iter_mut().zip(value.iter()) {
*to = *from;
}
result.deref_assign(value);
Ok(result)
}
}
impl<G: Grid<bool>> TryFrom<&Window<'_, bool, G>> for Bitmap {
impl<T: Grid<bool>> TryFrom<&Window<'_, bool, T>> for Bitmap {
type Error = ();
fn try_from(value: &Window<'_, bool, G>) -> Result<Self, Self::Error> {
fn try_from(value: &Window<bool, T>) -> Result<Self, Self::Error> {
let mut result = Self::new(value.width(), value.height()).ok_or(())?;
for y in 0..value.height() {
for x in 0..value.width() {
result.set(x, y, value.get(x, y));
}
}
result.deref_assign(value);
Ok(result)
}
}
impl From<&Bitmap> for ValueGrid<bool> {
/// Converts a [Bitmap] into a grid of [bool]s.
fn from(value: &Bitmap) -> Self {
let mut result = Self::new(value.width(), value.height());
for (to, from) in result.iter_mut().zip(value.iter()) {
*to = *from;
}
result
}
}
impl From<&Bitmap> for Payload {
fn from(value: &Bitmap) -> Self {
value.bit_vec.as_raw_slice().into()

View file

@ -100,7 +100,7 @@ mod tests {
let mut grid = BrightnessGrid::new(2, 2);
grid.set(1, 0, Brightness::MIN);
grid.set(0, 1, Brightness::MAX);
let actual = ValueGrid::from(&grid);
let actual: ValueGrid<u8> = ValueGrid::from(&grid);
assert_eq!(actual.data_ref(), &[11, 0, 11, 11]);
}

View file

@ -1,6 +1,4 @@
use crate::{
Grid, GridMut, SetValueSeriesError, TryLoadValueGridError, ValueGrid,
};
use crate::{CharGridMutExt, TryLoadValueGridError, ValueGrid};
use std::string::FromUtf8Error;
/// A grid containing UTF-8 characters.
@ -60,179 +58,10 @@ impl CharGrid {
}
result
}
/// Loads a [`CharGrid`] with the specified dimensions from the provided UTF-8 bytes.
///
/// returns: [`CharGrid`] that contains the provided data, or [`FromUtf8Error`] if the data is invalid.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::load_utf8(2, 2, [97u8, 98, 99, 100].to_vec());
/// ```
pub fn load_utf8(
width: usize,
height: usize,
bytes: Vec<u8>,
) -> Result<CharGrid, LoadUtf8Error> {
let s: Vec<char> = String::from_utf8(bytes)?.chars().collect();
CharGrid::load(width, height, &s).ok_or(LoadUtf8Error::TryLoadError(
TryLoadValueGridError::InvalidDimensions,
))
}
}
pub trait CharGridExt: Grid<char> {
/// Copies a column from the grid as a String.
///
/// Returns [None] if x is out of bounds.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::from("ab\ncd");
/// let col = grid.get_col_str(0).unwrap(); // "ac"
/// ```
#[must_use]
fn get_col_str(&self, x: usize) -> Option<String> {
Some((0..self.height()).map(|y| self.get(x, y)).collect())
}
/// Copies a row from the grid as a String.
///
/// Returns [None] if y is out of bounds.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let grid = CharGrid::from("ab\ncd");
/// let row = grid.get_row_str(0).unwrap(); // "ab"
/// ```
#[must_use]
fn get_row_str(&self, y: usize) -> Option<String> {
Some((0..self.width()).map(|x| self.get(x, y)).collect())
}
}
#[inherent::inherent]
impl CharGridExt for CharGrid {
#[must_use]
pub fn get_col_str(&self, x: usize) -> Option<String> {
Some(String::from_iter(self.get_col(x)?))
}
#[must_use]
pub fn get_row_str(&self, y: usize) -> Option<String> {
Some(String::from_iter(self.get_row(y)?))
}
}
pub trait CharGridMutExt: GridMut<char> {
/// Overwrites a row in the grid with a str.
///
/// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
///
/// # Examples
///
/// ```
/// # use servicepoint::{CharGrid, CharGridMutExt};
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_row_str(0, "ef").unwrap();
/// ```
fn set_row_str(
&mut self,
y: usize,
value: &str,
) -> Result<(), SetValueSeriesError> {
let width = self.width();
let len = value.len();
if len > width {
return Err(SetValueSeriesError::InvalidLength {
actual: len,
expected: width,
});
}
let height = self.height();
if y >= height {
return Err(SetValueSeriesError::OutOfBounds {
index: y,
size: height,
});
}
let chars = value.chars().take(width);
for (x, c) in chars.enumerate() {
self.set(x, y, c);
}
Ok(())
}
/// Overwrites a column in the grid with a str.
///
/// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
///
/// # Examples
///
/// ```
/// # use servicepoint::CharGrid;
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_col_str(0, "ef").unwrap();
/// ```
fn set_col_str(
&mut self,
x: usize,
value: &str,
) -> Result<(), SetValueSeriesError> {
let height = self.height();
let len = value.len();
if len > height {
return Err(SetValueSeriesError::InvalidLength {
actual: len,
expected: height,
});
}
let width = self.width();
if x >= width {
return Err(SetValueSeriesError::OutOfBounds {
index: x,
size: width,
});
}
let chars = value.chars().take(height);
for (y, c) in chars.enumerate() {
self.set(x, y, c);
}
Ok(())
}
}
#[inherent::inherent]
impl CharGridMutExt for CharGrid {
pub fn set_col_str(
&mut self,
x: usize,
value: &str,
) -> Result<(), SetValueSeriesError>;
pub fn set_row_str(
&mut self,
y: usize,
value: &str,
) -> Result<(), SetValueSeriesError>;
}
#[derive(Debug, thiserror::Error)]
pub enum LoadUtf8Error {
enum LoadUtf8Error {
#[error(transparent)]
FromUtf8Error(#[from] FromUtf8Error),
#[error(transparent)]
@ -308,7 +137,7 @@ impl From<&CharGrid> for Vec<u8> {
/// let grid = CharGrid::from("ab\ncd");
/// let height = grid.height();
/// let width = grid.width();
/// let grid = CharGrid::load_utf8(width, height, grid.into());
/// let bytes = Vec::<u8>::from(grid);
/// ```
fn from(value: &CharGrid) -> Self {
value.iter().collect::<String>().into_bytes()
@ -325,6 +154,8 @@ impl From<CharGrid> for Vec<u8> {
#[cfg(test)]
mod test {
use super::*;
use crate::{CharGridExt, SetValueSeriesError};
#[test]
fn col_str() {
let mut grid = CharGrid::new(2, 3);
@ -364,8 +195,15 @@ mod test {
fn round_trip_bytes() {
let grid = CharGrid::from("Hello\0\nWorld!\n...\0\0\0");
let bytes: Vec<u8> = grid.clone().into();
let copy =
CharGrid::load_utf8(grid.width(), grid.height(), bytes).unwrap();
let copy = CharGrid::load(
grid.width(),
grid.height(),
&String::from_utf8(bytes)
.unwrap()
.chars()
.collect::<Vec<_>>(),
)
.unwrap();
assert_eq!(grid, copy);
}

View file

@ -0,0 +1,145 @@
use crate::{Grid, GridMut, SetValueSeriesError};
/// Extension methods for any [`Grid<char>`]
pub trait CharGridExt {
/// Copies a column from the grid as a String.
///
/// Returns [None] if x is out of bounds.
///
/// # Examples
///
/// ```
/// # use servicepoint::{CharGrid, CharGridExt};
/// let grid = CharGrid::from("ab\ncd");
/// let col = grid.get_col_str(0).unwrap(); // "ac"
/// ```
#[must_use]
fn get_col_str(&self, x: usize) -> Option<String>;
/// Copies a row from the grid as a String.
///
/// Returns [None] if y is out of bounds.
///
/// # Examples
///
/// ```
/// # use servicepoint::{CharGrid, CharGridExt};
/// let grid = CharGrid::from("ab\ncd");
/// let row = grid.get_row_str(0).unwrap(); // "ab"
/// ```
#[must_use]
fn get_row_str(&self, y: usize) -> Option<String>;
}
/// Extension methods for any [`GridMut<char>`].
pub trait CharGridMutExt {
/// Overwrites a row in the grid with a str.
///
/// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
///
/// # Examples
///
/// ```
/// # use servicepoint::{CharGrid, CharGridMutExt};
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_row_str(0, "ef").unwrap();
/// ```
fn set_row_str(
&mut self,
y: usize,
value: &str,
) -> Result<(), SetValueSeriesError>;
/// Overwrites a column in the grid with a str.
///
/// Returns [`SetValueSeriesError`] if y is out of bounds or `row` is not of the correct size.
///
/// # Examples
///
/// ```
/// # use servicepoint::{CharGrid, CharGridMutExt};
/// let mut grid = CharGrid::from("ab\ncd");
/// grid.set_col_str(0, "ef").unwrap();
/// ```
fn set_col_str(
&mut self,
x: usize,
value: &str,
) -> Result<(), SetValueSeriesError>;
}
impl<G: Grid<char>> CharGridExt for G {
#[must_use]
fn get_col_str(&self, x: usize) -> Option<String> {
Some(String::from_iter(self.get_col(x)?))
}
#[must_use]
fn get_row_str(&self, y: usize) -> Option<String> {
Some(String::from_iter(self.get_row(y)?))
}
}
impl<G: GridMut<char>> CharGridMutExt for G {
fn set_row_str(
&mut self,
y: usize,
value: &str,
) -> Result<(), SetValueSeriesError> {
let width = self.width();
let len = value.len();
if len > width {
return Err(SetValueSeriesError::InvalidLength {
actual: len,
expected: width,
});
}
let height = self.height();
if y >= height {
return Err(SetValueSeriesError::OutOfBounds {
index: y,
size: height,
});
}
let chars = value.chars().take(width);
for (x, c) in chars.enumerate() {
self.set(x, y, c);
}
Ok(())
}
fn set_col_str(
&mut self,
x: usize,
value: &str,
) -> Result<(), SetValueSeriesError> {
let height = self.height();
let len = value.len();
if len > height {
return Err(SetValueSeriesError::InvalidLength {
actual: len,
expected: height,
});
}
let width = self.width();
if x >= width {
return Err(SetValueSeriesError::OutOfBounds {
index: x,
size: width,
});
}
let chars = value.chars().take(height);
for (y, c) in chars.enumerate() {
self.set(x, y, c);
}
Ok(())
}
}

View file

@ -53,6 +53,40 @@ pub trait Grid<T> {
let height = self.height();
assert!(y < height, "cannot access index [{x}, {y}] because y is outside of bounds [0..{height})");
}
/// Copies a row from the grid.
///
/// Returns [None] if y is out of bounds.
#[must_use]
fn get_row(&self, y: usize) -> Option<Vec<T>> {
if y >= self.height() {
return None;
}
let width = self.width();
let mut row = Vec::with_capacity(width);
for x in 0..width {
row.push(self.get(x, y));
}
Some(row)
}
/// Copies a column from the grid.
///
/// Returns [None] if x is out of bounds.
#[must_use]
fn get_col(&self, x: usize) -> Option<Vec<T>> {
if x >= self.width() {
return None;
}
let height = self.height();
let mut col = Vec::with_capacity(height);
for y in 0..height {
col.push(self.get(x, y));
}
Some(col)
}
}
/// A two-dimensional mutable grid of `T`
@ -87,4 +121,33 @@ pub trait GridMut<T>: Grid<T> {
/// Sets all cells in the grid to the specified value
fn fill(&mut self, value: T);
/// Fills the grid with the values from the provided grid.
///
/// The grids have to match in size exactly.
///
/// For 1D slices the equivalent would be `*slice = other_slice`.
fn deref_assign<O: Grid<T>>(&mut self, other: &O) {
let width = self.width();
let height = self.height();
assert_eq!(
width,
other.width(),
"Cannot assign grid of width {} to a window of width {}",
other.width(),
self.width()
);
assert_eq!(
height,
other.height(),
"Cannot assign grid of height {} to a height of width {}",
other.height(),
self.height()
);
for y in 0..height {
for x in 0..width {
self.set(x, y, other.get(x, y));
}
}
}
}

View file

@ -3,6 +3,7 @@ mod bitmap;
mod brightness_grid;
mod byte_grid;
mod char_grid;
mod char_grid_ext;
mod cp437_grid;
mod data_ref;
mod grid;
@ -13,13 +14,13 @@ pub use bit_vec::{bitvec, DisplayBitVec};
pub use bitmap::{Bitmap, LoadBitmapError};
pub use brightness_grid::BrightnessGrid;
pub use byte_grid::ByteGrid;
pub use char_grid::{CharGrid, CharGridExt, CharGridMutExt, LoadUtf8Error};
pub use char_grid::CharGrid;
pub use char_grid_ext::{CharGridExt, CharGridMutExt};
pub use cp437_grid::{Cp437Grid, InvalidCharError};
pub use data_ref::DataRef;
pub use grid::{Grid, GridMut};
pub use value_grid::{
EnumerateGrid, IterGridRows, SetValueSeriesError, TryLoadValueGridError,
Value, ValueGrid,
SetValueSeriesError, TryLoadValueGridError, Value, ValueGrid,
};
pub use window::{Window, WindowMut};

View file

@ -145,7 +145,7 @@ impl<T: Value> ValueGrid<T> {
}
/// Iterate over all rows in [`ValueGrid`] top to bottom.
pub fn iter_rows(&self) -> IterGridRows<T> {
pub fn iter_rows(&self) -> impl Iterator<Item = Iter<T>> + use<'_, T> {
IterGridRows { grid: self, row: 0 }
}
@ -224,28 +224,6 @@ impl<T: Value> ValueGrid<T> {
}
}
/// Copies a row from the grid.
///
/// Returns [None] if y is out of bounds.
#[must_use]
pub fn get_row(&self, y: usize) -> Option<Vec<T>> {
self.data
.chunks_exact(self.width())
.nth(y)
.map(<[T]>::to_vec)
}
/// Copies a column from the grid.
///
/// Returns [None] if x is out of bounds.
#[must_use]
pub fn get_col(&self, x: usize) -> Option<Vec<T>> {
self.data
.chunks_exact(self.width())
.map(|row| row.get(x).copied())
.collect()
}
/// Overwrites a column in the grid.
///
/// Returns [Err] if x is out of bounds or `col` is not of the correct size.
@ -432,9 +410,19 @@ impl<T: Value> From<&ValueGrid<T>> for Vec<T> {
}
}
impl<T: Value, G: Grid<T>> From<&G> for ValueGrid<T> {
fn from(grid: &G) -> Self {
let width = grid.width();
let height = grid.height();
let mut result = Self::new(width, height);
result.deref_assign(grid);
result
}
}
/// An iterator iver the rows in a [`ValueGrid`]
#[must_use]
pub struct IterGridRows<'t, T: Value> {
struct IterGridRows<'t, T: Value> {
grid: &'t ValueGrid<T>,
row: usize,
}
@ -455,7 +443,7 @@ impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
}
}
pub struct EnumerateGrid<'t, T: Value> {
struct EnumerateGrid<'t, T: Value> {
grid: &'t ValueGrid<T>,
row: usize,
column: usize,
@ -480,18 +468,6 @@ impl<T: Value> Iterator for EnumerateGrid<'_, T> {
}
}
impl<E: Value> From<&Window<'_, E, Self>> for ValueGrid<E> {
fn from(value: &Window<'_, E, Self>) -> Self {
let mut result = Self::new(value.width(), value.height());
for y in 0..value.height() {
for x in 0..value.width() {
result.set(x, y, value.get(x, y));
}
}
result
}
}
#[cfg(test)]
mod tests {
use crate::{SetValueSeriesError, ValueGrid, *};

View file

@ -1,9 +1,5 @@
use crate::{
containers::{
absolute_bounds_to_abs_range,
char_grid::{CharGridExt, CharGridMutExt},
relative_bounds_to_abs_range,
},
containers::{absolute_bounds_to_abs_range, relative_bounds_to_abs_range},
Grid, GridMut,
};
use std::{
@ -62,7 +58,7 @@ macro_rules! define_window {
#[must_use]
pub fn split_horizontal(
self,
&'t self,
left_width: usize,
) -> Option<(
Window<'t, TElement, TGrid>,
@ -75,14 +71,17 @@ macro_rules! define_window {
self.xs.start..middle_abs,
self.ys.clone(),
)?;
let right =
Window::new(self.grid, middle_abs..self.xs.end, self.ys)?;
let right = Window::new(
self.grid,
middle_abs..self.xs.end,
self.ys.clone(),
)?;
Some((left, right))
}
#[must_use]
pub fn split_vertical(
self,
&'t self,
top_height: usize,
) -> Option<(
Window<'t, TElement, TGrid>,
@ -95,8 +94,11 @@ macro_rules! define_window {
self.xs.clone(),
self.ys.start..middle_abs,
)?;
let bottom =
Window::new(self.grid, self.xs, middle_abs..self.ys.end)?;
let bottom = Window::new(
self.grid,
self.xs.clone(),
middle_abs..self.ys.end,
)?;
Some((top, bottom))
}
}
@ -123,9 +125,6 @@ macro_rules! define_window {
self.ys.len()
}
}
#[inherent::inherent]
impl<TGrid: Grid<char>> CharGridExt for $name<'_, char, TGrid> {}
};
}
@ -151,9 +150,6 @@ impl<TElement: Copy, TGrid: GridMut<TElement>> GridMut<TElement>
}
}
#[inherent::inherent]
impl<TGrid: GridMut<char>> CharGridMutExt for WindowMut<'_, char, TGrid> {}
impl<TElement: Copy, TGrid: GridMut<TElement>> WindowMut<'_, TElement, TGrid> {
/// Creates a mutable window into the grid.
///
@ -168,40 +164,47 @@ impl<TElement: Copy, TGrid: GridMut<TElement>> WindowMut<'_, TElement, TGrid> {
WindowMut::new(self.grid, xs, ys)
}
pub fn deref_assign<O: Grid<TElement>>(&mut self, other: &O) {
let width = self.width();
let height = self.height();
assert_eq!(width, other.width(), "Cannot assign grid of width {} to a window of width {}", other.width(), self.width());
assert_eq!(height, other.height(), "Cannot assign grid of height {} to a height of width {}", other.height(), self.height());
for y in 0..height {
for x in 0..width {
self.set(x, y, other.get(x, y));
}
}
}
#[must_use]
pub fn split_horizontal_mut(
self,
pub fn split_horizontal_mut<'t>(
&'t mut self,
left_width: usize,
) -> Option<(Self, Self)> {
) -> Option<(
WindowMut<'t, TElement, TGrid>,
WindowMut<'t, TElement, TGrid>,
)> {
assert!(left_width <= self.width());
let (grid1, grid2) = unsafe { Self::duplicate_mutable_ref(self.grid) };
let (grid1, grid2): (&'t mut TGrid, &'t mut TGrid) =
unsafe { Self::duplicate_mutable_ref(self.grid) };
let middle_abs = self.xs.start + left_width;
let left =
WindowMut::new(grid1, self.xs.start..middle_abs, self.ys.clone())?;
let right = WindowMut::new(grid2, middle_abs..self.xs.end, self.ys)?;
let right =
WindowMut::new(grid2, middle_abs..self.xs.end, self.ys.clone())?;
Some((left, right))
}
#[must_use]
pub fn split_vertical_mut(self, top_height: usize) -> Option<(Self, Self)> {
pub fn split_vertical_mut<'t>(
&'t mut self,
top_height: usize,
) -> Option<(
WindowMut<'t, TElement, TGrid>,
WindowMut<'t, TElement, TGrid>,
)> {
assert!(top_height <= self.height());
let (grid1, grid2) = unsafe { Self::duplicate_mutable_ref(self.grid) };
let (grid1, grid2): (&'t mut TGrid, &'t mut TGrid) =
unsafe { Self::duplicate_mutable_ref(self.grid) };
let middle_abs = self.ys.start + top_height;
let top =
WindowMut::new(grid1, self.xs.clone(), self.ys.start..middle_abs)?;
let bottom = WindowMut::new(grid2, self.xs, middle_abs..self.ys.end)?;
let top = WindowMut::<'t>::new(
grid1,
self.xs.clone(),
self.ys.start..middle_abs,
)?;
let bottom = WindowMut::<'t>::new(
grid2,
self.xs.clone(),
middle_abs..self.ys.end,
)?;
Some((top, bottom))
}
@ -361,7 +364,23 @@ mod tests {
let mut grid = ByteGrid::new(5, 5);
grid.fill(1);
assert_eq!((0..5).len(), 5);
//let mut w1 = grid
let mut win = grid.window_mut(.., ..).unwrap();
let (mut top, mut bottom) = win.split_vertical_mut(2).unwrap();
let (mut left, mut right) = bottom.split_horizontal_mut(2).unwrap();
top.fill(2);
left.fill(3);
right.fill(4);
let grid2 = ByteGrid::from(&win.window(1..4, 1..4).unwrap());
assert_eq!(grid2.data_ref(), &[2, 2, 2, 3, 4, 4, 3, 4, 4,]);
assert_eq!(
grid.data_ref(),
&[
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 4, 3, 3, 4, 4, 4, 3,
3, 4, 4, 4,
]
);
}
}