seqtkrs 0.1.1

A Rust reimplementation of seqtk, a fast and lightweight tool for processing biological sequences in FASTA/FASTQ format
Documentation
//! dropse命令:从双端数据中删除单端读段
//!
//! 该命令读取序列并比较连续两条序列的名称。如果名称相同(忽略/1、/2后缀),
//! 则输出这一对序列;如果不同,则跳过前一条(认为是单端读段)。

use anyhow::{Context, Result};
use clap::Args;

use crate::core::{SeqReader, SeqRecord, SeqWriter};

#[derive(Args, Debug)]
pub struct DropseArgs {
    /// 输入文件(FASTQ格式)
    #[arg(value_name = "in.fq", default_value = "-")]
    pub input: String,
}

/// 比较两个序列名称是否相同(忽略末尾的/1、/2后缀)
fn names_equal(name1: &[u8], name2: &[u8]) -> bool {
    if name1.len() != name2.len() {
        return false;
    }

    // 确定比较的长度:如果末尾是 /digit 格式,则忽略最后两个字符
    let compare_len = if name1.len() > 2
        && name1[name1.len() - 2] == b'/'
        && name2[name2.len() - 2] == b'/'
        && name1[name1.len() - 1].is_ascii_digit()
        && name2[name2.len() - 1].is_ascii_digit()
    {
        name1.len() - 2
    } else {
        name1.len()
    };

    name1[..compare_len] == name2[..compare_len]
}

pub fn run(args: &DropseArgs) -> Result<()> {
    // 打开输入文件
    let mut reader = if args.input == "-" {
        SeqReader::from_stdin()
    } else {
        SeqReader::from_path(&args.input)
            .with_context(|| format!("无法打开输入文件: {}", args.input))?
    };

    // 创建输出写入器
    let mut writer = SeqWriter::to_stdout();

    // 用于存储上一条序列的记录
    let mut last_record: Option<SeqRecord> = None;
    let mut current_record = SeqRecord::new(Vec::new(), Vec::new());

    // 读取所有序列
    while reader.read_next(&mut current_record)? {
        if let Some(last) = last_record.take() {
            // 比较当前序列和上一条序列的名称
            if names_equal(&last.name, &current_record.name) {
                // 名称相同,输出这一对序列
                writer.write_record(&last)?;
                writer.write_record(&current_record)?;
                // 不保存当前记录到last_record(因为已经输出了一对)
            } else {
                // 名称不同,跳过上一条(单端读段),保存当前记录
                last_record = Some(current_record.clone());
            }
        } else {
            // 第一条序列,保存到last_record
            last_record = Some(current_record.clone());
        }
    }

    writer.flush()?;
    Ok(())
}