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}