Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@ All notable changes to SeqMatcher will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - Unreleased
## [2.1.0] - 2026-07-14

### Added
- `build-release.sh` cross-platform release builder (macOS/Linux/Windows targets)
- `--no-report` flag to suppress run summary report generation
- Pre-built release binaries for macOS (arm64/x86_64) and Linux (amd64/arm64 musl)

### Changed
- Simplified `progress` module: removed unused `count_lines` public API in favor of `estimate_total_lines`
- Renamed internal `count_lines_in_buf` to `count_lines_exact` for clarity
- Updated `rustfmt.toml` to use only stable-channel formatting options
- `ThreadResult` now derives `Default` instead of manual implementation

### Fixed
- Clippy `too_many_arguments` warning on `process_sequences` (annotated allowed)
- Clippy `derivable_impls` warning on `ThreadResult::Default`
- Unused `create_temp_seq` helper in integration tests (annotated reserved)

## [2.0.0] - 2026-07-14

### Added
- Comprehensive IUPAC degenerate base support (S, W, B, D, H, V) in reverse complement table
Expand All @@ -17,42 +35,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `--timestamp-output` flag for time-stamped output filenames
- Input data validation with health checks
- Run summary report (`run_summary.json` and `run_summary.txt`)
- Statistical summary with primer hit ranking and coverage metrics
- Reproducibility features: SHA256 hashing of inputs, full CLI arg recording
- ASCII bar chart visualization for top primer hits
- Reproducibility features: full CLI arg recording in summary reports
- `CITATION.cff` for academic citation
- Benchmark suite using Criterion (AC matching, primer matching, reverse complement)
- Benchmark suite using Criterion (AC matching, reverse complement)
- Integration tests with end-to-end pipeline coverage
- Public library API via `src/lib.rs` for downstream crate consumption
- Comprehensive rustdoc documentation for all public APIs

### Changed
- Refactored `main.rs` (436 lines) into 5 focused modules: `cli`, `progress`, `matcher`, `pipeline`, `main`
- Refactored `main.rs` (436 lines) into focused modules: `cli`, `progress`, `matcher`, `pipeline`
- Replaced manual `csv_to_line` serialization with `csv::Writer` for correctness
- Optimized `write_variant_counts` O(P*V) HashMap lookups to O(1) array indexing
- Replaced `eprintln!` diagnostics with `tracing` structured logging
- Unified error messages to English (CLI help text remains bilingual)
- Eliminated double I/O from `count_lines` pre-scan; uses file-size estimation

### Fixed
- IUPAC degenerate bases S, W, B, D, H, V now correctly reverse-complemented
- **Critical**: IUPAC degenerate bases S, W, B, D, H, V now correctly reverse-complemented
(previously mapped to themselves, producing incorrect results for degenerate sequences)
- Version inconsistency between `Cargo.toml` (1.4.0) and `Cargo.lock` (1.3.0)

### Removed
- Unused `serde` dependency from `Cargo.toml`
- Manual `csv_to_line` function in favor of `csv::Writer`
- Redundant `num_primers` parameter from `process_sequences` and `process_chunk`

## [1.4.0] - 2025-06-25

### Added
- Streaming I/O with reusable hit buffer for reduced allocations
- In-place ASCII uppercase conversion

### Changed
- Performance optimization: streaming chunk processing replaces full-file buffering

## [1.3.0] - 2025-06-25

### Added
Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ authors:
given-names: Jiwen
orcid: "https://orcid.org/0000-0000-0000-0000"
url: "https://github.com/CropCoder"
version: 2.0.0
date-released: 2026-07-13
version: 2.1.0
date-released: 2026-07-14
repository-code: "https://github.com/CropCoder/SeqMatcher"
url: "https://github.com/CropCoder/SeqMatcher"
abstract: >
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "seq_matcher"
version = "2.0.0"
version = "2.1.0"
edition = "2021"

[dependencies]
Expand Down
44 changes: 21 additions & 23 deletions src/progress.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Progress bar and line counting utilities.
//!
//! Provides real-time progress display with throughput and ETA estimation,
//! plus efficient file line counting for progress tracking.
//! plus efficient file line estimation for progress tracking.

use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
Expand Down Expand Up @@ -51,31 +51,17 @@ pub fn estimate_total_lines(path: &str, sample_lines: usize) -> Result<(u64, u64
Ok((estimated.max(1), file_size))
} else {
// File is too short to sample — count exactly
file.seek(SeekFrom::Start(0))?;
let exact = count_lines_in_buf(&mut file)?;
let exact = count_lines_exact(&mut file)?;
file.seek(SeekFrom::Start(0))?;
Ok((exact, file_size))
}
}

/// Count newlines in a file by scanning raw bytes (256KB buffer).
///
/// Fast line counting that avoids UTF-8 validation overhead of
/// line-by-line reading. Handles files with or without trailing newline.
/// Seeks back to the beginning of the file after counting.
///
/// Note: for pipeline progress estimation, [`estimate_total_lines`] is
/// preferred as it avoids a full file scan.
#[allow(dead_code)]
pub fn count_lines(path: &str) -> Result<u64> {
let mut file = File::open(path)?;
let count = count_lines_in_buf(&mut file)?;
file.seek(SeekFrom::Start(0))?;
Ok(count)
}

/// Count newlines in an already-open file using a 256KB read buffer.
fn count_lines_in_buf(file: &mut File) -> Result<u64> {
///
/// Accurate but requires a full scan. Prefer [`estimate_total_lines`] for
/// progress-bar use; use this only when an exact count is required.
fn count_lines_exact(file: &mut File) -> Result<u64> {
let mut buf = [0u8; 256 * 1024];
let mut count = 0u64;
let mut non_empty = false;
Expand Down Expand Up @@ -104,12 +90,24 @@ fn count_lines_in_buf(file: &mut File) -> Result<u64> {
/// Uses `\r` to overwrite the same line. The bar width is configurable.
/// Callers should flush stderr after this to ensure immediate display.
pub fn print_progress(processed: u64, total: u64, start: &std::time::Instant, bar_width: usize) {
let pct = if total > 0 { processed as f64 / total as f64 } else { 0.0 };
let pct = if total > 0 {
processed as f64 / total as f64
} else {
0.0
};
let filled = (bar_width as f64 * pct) as usize;
let bar = "\u{2588}".repeat(filled) + &"\u{2591}".repeat(bar_width.saturating_sub(filled));
let elapsed = start.elapsed().as_secs_f64();
let rate = if elapsed > 0.0 { processed as f64 / elapsed } else { 0.0 };
let eta = if rate > 0.0 { (total.saturating_sub(processed)) as f64 / rate } else { 0.0 };
let rate = if elapsed > 0.0 {
processed as f64 / elapsed
} else {
0.0
};
let eta = if rate > 0.0 {
(total.saturating_sub(processed)) as f64 / rate
} else {
0.0
};

eprint!(
"\r [{bar}] {pct:5.1}% {processed}/{total} {rate:.0} seq/s ETA: {eta:.0}s",
Expand Down
Loading