• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rust-bio / rust-htslib / 19161687300

07 Nov 2025 07:43AM UTC coverage: 81.912% (-0.02%) from 81.935%
19161687300

Pull #488

github

web-flow
Merge 907dabbe9 into 8f1cdd75c
Pull Request #488: fix: Reason about Send/Sync-ness of types and change Rcs to Arcs

34 of 36 new or added lines in 7 files covered. (94.44%)

20 existing lines in 6 files now uncovered.

2785 of 3400 relevant lines covered (81.91%)

27209.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.48
/src/bam/header.rs
1
// Copyright 2014 Johannes Köster.
2
// Licensed under the MIT license (http://opensource.org/licenses/MIT)
3
// This file may not be copied, modified, or distributed
4
// except according to those terms.
5

6
use crate::bam::HeaderView;
7
use lazy_static::lazy_static;
8
use linear_map::LinearMap;
9
use regex::Regex;
10
use std::borrow::Cow;
11
use std::collections::HashMap;
12

13
/// A BAM header.
14
#[derive(Debug, Clone)]
15
pub struct Header {
16
    records: Vec<Vec<u8>>,
17
}
18

19
impl Default for Header {
20
    fn default() -> Self {
×
21
        Self::new()
×
22
    }
23
}
24

25
impl Header {
26
    /// Create a new header.
27
    pub fn new() -> Self {
15✔
28
        Header {
29
            records: Vec::new(),
15✔
30
        }
31
    }
32

33
    pub fn from_template(header: &HeaderView) -> Self {
12✔
34
        let mut record = header.as_bytes().to_owned();
30✔
35
        // Strip off any trailing newline character.
36
        // Otherwise there could be a blank line in the
37
        // header which samtools (<=1.6) will complain
38
        // about
39
        while let Some(&last_char) = record.last() {
42✔
40
            if last_char == b'\n' {
30✔
41
                record.pop();
12✔
42
            } else {
43
                break;
9✔
44
            }
45
        }
46
        Header {
47
            records: vec![record],
15✔
48
        }
49
    }
50

51
    /// Add a record to the header.
52
    pub fn push_record(&mut self, record: &HeaderRecord<'_>) -> &mut Self {
11✔
53
        self.records.push(record.to_bytes());
41✔
54
        self
10✔
55
    }
56

57
    /// Add a comment to the header.
58
    pub fn push_comment(&mut self, comment: &[u8]) -> &mut Self {
×
59
        self.records.push([&b"@CO"[..], comment].join(&b'\t'));
×
60
        self
61
    }
62

63
    pub fn to_bytes(&self) -> Vec<u8> {
26✔
64
        self.records.join(&b'\n')
47✔
65
    }
66

67
    /// This returns a header as a HashMap.
68
    /// Comment lines starting with "@CO" will NOT be included in the HashMap.
69
    /// Comment lines can be obtained by the `comments` function.
70
    pub fn to_hashmap(&self) -> HashMap<String, Vec<LinearMap<String, String>>> {
4✔
71
        let mut header_map = HashMap::default();
6✔
72

73
        lazy_static! {
2✔
74
            static ref REC_TYPE_RE: Regex = Regex::new(r"@([A-Z][A-Z])").unwrap();
75
            static ref TAG_RE: Regex = Regex::new(r"([A-Za-z][A-Za-z0-9]):([ -~]*)").unwrap();
76
        }
77

78
        let header_string = String::from_utf8(self.to_bytes()).unwrap();
14✔
79

80
        for line in header_string.split('\n').filter(|x| !x.is_empty()) {
36✔
81
            let parts: Vec<_> = line.split('\t').filter(|x| !x.is_empty()).collect();
106✔
82
            // assert!(rec_type_re.is_match(parts[0]));
83
            let record_type = REC_TYPE_RE
34✔
84
                .captures(parts[0])
26✔
85
                .unwrap()
86
                .get(1)
87
                .unwrap()
88
                .as_str()
89
                .to_owned();
90
            if record_type.eq("CO") {
26✔
91
                continue;
92
            }
93
            let mut field = LinearMap::default();
18✔
94
            for part in parts.iter().skip(1) {
54✔
95
                let cap = TAG_RE.captures(part).unwrap();
89✔
96
                let tag = cap.get(1).unwrap().as_str().to_owned();
89✔
97
                let value = cap.get(2).unwrap().as_str().to_owned();
89✔
98
                field.insert(tag, value);
53✔
99
            }
100
            header_map
16✔
101
                .entry(record_type)
18✔
102
                .or_insert_with(Vec::new)
10✔
103
                .push(field);
18✔
104
        }
105
        header_map
4✔
106
    }
107

108
    /// Returns an iterator of comment lines.
NEW
109
    pub fn comments(&'_ self) -> impl Iterator<Item = Cow<'_, str>> {
×
110
        self.records.iter().flat_map(|r| {
×
111
            r.split(|x| x == &b'\n')
×
112
                .filter(|x| x.starts_with(b"@CO\t"))
×
113
                .map(|x| String::from_utf8_lossy(&x[4..]))
×
114
        })
115
    }
116
}
117

118
/// Header record.
119
#[derive(Debug, Clone)]
120
pub struct HeaderRecord<'a> {
121
    rec_type: Vec<u8>,
122
    tags: Vec<(&'a [u8], Vec<u8>)>,
123
}
124

125
impl<'a> HeaderRecord<'a> {
126
    /// Create a new header record.
127
    /// See SAM format specification for possible record types.
128
    pub fn new(rec_type: &'a [u8]) -> Self {
12✔
129
        HeaderRecord {
130
            rec_type: [&b"@"[..], rec_type].concat(),
23✔
131
            tags: Vec::new(),
12✔
132
        }
133
    }
134

135
    /// Add a new tag to the record.
136
    ///
137
    /// # Arguments
138
    ///
139
    /// * `tag` - the tag identifier
140
    /// * `value` - the value. Can be any type convertible into a string. Preferably numbers or
141
    ///   strings.
142
    pub fn push_tag<V: ToString>(&mut self, tag: &'a [u8], value: V) -> &mut Self {
35✔
143
        self.tags.push((tag, value.to_string().into_bytes()));
163✔
144
        self
31✔
145
    }
146

147
    fn to_bytes(&self) -> Vec<u8> {
12✔
148
        let mut out = Vec::new();
23✔
149
        out.extend(self.rec_type.iter());
35✔
150
        for &(tag, ref value) in self.tags.iter() {
116✔
151
            out.push(b'\t');
94✔
152
            out.extend(tag.iter());
156✔
153
            out.push(b':');
94✔
154
            out.extend(value.iter());
94✔
155
        }
156
        out
12✔
157
    }
158
}
159

160
#[cfg(test)]
161
mod tests {
162
    use super::HeaderRecord;
163

164
    #[test]
165
    fn test_push_tag() {
166
        let mut record = HeaderRecord::new(b"HD");
167
        record.push_tag(b"X1", 0);
168
        record.push_tag(b"X2", 0);
169

170
        let x = "x".to_string();
171
        record.push_tag(b"X3", x.as_str());
172
        record.push_tag(b"X4", &x);
173
        record.push_tag(b"X5", x);
174

175
        assert_eq!(record.to_bytes(), b"@HD\tX1:0\tX2:0\tX3:x\tX4:x\tX5:x");
176
    }
177
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc