SeqAn3  3.1.0-rc.1
The Modern C++ library for sequence analysis.
format_sam.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2021, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2021, Knut Reinert & MPI für molekulare Genetik
4 // This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
5 // shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
6 // -----------------------------------------------------------------------------------------------------
7 
13 #pragma once
14 
15 #include <iterator>
16 #include <seqan3/std/ranges>
17 #include <string>
18 #include <vector>
19 
38 
39 namespace seqan3
40 {
41 
115 {
116 public:
120  // construction cannot be noexcept because this class has a std::string variable as a quality string buffer.
121  format_sam() = default;
122  format_sam(format_sam const &) = default;
123  format_sam & operator=(format_sam const &) = default;
124  format_sam(format_sam &&) = default;
125  format_sam & operator=(format_sam &&) = default;
126  ~format_sam() = default;
127 
129 
132  {
133  { "sam" },
134  };
135 
136 protected:
137  template <typename stream_type, // constraints checked by file
138  typename seq_legal_alph_type,
139  typename seq_type, // other constraints checked inside function
140  typename id_type,
141  typename qual_type>
142  void read_sequence_record(stream_type & stream,
144  seq_type & sequence,
145  id_type & id,
146  qual_type & qualities);
147 
148  template <typename stream_type, // constraints checked by file
149  typename seq_type, // other constraints checked inside function
150  typename id_type,
151  typename qual_type>
152  void write_sequence_record(stream_type & stream,
153  sequence_file_output_options const & SEQAN3_DOXYGEN_ONLY(options),
154  seq_type && sequence,
155  id_type && id,
156  qual_type && qualities);
157 
158  template <typename stream_type, // constraints checked by file
159  typename seq_legal_alph_type,
160  typename ref_seqs_type,
161  typename ref_ids_type,
162  typename seq_type,
163  typename id_type,
164  typename offset_type,
165  typename ref_seq_type,
166  typename ref_id_type,
167  typename ref_offset_type,
168  typename align_type,
169  typename cigar_type,
170  typename flag_type,
171  typename mapq_type,
172  typename qual_type,
173  typename mate_type,
174  typename tag_dict_type,
175  typename e_value_type,
176  typename bit_score_type>
177  void read_alignment_record(stream_type & stream,
178  sam_file_input_options<seq_legal_alph_type> const & SEQAN3_DOXYGEN_ONLY(options),
179  ref_seqs_type & ref_seqs,
181  seq_type & seq,
182  qual_type & qual,
183  id_type & id,
184  offset_type & offset,
185  ref_seq_type & SEQAN3_DOXYGEN_ONLY(ref_seq),
186  ref_id_type & ref_id,
187  ref_offset_type & ref_offset,
188  align_type & align,
189  cigar_type & cigar_vector,
190  flag_type & flag,
191  mapq_type & mapq,
192  mate_type & mate,
193  tag_dict_type & tag_dict,
194  e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
195  bit_score_type & SEQAN3_DOXYGEN_ONLY(bit_score));
196 
197  template <typename stream_type,
198  typename header_type,
199  typename seq_type,
200  typename id_type,
201  typename ref_seq_type,
202  typename ref_id_type,
203  typename align_type,
204  typename qual_type,
205  typename mate_type,
206  typename tag_dict_type,
207  typename e_value_type,
208  typename bit_score_type>
209  void write_alignment_record(stream_type & stream,
210  sam_file_output_options const & options,
211  header_type && header,
212  seq_type && seq,
213  qual_type && qual,
214  id_type && id,
215  int32_t const offset,
216  ref_seq_type && SEQAN3_DOXYGEN_ONLY(ref_seq),
217  ref_id_type && ref_id,
218  std::optional<int32_t> ref_offset,
219  align_type && align,
220  std::vector<cigar> const & cigar_vector,
221  sam_flag const flag,
222  uint8_t const mapq,
223  mate_type && mate,
224  tag_dict_type && tag_dict,
225  e_value_type && SEQAN3_DOXYGEN_ONLY(e_value),
226  bit_score_type && SEQAN3_DOXYGEN_ONLY(bit_score));
227 
228 private:
231 
233  static constexpr std::string_view dummy{};
234 
237 
240 
243  {
244  return dummy;
245  }
246 
248  template <typename t>
249  decltype(auto) default_or(t && v) const noexcept
250  {
251  return std::forward<t>(v);
252  }
253 
254  using format_sam_base::read_field; // inherit read_field functions from format_base explicitly
255 
256  template <typename stream_view_type, typename value_type>
258  stream_view_type && stream_view,
259  value_type value);
260 
261  template <typename stream_view_type>
263  stream_view_type && stream_view);
264 
265  template <typename stream_view_type>
266  void read_field(stream_view_type && stream_view, sam_tag_dictionary & target);
267 
268  template <typename stream_it_t, std::ranges::forward_range field_type>
269  void write_range_or_asterisk(stream_it_t & stream_it, field_type && field_value);
270 
271  template <typename stream_it_t>
272  void write_range_or_asterisk(stream_it_t & stream_it, char const * const field_value);
273 
274  template <typename stream_it_t>
275  void write_tag_fields(stream_it_t & stream, sam_tag_dictionary const & tag_dict, char const separator);
276 };
277 
279 template <typename stream_type, // constraints checked by file
280  typename seq_legal_alph_type,
281  typename seq_type, // other constraints checked inside function
282  typename id_type,
283  typename qual_type>
284 inline void format_sam::read_sequence_record(stream_type & stream,
286  seq_type & sequence,
287  id_type & id,
288  qual_type & qualities)
289 {
291 
292  {
293  read_alignment_record(stream, align_options, std::ignore, default_header, sequence, qualities, id,
294  std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore,
295  std::ignore, std::ignore, std::ignore, std::ignore, std::ignore, std::ignore);
296  }
297 
298  if constexpr (!detail::decays_to_ignore_v<seq_type>)
299  if (std::ranges::distance(sequence) == 0)
300  throw parse_error{"The sequence information must not be empty."};
301  if constexpr (!detail::decays_to_ignore_v<id_type>)
302  if (std::ranges::distance(id) == 0)
303  throw parse_error{"The id information must not be empty."};
304 
305  if (options.truncate_ids)
306  id = id | detail::take_until_and_consume(is_space) | views::to<id_type>;
307 }
308 
310 template <typename stream_type, // constraints checked by file
311  typename seq_type, // other constraints checked inside function
312  typename id_type,
313  typename qual_type>
314 inline void format_sam::write_sequence_record(stream_type & stream,
315  sequence_file_output_options const & SEQAN3_DOXYGEN_ONLY(options),
316  seq_type && sequence,
317  id_type && id,
318  qual_type && qualities)
319 {
320  using default_align_t = std::pair<std::span<gapped<char>>, std::span<gapped<char>>>;
321  using default_mate_t = std::tuple<std::string_view, std::optional<int32_t>, int32_t>;
322 
323  sam_file_output_options output_options;
324 
325  write_alignment_record(stream,
326  output_options,
327  /*header*/ std::ignore,
328  /*seq*/ default_or(sequence),
329  /*qual*/ default_or(qualities),
330  /*id*/ default_or(id),
331  /*offset*/ 0,
332  /*ref_seq*/ std::string_view{},
333  /*ref_id*/ std::string_view{},
334  /*ref_offset*/ -1,
335  /*align*/ default_align_t{},
336  /*cigar_vector*/ std::vector<cigar>{},
337  /*flag*/ sam_flag::none,
338  /*mapq*/ 0,
339  /*mate*/ default_mate_t{},
340  /*tag_dict*/ sam_tag_dictionary{},
341  /*e_value*/ 0,
342  /*bit_score*/ 0);
343 }
344 
346 template <typename stream_type, // constraints checked by file
347  typename seq_legal_alph_type,
348  typename ref_seqs_type,
349  typename ref_ids_type,
350  typename seq_type,
351  typename id_type,
352  typename offset_type,
353  typename ref_seq_type,
354  typename ref_id_type,
355  typename ref_offset_type,
356  typename align_type,
357  typename cigar_type,
358  typename flag_type,
359  typename mapq_type,
360  typename qual_type,
361  typename mate_type,
362  typename tag_dict_type,
363  typename e_value_type,
364  typename bit_score_type>
365 inline void format_sam::read_alignment_record(stream_type & stream,
366  sam_file_input_options<seq_legal_alph_type> const & SEQAN3_DOXYGEN_ONLY(options),
367  ref_seqs_type & ref_seqs,
369  seq_type & seq,
370  qual_type & qual,
371  id_type & id,
372  offset_type & offset,
373  ref_seq_type & SEQAN3_DOXYGEN_ONLY(ref_seq),
374  ref_id_type & ref_id,
375  ref_offset_type & ref_offset,
376  align_type & align,
377  cigar_type & cigar_vector,
378  flag_type & flag,
379  mapq_type & mapq,
380  mate_type & mate,
381  tag_dict_type & tag_dict,
382  e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
383  bit_score_type & SEQAN3_DOXYGEN_ONLY(bit_score))
384 {
385  static_assert(detail::decays_to_ignore_v<ref_offset_type> ||
386  detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
387  "The ref_offset must be a specialisation of std::optional.");
388 
389  auto stream_view = detail::istreambuf(stream);
390  auto field_view = stream_view | detail::take_until_or_throw_and_consume(is_char<'\t'>);
391 
392  // these variables need to be stored to compute the ALIGNMENT
393  int32_t ref_offset_tmp{};
394  std::ranges::range_value_t<decltype(header.ref_ids())> ref_id_tmp{};
395  [[maybe_unused]] int32_t offset_tmp{};
396  [[maybe_unused]] int32_t soft_clipping_end{};
397  [[maybe_unused]] std::vector<cigar> tmp_cigar_vector{};
398  [[maybe_unused]] int32_t ref_length{0}, seq_length{0}; // length of aligned part for ref and query
399 
400  // Header
401  // -------------------------------------------------------------------------------------------------------------
402  if (is_char<'@'>(*std::ranges::begin(stream_view))) // we always read the header if present
403  {
404  read_header(stream_view, header, ref_seqs);
405 
406  if (std::ranges::begin(stream_view) == std::ranges::end(stream_view)) // file has no records
407  return;
408  }
409 
410  // Fields 1-5: ID FLAG REF_ID REF_OFFSET MAPQ
411  // -------------------------------------------------------------------------------------------------------------
412  read_field(field_view, id);
413 
414  uint16_t flag_integral{};
415  read_field(field_view, flag_integral);
416  flag = sam_flag{flag_integral};
417 
418  read_field(field_view, ref_id_tmp);
419  check_and_assign_ref_id(ref_id, ref_id_tmp, header, ref_seqs);
420 
421  read_field(field_view, ref_offset_tmp);
422  --ref_offset_tmp; // SAM format is 1-based but SeqAn operates 0-based
423 
424  if (ref_offset_tmp == -1)
425  ref_offset = std::nullopt; // indicates an unmapped read -> ref_offset is not set
426  else if (ref_offset_tmp > -1)
427  ref_offset = ref_offset_tmp;
428  else if (ref_offset_tmp < -1)
429  throw format_error{"No negative values are allowed for field::ref_offset."};
430 
431  read_field(field_view, mapq);
432 
433  // Field 6: CIGAR
434  // -------------------------------------------------------------------------------------------------------------
435  if constexpr (!detail::decays_to_ignore_v<align_type> || !detail::decays_to_ignore_v<cigar_type>)
436  {
437  if (!is_char<'*'>(*std::ranges::begin(stream_view))) // no cigar information given
438  {
439  std::tie(tmp_cigar_vector, ref_length, seq_length) = detail::parse_cigar(field_view);
440  transfer_soft_clipping_to(tmp_cigar_vector, offset_tmp, soft_clipping_end);
441  // the actual cigar_vector is swapped with tmp_cigar_vector at the end to avoid copying
442  }
443  else
444  {
445  std::ranges::next(std::ranges::begin(field_view)); // skip '*'
446  }
447  }
448  else
449  {
450  detail::consume(field_view);
451  }
452 
453  offset = offset_tmp;
454 
455  // Field 7-9: (RNEXT PNEXT TLEN) = MATE
456  // -------------------------------------------------------------------------------------------------------------
457  if constexpr (!detail::decays_to_ignore_v<mate_type>)
458  {
459  std::ranges::range_value_t<decltype(header.ref_ids())> tmp_mate_ref_id{};
460  read_field(field_view, tmp_mate_ref_id); // RNEXT
461 
462  if (tmp_mate_ref_id == "=") // indicates "same as ref id"
463  {
464  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
465  get<0>(mate) = ref_id;
466  else
467  check_and_assign_ref_id(get<0>(mate), ref_id_tmp, header, ref_seqs);
468  }
469  else
470  {
471  check_and_assign_ref_id(get<0>(mate), tmp_mate_ref_id, header, ref_seqs);
472  }
473 
474  int32_t tmp_pnext{};
475  read_field(field_view, tmp_pnext); // PNEXT
476 
477  if (tmp_pnext > 0)
478  get<1>(mate) = --tmp_pnext; // SAM format is 1-based but SeqAn operates 0-based.
479  else if (tmp_pnext < 0)
480  throw format_error{"No negative values are allowed at the mate mapping position."};
481  // tmp_pnext == 0 indicates an unmapped mate -> do not fill std::optional get<1>(mate)
482 
483  read_field(field_view, get<2>(mate)); // TLEN
484  }
485  else
486  {
487  for (size_t i = 0; i < 3u; ++i)
488  {
489  detail::consume(field_view);
490  }
491  }
492 
493  // Field 10: Sequence
494  // -------------------------------------------------------------------------------------------------------------
495  if (!is_char<'*'>(*std::ranges::begin(stream_view))) // sequence information is given
496  {
497  auto constexpr is_legal_alph = char_is_valid_for<seq_legal_alph_type>;
498  auto seq_stream = field_view | std::views::transform([is_legal_alph] (char const c) // enforce legal alphabet
499  {
500  if (!is_legal_alph(c))
501  throw parse_error{std::string{"Encountered an unexpected letter: "} +
502  "char_is_valid_for<" +
503  detail::type_name_as_string<seq_legal_alph_type> +
504  "> evaluated to false on " +
506  return c;
507  });
508 
509  if constexpr (detail::decays_to_ignore_v<seq_type>)
510  {
511  if constexpr (!detail::decays_to_ignore_v<align_type>)
512  {
513  static_assert(sequence_container<std::remove_reference_t<decltype(get<1>(align))>>,
514  "If you want to read ALIGNMENT but not SEQ, the alignment"
515  " object must store a sequence container at the second (query) position.");
516 
517  if (!tmp_cigar_vector.empty()) // only parse alignment if cigar information was given
518  {
519 
520  auto tmp_iter = std::ranges::begin(seq_stream);
521  std::ranges::advance(tmp_iter, offset_tmp);
522 
523  for (; seq_length > 0; --seq_length) // seq_length is not needed anymore
524  {
525  get<1>(align).push_back(std::ranges::range_value_t<decltype(get<1>(align))>{}.assign_char(*tmp_iter));
526  ++tmp_iter;
527  }
528 
529  std::ranges::advance(tmp_iter, soft_clipping_end);
530  }
531  else
532  {
533  get<1>(align) = std::remove_reference_t<decltype(get<1>(align))>{}; // empty container
534  }
535  }
536  else
537  {
538  detail::consume(seq_stream);
539  }
540  }
541  else
542  {
543  read_field(seq_stream, seq);
544 
545  if constexpr (!detail::decays_to_ignore_v<align_type>)
546  {
547  if (!tmp_cigar_vector.empty()) // if no alignment info is given, the field::alignment should remain empty
548  {
549  assign_unaligned(get<1>(align),
550  seq | views::slice(static_cast<decltype(std::ranges::size(seq))>(offset_tmp),
551  std::ranges::size(seq) - soft_clipping_end));
552  }
553  }
554  }
555  }
556  else
557  {
558  std::ranges::next(std::ranges::begin(field_view)); // skip '*'
559  }
560 
561  // Field 11: Quality
562  // -------------------------------------------------------------------------------------------------------------
563  auto const tab_or_end = is_char<'\t'> || is_char<'\r'> || is_char<'\n'>;
564  read_field(stream_view | detail::take_until_or_throw(tab_or_end), qual);
565 
566  if constexpr (!detail::decays_to_ignore_v<seq_type> && !detail::decays_to_ignore_v<qual_type>)
567  {
568  if (std::ranges::distance(seq) != 0 && std::ranges::distance(qual) != 0 &&
569  std::ranges::distance(seq) != std::ranges::distance(qual))
570  {
571  throw format_error{detail::to_string("Sequence length (", std::ranges::distance(seq),
572  ") and quality length (", std::ranges::distance(qual),
573  ") must be the same.")};
574  }
575  }
576 
577  // All remaining optional fields if any: SAM tags dictionary
578  // -------------------------------------------------------------------------------------------------------------
579  while (is_char<'\t'>(*std::ranges::begin(stream_view))) // read all tags if present
580  {
581  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
582  read_field(stream_view | detail::take_until_or_throw(tab_or_end), tag_dict);
583  }
584 
585  detail::consume(stream_view | detail::take_until(!(is_char<'\r'> || is_char<'\n'>))); // consume new line
586 
587  // DONE READING - wrap up
588  // -------------------------------------------------------------------------------------------------------------
589  // Alignment object construction
590  // Note that the query sequence in get<1>(align) has already been filled while reading Field 10.
591  if constexpr (!detail::decays_to_ignore_v<align_type>)
592  {
593  int32_t ref_idx{(ref_id_tmp.empty()/*unmapped read?*/) ? -1 : 0};
594 
595  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>)
596  {
597  if (!ref_id_tmp.empty())
598  {
599  assert(header.ref_dict.count(ref_id_tmp) != 0); // taken care of in check_and_assign_ref_id()
600  ref_idx = header.ref_dict[ref_id_tmp]; // get index for reference sequence
601  }
602  }
603 
604  construct_alignment(align, tmp_cigar_vector, ref_idx, ref_seqs, ref_offset_tmp, ref_length);
605  }
606 
607  if constexpr (!detail::decays_to_ignore_v<cigar_type>)
608  std::swap(cigar_vector, tmp_cigar_vector);
609 }
610 
612 template <typename stream_type,
613  typename header_type,
614  typename seq_type,
615  typename id_type,
616  typename ref_seq_type,
617  typename ref_id_type,
618  typename align_type,
619  typename qual_type,
620  typename mate_type,
621  typename tag_dict_type,
622  typename e_value_type,
623  typename bit_score_type>
624 inline void format_sam::write_alignment_record(stream_type & stream,
625  sam_file_output_options const & options,
626  header_type && header,
627  seq_type && seq,
628  qual_type && qual,
629  id_type && id,
630  int32_t const offset,
631  ref_seq_type && SEQAN3_DOXYGEN_ONLY(ref_seq),
632  ref_id_type && ref_id,
633  std::optional<int32_t> ref_offset,
634  align_type && align,
635  std::vector<cigar> const & cigar_vector,
636  sam_flag const flag,
637  uint8_t const mapq,
638  mate_type && mate,
639  tag_dict_type && tag_dict,
640  e_value_type && SEQAN3_DOXYGEN_ONLY(e_value),
641  bit_score_type && SEQAN3_DOXYGEN_ONLY(bit_score))
642 {
643  /* Note the following general things:
644  *
645  * - Given the SAM specifications, all fields may be empty
646  *
647  * - arithmetic values default to 0 while all others default to '*'
648  *
649  * - Because of the former, arithmetic values can be directly streamed
650  * into 'stream' as operator<< is defined for all arithmetic types
651  * and the default value (0) is also the SAM default.
652  *
653  * - All other non-arithmetic values need to be checked for emptiness
654  */
655 
656  // ---------------------------------------------------------------------
657  // Type Requirements (as static asserts for user friendliness)
658  // ---------------------------------------------------------------------
659  static_assert((std::ranges::forward_range<seq_type> &&
660  alphabet<std::ranges::range_reference_t<seq_type>>),
661  "The seq object must be a std::ranges::forward_range over "
662  "letters that model seqan3::alphabet.");
663 
664  static_assert((std::ranges::forward_range<id_type> &&
665  alphabet<std::ranges::range_reference_t<id_type>>),
666  "The id object must be a std::ranges::forward_range over "
667  "letters that model seqan3::alphabet.");
668 
669  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
670  {
671  static_assert((std::ranges::forward_range<ref_id_type> ||
672  std::integral<std::remove_reference_t<ref_id_type>> ||
673  detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>, std::optional>),
674  "The ref_id object must be a std::ranges::forward_range "
675  "over letters that model seqan3::alphabet.");
676 
677  if constexpr (std::integral<std::remove_cvref_t<ref_id_type>> ||
678  detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>, std::optional>)
679  static_assert(!detail::decays_to_ignore_v<header_type>,
680  "If you give indices as reference id information the header must also be present.");
681  }
682 
684  "The align object must be a std::pair of two ranges whose "
685  "value_type is comparable to seqan3::gap");
686 
687  static_assert((std::tuple_size_v<std::remove_cvref_t<align_type>> == 2 &&
688  std::equality_comparable_with<gap, std::ranges::range_reference_t<decltype(std::get<0>(align))>> &&
689  std::equality_comparable_with<gap, std::ranges::range_reference_t<decltype(std::get<1>(align))>>),
690  "The align object must be a std::pair of two ranges whose "
691  "value_type is comparable to seqan3::gap");
692 
693  static_assert((std::ranges::forward_range<qual_type> &&
694  alphabet<std::ranges::range_reference_t<qual_type>>),
695  "The qual object must be a std::ranges::forward_range "
696  "over letters that model seqan3::alphabet.");
697 
699  "The mate object must be a std::tuple of size 3 with "
700  "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
701  "2) a std::integral or std::optional<std::integral>, and "
702  "3) a std::integral.");
703 
704  static_assert(((std::ranges::forward_range<decltype(std::get<0>(mate))> ||
705  std::integral<std::remove_cvref_t<decltype(std::get<0>(mate))>> ||
706  detail::is_type_specialisation_of_v<std::remove_cvref_t<decltype(std::get<0>(mate))>, std::optional>) &&
707  (std::integral<std::remove_cvref_t<decltype(std::get<1>(mate))>> ||
708  detail::is_type_specialisation_of_v<std::remove_cvref_t<decltype(std::get<1>(mate))>, std::optional>) &&
709  std::integral<std::remove_cvref_t<decltype(std::get<2>(mate))>>),
710  "The mate object must be a std::tuple of size 3 with "
711  "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
712  "2) a std::integral or std::optional<std::integral>, and "
713  "3) a std::integral.");
714 
715  if constexpr (std::integral<std::remove_cvref_t<decltype(std::get<0>(mate))>> ||
716  detail::is_type_specialisation_of_v<std::remove_cvref_t<decltype(std::get<0>(mate))>, std::optional>)
717  static_assert(!detail::decays_to_ignore_v<header_type>,
718  "If you give indices as mate reference id information the header must also be present.");
719 
720  static_assert(std::same_as<std::remove_cvref_t<tag_dict_type>, sam_tag_dictionary>,
721  "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
722 
723  // ---------------------------------------------------------------------
724  // logical Requirements
725  // ---------------------------------------------------------------------
726  if constexpr (!detail::decays_to_ignore_v<header_type> &&
727  !detail::decays_to_ignore_v<ref_id_type> &&
728  !std::integral<std::remove_reference_t<ref_id_type>> &&
729  !detail::is_type_specialisation_of_v<std::remove_reference_t<ref_id_type>, std::optional>)
730  {
731 
732  if (options.sam_require_header && !std::ranges::empty(ref_id))
733  {
734  auto id_it = header.ref_dict.end();
735 
736  if constexpr (std::ranges::contiguous_range<decltype(ref_id)> &&
737  std::ranges::sized_range<decltype(ref_id)> &&
738  std::ranges::borrowed_range<decltype(ref_id)>)
739  {
740  id_it = header.ref_dict.find(std::span{std::ranges::data(ref_id), std::ranges::size(ref_id)});
741  }
742  else
743  {
744  using header_ref_id_type = std::remove_reference_t<decltype(header.ref_ids()[0])>;
745 
747  "The ref_id type is not convertible to the reference id information stored in the "
748  "reference dictionary of the header object.");
749 
750  id_it = header.ref_dict.find(ref_id);
751  }
752 
753  if (id_it == header.ref_dict.end()) // no reference id matched
754  throw format_error{detail::to_string("The ref_id '", ref_id, "' was not in the list of references:",
755  header.ref_ids())};
756  }
757  }
758 
759  if (ref_offset.has_value() && (ref_offset.value() + 1) < 0)
760  throw format_error{"The ref_offset object must be an std::integral >= 0."};
761 
762  // ---------------------------------------------------------------------
763  // Writing the Header on first call
764  // ---------------------------------------------------------------------
765  if constexpr (!detail::decays_to_ignore_v<header_type>)
766  {
767  if (options.sam_require_header && !header_was_written)
768  {
769  write_header(stream, options, header);
770  header_was_written = true;
771  }
772  }
773 
774  // ---------------------------------------------------------------------
775  // Writing the Record
776  // ---------------------------------------------------------------------
777 
778  detail::fast_ostreambuf_iterator stream_it{*stream.rdbuf()};
779  constexpr char separator{'\t'};
780 
781  write_range_or_asterisk(stream_it, id);
782  *stream_it = separator;
783 
784  stream_it.write_number(static_cast<uint16_t>(flag));
785  *stream_it = separator;
786 
787  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
788  {
789  if constexpr (std::integral<std::remove_reference_t<ref_id_type>>)
790  {
791  write_range_or_asterisk(stream_it, (header.ref_ids())[ref_id]);
792  }
793  else if constexpr (detail::is_type_specialisation_of_v<std::remove_reference_t<ref_id_type>, std::optional>)
794  {
795  if (ref_id.has_value())
796  write_range_or_asterisk(stream_it, (header.ref_ids())[ref_id.value()]);
797  else
798  *stream_it = '*';
799  }
800  else
801  {
802  write_range_or_asterisk(stream_it, ref_id);
803  }
804  }
805  else
806  {
807  *stream_it = '*';
808  }
809 
810  *stream_it = separator;
811 
812  // SAM is 1 based, 0 indicates unmapped read if optional is not set
813  stream_it.write_number(ref_offset.value_or(-1) + 1);
814  *stream_it = separator;
815 
816  stream_it.write_number(static_cast<unsigned>(mapq));
817  *stream_it = separator;
818 
819  if (!std::ranges::empty(cigar_vector))
820  {
821  for (auto & c : cigar_vector) //TODO THIS IS PROBABLY TERRIBLE PERFORMANCE_WISE
822  stream_it.write_range(c.to_string());
823  }
824  else if (!std::ranges::empty(get<0>(align)) && !std::ranges::empty(get<1>(align)))
825  {
826  // compute possible distance from alignment end to sequence end
827  // which indicates soft clipping at the end.
828  // This should be replace by a free count_gaps function for
829  // aligned sequences which is more efficient if possible.
830  size_t off_end{std::ranges::size(seq) - offset};
831  for (auto chr : get<1>(align))
832  if (chr == gap{})
833  ++off_end;
834 
835  // Might happen if get<1>(align) doesn't correspond to the reference.
836  assert(off_end >= std::ranges::size(get<1>(align)));
837  off_end -= std::ranges::size(get<1>(align));
838 
839  write_range_or_asterisk(stream_it, detail::get_cigar_string(align, offset, off_end));
840  }
841  else
842  {
843  *stream_it = '*';
844  }
845 
846  *stream_it = separator;
847 
848  if constexpr (std::integral<std::remove_reference_t<decltype(get<0>(mate))>>)
849  {
850  write_range_or_asterisk(stream_it, (header.ref_ids())[get<0>(mate)]);
851  }
852  else if constexpr (detail::is_type_specialisation_of_v<std::remove_reference_t<decltype(get<0>(mate))>, std::optional>)
853  {
854  if (get<0>(mate).has_value())
855  // value_or(0) instead of value() (which is equivalent here) as a
856  // workaround for a ubsan false-positive in GCC8: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90058
857  write_range_or_asterisk(stream_it, header.ref_ids()[get<0>(mate).value_or(0)]);
858  else
859  *stream_it = '*';
860  }
861  else
862  {
863  write_range_or_asterisk(stream_it, get<0>(mate));
864  }
865 
866  *stream_it = separator;
867 
868  if constexpr (detail::is_type_specialisation_of_v<std::remove_cvref_t<decltype(get<1>(mate))>, std::optional>)
869  {
870  // SAM is 1 based, 0 indicates unmapped read if optional is not set
871  stream_it.write_number(get<1>(mate).value_or(-1) + 1);
872  *stream_it = separator;
873  }
874  else
875  {
876  stream_it.write_number(get<1>(mate));
877  *stream_it = separator;
878  }
879 
880  stream_it.write_number(get<2>(mate));
881  *stream_it = separator;
882 
883  write_range_or_asterisk(stream_it, seq);
884  *stream_it = separator;
885 
886  write_range_or_asterisk(stream_it, qual);
887 
888  write_tag_fields(stream_it, tag_dict, separator);
889 
890  stream_it.write_end_of_line(options.add_carriage_return);
891 }
892 
893 
911 template <typename stream_view_type, typename value_type>
913  stream_view_type && stream_view,
914  value_type value)
915 {
916  std::vector<value_type> tmp_vector;
917  while (std::ranges::begin(stream_view) != ranges::end(stream_view)) // not fully consumed yet
918  {
919  read_field(stream_view | detail::take_until(is_char<','>), value);
920  tmp_vector.push_back(value);
921 
922  if (is_char<','>(*std::ranges::begin(stream_view)))
923  std::ranges::next(std::ranges::begin(stream_view)); // skip ','
924  }
925  variant = std::move(tmp_vector);
926 }
927 
941 template <typename stream_view_type>
943  stream_view_type && stream_view)
944 {
945  std::vector<std::byte> tmp_vector;
946  std::byte value;
947 
948  while (std::ranges::begin(stream_view) != ranges::end(stream_view)) // not fully consumed yet
949  {
950  try
951  {
952  read_field(stream_view | detail::take_exactly_or_throw(2), value);
953  }
954  catch (std::exception const & e)
955  {
956  throw format_error{"Hexadecimal tag has an uneven number of digits!"};
957  }
958 
959  tmp_vector.push_back(value);
960  }
961 
962  variant = std::move(tmp_vector);
963 }
964 
982 template <typename stream_view_type>
983 inline void format_sam::read_field(stream_view_type && stream_view, sam_tag_dictionary & target)
984 {
985  /* Every SAM tag has the format "[TAG]:[TYPE_ID]:[VALUE]", where TAG is a two letter
986  name tag which is converted to a unique integer identifier and TYPE_ID is one character in [A,i,Z,H,B,f]
987  describing the type for the upcoming VALUES. If TYPE_ID=='B' it signals an array of comma separated
988  VALUE's and the inner value type is identified by the character following ':', one of [cCsSiIf].
989  */
990  uint16_t tag = static_cast<uint16_t>(*std::ranges::begin(stream_view)) << 8;
991  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
992  tag += static_cast<uint16_t>(*std::ranges::begin(stream_view));
993  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
994  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
995  char type_id = *std::ranges::begin(stream_view);
996  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
997  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
998 
999  switch (type_id)
1000  {
1001  case 'A' : // char
1002  {
1003  target[tag] = static_cast<char>(*std::ranges::begin(stream_view));
1004  std::ranges::next(std::ranges::begin(stream_view)); // skip char that has been read
1005  break;
1006  }
1007  case 'i' : // int32_t
1008  {
1009  int32_t tmp;
1010  read_field(stream_view, tmp);
1011  target[tag] = tmp;
1012  break;
1013  }
1014  case 'f' : // float
1015  {
1016  float tmp;
1017  read_field(stream_view, tmp);
1018  target[tag] = tmp;
1019  break;
1020  }
1021  case 'Z' : // string
1022  {
1023  target[tag] = stream_view | views::to<std::string>;
1024  break;
1025  }
1026  case 'H' :
1027  {
1028  read_sam_byte_vector(target[tag], stream_view);
1029  break;
1030  }
1031  case 'B' : // Array. Value type depends on second char [cCsSiIf]
1032  {
1033  char array_value_type_id = *std::ranges::begin(stream_view);
1034  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
1035  std::ranges::next(std::ranges::begin(stream_view)); // skip first ','
1036 
1037  switch (array_value_type_id)
1038  {
1039  case 'c' : // int8_t
1040  read_sam_dict_vector(target[tag], stream_view, int8_t{});
1041  break;
1042  case 'C' : // uint8_t
1043  read_sam_dict_vector(target[tag], stream_view, uint8_t{});
1044  break;
1045  case 's' : // int16_t
1046  read_sam_dict_vector(target[tag], stream_view, int16_t{});
1047  break;
1048  case 'S' : // uint16_t
1049  read_sam_dict_vector(target[tag], stream_view, uint16_t{});
1050  break;
1051  case 'i' : // int32_t
1052  read_sam_dict_vector(target[tag], stream_view, int32_t{});
1053  break;
1054  case 'I' : // uint32_t
1055  read_sam_dict_vector(target[tag], stream_view, uint32_t{});
1056  break;
1057  case 'f' : // float
1058  read_sam_dict_vector(target[tag], stream_view, float{});
1059  break;
1060  default:
1061  throw format_error{std::string("The first character in the numerical ") +
1062  "id of a SAM tag must be one of [cCsSiIf] but '" + array_value_type_id +
1063  "' was given."};
1064  }
1065  break;
1066  }
1067  default:
1068  throw format_error{std::string("The second character in the numerical id of a "
1069  "SAM tag must be one of [A,i,Z,H,B,f] but '") + type_id + "' was given."};
1070  }
1071 }
1072 
1080 template <typename stream_it_t, std::ranges::forward_range field_type>
1081 inline void format_sam::write_range_or_asterisk(stream_it_t & stream_it, field_type && field_value)
1082 {
1083  if (std::ranges::empty(field_value))
1084  {
1085  *stream_it = '*';
1086  }
1087  else
1088  {
1089  if constexpr (std::same_as<std::remove_cvref_t<std::ranges::range_reference_t<field_type>>, char>)
1090  stream_it.write_range(field_value);
1091  else // convert from alphabets to their character representation
1092  stream_it.write_range(field_value | views::to_char);
1093  }
1094 }
1095 
1102 template <typename stream_it_t>
1103 inline void format_sam::write_range_or_asterisk(stream_it_t & stream_it, char const * const field_value)
1104 {
1105  write_range_or_asterisk(stream_it, std::string_view{field_value});
1106 }
1107 
1115 template <typename stream_it_t>
1116 inline void format_sam::write_tag_fields(stream_it_t & stream_it, sam_tag_dictionary const & tag_dict, char const separator)
1117 {
1118  auto const stream_variant_fn = [&stream_it] (auto && arg) // helper to print an std::variant
1119  {
1120  using T = std::remove_cvref_t<decltype(arg)>;
1121 
1122  if constexpr (std::ranges::input_range<T>)
1123  {
1124  if constexpr (std::same_as<std::remove_cvref_t<std::ranges::range_reference_t<T>>, char>)
1125  {
1126  stream_it.write_range(arg);
1127  }
1128  else if constexpr (std::same_as<std::remove_cvref_t<std::ranges::range_reference_t<T>>, std::byte>)
1129  {
1130  if (!std::ranges::empty(arg))
1131  {
1132  stream_it.write_number(std::to_integer<uint8_t>(*std::ranges::begin(arg)));
1133 
1134  for (auto && elem : arg | std::views::drop(1))
1135  {
1136  *stream_it = ',';
1137  stream_it.write_number(std::to_integer<uint8_t>(elem));
1138  }
1139  }
1140  }
1141  else
1142  {
1143  if (!std::ranges::empty(arg))
1144  {
1145  stream_it.write_number(*std::ranges::begin(arg));
1146 
1147  for (auto && elem : arg | std::views::drop(1))
1148  {
1149  *stream_it = ',';
1150  stream_it.write_number(elem);
1151  }
1152  }
1153  }
1154  }
1155  else if constexpr (std::same_as<std::remove_cvref_t<T>, char>)
1156  {
1157  *stream_it = arg;
1158  }
1159  else // number
1160  {
1161  stream_it.write_number(arg);
1162  }
1163  };
1164 
1165  for (auto & [tag, variant] : tag_dict)
1166  {
1167  *stream_it = separator;
1168 
1169  char const char0 = tag / 256;
1170  char const char1 = tag % 256;
1171 
1172  *stream_it = char0;
1173  *stream_it = char1;
1174  *stream_it = ':';
1175  *stream_it = detail::sam_tag_type_char[variant.index()];
1176  *stream_it = ':';
1177 
1178  if (detail::sam_tag_type_char_extra[variant.index()] != '\0')
1179  {
1180  *stream_it = detail::sam_tag_type_char_extra[variant.index()];
1181  *stream_it = ',';
1182  }
1183 
1184  std::visit(stream_variant_fn, variant);
1185  }
1186 }
1187 
1188 } // namespace seqan3
Core alphabet concept and free function/type trait wrappers.
Functionally the same as std::ostreambuf_iterator, but offers writing a range more efficiently.
Definition: fast_ostreambuf_iterator.hpp:39
The alignment base format.
Definition: format_sam_base.hpp:44
void check_and_assign_ref_id(ref_id_type &ref_id, ref_id_tmp_type &ref_id_tmp, header_type &header, ref_seqs_type &)
Checks for known reference ids or adds a new reference is and assigns a reference id to ref_id.
Definition: format_sam_base.hpp:129
void write_header(stream_t &stream, sam_file_output_options const &options, sam_file_header< ref_ids_type > &header)
Writes the SAM header.
Definition: format_sam_base.hpp:723
void transfer_soft_clipping_to(std::vector< cigar > const &cigar_vector, int32_t &sc_begin, int32_t &sc_end) const
Transfer soft clipping information from the cigar_vector to sc_begin and sc_end.
Definition: format_sam_base.hpp:171
bool header_was_written
A variable that tracks whether the content of header has been written or not.
Definition: format_sam_base.hpp:65
void read_header(stream_view_type &&stream_view, sam_file_header< ref_ids_type > &hdr, ref_seqs_type &)
Reads the SAM header.
Definition: format_sam_base.hpp:401
void construct_alignment(align_type &align, std::vector< cigar > &cigar_vector, [[maybe_unused]] int32_t rid, [[maybe_unused]] ref_seqs_type &ref_seqs, [[maybe_unused]] int32_t ref_start, size_t ref_length)
Construct the field::alignment depending on the given information.
Definition: format_sam_base.hpp:213
The SAM format (tag).
Definition: format_sam.hpp:115
sam_file_header default_header
The default header for the alignment format.
Definition: format_sam.hpp:236
format_sam & operator=(format_sam const &)=default
Defaulted.
~format_sam()=default
Defaulted.
format_sam & operator=(format_sam &&)=default
Defaulted.
void read_sam_byte_vector(seqan3::detail::sam_tag_variant &variant, stream_view_type &&stream_view)
Reads a list of byte pairs as it is the case for SAM tag byte arrays.
Definition: format_sam.hpp:942
void read_alignment_record(stream_type &stream, sam_file_input_options< seq_legal_alph_type > const &options, ref_seqs_type &ref_seqs, sam_file_header< ref_ids_type > &header, seq_type &seq, qual_type &qual, id_type &id, offset_type &offset, ref_seq_type &ref_seq, ref_id_type &ref_id, ref_offset_type &ref_offset, align_type &align, cigar_type &cigar_vector, flag_type &flag, mapq_type &mapq, mate_type &mate, tag_dict_type &tag_dict, e_value_type &e_value, bit_score_type &bit_score)
Read from the specified stream and back-insert into the given field buffers.
Definition: format_sam.hpp:365
static std::vector< std::string > file_extensions
The valid file extensions for this format; note that you can modify this value.
Definition: format_sam.hpp:132
std::string_view const & default_or(detail::ignore_t) const noexcept
brief Returns a reference to dummy if passed a std::ignore.
Definition: format_sam.hpp:242
void write_sequence_record(stream_type &stream, sequence_file_output_options const &options, seq_type &&sequence, id_type &&id, qual_type &&qualities)
Write the given fields to the specified stream.
Definition: format_sam.hpp:314
void read_field(stream_view_type &&stream_view, sam_tag_dictionary &target)
Reads the optional tag fields into the seqan3::sam_tag_dictionary.
Definition: format_sam.hpp:983
format_sam(format_sam &&)=default
Defaulted.
void write_tag_fields(stream_it_t &stream, sam_tag_dictionary const &tag_dict, char const separator)
Writes the optional fields of the seqan3::sam_tag_dictionary.
Definition: format_sam.hpp:1116
void read_sam_dict_vector(seqan3::detail::sam_tag_variant &variant, stream_view_type &&stream_view, value_type value)
Reads a list of values separated by comma as it is the case for SAM tag arrays.
Definition: format_sam.hpp:912
static constexpr std::string_view dummy
An empty dummy container to pass to align_format.write() such that an empty field is written.
Definition: format_sam.hpp:233
void read_sequence_record(stream_type &stream, sequence_file_input_options< seq_legal_alph_type > const &options, seq_type &sequence, id_type &id, qual_type &qualities)
Read from the specified stream and back-insert into the given field buffers.
Definition: format_sam.hpp:284
void write_range_or_asterisk(stream_it_t &stream_it, field_type &&field_value)
Writes a field value to the stream.
Definition: format_sam.hpp:1081
bool ref_info_present_in_header
Tracks whether reference information (@SR tag) were found in the SAM header.
Definition: format_sam.hpp:239
std::string tmp_qual
Stores quality values temporarily if seq and qual information are combined (not supported by SAM yet)...
Definition: format_sam.hpp:230
void write_alignment_record(stream_type &stream, sam_file_output_options const &options, header_type &&header, seq_type &&seq, qual_type &&qual, id_type &&id, int32_t const offset, ref_seq_type &&ref_seq, ref_id_type &&ref_id, std::optional< int32_t > ref_offset, align_type &&align, std::vector< cigar > const &cigar_vector, sam_flag const flag, uint8_t const mapq, mate_type &&mate, tag_dict_type &&tag_dict, e_value_type &&e_value, bit_score_type &&bit_score)
Write the given fields to the specified stream.
Definition: format_sam.hpp:624
format_sam()=default
Defaulted.
format_sam(format_sam const &)=default
Defaulted.
The alphabet of a gap character '-'.
Definition: gap.hpp:39
Stores the header information of alignment files.
Definition: header.hpp:32
std::unordered_map< key_type, int32_t, std::hash< key_type >, detail::view_equality_fn > ref_dict
The mapping of reference id to position in the ref_ids() range and the ref_id_info range.
Definition: header.hpp:157
ref_ids_type & ref_ids()
The range of reference ids.
Definition: header.hpp:118
The SAM tag dictionary class that stores all optional SAM fields.
Definition: sam_tag_dictionary.hpp:334
Provides seqan3::detail::fast_ostreambuf_iterator.
Provides the seqan3::format_sam_base that can be inherited from.
auto const to_char
A view that calls seqan3::to_char() on each element in the input range.
Definition: to_char.hpp:63
constexpr void consume(rng_t &&rng)
Iterate over a range (consumes single-pass input ranges).
Definition: misc.hpp:28
std::tuple< std::vector< cigar >, int32_t, int32_t > parse_cigar(cigar_input_type &&cigar_input)
Parses a cigar string into a vector of operation-count pairs (e.g. (M, 3)).
Definition: cigar.hpp:137
sam_flag
An enum flag that describes the properties of an aligned read (given as a SAM record).
Definition: sam_flag.hpp:74
std::string get_cigar_string(std::vector< cigar > const &cigar_vector)
Transforms a vector of cigar elements into a string representation.
Definition: cigar.hpp:266
constexpr char sam_tag_type_char_extra[12]
Each types SAM tag type extra char id. Index corresponds to the seqan3::detail::sam_tag_variant types...
Definition: sam_tag_dictionary.hpp:40
constexpr char sam_tag_type_char[12]
Each SAM tag type char identifier. Index corresponds to the seqan3::detail::sam_tag_variant types.
Definition: sam_tag_dictionary.hpp:37
@ none
None of the flags below are set.
constexpr auto take_until
A view adaptor that returns elements from the underlying range until the functor evaluates to true (o...
Definition: take_until_view.hpp:584
constexpr auto take_exactly_or_throw
A view adaptor that returns the first size elements from the underlying range and also exposes size i...
Definition: take_exactly_view.hpp:608
constexpr auto take_until_or_throw_and_consume
A view adaptor that returns elements from the underlying range until the functor evaluates to true (t...
Definition: take_until_view.hpp:626
constexpr auto take_until_and_consume
A view adaptor that returns elements from the underlying range until the functor evaluates to true (o...
Definition: take_until_view.hpp:612
constexpr auto take_until_or_throw
A view adaptor that returns elements from the underlying range until the functor evaluates to true (t...
Definition: take_until_view.hpp:598
constexpr auto istreambuf
A view factory that returns a view over the stream buffer of an input stream.
Definition: istreambuf_view.hpp:110
@ flag
The alignment flag (bit information), uint16_t value.
@ ref_offset
Sequence (seqan3::field::ref_seq) relative start position (0-based), unsigned value.
@ mapq
The mapping quality of the seqan3::field::seq alignment, usually a Phred-scaled score.
@ offset
Sequence (seqan3::field::seq) relative start position (0-based), unsigned value.
@ mate
The mate pair information given as a std::tuple of reference name, offset and template length.
@ ref_id
The identifier of the (reference) sequence that seqan3::field::seq was aligned to.
@ seq
The "sequence", usually a range of nucleotides or amino acids.
@ qual
The qualities, usually in Phred score notation.
std::string make_printable(char const c)
Returns a printable value for the given character c.
Definition: pretty_print.hpp:48
constexpr auto is_space
Checks whether c is a space character.
Definition: predicate.hpp:128
typename decltype(detail::split_after< i >(list_t{}))::second_type drop
Return a seqan3::type_list of the types in the input type list, except the first n.
Definition: traits.hpp:388
decltype(detail::transform< trait_t >(list_t{})) transform
Apply a transformation trait to every type in the list and return a seqan3::type_list of the results.
Definition: traits.hpp:471
constexpr size_t size
The size of a type pack.
Definition: traits.hpp:151
constexpr auto slice
A view adaptor that returns a half-open interval on the underlying range.
Definition: slice.hpp:183
Provides the seqan3::sam_file_header class.
The generic alphabet concept that covers most data types used in ranges.
Resolves to std::ranges::implicitly_convertible_to<type1, type2>(). <dl class="no-api">This entity i...
A more refined container concept than seqan3::container.
The generic concept for a (biological) sequence.
Whether a type behaves like a tuple.
Auxiliary functions for the alignment IO.
Provides seqan3::detail::istreambuf.
std::string to_string(value_type &&...values)
Streams all parameters via the seqan3::debug_stream and returns a concatenated string.
Definition: to_string.hpp:29
The main SeqAn3 namespace.
Definition: aligned_sequence_concept.hpp:29
T push_back(T... args)
Adaptations of concepts from the Ranges TS.
Provides seqan3::sam_file_input_format and auxiliary classes.
Provides seqan3::sam_file_output_options.
Provides helper data structures for the seqan3::sam_file_output.
Provides the seqan3::sam_tag_dictionary class and auxiliaries.
Provides seqan3::sequence_file_input_format and auxiliary classes.
Provides seqan3::sequence_file_output_options.
Provides seqan3::views::slice.
Thrown if information given to output format didn't match expectations.
Definition: exception.hpp:91
Thrown if there is a parse error, such as reading an unexpected character from an input stream.
Definition: exception.hpp:48
The options type defines various option members that influence the behaviour of all or some formats.
Definition: input_options.hpp:24
The options type defines various option members that influence the behavior of all or some formats.
Definition: output_options.hpp:23
bool add_carriage_return
The default plain text line-ending is "\n", but on Windows an additional carriage return is recommend...
Definition: output_options.hpp:27
bool sam_require_header
Whether to require a header for SAM files.
Definition: output_options.hpp:41
The options type defines various option members that influence the behaviour of all or some formats.
Definition: input_options.hpp:25
bool truncate_ids
Read the ID string only up until the first whitespace character.
Definition: input_options.hpp:27
The options type defines various option members that influence the behaviour of all or some formats.
Definition: output_options.hpp:23
T swap(T... args)
Provides seqan3::views::take_until and seqan3::views::take_until_or_throw.
T tie(T... args)
Provides seqan3::views::to.
Provides seqan3::views::to_char.
T tuple_size_v
Provides traits to inspect some information of a type, for example its name.
Provides seqan3::tuple_like.
T visit(T... args)