noodles_cram/io/
writer.rs

1//! CRAM writer.
2
3pub(crate) mod builder;
4mod collections;
5pub(crate) mod container;
6pub(crate) mod header;
7pub(crate) mod num;
8mod options;
9pub(crate) mod record;
10
11use std::io::{self, Write};
12
13use noodles_fasta as fasta;
14use noodles_sam::{self as sam, alignment::io::Write as _};
15
16pub use self::builder::Builder;
17use self::{
18    container::write_container,
19    header::{write_file_definition, write_file_header, write_header},
20};
21pub(crate) use self::{options::Options, record::Record};
22use crate::FileDefinition;
23
24const DEFAULT_SLICES_PER_CONTAINER: usize = 1;
25const DEFAULT_RECORDS_PER_SLICE: usize = 10240;
26pub(crate) const RECORDS_PER_CONTAINER: usize =
27    DEFAULT_SLICES_PER_CONTAINER * DEFAULT_RECORDS_PER_SLICE;
28
29/// A CRAM writer.
30///
31/// A call to [`Self::try_finish`] must be made before the writer is dropped.
32///
33/// # Examples
34///
35/// ```
36/// # use std::io;
37/// use noodles_cram as cram;
38/// use noodles_sam::{self as sam, alignment::io::Write};
39///
40/// let mut writer = cram::io::Writer::new(io::sink());
41///
42/// let header = sam::Header::default();
43/// writer.write_header(&header)?;
44///
45/// let record = sam::Record::default();
46/// writer.write_alignment_record(&header, &record)?;
47///
48/// writer.try_finish(&header)?;
49/// # Ok::<(), io::Error>(())
50/// ```
51#[derive(Debug)]
52pub struct Writer<W> {
53    inner: W,
54    reference_sequence_repository: fasta::Repository,
55    options: Options,
56    records: Vec<Record>,
57    record_counter: u64,
58}
59
60impl<W> Writer<W> {
61    /// Returns a reference to the underlying writer.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// # use std::io;
67    /// use noodles_cram as cram;
68    /// let writer = cram::io::Writer::new(io::sink());
69    /// let _inner = writer.get_ref();
70    /// ```
71    pub fn get_ref(&self) -> &W {
72        &self.inner
73    }
74
75    /// Returns a mutable reference to the underlying writer.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use std::io;
81    /// use noodles_cram as cram;
82    /// let mut writer = cram::io::Writer::new(io::sink());
83    /// let _inner = writer.get_mut();
84    /// ```
85    pub fn get_mut(&mut self) -> &mut W {
86        &mut self.inner
87    }
88
89    /// Returns the underlying writer.
90    ///
91    /// # Examples
92    ///
93    /// ```
94    /// # use std::io;
95    /// use noodles_cram as cram;
96    /// let mut writer = cram::io::Writer::new(io::sink());
97    /// let _inner = writer.into_inner();
98    /// ```
99    pub fn into_inner(self) -> W {
100        self.inner
101    }
102}
103
104impl<W> Writer<W>
105where
106    W: Write,
107{
108    /// Creates a new CRAM writer with default options.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// # use std::io;
114    /// use noodles_cram as cram;
115    /// let writer = cram::io::Writer::new(io::sink());
116    /// ```
117    pub fn new(inner: W) -> Self {
118        Builder::default().build_from_writer(inner)
119    }
120
121    /// Attempts to finish the output stream by writing any pending containers and a final EOF
122    /// container.
123    ///
124    /// This is typically only manually called if the underlying stream is needed before the writer
125    /// is dropped.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// # use std::io;
131    /// use noodles_cram as cram;
132    /// use noodles_sam as sam;
133    ///
134    /// let header = sam::Header::default();
135    /// let mut writer = cram::io::Writer::new(io::sink());
136    /// writer.try_finish(&header)?;
137    /// # Ok::<(), io::Error>(())
138    /// ```
139    pub fn try_finish(&mut self, header: &sam::Header) -> io::Result<()> {
140        use self::container::write_eof_container;
141        self.flush(header)?;
142        write_eof_container(&mut self.inner)
143    }
144
145    /// Writes a CRAM file definition.
146    ///
147    /// The file ID is set as a blank value (`[0x00; 20]`).
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// # use std::io;
153    /// use noodles_cram as cram;
154    /// let mut writer = cram::io::Writer::new(io::sink());
155    /// writer.write_file_definition()?;
156    /// # Ok::<(), io::Error>(())
157    /// ```
158    pub fn write_file_definition(&mut self) -> io::Result<()> {
159        let file_definition = FileDefinition::new(self.options.version, Default::default());
160        write_file_definition(&mut self.inner, &file_definition)
161    }
162
163    /// Writes a CRAM file header container.
164    ///
165    /// The position of the stream is expected to be directly after the file definition.
166    ///
167    /// Entries in the reference sequence dictionary that are missing MD5 checksums (`M5`) will
168    /// automatically be calculated and added to the written record.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// # use std::io;
174    /// use noodles_cram as cram;
175    /// use noodles_sam as sam;
176    ///
177    /// let mut writer = cram::io::Writer::new(io::sink());
178    /// writer.write_file_definition()?;
179    ///
180    /// let header = sam::Header::default();
181    /// writer.write_file_header(&header)?;
182    ///
183    /// writer.try_finish(&header)?;
184    /// # Ok::<(), io::Error>(())
185    /// ```
186    pub fn write_file_header(&mut self, header: &sam::Header) -> io::Result<()> {
187        write_file_header(&mut self.inner, &self.reference_sequence_repository, header)
188    }
189
190    /// Writes a SAM header.
191    ///
192    /// This writes the CRAM magic number, the file definition, and file header using the given SAM
193    /// header.
194    ///
195    /// ```
196    /// # use std::io;
197    /// use noodles_cram as cram;
198    /// use noodles_sam as sam;
199    ///
200    /// let mut writer = cram::io::Writer::new(io::sink());
201    ///
202    /// let header = sam::Header::default();
203    /// writer.write_header(&header)?;
204    /// # Ok::<_, io::Error>(())
205    /// ```
206    pub fn write_header(&mut self, header: &sam::Header) -> io::Result<()> {
207        let file_definition = FileDefinition::new(self.options.version, Default::default());
208
209        write_header(
210            &mut self.inner,
211            &self.reference_sequence_repository,
212            &file_definition,
213            header,
214        )
215    }
216
217    /// Writes a CRAM record.
218    ///
219    /// # Examples
220    ///
221    /// ```
222    /// # use std::io;
223    /// use noodles_cram as cram;
224    /// use noodles_sam::{self as sam, alignment::io::Write};
225    ///
226    /// let mut writer = cram::io::Writer::new(io::sink());
227    ///
228    /// let header = sam::Header::default();
229    /// writer.write_header(&header)?;
230    ///
231    /// let record = cram::Record::default();
232    /// writer.write_record(&header, &record)?;
233    ///
234    /// writer.try_finish(&header)?;
235    /// # Ok::<(), io::Error>(())
236    /// ```
237    pub fn write_record(
238        &mut self,
239        header: &sam::Header,
240        record: &crate::Record<'_>,
241    ) -> io::Result<()> {
242        self.write_alignment_record(header, record)
243    }
244
245    fn add_record(&mut self, header: &sam::Header, record: Record) -> io::Result<()> {
246        self.records.push(record);
247
248        if self.records.len() >= self.records.capacity() {
249            self.flush(header)?;
250        }
251
252        Ok(())
253    }
254
255    fn flush(&mut self, header: &sam::Header) -> io::Result<()> {
256        write_container(
257            &mut self.inner,
258            &self.reference_sequence_repository,
259            &self.options,
260            header,
261            self.record_counter,
262            &mut self.records,
263        )?;
264
265        let record_count = u64::try_from(self.records.len())
266            .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
267        self.record_counter += record_count;
268
269        self.records.clear();
270
271        Ok(())
272    }
273}
274
275impl<W> sam::alignment::io::Write for Writer<W>
276where
277    W: Write,
278{
279    fn write_alignment_header(&mut self, header: &sam::Header) -> io::Result<()> {
280        self.write_header(header)
281    }
282
283    fn write_alignment_record(
284        &mut self,
285        header: &sam::Header,
286        record: &dyn sam::alignment::Record,
287    ) -> io::Result<()> {
288        let record = Record::try_from_alignment_record(header, record)?;
289        self.add_record(header, record)
290    }
291
292    fn finish(&mut self, header: &sam::Header) -> io::Result<()> {
293        self.try_finish(header)
294    }
295}