From 9cfd5f15aafb469d9209dd574e33816e74694334 Mon Sep 17 00:00:00 2001 From: Yomguithereal Date: Mon, 20 Apr 2026 13:28:52 +0200 Subject: [PATCH 1/5] Refactoring writer using a ShapeHint enum So we can add WriterBuilder::begin_2d later on Related to #78 --- src/write.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/write.rs b/src/write.rs index ce66c60..b00edc4 100644 --- a/src/write.rs +++ b/src/write.rs @@ -15,10 +15,16 @@ use crate::read::Order; // Used when no shape is provided. const FILLER_FOR_UNKNOWN_SIZE: &'static [u8] = &[b'*'; 19]; +enum ShapeHint { + NDimensional(Vec), + // TwoDimensional(u64), + OneDimensional, +} + struct DataFromBuilder { order: Order, dtype: DType, - shape: Option>, + shape: ShapeHint, _marker: PhantomData, // contravariant } @@ -124,7 +130,7 @@ pub mod write_options { NpyWriter::_begin(DataFromBuilder { dtype: self.__get_dtype(), order: self.__get_order(), - shape: Some(self.__get_shape()), + shape: ShapeHint::NDimensional(self.__get_shape()), _marker: PhantomData, }, MaybeSeek::Isnt(self.__into_writer())) } @@ -145,7 +151,7 @@ pub mod write_options { NpyWriter::_begin(DataFromBuilder { dtype: self.__get_dtype(), order: self.__get_order(), - shape: None, + shape: ShapeHint::OneDimensional, _marker: PhantomData, }, MaybeSeek::new_seek(self.__into_writer())) } @@ -166,7 +172,7 @@ pub mod write_options { let order = self.__get_order(); let shape = self.__get_shape(); - write_header(self.__writer_mut(), &dtype, order, Some(shape.as_slice()))?; + write_header(self.__writer_mut(), &dtype, order, &ShapeHint::NDimensional(shape))?; Ok(self.__into_writer()) } @@ -412,7 +418,7 @@ impl NpyWriter { MaybeSeek::Isnt(_) => None, }; - let (shape_info, version_props) = write_header(&mut fw, &dtype, order, shape.as_deref())?; + let (shape_info, version_props) = write_header(&mut fw, &dtype, order, &shape)?; let writer = match Row::writer(&dtype) { Ok(writer) => writer, @@ -482,7 +488,7 @@ fn write_header( fw: &mut W, dtype: &DType, order: Order, - shape: Option<&[u64]>, + shape: &ShapeHint, ) -> io::Result<(ShapeInfo, VersionProps)> { if let DType::Array(..) = dtype { panic!("the outermost dtype cannot be an array (got: {:?})", dtype); @@ -511,7 +517,7 @@ fn write_header( Ok((shape_info, version_props)) } -fn create_dict(dtype: &DType, order: Order, shape: Option<&[u64]>) -> (Vec, ShapeInfo) { +fn create_dict(dtype: &DType, order: Order, shape: &ShapeHint) -> (Vec, ShapeInfo) { let mut header: Vec = vec![]; header.extend(&b"{'descr': "[..]); header.extend(dtype.descr().as_bytes()); @@ -522,14 +528,14 @@ fn create_dict(dtype: &DType, order: Order, shape: Option<&[u64]>) -> (Vec, } header.extend(&b", 'shape': ("[..]); let shape_info = match shape { - Some(shape) => { + ShapeHint::NDimensional(shape) => { for x in shape { write!(header, "{}, ", x).unwrap(); } header.extend(&b"), }"[..]); ShapeInfo::Known { expected_num_items: shape.iter().product() } }, - None => { + ShapeHint::OneDimensional => { let shape_offset = header.len() as u64; header.extend(FILLER_FOR_UNKNOWN_SIZE); header.extend(&b",), }"[..]); From 361c892a96690fab9cf705ded2c26599425da12e Mon Sep 17 00:00:00 2001 From: Yomguithereal Date: Mon, 20 Apr 2026 13:40:48 +0200 Subject: [PATCH 2/5] Adding WriterBuilder::begin_2d Related to #78 --- src/write.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/src/write.rs b/src/write.rs index b00edc4..eabe21f 100644 --- a/src/write.rs +++ b/src/write.rs @@ -17,7 +17,7 @@ const FILLER_FOR_UNKNOWN_SIZE: &'static [u8] = &[b'*'; 19]; enum ShapeHint { NDimensional(Vec), - // TwoDimensional(u64), + TwoDimensional(u64), OneDimensional, } @@ -156,6 +156,27 @@ pub mod write_options { }, MaybeSeek::new_seek(self.__into_writer())) } + /// Begin writing a 2d array, of length to be inferred from the number of elements written. + /// + /// Notice that, in contrast to [`Self::begin_nd`], this method requires [`Seek`]. If you have + /// a `Vec`, you can wrap it in an [`io::Cursor`] to satisfy this requirement. + /// + /// **Note:** At present, any [`shape`][Self::shape] you *did* happen to provide will be ignored and not + /// validated against the number of elements written. This may change in the future. + fn begin_2d(self, record_size: u64) -> io::Result::Writer>> + where + T: Serialize, + Self: HasDType + HasWriter, + ::Writer: Write + Seek, + { + NpyWriter::_begin(DataFromBuilder { + dtype: self.__get_dtype(), + order: self.__get_order(), + shape: ShapeHint::TwoDimensional(record_size), + _marker: PhantomData, + }, MaybeSeek::new_seek(self.__into_writer())) + } + /// Write the header, and recover the writer, which now points to where the data should be written. /// /// (If the original writer object was passed in by reference, it too is guaranteed to point to this location @@ -375,7 +396,9 @@ pub struct NpyWriter { enum ShapeInfo { // No shape was written; we'll return to write a 1D shape on `finish()`. - Automatic { offset_in_header_text: u64 }, + Automatic1D { offset_in_header_text: u64 }, + // Only partial shape was written; we'll return to write the missing part of shape on `finish()`. + Automatic2D { offset_in_header_text: u64, expected_record_size: u64 }, // The complete shape has already been written. // Raise an error on `finish()` if the wrong number of elements is given. Known { expected_num_items: u64 }, @@ -455,7 +478,7 @@ impl NpyWriter { })); } }, - ShapeInfo::Automatic { offset_in_header_text } => { + ShapeInfo::Automatic1D { offset_in_header_text } => { // Write the size to the header let shape_pos = self.start_pos.unwrap() + self.version_props.bytes_before_text() as u64 + offset_in_header_text; let end_pos = self.fw.seek(SeekFrom::Current(0))?; @@ -467,6 +490,24 @@ impl NpyWriter { self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; self.fw.seek(SeekFrom::Start(end_pos))?; }, + ShapeInfo::Automatic2D { offset_in_header_text, expected_record_size } => { + if self.num_items % expected_record_size != 0 { + return Err(io::Error::new(io::ErrorKind::InvalidData, { + format!("{} item(s) is not divisible by {}!", self.num_items, expected_record_size) + })); + } + + // Write the size to the header + let shape_pos = self.start_pos.unwrap() + self.version_props.bytes_before_text() as u64 + offset_in_header_text; + let end_pos = self.fw.seek(SeekFrom::Current(0))?; + + self.fw.seek(SeekFrom::Start(shape_pos))?; + let length = format!("{}", self.num_items / expected_record_size); + self.fw.write_all(length.as_bytes())?; + write!(self.fw, ", {}), }}", expected_record_size).unwrap(); + self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; + self.fw.seek(SeekFrom::Start(end_pos))?; + } } self.fw.flush()?; Ok(()) @@ -535,11 +576,17 @@ fn create_dict(dtype: &DType, order: Order, shape: &ShapeHint) -> (Vec, Shap header.extend(&b"), }"[..]); ShapeInfo::Known { expected_num_items: shape.iter().product() } }, + ShapeHint::TwoDimensional(record_size) => { + let shape_offset = header.len() as u64; + header.extend(FILLER_FOR_UNKNOWN_SIZE); + write!(header, ", {}), }}", record_size).unwrap(); + ShapeInfo::Automatic2D { offset_in_header_text: shape_offset, expected_record_size: *record_size } + }, ShapeHint::OneDimensional => { let shape_offset = header.len() as u64; header.extend(FILLER_FOR_UNKNOWN_SIZE); header.extend(&b",), }"[..]); - ShapeInfo::Automatic { offset_in_header_text: shape_offset } + ShapeInfo::Automatic1D { offset_in_header_text: shape_offset } }, }; (header, shape_info) @@ -719,6 +766,21 @@ pub(crate) fn to_writer_1d(writer: W, to_writer_1d_with_seeking(writer, data) } +/// Quick API for writing a 2D array to a vector of bytes. +#[cfg(test)] +pub(crate) fn to_bytes_2d(record_size: u64, data: &[T]) -> io::Result> { + let mut cursor = io::Cursor::new(vec![]); + to_writer_2d(&mut cursor, record_size, data)?; + Ok(cursor.into_inner()) +} + +/// Quick API for writing a 2D array to an io::Write. +#[cfg(test)] +pub(crate) fn to_writer_2d(writer: W, record_size: u64, data: &[T]) -> io::Result<()> { + // we might change this later and/or remove the Seek bound from the current function, but for now this will do + to_writer_2d_with_seeking(writer, record_size, data) +} + /// Quick API for writing an n-d array to an io::Write. #[cfg(test)] pub(crate) fn to_writer_nd(writer: W, data: &[T], shape: &[u64]) -> io::Result<()> { @@ -738,6 +800,17 @@ pub(crate) fn to_writer_1d_with_seeking(writer: W, record_size: u64, data: &[T]) -> io::Result<()> { + let mut writer = WriteOptions::new().default_dtype().writer(writer).begin_2d(record_size)?; + writer.extend(data)?; + writer.finish() +} + #[cfg(test)] mod tests { use super::*; @@ -787,6 +860,51 @@ mod tests { Ok(()) } + #[test] + fn write_2d_simple() -> io::Result<()> { + // 3 columns + let raw_buffer = to_bytes_2d(3, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + + let reader = NpyFile::new(&raw_buffer[..])?; + assert_eq!(reader.shape(), &[2, 3][..]); + assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, -4.0, 3.0, 2.0]); + + // same but with 2 columns + let raw_buffer = to_bytes_2d(2, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + + let reader = NpyFile::new(&raw_buffer[..])?; + assert_eq!(reader.shape(), &[3, 2][..]); + assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, -4.0, 3.0, 2.0]); + + Ok(()) + } + + #[test] + fn write_2d_in_the_middle() -> io::Result<()> { + let mut cursor = Cursor::new(vec![]); + + let prefix = b"lorem ipsum dolor sit amet."; + let suffix = b"and they lived happily ever after."; + + // write to the cursor both before and after writing the file + cursor.write_all(prefix)?; + to_writer_2d_with_seeking(&mut cursor, 3, &[1.0, 3.0, 5.0, 6.0, -1.0, 4.0])?; + cursor.write_all(suffix)?; + + // check that the seeking did not interfere with our extra writes + let raw_buffer = cursor.into_inner(); + assert!(raw_buffer.starts_with(prefix)); + assert!(raw_buffer.ends_with(suffix)); + + // check the bytes written by `OutFile` + let written_bytes = &raw_buffer[prefix.len()..raw_buffer.len() - suffix.len()]; + let reader = NpyFile::new(&written_bytes[..])?; + assert_eq!(reader.shape(), &[2, 3][..]); + assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, 6.0, -1.0, 4.0]); + + Ok(()) + } + #[test] fn implicit_finish() -> io::Result<()> { let mut cursor = Cursor::new(vec![]); @@ -834,6 +952,24 @@ mod tests { Ok(()) } + #[test] + fn write_2d_wrong_len() -> io::Result<()> { + let try_writing = |elems: &[i32]| -> io::Result<()> { + let mut buf = io::Cursor::new(vec![]); + let mut writer = WriteOptions::new().default_dtype().writer(&mut buf).begin_2d(3)?; + for &x in elems { + writer.push(&x)?; + } + writer.finish()?; + Ok(()) + }; + assert!(try_writing(&[00, 01, 02, 10, 11]).is_err()); + assert!(try_writing(&[00, 01, 02, 10, 11, 12]).is_ok()); + assert!(try_writing(&[00, 01, 02, 10, 11, 12, 20]).is_err()); + + Ok(()) + } + #[test] fn write_header_only_positions() -> io::Result<()> { let mut cursor = Cursor::new(vec![]); From 2d639e73209524e47d0a5b45731b576b34d8c564 Mon Sep 17 00:00:00 2001 From: Yomguithereal Date: Wed, 29 Apr 2026 12:01:26 +0200 Subject: [PATCH 3/5] Renaming `record_size` to `record_len` --- src/write.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/write.rs b/src/write.rs index eabe21f..a2f0a10 100644 --- a/src/write.rs +++ b/src/write.rs @@ -163,7 +163,7 @@ pub mod write_options { /// /// **Note:** At present, any [`shape`][Self::shape] you *did* happen to provide will be ignored and not /// validated against the number of elements written. This may change in the future. - fn begin_2d(self, record_size: u64) -> io::Result::Writer>> + fn begin_2d(self, record_len: u64) -> io::Result::Writer>> where T: Serialize, Self: HasDType + HasWriter, @@ -172,7 +172,7 @@ pub mod write_options { NpyWriter::_begin(DataFromBuilder { dtype: self.__get_dtype(), order: self.__get_order(), - shape: ShapeHint::TwoDimensional(record_size), + shape: ShapeHint::TwoDimensional(record_len), _marker: PhantomData, }, MaybeSeek::new_seek(self.__into_writer())) } @@ -398,7 +398,7 @@ enum ShapeInfo { // No shape was written; we'll return to write a 1D shape on `finish()`. Automatic1D { offset_in_header_text: u64 }, // Only partial shape was written; we'll return to write the missing part of shape on `finish()`. - Automatic2D { offset_in_header_text: u64, expected_record_size: u64 }, + Automatic2D { offset_in_header_text: u64, record_len: u64 }, // The complete shape has already been written. // Raise an error on `finish()` if the wrong number of elements is given. Known { expected_num_items: u64 }, @@ -490,10 +490,10 @@ impl NpyWriter { self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; self.fw.seek(SeekFrom::Start(end_pos))?; }, - ShapeInfo::Automatic2D { offset_in_header_text, expected_record_size } => { - if self.num_items % expected_record_size != 0 { + ShapeInfo::Automatic2D { offset_in_header_text, record_len } => { + if self.num_items % record_len != 0 { return Err(io::Error::new(io::ErrorKind::InvalidData, { - format!("{} item(s) is not divisible by {}!", self.num_items, expected_record_size) + format!("{} item(s) is not divisible by {}!", self.num_items, record_len) })); } @@ -502,9 +502,9 @@ impl NpyWriter { let end_pos = self.fw.seek(SeekFrom::Current(0))?; self.fw.seek(SeekFrom::Start(shape_pos))?; - let length = format!("{}", self.num_items / expected_record_size); + let length = format!("{}", self.num_items / record_len); self.fw.write_all(length.as_bytes())?; - write!(self.fw, ", {}), }}", expected_record_size).unwrap(); + write!(self.fw, ", {}), }}", record_len).unwrap(); self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; self.fw.seek(SeekFrom::Start(end_pos))?; } @@ -576,11 +576,11 @@ fn create_dict(dtype: &DType, order: Order, shape: &ShapeHint) -> (Vec, Shap header.extend(&b"), }"[..]); ShapeInfo::Known { expected_num_items: shape.iter().product() } }, - ShapeHint::TwoDimensional(record_size) => { + ShapeHint::TwoDimensional(record_len) => { let shape_offset = header.len() as u64; header.extend(FILLER_FOR_UNKNOWN_SIZE); - write!(header, ", {}), }}", record_size).unwrap(); - ShapeInfo::Automatic2D { offset_in_header_text: shape_offset, expected_record_size: *record_size } + write!(header, ", {}), }}", record_len).unwrap(); + ShapeInfo::Automatic2D { offset_in_header_text: shape_offset, record_len: *record_len } }, ShapeHint::OneDimensional => { let shape_offset = header.len() as u64; @@ -768,17 +768,17 @@ pub(crate) fn to_writer_1d(writer: W, /// Quick API for writing a 2D array to a vector of bytes. #[cfg(test)] -pub(crate) fn to_bytes_2d(record_size: u64, data: &[T]) -> io::Result> { +pub(crate) fn to_bytes_2d(record_len: u64, data: &[T]) -> io::Result> { let mut cursor = io::Cursor::new(vec![]); - to_writer_2d(&mut cursor, record_size, data)?; + to_writer_2d(&mut cursor, record_len, data)?; Ok(cursor.into_inner()) } /// Quick API for writing a 2D array to an io::Write. #[cfg(test)] -pub(crate) fn to_writer_2d(writer: W, record_size: u64, data: &[T]) -> io::Result<()> { +pub(crate) fn to_writer_2d(writer: W, record_len: u64, data: &[T]) -> io::Result<()> { // we might change this later and/or remove the Seek bound from the current function, but for now this will do - to_writer_2d_with_seeking(writer, record_size, data) + to_writer_2d_with_seeking(writer, record_len, data) } /// Quick API for writing an n-d array to an io::Write. @@ -805,8 +805,8 @@ pub(crate) fn to_writer_1d_with_seeking(writer: W, record_size: u64, data: &[T]) -> io::Result<()> { - let mut writer = WriteOptions::new().default_dtype().writer(writer).begin_2d(record_size)?; +pub(crate) fn to_writer_2d_with_seeking(writer: W, record_len: u64, data: &[T]) -> io::Result<()> { + let mut writer = WriteOptions::new().default_dtype().writer(writer).begin_2d(record_len)?; writer.extend(data)?; writer.finish() } From ac4a77a0924be94240708f97c25fafaed6f59b3e Mon Sep 17 00:00:00 2001 From: Yomguithereal Date: Wed, 29 Apr 2026 12:25:01 +0200 Subject: [PATCH 4/5] Change FILLER_FOR_UNKNOWN_SIZE to be 20 chars instead of 19 This is to accomodate max number of digits in a u64 number. --- src/write.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/write.rs b/src/write.rs index a2f0a10..1d5a124 100644 --- a/src/write.rs +++ b/src/write.rs @@ -13,7 +13,7 @@ use crate::read::Order; // Long enough to accomodate a large integer followed by ",), }". // Used when no shape is provided. -const FILLER_FOR_UNKNOWN_SIZE: &'static [u8] = &[b'*'; 19]; +const FILLER_FOR_UNKNOWN_SIZE: &'static [u8] = &[b'*'; 20]; enum ShapeHint { NDimensional(Vec), From 2d98effecc6edee1d8c39027b4c54d84e0676ab4 Mon Sep 17 00:00:00 2001 From: Yomguithereal Date: Wed, 29 Apr 2026 14:15:06 +0200 Subject: [PATCH 5/5] Adding Order::Fortran support for WriterBuilder::begin_2d --- src/write.rs | 73 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/src/write.rs b/src/write.rs index 1d5a124..65c8c64 100644 --- a/src/write.rs +++ b/src/write.rs @@ -398,7 +398,8 @@ enum ShapeInfo { // No shape was written; we'll return to write a 1D shape on `finish()`. Automatic1D { offset_in_header_text: u64 }, // Only partial shape was written; we'll return to write the missing part of shape on `finish()`. - Automatic2D { offset_in_header_text: u64, record_len: u64 }, + // We need to keep track of the `Order` to be able to writer the missing part correctly. + Automatic2D { offset_in_header_text: u64, record_len: u64, order: Order }, // The complete shape has already been written. // Raise an error on `finish()` if the wrong number of elements is given. Known { expected_num_items: u64 }, @@ -490,7 +491,7 @@ impl NpyWriter { self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; self.fw.seek(SeekFrom::Start(end_pos))?; }, - ShapeInfo::Automatic2D { offset_in_header_text, record_len } => { + ShapeInfo::Automatic2D { offset_in_header_text, record_len, order } => { if self.num_items % record_len != 0 { return Err(io::Error::new(io::ErrorKind::InvalidData, { format!("{} item(s) is not divisible by {}!", self.num_items, record_len) @@ -503,8 +504,16 @@ impl NpyWriter { self.fw.seek(SeekFrom::Start(shape_pos))?; let length = format!("{}", self.num_items / record_len); - self.fw.write_all(length.as_bytes())?; - write!(self.fw, ", {}), }}", record_len).unwrap(); + + match order { + Order::C => { + write!(self.fw, "{}, {}), }}", length, record_len)?; + }, + Order::Fortran => { + write!(self.fw, "{}, {}), }}", record_len, length)?; + } + }; + self.fw.write_all(&::std::iter::repeat(b' ').take(FILLER_FOR_UNKNOWN_SIZE.len() - length.len()).collect::>())?; self.fw.seek(SeekFrom::Start(end_pos))?; } @@ -578,9 +587,20 @@ fn create_dict(dtype: &DType, order: Order, shape: &ShapeHint) -> (Vec, Shap }, ShapeHint::TwoDimensional(record_len) => { let shape_offset = header.len() as u64; - header.extend(FILLER_FOR_UNKNOWN_SIZE); - write!(header, ", {}), }}", record_len).unwrap(); - ShapeInfo::Automatic2D { offset_in_header_text: shape_offset, record_len: *record_len } + + match order { + Order::C => { + header.extend(FILLER_FOR_UNKNOWN_SIZE); + write!(header, ", {}), }}", record_len).unwrap(); + }, + Order::Fortran => { + write!(header, "{}, ", record_len).unwrap(); + header.extend(FILLER_FOR_UNKNOWN_SIZE); + header.extend(b"), }"); + } + }; + + ShapeInfo::Automatic2D { offset_in_header_text: shape_offset, record_len: *record_len, order } }, ShapeHint::OneDimensional => { let shape_offset = header.len() as u64; @@ -768,17 +788,17 @@ pub(crate) fn to_writer_1d(writer: W, /// Quick API for writing a 2D array to a vector of bytes. #[cfg(test)] -pub(crate) fn to_bytes_2d(record_len: u64, data: &[T]) -> io::Result> { +pub(crate) fn to_bytes_2d(order: Order, record_len: u64, data: &[T]) -> io::Result> { let mut cursor = io::Cursor::new(vec![]); - to_writer_2d(&mut cursor, record_len, data)?; + to_writer_2d(&mut cursor, order, record_len, data)?; Ok(cursor.into_inner()) } /// Quick API for writing a 2D array to an io::Write. #[cfg(test)] -pub(crate) fn to_writer_2d(writer: W, record_len: u64, data: &[T]) -> io::Result<()> { +pub(crate) fn to_writer_2d(writer: W, order: Order, record_len: u64, data: &[T]) -> io::Result<()> { // we might change this later and/or remove the Seek bound from the current function, but for now this will do - to_writer_2d_with_seeking(writer, record_len, data) + to_writer_2d_with_seeking(writer, order, record_len, data) } /// Quick API for writing an n-d array to an io::Write. @@ -805,8 +825,8 @@ pub(crate) fn to_writer_1d_with_seeking(writer: W, record_len: u64, data: &[T]) -> io::Result<()> { - let mut writer = WriteOptions::new().default_dtype().writer(writer).begin_2d(record_len)?; +pub(crate) fn to_writer_2d_with_seeking(writer: W, order: Order, record_len: u64, data: &[T]) -> io::Result<()> { + let mut writer = WriteOptions::new().default_dtype().order(order).writer(writer).begin_2d(record_len)?; writer.extend(data)?; writer.finish() } @@ -863,14 +883,14 @@ mod tests { #[test] fn write_2d_simple() -> io::Result<()> { // 3 columns - let raw_buffer = to_bytes_2d(3, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + let raw_buffer = to_bytes_2d(Order::C, 3, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; let reader = NpyFile::new(&raw_buffer[..])?; assert_eq!(reader.shape(), &[2, 3][..]); assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, -4.0, 3.0, 2.0]); // same but with 2 columns - let raw_buffer = to_bytes_2d(2, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + let raw_buffer = to_bytes_2d(Order::C, 2, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; let reader = NpyFile::new(&raw_buffer[..])?; assert_eq!(reader.shape(), &[3, 2][..]); @@ -879,6 +899,27 @@ mod tests { Ok(()) } + #[test] + fn write_2d_simple_fortran_order() -> io::Result<()> { + // 3 columns + let raw_buffer = to_bytes_2d(Order::Fortran, 3, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + + let reader = NpyFile::new(&raw_buffer[..])?; + assert_eq!(reader.order(), Order::Fortran); + assert_eq!(reader.shape(), &[3, 2][..]); + assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, -4.0, 3.0, 2.0]); + + // same but with 2 columns + let raw_buffer = to_bytes_2d(Order::Fortran, 2, &[1.0, 3.0, 5.0, -4.0, 3.0, 2.0])?; + + let reader = NpyFile::new(&raw_buffer[..])?; + assert_eq!(reader.order(), Order::Fortran); + assert_eq!(reader.shape(), &[2, 3][..]); + assert_eq!(reader.into_vec::()?, vec![1.0, 3.0, 5.0, -4.0, 3.0, 2.0]); + + Ok(()) + } + #[test] fn write_2d_in_the_middle() -> io::Result<()> { let mut cursor = Cursor::new(vec![]); @@ -888,7 +929,7 @@ mod tests { // write to the cursor both before and after writing the file cursor.write_all(prefix)?; - to_writer_2d_with_seeking(&mut cursor, 3, &[1.0, 3.0, 5.0, 6.0, -1.0, 4.0])?; + to_writer_2d_with_seeking(&mut cursor, Order::C, 3, &[1.0, 3.0, 5.0, 6.0, -1.0, 4.0])?; cursor.write_all(suffix)?; // check that the seeking did not interfere with our extra writes