Skip to content

Commit

Permalink
Make args-iterator helpers support any iterable collection
Browse files Browse the repository at this point in the history
Also, since there has been no release with this feature yet, sneak
in a name change so the function names are not needlessly verbose.
  • Loading branch information
csnover committed Nov 16, 2022
1 parent 7193d39 commit 6636452
Showing 1 changed file with 102 additions and 90 deletions.
192 changes: 102 additions & 90 deletions binrw/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,108 @@ where
}
}

/// Creates a parser that builds a collection using items from the given
/// iterable object as arguments for the parser.
///
/// This helper can be used to read into any collection type that implements
/// [`FromIterator`].
///
/// # Examples
///
/// Reading an object containing header data followed by body data:
///
/// ```
/// # use binrw::{args, BinRead, BinReaderExt, helpers::args_iter, io::Cursor};
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Header {
/// count: u16,
///
/// #[br(args { count: count.into() })]
/// sizes: Vec<u16>,
/// }
///
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Object {
/// header: Header,
/// #[br(parse_with = args_iter(header.sizes.iter().map(|&size| -> <Vec<u8> as BinRead>::Args {
/// args! { count: size.into() }
/// })))]
/// segments: Vec<Vec<u8>>,
/// }
///
/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
/// # let x = Object::read(&mut x).unwrap();
/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
pub fn args_iter<R, T, Arg, Ret, It>(
it: It,
) -> impl FnOnce(&mut R, &ReadOptions, ()) -> BinResult<Ret>
where
T: BinRead<Args = Arg>,
R: Read + Seek,
Arg: Clone,
Ret: FromIterator<T>,
It: IntoIterator<Item = Arg>,
{
args_iter_with(it, default_reader)
}

/// Creates a parser that uses a given function to build a collection, using
/// items from the given iterable object as arguments for the function.
///
/// The given `read` function should return one item each time it is called.
///
/// This helper can be used to read into any collection type that implements
/// [`FromIterator`].
///
/// # Examples
///
/// Reading an object containing header data followed by body data:
///
/// ```
/// # use binrw::{args, BinRead, BinReaderExt, helpers::args_iter_with, io::Cursor};
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Header {
/// count: u16,
///
/// #[br(args { count: count.into() })]
/// sizes: Vec<u16>,
/// }
///
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Object {
/// header: Header,
/// #[br(parse_with = args_iter_with(&header.sizes, |reader, options, &size| {
/// Vec::<u8>::read_options(reader, options, args! { count: size.into() })
/// }))]
/// segments: Vec<Vec<u8>>,
/// }
///
/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
/// # let x = Object::read(&mut x).unwrap();
/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
pub fn args_iter_with<Reader, T, Arg, Ret, It, ReadFn>(
it: It,
read: ReadFn,
) -> impl FnOnce(&mut Reader, &ReadOptions, ()) -> BinResult<Ret>
where
T: BinRead,
Reader: Read + Seek,
Arg: Clone,
Ret: FromIterator<T>,
It: IntoIterator<Item = Arg>,
ReadFn: Fn(&mut Reader, &ReadOptions, Arg) -> BinResult<T>,
{
move |reader, options, _| {
it.into_iter()
.map(|arg| read(reader, options, arg))
.collect()
}
}

/// Creates a parser that reads N items into a collection.
///
/// This helper is similar to using `#[br(count = N)]` with [`Vec`], but is more
Expand Down Expand Up @@ -375,96 +477,6 @@ where
}
}

/// Creates a parser that uses an iterator to read into a collection, using
/// each item of the iterator as an argument.
///
/// # Examples
///
/// Reading an object containing header data followed by body data:
///
/// ```
/// # use binrw::{args, BinRead, helpers::from_iterator_args, io::Cursor, BinReaderExt};
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Header {
/// count: u16,
///
/// #[br(args { count: count.into() })]
/// sizes: Vec<u16>,
/// }
///
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Object {
/// header: Header,
/// #[br(parse_with = from_iterator_args(header.sizes.iter().map(|size| -> <Vec<u8> as BinRead>::Args {
/// args! { count: (*size).into() }
/// })))]
/// segments: Vec<Vec<u8>>,
/// }
///
/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
/// # let x = Object::read(&mut x).unwrap();
/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
pub fn from_iterator_args<R, T, Arg, Ret, It>(
it: It,
) -> impl FnOnce(&mut R, &ReadOptions, ()) -> BinResult<Ret>
where
T: BinRead<Args = Arg>,
R: Read + Seek,
Arg: Clone,
Ret: FromIterator<T> + 'static,
It: Iterator<Item = Arg>,
{
from_iterator_args_with(it, default_reader)
}

/// Creates a parser that uses an iterator to read into a collection, using
/// each item of the iterator as an argument passed to the given read function.
///
/// # Examples
///
/// Reading an object containing header data followed by body data:
///
/// ```
/// # use binrw::{args, BinRead, helpers::from_iterator_args_with, io::Cursor, BinReaderExt};
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Header {
/// count: u16,
///
/// #[br(args { count: count.into() })]
/// sizes: Vec<u16>,
/// }
///
/// #[derive(BinRead)]
/// #[br(big)]
/// struct Object {
/// header: Header,
/// #[br(parse_with = from_iterator_args_with(header.sizes.iter(), |reader, options, size| {
/// Vec::<u8>::read_options(reader, options, args! { count: (*size).into() })
/// }))]
/// segments: Vec<Vec<u8>>,
/// }
///
/// # let mut x = Cursor::new(b"\0\x02\0\x01\0\x02\x03\x04\x05");
/// # let x = Object::read(&mut x).unwrap();
/// # assert_eq!(x.segments, &[vec![3], vec![4, 5]]);
pub fn from_iterator_args_with<Reader, T, Arg, Ret, It, ReadFn>(
it: It,
read: ReadFn,
) -> impl FnOnce(&mut Reader, &ReadOptions, ()) -> BinResult<Ret>
where
T: BinRead,
Reader: Read + Seek,
Arg: Clone,
Ret: FromIterator<T> + 'static,
It: Iterator<Item = Arg>,
ReadFn: Fn(&mut Reader, &ReadOptions, Arg) -> BinResult<T>,
{
move |reader, options, _| it.map(|arg| read(reader, options, arg)).collect()
}

// Lint: Non-consumed argument is required to match the API.
#[allow(clippy::trivially_copy_pass_by_ref)]
fn default_reader<R: Read + Seek, Arg: Clone, T: BinRead<Args = Arg>>(
Expand Down

0 comments on commit 6636452

Please sign in to comment.