SeqAn3  3.0.1
The Modern C++ library for sequence analysis.
format_sam_base.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2020, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2020, 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 <string>
17 #include <vector>
18 
46 #include <seqan3/std/algorithm>
47 #include <seqan3/std/charconv>
48 #include <seqan3/std/concepts>
49 #include <seqan3/std/ranges>
50 
51 namespace seqan3::detail
52 {
53 
62 class format_sam_base
63 {
64 protected:
68  format_sam_base() noexcept = default;
69  format_sam_base(format_sam_base const &) noexcept = default;
70  format_sam_base & operator=(format_sam_base const &) noexcept = default;
71  format_sam_base(format_sam_base &&) noexcept = default;
72  format_sam_base & operator=(format_sam_base &&) noexcept = default;
73  ~format_sam_base() noexcept = default;
74 
77  static constexpr std::array format_version{'1', '.', '6'};
78 
80  std::array<char, 316> arithmetic_buffer{}; // Doubles can be up to 316 characters
81 
83  bool header_was_written{false};
84 
86  bool ref_info_present_in_header{false};
87 
88  template <typename ref_id_type,
89  typename ref_id_tmp_type,
90  typename header_type,
91  typename ref_seqs_type>
92  void check_and_assign_ref_id(ref_id_type & ref_id,
93  ref_id_tmp_type & ref_id_tmp,
94  header_type & header,
95  ref_seqs_type & /*tag*/);
96 
97  static void update_alignment_lengths(int32_t & ref_length,
98  int32_t & seq_length,
99  char const cigar_operation,
100  uint32_t const cigar_count);
101 
102  template <typename align_type, typename ref_seqs_type>
103  void construct_alignment(align_type & align,
104  std::vector<cigar> & cigar_vector,
105  [[maybe_unused]] int32_t rid,
106  [[maybe_unused]] ref_seqs_type & ref_seqs,
107  [[maybe_unused]] int32_t ref_start,
108  size_t ref_length);
109 
110  void transfer_soft_clipping_to(std::vector<cigar> const & cigar_vector, int32_t & sc_begin, int32_t & sc_end) const;
111 
112  template <typename cigar_input_type>
113  std::tuple<std::vector<cigar>, int32_t, int32_t> parse_cigar(cigar_input_type && cigar_input) const;
114 
115  template <typename stream_view_type>
116  void read_field(stream_view_type && stream_view, detail::ignore_t const & SEQAN3_DOXYGEN_ONLY(target));
117 
118  template <typename stream_view_type, std::ranges::forward_range target_range_type>
119  void read_field(stream_view_type && stream_view, target_range_type & target);
120 
121  template <typename stream_view_t, arithmetic arithmetic_target_type>
122  void read_field(stream_view_t && stream_view, arithmetic_target_type & arithmetic_target);
123 
124  template <typename stream_view_type, typename optional_value_type>
125  void read_field(stream_view_type && stream_view, std::optional<optional_value_type> & target);
126 
127  template <typename stream_view_type, typename ref_ids_type, typename ref_seqs_type>
128  void read_header(stream_view_type && stream_view,
129  alignment_file_header<ref_ids_type> & hdr,
130  ref_seqs_type & /*ref_id_to_pos_map*/);
131 
132  template <typename stream_t, typename ref_ids_type>
133  void write_header(stream_t & stream,
134  alignment_file_output_options const & options,
135  alignment_file_header<ref_ids_type> & header);
136 };
137 
148 template <typename ref_id_type,
149  typename ref_id_tmp_type,
150  typename header_type,
151  typename ref_seqs_type>
152 inline void format_sam_base::check_and_assign_ref_id(ref_id_type & ref_id,
153  ref_id_tmp_type & ref_id_tmp,
154  header_type & header,
155  ref_seqs_type & /*tag*/)
156 {
157  if (!std::ranges::empty(ref_id_tmp)) // otherwise the std::optional will not be filled
158  {
159  auto search = header.ref_dict.find(ref_id_tmp);
160 
161  if (search == header.ref_dict.end())
162  {
163  if constexpr(detail::decays_to_ignore_v<ref_seqs_type>) // no reference information given
164  {
165  if (ref_info_present_in_header)
166  {
167  throw format_error{"Unknown reference id found in record which is not present in the header."};
168  }
169  else
170  {
171  header.ref_ids().push_back(ref_id_tmp);
172  auto pos = std::ranges::size(header.ref_ids()) - 1;
173  header.ref_dict[header.ref_ids()[pos]] = pos;
174  ref_id = pos;
175  }
176  }
177  else
178  {
179  throw format_error{"Unknown reference id found in record which is not present in the given ids."};
180  }
181  }
182  else
183  {
184  ref_id = search->second;
185  }
186  }
187 }
188 
195 inline void format_sam_base::update_alignment_lengths(int32_t & ref_length,
196  int32_t & seq_length,
197  char const cigar_operation,
198  uint32_t const cigar_count)
199 {
200  switch (cigar_operation)
201  {
202  case 'M': case '=': case 'X': ref_length += cigar_count, seq_length += cigar_count; break;
203  case 'D': case 'N': ref_length += cigar_count; break;
204  case 'I' : seq_length += cigar_count; break;
205  case 'S': case 'H': case 'P': break; // no op (soft-clipping or padding does not increase either length)
206  default: throw format_error{"Illegal cigar operation: " + std::string{cigar_operation}};
207  }
208 }
209 
215 inline void format_sam_base::transfer_soft_clipping_to(std::vector<cigar> const & cigar_vector,
216  int32_t & sc_begin,
217  int32_t & sc_end) const
218 {
219  // Checks if the given index in the cigar vector is a soft clip.
220  auto soft_clipping_at = [&] (size_t const index) { return cigar_vector[index] == 'S'_cigar_op; };
221  // Checks if the given index in the cigar vector is a hard clip.
222  auto hard_clipping_at = [&] (size_t const index) { return cigar_vector[index] == 'H'_cigar_op; };
223  // Checks if the given cigar vector as at least min_size many elements.
224  auto vector_size_at_least = [&] (size_t const min_size) { return cigar_vector.size() >= min_size; };
225  // Returns the cigar count of the ith cigar element in the given cigar vector.
226  auto cigar_count_at = [&] (size_t const index) { return get<0>(cigar_vector[index]); };
227 
228  // check for soft clipping at the first two positions
229  if (vector_size_at_least(1) && soft_clipping_at(0))
230  sc_begin = cigar_count_at(0);
231  else if (vector_size_at_least(2) && hard_clipping_at(0) && soft_clipping_at(1))
232  sc_begin = cigar_count_at(1);
233 
234  // Check for soft clipping at the last two positions. But only if the vector size has at least 2, respectively
235  // 3 elements. Accordingly, if the following arithmetics overflow they are protected by the corresponding
236  // if expressions below.
237  auto last_index = cigar_vector.size() - 1;
238  auto second_last_index = last_index - 1;
239 
240  if (vector_size_at_least(2) && soft_clipping_at(last_index))
241  sc_end = cigar_count_at(last_index);
242  else if (vector_size_at_least(3) && hard_clipping_at(last_index) && soft_clipping_at(second_last_index))
243  sc_end = cigar_count_at(second_last_index);
244 }
245 
259 template <typename cigar_input_type>
260 inline std::tuple<std::vector<cigar>, int32_t, int32_t> format_sam_base::parse_cigar(cigar_input_type && cigar_input) const
261 {
262  std::vector<cigar> operations{};
263  std::array<char, 20> buffer{}; // buffer to parse numbers with from_chars. Biggest number should fit in uint64_t
264  char cigar_operation{};
265  uint32_t cigar_count{};
266  int32_t ref_length{}, seq_length{}; // length of aligned part for ref and query
267 
268  // transform input into a single input view if it isn't already
269  auto cigar_view = cigar_input | views::single_pass_input;
270 
271  // parse the rest of the cigar
272  // -------------------------------------------------------------------------------------------------------------
273  while (std::ranges::begin(cigar_view) != std::ranges::end(cigar_view)) // until stream is not empty
274  {
275  auto buff_end = (std::ranges::copy(cigar_view | views::take_until_or_throw(!is_digit), buffer.data())).out;
276  cigar_operation = *std::ranges::begin(cigar_view);
277  std::ranges::next(std::ranges::begin(cigar_view));
278 
279  if (std::from_chars(buffer.begin(), buff_end, cigar_count).ec != std::errc{})
280  throw format_error{"Corrupted cigar string encountered"};
281 
282  update_alignment_lengths(ref_length, seq_length, cigar_operation, cigar_count);
283  operations.emplace_back(cigar_count, cigar_op{}.assign_char(cigar_operation));
284  }
285 
286  return {operations, ref_length, seq_length};
287 }
288 
299 template <typename align_type, typename ref_seqs_type>
300 inline void format_sam_base::construct_alignment(align_type & align,
301  std::vector<cigar> & cigar_vector,
302  [[maybe_unused]] int32_t rid,
303  [[maybe_unused]] ref_seqs_type & ref_seqs,
304  [[maybe_unused]] int32_t ref_start,
305  size_t ref_length)
306 {
307  if (rid > -1 && ref_start > -1 && // read is mapped
308  !cigar_vector.empty() && // alignment field was not empty
309  !std::ranges::empty(get<1>(align))) // seq field was not empty
310  {
311  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>)
312  {
313  assert(static_cast<size_t>(ref_start + ref_length) <= std::ranges::size(ref_seqs[rid]));
314  // copy over unaligned reference sequence part
315  assign_unaligned(get<0>(align), ref_seqs[rid] | views::slice(ref_start, ref_start + ref_length));
316  }
317  else
318  {
319  using unaligned_t = remove_cvref_t<detail::unaligned_seq_t<decltype(get<0>(align))>>;
320  auto dummy_seq = views::repeat_n(value_type_t<unaligned_t>{}, ref_length)
321  | std::views::transform(detail::access_restrictor_fn{});
322  static_assert(std::same_as<unaligned_t, decltype(dummy_seq)>,
323  "No reference information was given so the type of the first alignment tuple position"
324  "must have an unaligned sequence type of a dummy sequence ("
325  "views::repeat_n(dna5{}, size_t{}) | "
326  "std::views::transform(detail::access_restrictor_fn{}))");
327 
328  assign_unaligned(get<0>(align), dummy_seq); // assign dummy sequence
329  }
330 
331  // insert gaps according to the cigar information
332  detail::alignment_from_cigar(align, cigar_vector);
333  }
334  else // not enough information for an alignment, assign an empty view/dummy_sequence
335  {
336  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>) // reference info given
337  {
338  assert(std::ranges::size(ref_seqs) > 0); // we assume that the given ref info is not empty
339  assign_unaligned(get<0>(align), ref_seqs[0] | views::slice(0, 0));
340  }
341  else
342  {
343  using unaligned_t = remove_cvref_t<detail::unaligned_seq_t<decltype(get<0>(align))>>;
344  assign_unaligned(get<0>(align), views::repeat_n(value_type_t<unaligned_t>{}, 0)
345  | std::views::transform(detail::access_restrictor_fn{}));
346  }
347  }
348 }
349 
356 template <typename stream_view_type>
357 inline void format_sam_base::read_field(stream_view_type && stream_view,
358  detail::ignore_t const & SEQAN3_DOXYGEN_ONLY(target))
359 {
360  detail::consume(stream_view);
361 }
362 
370 template <typename stream_view_type, std::ranges::forward_range target_range_type>
371 inline void format_sam_base::read_field(stream_view_type && stream_view, target_range_type & target)
372 {
373  if (!is_char<'*'>(*std::ranges::begin(stream_view)))
374  std::ranges::copy(stream_view | views::char_to<value_type_t<target_range_type>>,
375  std::ranges::back_inserter(target));
376  else
377  std::ranges::next(std::ranges::begin(stream_view)); // skip '*'
378 }
379 
390 template <typename stream_view_t, arithmetic arithmetic_target_type>
391 inline void format_sam_base::read_field(stream_view_t && stream_view, arithmetic_target_type & arithmetic_target)
392 {
393  // unfortunately std::from_chars only accepts char const * so we need a buffer.
394  auto [ignore, end] = std::ranges::copy(stream_view, arithmetic_buffer.data());
395  (void) ignore;
396  std::from_chars_result res = std::from_chars(arithmetic_buffer.begin(), end, arithmetic_target);
397 
398  if (res.ec == std::errc::invalid_argument || res.ptr != end)
399  throw format_error{std::string("[CORRUPTED SAM FILE] The string '") +
400  std::string(arithmetic_buffer.begin(), end) +
401  "' could not be cast into type " +
402  detail::type_name_as_string<arithmetic_target_type>};
403 
404  if (res.ec == std::errc::result_out_of_range)
405  throw format_error{std::string("[CORRUPTED SAM FILE] Casting '") + std::string(arithmetic_buffer.begin(), end) +
406  "' into type " + detail::type_name_as_string<arithmetic_target_type> +
407  " would cause an overflow."};
408 }
409 
420 template <typename stream_view_type, typename optional_value_type>
421 inline void format_sam_base::read_field(stream_view_type && stream_view, std::optional<optional_value_type> & target)
422 {
423  optional_value_type tmp;
424  read_field(std::forward<stream_view_type>(stream_view), tmp);
425  target = tmp;
426 }
427 
444 template <typename stream_view_type, typename ref_ids_type, typename ref_seqs_type>
445 inline void format_sam_base::read_header(stream_view_type && stream_view,
446  alignment_file_header<ref_ids_type> & hdr,
447  ref_seqs_type & /*ref_id_to_pos_map*/)
448 {
449  auto parse_tag_value = [&stream_view, this] (auto & value) // helper function to parse the next tag value
450  {
451  detail::consume(stream_view | views::take_until_or_throw(is_char<':'>)); // skip tag name
452  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
453  read_field(stream_view | views::take_until_or_throw(is_char<'\t'> || is_char<'\n'>), value);
454  };
455 
456  while (is_char<'@'>(*std::ranges::begin(stream_view)))
457  {
458  std::ranges::next(std::ranges::begin(stream_view)); // skip @
459 
460  if (is_char<'H'>(*std::ranges::begin(stream_view))) // HD (header) tag
461  {
462  parse_tag_value(hdr.format_version); // parse required VN (version) tag
463 
464  // The SO, SS and GO tag are optional and can appear in any order
465  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
466  {
467  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
468  std::string * who = std::addressof(hdr.grouping);
469 
470  if (is_char<'S'>(*std::ranges::begin(stream_view)))
471  {
472  std::ranges::next(std::ranges::begin(stream_view)); // skip S
473 
474  if (is_char<'O'>(*std::ranges::begin(stream_view))) // SO (sorting) tag
475  who = std::addressof(hdr.sorting);
476  else if (is_char<'S'>(*std::ranges::begin(stream_view))) // SS (sub-order) tag
477  who = std::addressof(hdr.subsorting);
478  else
479  throw format_error{std::string{"Illegal SAM header tag: S"} +
480  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
481  }
482  else if (!is_char<'G'>(*std::ranges::begin(stream_view))) // GO (grouping) tag
483  {
484  throw format_error{std::string{"Illegal SAM header tag in @HG starting with:"} +
485  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
486  }
487 
488  parse_tag_value(*who);
489  }
490  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
491  }
492  else if (is_char<'S'>(*std::ranges::begin(stream_view))) // SQ (sequence dictionary) tag
493  {
494  ref_info_present_in_header = true;
495  value_type_t<decltype(hdr.ref_ids())> id;
497 
498  parse_tag_value(id); // parse required SN (sequence name) tag
499  std::ranges::next(std::ranges::begin(stream_view)); // skip tab or newline
500  parse_tag_value(get<0>(info)); // parse required LN (length) tag
501 
502  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
503  {
504  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
505  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), get<1>(info));
506  }
507  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
508 
509  /* If reference information were given, the ids exist and we can fill ref_dict directly.
510  * If not, wee need to update the ids first and fill the reference dictionary afterwards. */
511  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>) // reference information given
512  {
513  auto id_it = hdr.ref_dict.find(id);
514 
515  if (id_it == hdr.ref_dict.end())
516  throw format_error{detail::to_string("Unknown reference name '", id, "' found in SAM header ",
517  "(header.ref_ids(): ", hdr.ref_ids(), ").")};
518 
519  auto & given_ref_info = hdr.ref_id_info[id_it->second];
520 
521  if (std::get<0>(given_ref_info) != std::get<0>(info))
522  throw format_error{"Provided reference has unequal length as specified in the header."};
523 
524  hdr.ref_id_info[id_it->second] = std::move(info);
525  }
526  else
527  {
528  static_assert(!detail::is_type_specialisation_of_v<decltype(hdr.ref_ids()), std::deque>,
529  "The range over reference ids must be of type std::deque such that "
530  "pointers are not invalidated.");
531 
532  hdr.ref_ids().push_back(id);
533  hdr.ref_id_info.push_back(info);
534  hdr.ref_dict[(hdr.ref_ids())[(hdr.ref_ids()).size() - 1]] = (hdr.ref_ids()).size() - 1;
535  }
536  }
537  else if (is_char<'R'>(*std::ranges::begin(stream_view))) // RG (read group) tag
538  {
540 
541  parse_tag_value(get<0>(tmp)); // read required ID tag
542 
543  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
544  {
545  std::ranges::next(std::ranges::begin(stream_view));
546  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), get<1>(tmp));
547  }
548  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
549 
550  hdr.read_groups.emplace_back(std::move(tmp));
551  }
552  else if (is_char<'P'>(*std::ranges::begin(stream_view))) // PG (program) tag
553  {
554  typename alignment_file_header<ref_ids_type>::program_info_t tmp{};
555 
556  parse_tag_value(tmp.id); // read required ID tag
557 
558  // The PN, CL, PP, DS, VN are optional tags and can be given in any order.
559  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
560  {
561  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
562  std::string * who = &tmp.version;
563 
564  if (is_char<'P'>(*std::ranges::begin(stream_view)))
565  {
566  std::ranges::next(std::ranges::begin(stream_view)); // skip P
567 
568  if (is_char<'N'>(*std::ranges::begin(stream_view))) // PN (program name) tag
569  who = &tmp.name;
570  else // PP (previous program) tag
571  who = &tmp.previous;
572  }
573  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CL (command line) tag
574  {
575  who = &tmp.command_line_call;
576  }
577  else if (is_char<'D'>(*std::ranges::begin(stream_view))) // DS (description) tag
578  {
579  who = &tmp.description;
580  }
581  else if (!is_char<'V'>(*std::ranges::begin(stream_view))) // VN (version) tag
582  {
583  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
584  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
585  }
586 
587  parse_tag_value(*who);
588  }
589  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
590 
591  hdr.program_infos.emplace_back(std::move(tmp));
592  }
593  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CO (comment) tag
594  {
595  std::string tmp;
596  std::ranges::next(std::ranges::begin(stream_view)); // skip C
597  std::ranges::next(std::ranges::begin(stream_view)); // skip O
598  std::ranges::next(std::ranges::begin(stream_view)); // skip :
599  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), tmp);
600  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
601 
602  hdr.comments.emplace_back(std::move(tmp));
603  }
604  else
605  {
606  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
607  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
608  }
609  }
610 }
611 
628 template <typename stream_t, typename ref_ids_type>
629 inline void format_sam_base::write_header(stream_t & stream,
630  alignment_file_output_options const & options,
631  alignment_file_header<ref_ids_type> & header)
632 {
633  // -----------------------------------------------------------------
634  // Check Header
635  // -----------------------------------------------------------------
636 
637  // (@HD) Check header line
638  // The format version string will be taken from the local member variable
639  if (!header.sorting.empty() &&
640  !(header.sorting == "unknown" ||
641  header.sorting == "unsorted" ||
642  header.sorting == "queryname" ||
643  header.sorting == "coordinate" ))
644  throw format_error{"SAM format error: The header.sorting member must be "
645  "one of [unknown, unsorted, queryname, coordinate]."};
646 
647  if (!header.grouping.empty() &&
648  !(header.grouping == "none" ||
649  header.grouping == "query" ||
650  header.grouping == "reference"))
651  throw format_error{"SAM format error: The header.grouping member must be "
652  "one of [none, query, reference]."};
653 
654  // (@SQ) Check Reference Sequence Dictionary lines
655 
656  // TODO
657 
658  // - sorting order be one of ...
659  // - grouping can be one of ...
660  // - reference names must be unique
661  // - ids of read groups must be unique
662  // - program ids need to be unique
663  // many more small semantic things, like fits REGEX
664 
665  // -----------------------------------------------------------------
666  // Write Header
667  // -----------------------------------------------------------------
668  seqan3::ostreambuf_iterator stream_it{stream};
669 
670  // (@HD) Write header line [required].
671  stream << "@HD\tVN:";
672  std::ranges::copy(format_version, stream_it);
673 
674  if (!header.sorting.empty())
675  stream << "\tSO:" << header.sorting;
676 
677  if (!header.subsorting.empty())
678  stream << "\tSS:" << header.subsorting;
679 
680  if (!header.grouping.empty())
681  stream << "\tGO:" << header.grouping;
682 
683  detail::write_eol(stream_it, options.add_carriage_return);
684 
685  // (@SQ) Write Reference Sequence Dictionary lines [required].
686  for (auto const & [ref_name, ref_info] : views::zip(header.ref_ids(), header.ref_id_info))
687  {
688  stream << "@SQ\tSN:";
689 
690  std::ranges::copy(ref_name, stream_it);
691 
692  stream << "\tLN:" << get<0>(ref_info);
693 
694  if (!get<1>(ref_info).empty())
695  stream << "\t" << get<1>(ref_info);
696 
697  detail::write_eol(stream_it, options.add_carriage_return);
698  }
699 
700  // Write read group (@RG) lines if specified.
701  for (auto const & read_group : header.read_groups)
702  {
703  stream << "@RG"
704  << "\tID:" << get<0>(read_group);
705 
706  if (!get<1>(read_group).empty())
707  stream << "\t" << get<1>(read_group);
708 
709  detail::write_eol(stream_it, options.add_carriage_return);
710  }
711 
712  // Write program (@PG) lines if specified.
713  for (auto const & program : header.program_infos)
714  {
715  stream << "@PG"
716  << "\tID:" << program.id;
717 
718  if (!program.name.empty())
719  stream << "\tPN:" << program.name;
720 
721  if (!program.command_line_call.empty())
722  stream << "\tCL:" << program.command_line_call;
723 
724  if (!program.previous.empty())
725  stream << "\tPP:" << program.previous;
726 
727  if (!program.description.empty())
728  stream << "\tDS:" << program.description;
729 
730  if (!program.version.empty())
731  stream << "\tVN:" << program.version;
732 
733  detail::write_eol(stream_it, options.add_carriage_return);
734  }
735 
736  // Write comment (@CO) lines if specified.
737  for (auto const & comment : header.comments)
738  {
739  stream << "@CO\t" << comment;
740  detail::write_eol(stream_it, options.add_carriage_return);
741  }
742 }
743 
744 } // namespace seqan3::detail
zip.hpp
Provides seqan3::views::zip.
take_until.hpp
Provides seqan3::views::take_until and seqan3::views::take_until_or_throw.
seqan3::search
auto search(queries_t &&queries, index_t const &index, configuration_t const &cfg=search_cfg::default_configuration)
Search a query or a range of queries in an index.
Definition: search.hpp:167
std::string
tuple.hpp
Provides seqan3::tuple_like.
sam_tag_dictionary.hpp
Provides the seqan3::sam_tag_dictionary class and auxiliaries.
std::pair
charconv
Provides std::from_chars and std::to_chars if not defined in the stl <charconv> header.
vector
std::vector::size
T size(T... args)
seqan3::views::char_to
const auto char_to
A view over an alphabet, given a range of characters.
Definition: char_to.hpp:69
seqan3::views::move
const auto move
A view that turns lvalue-references into rvalue-references.
Definition: move.hpp:68
template_inspection.hpp
Provides seqan3::type_list and auxiliary type traits.
std::tuple
algorithm
Adaptations of algorithms from the Ranges TS.
std::from_chars_result
Result type of std::from_chars.
Definition: charconv:61
concepts
The Concepts library.
input_format_concept.hpp
Provides seqan3::alignment_file_input_format and auxiliary classes.
same_as
The concept std::same_as<T, U> is satisfied if and only if T and U denote the same type.
std::addressof
T addressof(T... args)
slice.hpp
Provides seqan3::views::slice.
repeat_n.hpp
Provides seqan3::views::repeat_n.
to.hpp
Provides seqan3::views::to.
range.hpp
Provides various transformation traits used by the range module.
core_language.hpp
Provides concepts for core language types and relations that don't have concepts in C++20 (yet).
seqan3::value_type_t
typename value_type< t >::type value_type_t
Shortcut for seqan3::value_type (transformation_trait shortcut).
Definition: pre.hpp:48
seqan3::field::comment
Comment field of arbitrary content, usually a string.
seqan3::ostreambuf_iterator
::ranges::ostreambuf_iterator ostreambuf_iterator
Alias for ranges::ostreambuf_iterator. Writes successive characters onto the output stream from which...
Definition: iterator.hpp:204
header.hpp
Provides the seqan3::alignment_file_header class.
std::array
std::errc
std::deque
seqan3::is_digit
constexpr auto is_digit
Checks whether c is a digital character.
Definition: predicate.hpp:287
detail.hpp
Auxiliary functions for the alignment IO.
std::from_chars
T from_chars(T... args)
output_options.hpp
Provides seqan3::sequence_file_output_options.
output_options.hpp
Provides seqan3::alignment_file_output_options.
seqan3::pack_traits::size
constexpr size_t size
The size of a type pack.
Definition: traits.hpp:116
to_char.hpp
Provides seqan3::views::to_char.
ranges
Adaptations of concepts from the Ranges TS.
seqan3::views::take_until_or_throw
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.hpp:599
std::ranges::begin
T begin(T... args)
predicate.hpp
Provides character predicates for tokenisation.
std
SeqAn specific customisations in the standard namespace.
seqan3::field::ref_id
The identifier of the (reference) sequence that SEQ was aligned to.
type_inspection.hpp
Provides traits to inspect some information of a type, for example its name.
seqan3::views::slice
constexpr auto slice
A view adaptor that returns a half-open interval on the underlying range.
Definition: slice.hpp:141
ignore_output_iterator.hpp
Provides seqan3::detail::ignore_output_iterator for writing to null stream.
output_format_concept.hpp
Provides seqan3::alignment_file_output_format and auxiliary classes.
std::vector::empty
T empty(T... args)
std::optional
to_string.hpp
Auxiliary for pretty printing of exception messages.
istreambuf.hpp
Provides seqan3::views::istreambuf.
seqan3::views::repeat_n
constexpr auto repeat_n
A view factory that repeats a given value n times.
Definition: repeat_n.hpp:94
std::end
T end(T... args)
seqan3::pack_traits::transform
seqan3::type_list< trait_t< pack_t >... > transform
Apply a transformation trait to every type in the pack and return a seqan3::type_list of the results.
Definition: traits.hpp:307
iterator.hpp
Provides seqan3::ostream and seqan3::ostreambuf iterator.
misc.hpp
Provides various utility functions.
input_options.hpp
Provides seqan3::alignment_file_input_options.
misc.hpp
Provides various utility functions.
char_to.hpp
Provides seqan3::views::char_to.
seqan3::views::single_pass_input
constexpr auto single_pass_input
A view adapter that decays most of the range properties and adds single pass behavior.
Definition: single_pass_input.hpp:378
seqan3::assign_unaligned
void assign_unaligned(aligned_seq_t &aligned_seq, unaligned_sequence_type &&unaligned_seq)
An implementation of seqan3::aligned_sequence::assign_unaligned_sequence for sequence containers.
Definition: aligned_sequence_concept.hpp:375
string