-
Notifications
You must be signed in to change notification settings - Fork 4
feat: TemplateIterator #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ms_20_template
Are you sure you want to change the base?
Conversation
| /// Errors that can occur when iterating over templates. | ||
| #[derive(Error, Debug)] | ||
| pub enum TemplateIteratorError { | ||
| /// Error building a template from records. | ||
| #[error("Failed to build template: {0}")] | ||
| TemplateBuildError(#[from] TemplateError), | ||
|
|
||
| /// Error reading a BAM record. | ||
| #[error("Failed to read BAM record: {0}")] | ||
| BamReadError(#[from] rust_htslib::errors::Error), | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/bam/mod.rs
Outdated
| /// for template in TemplateIterator::new(reader.records()) { | ||
| /// let template = template?; | ||
| /// println!("Template: {:?}", template.name()); | ||
| /// } | ||
| /// ``` | ||
| pub struct TemplateIterator<I> | ||
| where | ||
| I: Iterator<Item = Result<Record, rust_htslib::errors::Error>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't convinced that this should be an iterator over Result<Record, E> instead of an iterator over Record, because the return type of Reader.records() is Records, but claude pointed out that where Records implements Iterator, it's over Result<Record, E>.
https://docs.rs/rust-htslib/latest/rust_htslib/bam/struct.Records.html
src/bam/mod.rs
Outdated
| //! fgbio (Scala) and fgpyo (Python). | ||
| use rust_htslib::bam::Record; | ||
| use std::iter::Peekable; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice that this is part of the std lib!
| /// Extension trait for creating a [`TemplateIterator`] from BAM record iterators. | ||
| /// | ||
| /// This trait provides an ergonomic way to convert a BAM record iterator into | ||
| /// a template iterator using method chaining. | ||
| /// | ||
| /// # Example | ||
| /// | ||
| /// ```ignore | ||
| /// use fgoxide::bam::IntoTemplateIterator; | ||
| /// use rust_htslib::bam::Reader; | ||
| /// | ||
| /// let reader = Reader::from_path("queryname_sorted.bam")?; | ||
| /// for template in reader.records().templates() { | ||
| /// let template = template?; | ||
| /// // process template... | ||
| /// } | ||
| /// ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super cute and I would not have discovered it on my own for a while. Thanks Claude!
| } | ||
|
|
||
| mod template_iterator_tests { | ||
| use super::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd feel better if we had one test that read from an actual BAM file. @nh13 @tfenne I know the general preference is to synthesize test data on the fly, but would you be open to having one test case to read a small BAM? Maybe two read pairs? Otherwise we're not directly testing that TemplateIterator can wrap rust-htslib's Reader::from_path
FusedIterator is a marker trait that guarantees an iterator will always return None after returning None the first time. Most iterators behave this way naturally, but Rust doesn't assume it. Benefits of implementing FusedIterator: - Iterator::fuse() becomes a no-op, allowing the compiler to optimize away redundant fuse() calls - Enables downstream optimizations for code consuming the iterator - Documents the iterator's behavior to API users TemplateIterator qualifies because once the inner iterator is exhausted, there's no way to produce more templates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Move the specific Item type bound to impl blocks, keeping only the minimal `I: Iterator` bound required by Peekable<I> on the struct definition. This follows the Rust convention of placing bounds on impl blocks rather than struct definitions when possible, making the type more flexible and improving compile-time error messages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Replace verbose match/loop pattern with idiomatic `while let` for collecting records with the same query name. The `while let Some(Ok(rec)) = self.inner.peek()` pattern is cleaner and handles the None and Err cases implicitly by exiting the loop, which is the desired behavior (errors are deferred to the next iteration). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
The #[must_use] attribute warns when the return value of new() is discarded. Since TemplateIterator::new() creates an iterator that must be consumed to have any effect, discarding it is almost certainly a bug. This follows the Rust API guidelines for constructors that return values which should not be ignored. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Closes #20.
This PR adds
TemplateIterator, based on the similar constructs in Python and Scala. Like #26 , this was mostly Claude.