SeqAn3
The Modern C++ library for sequence analysis.
format_sam_base.hpp
Go to the documentation of this file.
1 // -----------------------------------------------------------------------------------------------------
2 // Copyright (c) 2006-2019, Knut Reinert & Freie Universität Berlin
3 // Copyright (c) 2016-2019, 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  // @HQ line
457  // -------------------------------------------------------------------------------------------------------------
458  parse_tag_value(hdr.format_version); // parse required VN (version) tag
459 
460  // The SO, SS and GO tag are optional and can appear in any order
461  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
462  {
463  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
464  std::string * who = std::addressof(hdr.grouping);
465 
466  if (is_char<'S'>(*std::ranges::begin(stream_view)))
467  {
468  std::ranges::next(std::ranges::begin(stream_view)); // skip S
469 
470  if (is_char<'O'>(*std::ranges::begin(stream_view))) // SO (sorting) tag
471  who = std::addressof(hdr.sorting);
472  else if (is_char<'S'>(*std::ranges::begin(stream_view))) // SS (sub-order) tag
473  who = std::addressof(hdr.subsorting);
474  else
475  throw format_error{std::string{"Illegal SAM header tag: S"} +
476  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
477  }
478  else if (!is_char<'G'>(*std::ranges::begin(stream_view))) // GO (grouping) tag
479  {
480  throw format_error{std::string{"Illegal SAM header tag in @HG starting with:"} +
481  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
482  }
483 
484  parse_tag_value(*who);
485  }
486  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
487 
488  // The rest of the header lines
489  // -------------------------------------------------------------------------------------------------------------
490  while (is_char<'@'>(*std::ranges::begin(stream_view)))
491  {
492  std::ranges::next(std::ranges::begin(stream_view)); // skip @
493 
494  if (is_char<'S'>(*std::ranges::begin(stream_view))) // SQ (sequence dictionary) tag
495  {
496  ref_info_present_in_header = true;
497  value_type_t<decltype(hdr.ref_ids())> id;
499 
500  parse_tag_value(id); // parse required SN (sequence name) tag
501  std::ranges::next(std::ranges::begin(stream_view)); // skip tab or newline
502  parse_tag_value(get<0>(info)); // parse required LN (length) tag
503 
504  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
505  {
506  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
507  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), get<1>(info));
508  }
509  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
510 
511  /* If reference information were given, the ids exist and we can fill ref_dict directly.
512  * If not, wee need to update the ids first and fill the reference dictionary afterwards. */
513  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>) // reference information given
514  {
515  auto id_it = hdr.ref_dict.find(id);
516 
517  if (id_it == hdr.ref_dict.end())
518  throw format_error{detail::to_string("Unknown reference name '", id, "' found in SAM header ",
519  "(header.ref_ids(): ", hdr.ref_ids(), ").")};
520 
521  auto & given_ref_info = hdr.ref_id_info[id_it->second];
522 
523  if (std::get<0>(given_ref_info) != std::get<0>(info))
524  throw format_error{"Provided reference has unequal length as specified in the header."};
525 
526  hdr.ref_id_info[id_it->second] = std::move(info);
527  }
528  else
529  {
530  static_assert(!detail::is_type_specialisation_of_v<decltype(hdr.ref_ids()), std::deque>,
531  "The range over reference ids must be of type std::deque such that "
532  "pointers are not invalidated.");
533 
534  hdr.ref_ids().push_back(id);
535  hdr.ref_id_info.push_back(info);
536  hdr.ref_dict[(hdr.ref_ids())[(hdr.ref_ids()).size() - 1]] = (hdr.ref_ids()).size() - 1;
537  }
538  }
539  else if (is_char<'R'>(*std::ranges::begin(stream_view))) // RG (read group) tag
540  {
542 
543  parse_tag_value(get<0>(tmp)); // read required ID tag
544 
545  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
546  {
547  std::ranges::next(std::ranges::begin(stream_view));
548  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), get<1>(tmp));
549  }
550  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
551 
552  hdr.read_groups.emplace_back(std::move(tmp));
553  }
554  else if (is_char<'P'>(*std::ranges::begin(stream_view))) // PG (program) tag
555  {
556  typename alignment_file_header<ref_ids_type>::program_info_t tmp{};
557 
558  parse_tag_value(tmp.id); // read required ID tag
559 
560  // The PN, CL, PP, DS, VN are optional tags and can be given in any order.
561  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
562  {
563  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
564  std::string * who = &tmp.version;
565 
566  if (is_char<'P'>(*std::ranges::begin(stream_view)))
567  {
568  std::ranges::next(std::ranges::begin(stream_view)); // skip P
569 
570  if (is_char<'N'>(*std::ranges::begin(stream_view))) // PN (program name) tag
571  who = &tmp.name;
572  else // PP (previous program) tag
573  who = &tmp.previous;
574  }
575  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CL (command line) tag
576  {
577  who = &tmp.command_line_call;
578  }
579  else if (is_char<'D'>(*std::ranges::begin(stream_view))) // DS (description) tag
580  {
581  who = &tmp.description;
582  }
583  else if (!is_char<'V'>(*std::ranges::begin(stream_view))) // VN (version) tag
584  {
585  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
586  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
587  }
588 
589  parse_tag_value(*who);
590  }
591  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
592 
593  hdr.program_infos.emplace_back(std::move(tmp));
594  }
595  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CO (comment) tag
596  {
597  std::string tmp;
598  std::ranges::next(std::ranges::begin(stream_view)); // skip C
599  std::ranges::next(std::ranges::begin(stream_view)); // skip O
600  std::ranges::next(std::ranges::begin(stream_view)); // skip :
601  read_field(stream_view | views::take_until_or_throw(is_char<'\n'>), tmp);
602  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
603 
604  hdr.comments.emplace_back(std::move(tmp));
605  }
606  else
607  {
608  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
609  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
610  }
611  }
612 }
613 
630 template <typename stream_t, typename ref_ids_type>
631 inline void format_sam_base::write_header(stream_t & stream,
632  alignment_file_output_options const & options,
633  alignment_file_header<ref_ids_type> & header)
634 {
635  // -----------------------------------------------------------------
636  // Check Header
637  // -----------------------------------------------------------------
638 
639  // (@HD) Check header line
640  // The format version string will be taken from the local member variable
641  if (!header.sorting.empty() &&
642  !(header.sorting == "unknown" ||
643  header.sorting == "unsorted" ||
644  header.sorting == "queryname" ||
645  header.sorting == "coordinate" ))
646  throw format_error{"SAM format error: The header.sorting member must be "
647  "one of [unknown, unsorted, queryname, coordinate]."};
648 
649  if (!header.grouping.empty() &&
650  !(header.grouping == "none" ||
651  header.grouping == "query" ||
652  header.grouping == "reference"))
653  throw format_error{"SAM format error: The header.grouping member must be "
654  "one of [none, query, reference]."};
655 
656  // (@SQ) Check Reference Sequence Dictionary lines
657 
658  // TODO
659 
660  // - sorting order be one of ...
661  // - grouping can be one of ...
662  // - reference names must be unique
663  // - ids of read groups must be unique
664  // - program ids need to be unique
665  // many more small semantic things, like fits REGEX
666 
667  // -----------------------------------------------------------------
668  // Write Header
669  // -----------------------------------------------------------------
670  seqan3::ostreambuf_iterator stream_it{stream};
671 
672  // (@HD) Write header line [required].
673  stream << "@HD\tVN:";
674  std::ranges::copy(format_version, stream_it);
675 
676  if (!header.sorting.empty())
677  stream << "\tSO:" << header.sorting;
678 
679  if (!header.subsorting.empty())
680  stream << "\tSS:" << header.subsorting;
681 
682  if (!header.grouping.empty())
683  stream << "\tGO:" << header.grouping;
684 
685  detail::write_eol(stream_it, options.add_carriage_return);
686 
687  // (@SQ) Write Reference Sequence Dictionary lines [required].
688  for (auto const & [ref_name, ref_info] : views::zip(header.ref_ids(), header.ref_id_info))
689  {
690  stream << "@SQ\tSN:";
691 
692  std::ranges::copy(ref_name, stream_it);
693 
694  stream << "\tLN:" << get<0>(ref_info);
695 
696  if (!get<1>(ref_info).empty())
697  stream << "\t" << get<1>(ref_info);
698 
699  detail::write_eol(stream_it, options.add_carriage_return);
700  }
701 
702  // Write read group (@RG) lines if specified.
703  for (auto const & read_group : header.read_groups)
704  {
705  stream << "@RG"
706  << "\tID:" << get<0>(read_group);
707 
708  if (!get<1>(read_group).empty())
709  stream << "\t" << get<1>(read_group);
710 
711  detail::write_eol(stream_it, options.add_carriage_return);
712  }
713 
714  // Write program (@PG) lines if specified.
715  for (auto const & program : header.program_infos)
716  {
717  stream << "@PG"
718  << "\tID:" << program.id;
719 
720  if (!program.name.empty())
721  stream << "\tPN:" << program.name;
722 
723  if (!program.command_line_call.empty())
724  stream << "\tCL:" << program.command_line_call;
725 
726  if (!program.previous.empty())
727  stream << "\tPP:" << program.previous;
728 
729  if (!program.description.empty())
730  stream << "\tDS:" << program.description;
731 
732  if (!program.version.empty())
733  stream << "\tVN:" << program.version;
734 
735  detail::write_eol(stream_it, options.add_carriage_return);
736  }
737 
738  // Write comment (@CO) lines if specified.
739  for (auto const & comment : header.comments)
740  {
741  stream << "@CO\t" << comment;
742  detail::write_eol(stream_it, options.add_carriage_return);
743  }
744 }
745 
746 } // 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:86
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::from_chars
std::from_chars_result from_chars(char const *first, char const *last, value_type &value, int base) noexcept
Parse a char sequence into an integral.
Definition: charconv:935
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.
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
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