use crate::ascii_table::{AsciiColumnFormat, AsciiFieldDefinition, AsciiRow, write};
use crate::bin_table::Value;
use crate::header::Header;
#[cfg(feature = "rayon")]
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::error::Error;
#[derive(Debug, Clone, Default)]
pub struct AsciiTable {
data: Vec<u8>,
field_definitions: Vec<AsciiFieldDefinition>,
rows: usize,
bytes_per_row: usize,
}
impl AsciiTable {
pub fn from_u8(header: &Header, data: Vec<u8>) -> Result<Self, Box<dyn Error + Send + Sync>> {
if header.naxis() != Some(2) {
return Err("Only two dimensions are supported.".into());
}
let bytes_per_row = header
.naxis_n(0)
.ok_or("Table header is missing its NAXIS1 card")?;
let rows = header
.naxis_n(1)
.ok_or("Table header is missing its NAXIS2 card")?;
let bytes_per_row = usize::try_from(bytes_per_row)
.map_err(|_| format!("NAXIS1 must not be negative, but was {}", bytes_per_row))?;
let rows = usize::try_from(rows)
.map_err(|_| format!("NAXIS2 must not be negative, but was {}", rows))?;
let expected = rows
.checked_mul(bytes_per_row)
.ok_or("Table dimensions overflow the address space")?;
if data.len() < expected {
return Err(format!(
"Data vec is too short, expected {} bytes, but data was only {} bytes long",
expected,
data.len()
)
.into());
}
Ok(Self {
field_definitions: AsciiFieldDefinition::all_from_header(header)?,
data,
bytes_per_row,
rows,
})
}
pub fn row(&'_ self, row: usize) -> Option<AsciiRow<'_>> {
if row < self.rows {
let offset = self.bytes_per_row * row;
let data = &self.data[offset..offset + self.bytes_per_row];
Some(AsciiRow::new(&self.field_definitions, data))
} else {
None
}
}
pub fn rows(&'_ self) -> impl Iterator<Item = AsciiRow<'_>> + '_ {
(0..(self.rows)).filter_map(move |row| self.row(row))
}
#[cfg(feature = "rayon")]
pub fn rows_parallel(&'_ self) -> impl ParallelIterator<Item = AsciiRow<'_>> + '_ {
(0..(self.rows))
.into_par_iter()
.filter_map(move |row| self.row(row))
}
pub fn field_definitions(&self) -> &[AsciiFieldDefinition] {
&self.field_definitions
}
pub fn field_definitions_mut(&mut self) -> &mut [AsciiFieldDefinition] {
&mut self.field_definitions
}
pub fn bytes_per_row(&self) -> usize {
self.bytes_per_row
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn from_columns(columns: &[(String, AsciiColumnFormat)]) -> Self {
let mut field_definitions = Vec::with_capacity(columns.len());
let mut offset = 0;
for (name, format) in columns {
field_definitions.push(AsciiFieldDefinition {
format: *format,
offset,
name: name.clone(),
null: None,
scale: None,
zero: None,
});
offset += format.bytes_len() + 1;
}
let bytes_per_row = offset.saturating_sub(1);
Self {
data: Vec::new(),
field_definitions,
rows: 0,
bytes_per_row,
}
}
pub fn push_row(&mut self, values: &[Value]) -> Result<(), Box<dyn Error + Send + Sync>> {
if values.len() != self.field_definitions.len() {
return Err(format!(
"This table has {} columns, but the row has {} values",
self.field_definitions.len(),
values.len()
)
.into());
}
let start = self.data.len();
self.data.resize(start + self.bytes_per_row, b' ');
for (field, value) in self.field_definitions.iter().zip(values) {
let text = write::encode(value, field.format, field.null.as_deref());
let at = start + field.offset;
let width = field
.format
.bytes_len()
.min(self.bytes_per_row - field.offset);
self.data[at..at + width].copy_from_slice(&text.as_bytes()[..width]);
}
self.rows += 1;
Ok(())
}
pub fn len(&self) -> usize {
self.rows
}
pub fn is_empty(&self) -> bool {
self.rows == 0
}
}