SeqAn3  3.0.0
The Modern C++ library for sequence analysis.
format_sam.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 
14 #pragma once
15 
16 #include <iterator>
17 #include <string>
18 #include <vector>
19 
45 #include <seqan3/std/algorithm>
46 #include <seqan3/std/charconv>
47 #include <seqan3/std/concepts>
48 #include <seqan3/std/ranges>
49 #include <seqan3/version.hpp>
50 
51 namespace seqan3
52 {
53 
127 {
130  {
131  { "sam" },
132  };
133 
135  static constexpr char format_version[4] = "1.6";
136 };
137 
138 } // namespace seqan3
139 
140 namespace seqan3::detail
141 {
142 
145 template <>
146 class alignment_file_input_format<format_sam>
147 {
148 public:
150  using format_tag = format_sam;
151 
155  alignment_file_input_format() noexcept = default;
156  alignment_file_input_format(alignment_file_input_format const &) = delete;
159  alignment_file_input_format & operator=(alignment_file_input_format const &) = delete;
160  alignment_file_input_format(alignment_file_input_format &&) noexcept = default;
161  alignment_file_input_format & operator=(alignment_file_input_format &&) noexcept = default;
162  ~alignment_file_input_format() noexcept = default;
163 
166  template <typename stream_type, // constraints checked by file
167  typename seq_legal_alph_type,
168  typename ref_seqs_type,
169  typename ref_ids_type,
170  typename seq_type,
171  typename id_type,
172  typename offset_type,
173  typename ref_seq_type,
174  typename ref_id_type,
175  typename ref_offset_type,
176  typename align_type,
177  typename flag_type,
178  typename mapq_type,
179  typename qual_type,
180  typename mate_type,
181  typename tag_dict_type,
182  typename e_value_type,
183  typename bit_score_type>
184  void read(stream_type & stream,
185  alignment_file_input_options<seq_legal_alph_type> const & SEQAN3_DOXYGEN_ONLY(options),
186  ref_seqs_type & ref_seqs,
187  alignment_file_header<ref_ids_type> & header,
188  seq_type & seq,
189  qual_type & qual,
190  id_type & id,
191  offset_type & offset,
192  ref_seq_type & SEQAN3_DOXYGEN_ONLY(ref_seq),
193  ref_id_type & ref_id,
194  ref_offset_type & ref_offset,
195  align_type & align,
196  flag_type & flag,
197  mapq_type & mapq,
198  mate_type & mate,
199  tag_dict_type & tag_dict,
200  e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
201  bit_score_type & SEQAN3_DOXYGEN_ONLY(bit_score))
202  {
203  static_assert(detail::decays_to_ignore_v<ref_offset_type> ||
204  detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
205  "The ref_offset must be a specialisation of std::optional.");
206 
207  auto stream_view = view::istreambuf(stream);
208  auto field_view = stream_view | view::take_until_or_throw_and_consume(is_char<'\t'>);
209 
210  // these variables need to be stored to compute the ALIGNMENT
211  int32_t ref_offset_tmp{};
212  value_type_t<decltype(header.ref_ids())> ref_id_tmp{};
213  [[maybe_unused]] int32_t offset_tmp{};
214  [[maybe_unused]] int32_t soft_clipping_end{};
215  [[maybe_unused]] std::vector<std::pair<char, size_t>> cigar{};
216  [[maybe_unused]] int32_t ref_length{0}, seq_length{0}; // length of aligned part for ref and query
217 
218  // Header
219  // -------------------------------------------------------------------------------------------------------------
220  if (is_char<'@'>(*std::ranges::begin(stream_view))) // we always read the header if present
221  {
222  read_header(stream_view, header, ref_seqs);
223 
224  if (std::ranges::begin(stream_view) == std::ranges::end(stream_view)) // file has no records
225  return;
226  }
227 
228  // Fields 1-5: ID FLAG REF_ID REF_OFFSET MAPQ
229  // -------------------------------------------------------------------------------------------------------------
230  read_field(field_view, id);
231 
232  read_field(field_view, flag);
233 
234  read_field(field_view, ref_id_tmp);
235  check_and_assign_ref_id(ref_id, ref_id_tmp, header, ref_seqs);
236 
237  read_field(field_view, ref_offset_tmp);
238  --ref_offset_tmp; // SAM format is 1-based but SeqAn operates 0-based
239 
240  if (ref_offset_tmp == -1)
241  ref_offset = std::nullopt; // indicates an unmapped read -> ref_offset is not set
242  else if (ref_offset_tmp > -1)
243  ref_offset = ref_offset_tmp;
244  else if (ref_offset_tmp < -1)
245  throw format_error{"No negative values are allowed for field::REF_OFFSET."};
246 
247  read_field(field_view, mapq);
248 
249  // Field 6: CIGAR
250  // -------------------------------------------------------------------------------------------------------------
251  if constexpr (!detail::decays_to_ignore_v<align_type>)
252  {
253  if (!is_char<'*'>(*std::ranges::begin(stream_view))) // no cigar information given
254  {
255  std::tie(cigar, ref_length, seq_length, offset_tmp, soft_clipping_end) = parse_cigar(field_view);
256  }
257  else
258  {
259  std::ranges::next(std::ranges::begin(field_view)); // skip '*'
260  }
261  }
262  else
263  {
264  detail::consume(field_view);
265  }
266 
267  offset = offset_tmp;
268 
269  // Field 7-9: (RNEXT PNEXT TLEN) = MATE
270  // -------------------------------------------------------------------------------------------------------------
271  if constexpr (!detail::decays_to_ignore_v<mate_type>)
272  {
273  value_type_t<decltype(header.ref_ids())> tmp_mate_ref_id{};
274  read_field(field_view, tmp_mate_ref_id); // RNEXT
275 
276  if (tmp_mate_ref_id == "=") // indicates "same as ref id"
277  {
278  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
279  get<0>(mate) = ref_id;
280  else
281  check_and_assign_ref_id(get<0>(mate), ref_id_tmp, header, ref_seqs);
282  }
283  else
284  {
285  check_and_assign_ref_id(get<0>(mate), tmp_mate_ref_id, header, ref_seqs);
286  }
287 
288  int32_t tmp_pnext{};
289  read_field(field_view, tmp_pnext); // PNEXT
290 
291  if (tmp_pnext > 0)
292  get<1>(mate) = --tmp_pnext; // SAM format is 1-based but SeqAn operates 0-based.
293  else if (tmp_pnext < 0)
294  throw format_error{"No negative values are allowed at the mate mapping position."};
295  // tmp_pnext == 0 indicates an unmapped mate -> do not fill std::optional get<1>(mate)
296 
297  read_field(field_view, get<2>(mate)); // TLEN
298  }
299  else
300  {
301  for (size_t i = 0; i < 3u; ++i)
302  {
303  detail::consume(field_view);
304  }
305  }
306 
307  // Field 10: Sequence
308  // -------------------------------------------------------------------------------------------------------------
309  if (!is_char<'*'>(*std::ranges::begin(stream_view))) // sequence information is given
310  {
311  auto constexpr is_legal_alph = is_in_alphabet<seq_legal_alph_type>;
312  auto seq_stream = field_view | std::view::transform([is_legal_alph] (char const c) // enforce legal alphabet
313  {
314  if (!is_legal_alph(c))
315  throw format_error{std::string{"Encountered an unexpected letter: "} +
316  is_legal_alph.msg.str() +
317  " evaluated to false on " +
318  detail::make_printable(c)};
319  return c;
320  });
321 
322  if constexpr (detail::decays_to_ignore_v<seq_type>)
323  {
324  if constexpr (!detail::decays_to_ignore_v<align_type>)
325  {
326  static_assert(SequenceContainer<std::remove_reference_t<decltype(get<1>(align))>>,
327  "If you want to read ALIGNMENT but not SEQ, the alignment"
328  " object must store a sequence container at the second (query) position.");
329 
330  if (!cigar.empty()) // only parse alignment if cigar information was given
331  {
332 
333  auto tmp_iter = std::ranges::begin(seq_stream);
334  std::ranges::advance(tmp_iter, offset_tmp);
335 
336  for (; seq_length > 0; --seq_length) // seq_length is not needed anymore
337  {
338  get<1>(align).push_back(value_type_t<decltype(get<1>(align))>{}.assign_char(*tmp_iter));
339  ++tmp_iter;
340  }
341 
342  std::ranges::advance(tmp_iter, soft_clipping_end);
343  }
344  else
345  {
346  get<1>(align) = std::remove_reference_t<decltype(get<1>(align))>{}; // empty container
347  }
348  }
349  else
350  {
351  detail::consume(seq_stream);
352  }
353  }
354  else
355  {
356  read_field(seq_stream, seq);
357 
358  if constexpr (!detail::decays_to_ignore_v<align_type>)
359  {
360  if (!cigar.empty()) // if no alignment info is given, the field::ALIGNMENT should remain empty
361  {
362  assign_unaligned(get<1>(align),
363  seq | view::slice(static_cast<decltype(std::ranges::size(seq))>(offset_tmp),
364  std::ranges::size(seq) - soft_clipping_end));
365  }
366  }
367  }
368  }
369  else
370  {
371  std::ranges::next(std::ranges::begin(field_view)); // skip '*'
372  }
373 
374  // Field 11: Quality
375  // -------------------------------------------------------------------------------------------------------------
376  auto const tab_or_end = is_char<'\t'> || is_char<'\r'> || is_char<'\n'>;
377  read_field(stream_view | view::take_until_or_throw(tab_or_end), qual);
378 
379  if constexpr (!detail::decays_to_ignore_v<seq_type> && !detail::decays_to_ignore_v<qual_type>)
380  {
381  if (std::ranges::distance(seq) != 0 && std::ranges::distance(qual) != 0 &&
383  {
384  throw format_error{to_string("Sequence length (", std::ranges::distance(seq), ") and quality length (",
385  std::ranges::distance(qual), ") must be the same.")};
386  }
387  }
388 
389  // All remaining optional fields if any: SAM tags dictionary
390  // -------------------------------------------------------------------------------------------------------------
391  while (is_char<'\t'>(*std::ranges::begin(stream_view))) // read all tags if present
392  {
393  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
394  read_field(stream_view | view::take_until_or_throw(tab_or_end), tag_dict);
395  }
396 
397  detail::consume(stream_view | view::take_until(!(is_char<'\r'> || is_char<'\n'>))); // consume new line
398 
399  // DONE READING - wrap up
400  // -------------------------------------------------------------------------------------------------------------
401  // Alignment object construction
402  // Note that the query sequence in get<1>(align) has already been filled while reading Field 10.
403  if constexpr (!detail::decays_to_ignore_v<align_type>)
404  {
405  int32_t ref_idx{(ref_id_tmp.empty()/*unmapped read?*/) ? -1 : 0};
406 
407  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>)
408  {
409  if (!ref_id_tmp.empty())
410  {
411  assert(header.ref_dict.count(ref_id_tmp) != 0); // taken care of in check_and_assign_ref_id()
412  ref_idx = header.ref_dict[ref_id_tmp]; // get index for reference sequence
413  }
414  }
415 
416  construct_alignment(align, cigar, ref_idx, ref_seqs, ref_offset_tmp, ref_length);
417  }
418  }
419 
420 protected:
423  std::array<char, 316> buffer{}; // Doubles can be up to 316 characters
424 
426  bool ref_info_present_in_header{false};
427 
438  template <typename ref_id_type,
439  typename ref_id_tmp_type,
440  typename header_type,
441  typename ref_seqs_type>
442  void check_and_assign_ref_id(ref_id_type & ref_id,
443  ref_id_tmp_type & ref_id_tmp,
444  header_type & header,
445  ref_seqs_type & /*tag*/)
446  {
447  if (!std::ranges::empty(ref_id_tmp)) // otherwise the std::optional will not be filled
448  {
449  auto search = header.ref_dict.find(ref_id_tmp);
450 
451  if (search == header.ref_dict.end())
452  {
453  if constexpr(detail::decays_to_ignore_v<ref_seqs_type>) // no reference information given
454  {
455  if (ref_info_present_in_header)
456  {
457  throw format_error{"Unknown reference id found in record which is not present in the header."};
458  }
459  else
460  {
461  header.ref_ids().push_back(ref_id_tmp);
462  auto pos = std::ranges::size(header.ref_ids()) - 1;
463  header.ref_dict[header.ref_ids()[pos]] = pos;
464  ref_id = pos;
465  }
466  }
467  else
468  {
469  throw format_error{"Unknown reference id found in record which is not present in the given ids."};
470  }
471  }
472  else
473  {
474  ref_id = search->second;
475  }
476  }
477  }
478 
489  template <typename align_type, typename ref_seqs_type>
490  void construct_alignment(align_type & align,
492  [[maybe_unused]] int32_t rid,
493  [[maybe_unused]] ref_seqs_type & ref_seqs,
494  [[maybe_unused]] int32_t ref_start,
495  size_t ref_length)
496  {
497  if (rid > -1 && ref_start > -1 && // read is mapped
498  !cigar.empty() && // alignment field was not empty
499  !std::ranges::empty(get<1>(align))) // seq field was not empty
500  {
501  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>)
502  {
503  assert(static_cast<size_t>(ref_start + ref_length) <= std::ranges::size(ref_seqs[rid]));
504  // copy over unaligned reference sequence part
505  assign_unaligned(get<0>(align), ref_seqs[rid] | view::slice(ref_start, ref_start + ref_length));
506  }
507  else
508  {
509  using unaligned_t = remove_cvref_t<detail::unaligned_seq_t<decltype(get<0>(align))>>;
510  auto dummy_seq = view::repeat_n(value_type_t<unaligned_t>{}, ref_length)
511  | std::view::transform(detail::access_restrictor_fn{});
512  static_assert(std::Same<unaligned_t, decltype(dummy_seq)>,
513  "No reference information was given so the type of the first alignment tuple position"
514  "must have an unaligned sequence type of a dummy sequence ("
515  "view::repeat_n(dna5{}, size_t{}) | "
516  "std::view::transform(detail::access_restrictor_fn{}))");
517 
518  assign_unaligned(get<0>(align), dummy_seq); // assign dummy sequence
519  }
520 
521  // insert gaps according to the cigar information
522  detail::alignment_from_cigar(align, cigar);
523  }
524  else // not enough information for an alignment, assign an empty view/dummy_sequence
525  {
526  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>) // reference info given
527  {
528  assert(std::ranges::size(ref_seqs) > 0); // we assume that the given ref info is not empty
529  assign_unaligned(get<0>(align), ref_seqs[0] | view::slice(0, 0));
530  }
531  else
532  {
533  using unaligned_t = remove_cvref_t<detail::unaligned_seq_t<decltype(get<0>(align))>>;
534  assign_unaligned(get<0>(align), view::repeat_n(value_type_t<unaligned_t>{}, 0)
535  | std::view::transform(detail::access_restrictor_fn{}));
536  }
537  }
538  }
539 
546  template <typename stream_view_type>
547  void read_field(stream_view_type && stream_view, detail::ignore_t const & SEQAN3_DOXYGEN_ONLY(target))
548  {
549  detail::consume(stream_view);
550  }
551 
559  template <typename stream_view_type, std::ranges::ForwardRange target_range_type>
560  void read_field(stream_view_type && stream_view, target_range_type & target)
561  {
562  if (!is_char<'*'>(*std::ranges::begin(stream_view)))
563  std::ranges::copy(stream_view | view::char_to<value_type_t<target_range_type>>,
564  std::back_inserter(target));
565  else
566  std::ranges::next(std::ranges::begin(stream_view)); // skip '*'
567  }
568 
579  template <typename stream_view_type, Arithmetic target_type>
580  void read_field(stream_view_type && stream_view, target_type & target)
581  {
582  // unfortunately std::from_chars only accepts char const * so we need a buffer.
583  auto [ignore, end] = std::ranges::copy(stream_view, buffer.data());
584  (void) ignore;
585  std::from_chars_result res = std::from_chars(buffer.begin(), end, target);
586 
587  if (res.ec == std::errc::invalid_argument || res.ptr != end)
588  throw format_error{std::string("[CORRUPTED SAM FILE] The string '") + std::string(buffer.begin(), end) +
589  "' could not be cast into type " +
590  detail::get_display_name_v<target_type>.str()};
591 
592  if (res.ec == std::errc::result_out_of_range)
593  throw format_error{std::string("[CORRUPTED SAM FILE] Casting '") + std::string(buffer.begin(), end) +
594  "' into type " + detail::get_display_name_v<target_type>.str() +
595  " would cause an overflow."};
596  }
597 
608  template <typename stream_view_type, typename optional_value_type>
609  void read_field(stream_view_type && stream_view, std::optional<optional_value_type> & target)
610  {
611  optional_value_type tmp;
612  read_field(std::forward<stream_view_type>(stream_view), tmp);
613  target = tmp;
614  }
615 
633  template <typename stream_view_type, typename value_type>
634  void read_sam_dict_vector(seqan3::detail::sam_tag_variant & variant,
635  stream_view_type && stream_view,
636  value_type value)
637  {
638  std::vector<value_type> tmp_vector;
639  while (std::ranges::begin(stream_view) != ranges::end(stream_view)) // not fully consumed yet
640  {
641  read_field(stream_view | view::take_until(is_char<','>), value);
642  tmp_vector.push_back(value);
643 
644  if (is_char<','>(*std::ranges::begin(stream_view)))
645  std::ranges::next(std::ranges::begin(stream_view)); // skip ','
646  }
647  variant = std::move(tmp_vector);
648  }
649 
667  template <typename stream_view_type>
668  void read_field(stream_view_type && stream_view, sam_tag_dictionary & target)
669  {
670  /* Every SAM tag has the format "[TAG]:[TYPE_ID]:[VALUE]", where TAG is a two letter
671  name tag which is converted to a unique integer identifier and TYPE_ID is one character in [A,i,Z,H,B,f]
672  describing the type for the upcoming VALUES. If TYPE_ID=='B' it signals an array of comma separated
673  VALUE's and the inner value type is identified by the character following ':', one of [cCsSiIf].
674  */
675  uint16_t tag = static_cast<uint16_t>(*std::ranges::begin(stream_view)) << 8;
676  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
677  tag += static_cast<uint16_t>(*std::ranges::begin(stream_view));
678  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
679  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
680  char type_id = *std::ranges::begin(stream_view);
681  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
682  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
683 
684  switch (type_id)
685  {
686  case 'A' : // char
687  {
688  target[tag] = static_cast<char>(*std::ranges::begin(stream_view));
689  std::ranges::next(std::ranges::begin(stream_view)); // skip char that has been read
690  break;
691  }
692  case 'i' : // int32_t
693  {
694  int32_t tmp;
695  read_field(stream_view, tmp);
696  target[tag] = tmp;
697  break;
698  }
699  case 'f' : // float
700  {
701  float tmp;
702  read_field(stream_view, tmp);
703  target[tag] = tmp;
704  break;
705  }
706  case 'Z' : // string
707  {
708  target[tag] = std::string(stream_view);
709  break;
710  }
711  case 'H' :
712  {
713  // TODO
714  break;
715  }
716  case 'B' : // Array. Value type depends on second char [cCsSiIf]
717  {
718  char array_value_type_id = *std::ranges::begin(stream_view);
719  std::ranges::next(std::ranges::begin(stream_view)); // skip char read before
720  std::ranges::next(std::ranges::begin(stream_view)); // skip first ','
721 
722  switch (array_value_type_id)
723  {
724  case 'c' : // int8_t
725  {
726  read_sam_dict_vector(target[tag], stream_view, int8_t{});
727  break;
728  }
729  case 'C' : // uint8_t
730  {
731  read_sam_dict_vector(target[tag], stream_view, uint8_t{});
732  break;
733  }
734  case 's' : // int16_t
735  {
736  read_sam_dict_vector(target[tag], stream_view, int16_t{});
737  break;
738  }
739  case 'S' : // uint16_t
740  {
741  read_sam_dict_vector(target[tag], stream_view, uint16_t{});
742  break;
743  }
744  case 'i' : // int32_t
745  {
746  read_sam_dict_vector(target[tag], stream_view, int32_t{});
747  break;
748  }
749  case 'I' : // uint32_t
750  {
751  read_sam_dict_vector(target[tag], stream_view, uint32_t{});
752  break;
753  }
754  case 'f' : // float
755  {
756  read_sam_dict_vector(target[tag], stream_view, float{});
757  break;
758  }
759  default:
760  throw format_error{std::string("The first character in the numerical ") +
761  "id of a SAM tag must be one of [cCsSiIf] but '" + array_value_type_id +
762  "' was given."};
763  }
764  break;
765  }
766  default:
767  throw format_error{std::string("The second character in the numerical id of a "
768  "SAM tag must be one of [A,i,Z,H,B,f] but '") + type_id + "' was given."};
769  }
770  }
771 
788  template <typename stream_view_type, typename ref_ids_type, typename ref_seqs_type>
789  void read_header(stream_view_type && stream_view,
790  alignment_file_header<ref_ids_type> & hdr,
791  ref_seqs_type & /*ref_id_to_pos_map*/)
792  {
793  auto parse_tag_value = [&stream_view, this] (auto & value) // helper function to parse the next tag value
794  {
795  detail::consume(stream_view | view::take_until_or_throw(is_char<':'>)); // skip tag name
796  std::ranges::next(std::ranges::begin(stream_view)); // skip ':'
797  read_field(stream_view | view::take_until_or_throw(is_char<'\t'> || is_char<'\n'>), value);
798  };
799 
800  // @HQ line
801  // -------------------------------------------------------------------------------------------------------------
802  parse_tag_value(hdr.format_version); // parse required VN (version) tag
803 
804  // The SO, SS and GO tag are optional and can appear in any order
805  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
806  {
807  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
808  std::string * who = std::addressof(hdr.grouping);
809 
810  if (is_char<'S'>(*std::ranges::begin(stream_view)))
811  {
812  std::ranges::next(std::ranges::begin(stream_view)); // skip S
813 
814  if (is_char<'O'>(*std::ranges::begin(stream_view))) // SO (sorting) tag
815  who = std::addressof(hdr.sorting);
816  else if (is_char<'S'>(*std::ranges::begin(stream_view))) // SS (sub-order) tag
817  who = std::addressof(hdr.subsorting);
818  else
819  throw format_error{std::string{"Illegal SAM header tag: S"} +
820  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
821  }
822  else if (!is_char<'G'>(*std::ranges::begin(stream_view))) // GO (grouping) tag
823  {
824  throw format_error{std::string{"Illegal SAM header tag in @HG starting with:"} +
825  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
826  }
827 
828  parse_tag_value(*who);
829  }
830  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
831 
832  // The rest of the header lines
833  // -------------------------------------------------------------------------------------------------------------
834  while (is_char<'@'>(*std::ranges::begin(stream_view)))
835  {
836  std::ranges::next(std::ranges::begin(stream_view)); // skip @
837 
838  if (is_char<'S'>(*std::ranges::begin(stream_view))) // SQ (sequence dictionary) tag
839  {
840  ref_info_present_in_header = true;
841  std::string id;
843 
844  parse_tag_value(id); // parse required SN (sequence name) tag
845  std::ranges::next(std::ranges::begin(stream_view)); // skip tab or newline
846  parse_tag_value(get<0>(info)); // parse required LN (length) tag
847 
848  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
849  {
850  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
851  read_field(stream_view | view::take_until_or_throw(is_char<'\n'>), get<1>(info));
852  }
853  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
854 
855  /* If reference information were given, the ids exist and we can fill ref_dict directly.
856  * If not, wee need to update the ids first and fill the reference dictionary afterwards. */
857  if constexpr (!detail::decays_to_ignore_v<ref_seqs_type>) // reference information given
858  {
859  auto id_it = hdr.ref_dict.find(id);
860 
861  if (id_it == hdr.ref_dict.end())
862  throw format_error{to_string("Unknown reference name '", id, "' found in SAM header ",
863  "(header.ref_ids(): ", hdr.ref_ids(), ").")};
864 
865  auto & given_ref_info = hdr.ref_id_info[id_it->second];
866 
867  if (std::get<0>(given_ref_info) != std::get<0>(info))
868  throw format_error{"Provided reference has unequal length as specified in the header."};
869 
870  hdr.ref_id_info[id_it->second] = std::move(info);
871  }
872  else
873  {
874  static_assert(!detail::is_type_specialisation_of_v<decltype(hdr.ref_ids()), std::deque>,
875  "The range over reference ids must be of type std::deque such that "
876  "pointers are not invalidated.");
877 
878  hdr.ref_ids().push_back(id);
879  hdr.ref_id_info.push_back(info);
880  hdr.ref_dict[(hdr.ref_ids())[(hdr.ref_ids()).size() - 1]] = (hdr.ref_ids()).size() - 1;
881  }
882  }
883  else if (is_char<'R'>(*std::ranges::begin(stream_view))) // RG (read group) tag
884  {
886 
887  parse_tag_value(get<0>(tmp)); // read required ID tag
888 
889  if (is_char<'\t'>(*std::ranges::begin(stream_view))) // read rest of the tags
890  {
892  read_field(stream_view | view::take_until_or_throw(is_char<'\n'>), get<1>(tmp));
893  }
894  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
895 
896  hdr.read_groups.emplace_back(std::move(tmp));
897  }
898  else if (is_char<'P'>(*std::ranges::begin(stream_view))) // PG (program) tag
899  {
900  typename alignment_file_header<ref_ids_type>::program_info_t tmp{};
901 
902  parse_tag_value(tmp.id); // read required ID tag
903 
904  // The PN, CL, PP, DS, VN are optional tags and can be given in any order.
905  while (is_char<'\t'>(*std::ranges::begin(stream_view)))
906  {
907  std::ranges::next(std::ranges::begin(stream_view)); // skip tab
908  std::string * who = &tmp.version;
909 
910  if (is_char<'P'>(*std::ranges::begin(stream_view)))
911  {
912  std::ranges::next(std::ranges::begin(stream_view)); // skip P
913 
914  if (is_char<'N'>(*std::ranges::begin(stream_view))) // PN (program name) tag
915  who = &tmp.name;
916  else // PP (previous program) tag
917  who = &tmp.previous;
918  }
919  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CL (command line) tag
920  {
921  who = &tmp.command_line_call;
922  }
923  else if (is_char<'D'>(*std::ranges::begin(stream_view))) // DS (description) tag
924  {
925  who = &tmp.description;
926  }
927  else if (!is_char<'V'>(*std::ranges::begin(stream_view))) // VN (version) tag
928  {
929  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
930  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
931  }
932 
933  parse_tag_value(*who);
934  }
935  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
936 
937  hdr.program_infos.emplace_back(std::move(tmp));
938  }
939  else if (is_char<'C'>(*std::ranges::begin(stream_view))) // CO (comment) tag
940  {
941  std::string tmp;
942  std::ranges::next(std::ranges::begin(stream_view)); // skip C
943  std::ranges::next(std::ranges::begin(stream_view)); // skip O
944  std::ranges::next(std::ranges::begin(stream_view)); // skip :
945  read_field(stream_view | view::take_until_or_throw(is_char<'\n'>), tmp);
946  std::ranges::next(std::ranges::begin(stream_view)); // skip newline
947 
948  hdr.comments.emplace_back(std::move(tmp));
949  }
950  else
951  {
952  throw format_error{std::string{"Illegal SAM header tag starting with:"} +
953  std::string{static_cast<char>(*std::ranges::begin(stream_view))}};
954  }
955  }
956  }
957 };
958 
961 template <>
962 class alignment_file_output_format<format_sam>
963 {
964 public:
966  using format_tag = format_sam;
967 
971  alignment_file_output_format() noexcept = default;
972  alignment_file_output_format(alignment_file_output_format const &) = delete;
975  alignment_file_output_format & operator=(alignment_file_output_format const &) = delete;
976  alignment_file_output_format(alignment_file_output_format &&) noexcept = default;
977  alignment_file_output_format & operator=(alignment_file_output_format &&) noexcept = default;
978  ~alignment_file_output_format() noexcept = default;
979 
982  template <typename stream_type,
983  typename header_type,
984  typename seq_type,
985  typename id_type,
986  typename ref_seq_type,
987  typename ref_id_type,
988  typename align_type,
989  typename qual_type,
990  typename mate_type,
991  typename tag_dict_type>
992  void write(stream_type & stream,
993  alignment_file_output_options const & options,
994  header_type && header,
995  seq_type && seq,
996  qual_type && qual,
997  id_type && id,
998  int32_t offset,
999  ref_seq_type && SEQAN3_DOXYGEN_ONLY(ref_seq),
1000  ref_id_type && ref_id,
1001  std::optional<int32_t> ref_offset,
1002  align_type && align,
1003  uint16_t flag,
1004  uint8_t mapq,
1005  mate_type && mate,
1006  tag_dict_type && tag_dict,
1007  double SEQAN3_DOXYGEN_ONLY(e_value),
1008  double SEQAN3_DOXYGEN_ONLY(bit_score))
1009  {
1010  /* Note the following general things:
1011  *
1012  * - Given the SAM specifications, all fields may be empty
1013  *
1014  * - Arithmetic values default to 0 while all others default to '*'
1015  *
1016  * - Because of the former, arithmetic values can be directly streamed
1017  * into 'stream' as operator<< is defined for all arithmetic types
1018  * and the default value (0) is also the SAM default.
1019  *
1020  * - All other non-arithmetic values need to be checked for emptiness
1021  */
1022 
1023  // ---------------------------------------------------------------------
1024  // Type Requirements (as static asserts for user friendliness)
1025  // ---------------------------------------------------------------------
1026  static_assert((std::ranges::ForwardRange<seq_type> &&
1027  Alphabet<reference_t<seq_type>>),
1028  "The seq object must be a std::ranges::ForwardRange over "
1029  "letters that model seqan3::Alphabet.");
1030 
1031  static_assert((std::ranges::ForwardRange<id_type> &&
1032  Alphabet<reference_t<id_type>>),
1033  "The id object must be a std::ranges::ForwardRange over "
1034  "letters that model seqan3::Alphabet.");
1035 
1036  static_assert((std::ranges::ForwardRange<ref_seq_type> &&
1037  Alphabet<reference_t<ref_seq_type>>),
1038  "The ref_seq object must be a std::ranges::ForwardRange "
1039  "over letters that model seqan3::Alphabet.");
1040 
1041  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
1042  {
1043  static_assert((std::ranges::ForwardRange<ref_id_type> ||
1045  detail::is_type_specialisation_of_v<remove_cvref_t<ref_id_type>, std::optional>),
1046  "The ref_id object must be a std::ranges::ForwardRange "
1047  "over letters that model seqan3::Alphabet.");
1048 
1049  if constexpr (std::Integral<remove_cvref_t<ref_id_type>> ||
1050  detail::is_type_specialisation_of_v<remove_cvref_t<ref_id_type>, std::optional>)
1051  static_assert(!detail::decays_to_ignore_v<header_type>,
1052  "If you give indices as reference id information the header must also be present.");
1053  }
1054 
1055  static_assert(TupleLike<remove_cvref_t<align_type>>,
1056  "The align object must be a std::pair of two ranges whose "
1057  "value_type is comparable to seqan3::gap");
1058 
1059  static_assert((std::tuple_size_v<remove_cvref_t<align_type>> == 2 &&
1060  std::EqualityComparableWith<gap, reference_t<decltype(std::get<0>(align))>> &&
1061  std::EqualityComparableWith<gap, reference_t<decltype(std::get<1>(align))>>),
1062  "The align object must be a std::pair of two ranges whose "
1063  "value_type is comparable to seqan3::gap");
1064 
1065  static_assert((std::ranges::ForwardRange<qual_type> &&
1066  Alphabet<reference_t<qual_type>>),
1067  "The qual object must be a std::ranges::ForwardRange "
1068  "over letters that model seqan3::Alphabet.");
1069 
1070  static_assert(TupleLike<remove_cvref_t<mate_type>>,
1071  "The mate object must be a std::tuple of size 3 with "
1072  "1) a std::ranges::ForwardRange with a value_type modelling seqan3::Alphabet, "
1073  "2) a std::Integral or std::optional<std::Integral>, and "
1074  "3) a std::Integral.");
1075 
1076  static_assert(((std::ranges::ForwardRange<decltype(std::get<0>(mate))> ||
1077  std::Integral<remove_cvref_t<decltype(std::get<0>(mate))>> ||
1078  detail::is_type_specialisation_of_v<remove_cvref_t<decltype(std::get<0>(mate))>, std::optional>) &&
1079  (std::Integral<remove_cvref_t<decltype(std::get<1>(mate))>> ||
1080  detail::is_type_specialisation_of_v<remove_cvref_t<decltype(std::get<1>(mate))>, std::optional>) &&
1081  std::Integral<remove_cvref_t<decltype(std::get<2>(mate))>>),
1082  "The mate object must be a std::tuple of size 3 with "
1083  "1) a std::ranges::ForwardRange with a value_type modelling seqan3::Alphabet, "
1084  "2) a std::Integral or std::optional<std::Integral>, and "
1085  "3) a std::Integral.");
1086 
1087  if constexpr (std::Integral<remove_cvref_t<decltype(std::get<0>(mate))>> ||
1088  detail::is_type_specialisation_of_v<remove_cvref_t<decltype(std::get<0>(mate))>, std::optional>)
1089  static_assert(!detail::decays_to_ignore_v<header_type>,
1090  "If you give indices as mate reference id information the header must also be present.");
1091 
1092  static_assert(std::Same<remove_cvref_t<tag_dict_type>, sam_tag_dictionary>,
1093  "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
1094 
1095  // ---------------------------------------------------------------------
1096  // logical Requirements
1097  // ---------------------------------------------------------------------
1098  if constexpr (!detail::decays_to_ignore_v<header_type> &&
1099  !detail::decays_to_ignore_v<ref_id_type> &&
1100  !std::Integral<std::remove_reference_t<ref_id_type>> &&
1101  !detail::is_type_specialisation_of_v<std::remove_reference_t<ref_id_type>, std::optional>)
1102  {
1103  static_assert(ImplicitlyConvertibleTo<ref_id_type &, typename decltype(header.ref_dict)::key_type>,
1104  "The ref_id type is not convertible to the reference id information stored in the "
1105  "reference dictionary of the header object.");
1106 
1107  if (options.sam_require_header && !std::ranges::empty(ref_id))
1108  {
1109  if ((header.ref_dict).count(ref_id) == 0) // no reference id matched
1110  throw format_error{std::string("The ref_id '") + std::string(ref_id) +
1111  "' was not in the list of references"};
1112  }
1113  }
1114 
1115  if (ref_offset.has_value() && (ref_offset.value() + 1) < 0)
1116  throw format_error{"The ref_offset object must be an std::Integral >= 0."};
1117 
1118  // ---------------------------------------------------------------------
1119  // Writing the Header on first call
1120  // ---------------------------------------------------------------------
1121  if constexpr (!detail::decays_to_ignore_v<header_type>)
1122  {
1123  if (options.sam_require_header && !written_header)
1124  {
1125  write_header(stream, options, header);
1126  written_header = true;
1127  }
1128  }
1129 
1130  // ---------------------------------------------------------------------
1131  // Writing the Record
1132  // ---------------------------------------------------------------------
1133  seqan3::ostreambuf_iterator stream_it{stream};
1134  char const separator{'\t'};
1135 
1136  write_range(stream_it, std::forward<id_type>(id));
1137 
1138  stream << separator;
1139 
1140  stream << flag << separator;
1141 
1142  if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
1143  {
1145  {
1146  write_range(stream_it, (header.ref_ids())[ref_id]);
1147  }
1148  else if constexpr (detail::is_type_specialisation_of_v<std::remove_reference_t<ref_id_type>, std::optional>)
1149  {
1150  if (ref_id.has_value())
1151  write_range(stream_it, (header.ref_ids())[ref_id.value()]);
1152  else
1153  stream << '*';
1154  }
1155  else
1156  {
1157  write_range(stream_it, std::forward<ref_id_type>(ref_id));
1158  }
1159  }
1160  else
1161  {
1162  stream << '*';
1163  }
1164 
1165  stream << separator;
1166 
1167  // SAM is 1 based, 0 indicates unmapped read if optional is not set
1168  stream << (ref_offset.value_or(-1) + 1) << separator;
1169 
1170  stream << static_cast<unsigned>(mapq) << separator;
1171 
1172  if (!std::ranges::empty(get<0>(align)) && !std::ranges::empty(get<1>(align)))
1173  {
1174  // compute possible distance from alignment end to sequence end
1175  // which indicates soft clipping at the end.
1176  // This should be replace by a free count_gaps function for
1177  // aligned sequences which is more efficient if possible.
1178  size_t off_end{std::ranges::size(seq) - offset};
1179  for (auto chr : get<1>(align))
1180  if (chr == gap{})
1181  ++off_end;
1182  off_end -= std::ranges::size(get<1>(align));
1183 
1184  write_range(stream_it, detail::get_cigar_string(std::forward<align_type>(align), offset, off_end));
1185  }
1186  else
1187  {
1188  stream << '*';
1189  }
1190 
1191  stream << separator;
1192 
1193  if constexpr (std::Integral<std::remove_reference_t<decltype(get<0>(mate))>>)
1194  {
1195  write_range(stream_it, (header.ref_ids())[get<0>(mate)]);
1196  }
1197  else if constexpr (detail::is_type_specialisation_of_v<std::remove_reference_t<decltype(get<0>(mate))>, std::optional>)
1198  {
1199  if (get<0>(mate).has_value())
1200  // value_or(0) instead of value() (which is equivalent here) as a
1201  // workaround for a ubsan false-positive in GCC8: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90058
1202  write_range(stream_it, header.ref_ids()[get<0>(mate).value_or(0)]);
1203  else
1204  stream << '*';
1205  }
1206  else
1207  {
1208  write_range(stream_it, get<0>(mate));
1209  }
1210 
1211  stream << separator;
1212 
1213  if constexpr (detail::is_type_specialisation_of_v<remove_cvref_t<decltype(get<1>(mate))>, std::optional>)
1214  {
1215  // SAM is 1 based, 0 indicates unmapped read if optional is not set
1216  stream << (get<1>(mate).value_or(-1) + 1) << separator;
1217  }
1218  else
1219  {
1220  stream << get<1>(mate) << separator;
1221  }
1222 
1223  stream << get<2>(mate) << separator;
1224 
1225  write_range(stream_it, std::forward<seq_type>(seq));
1226 
1227  stream << separator;
1228 
1229  write_range(stream_it, std::forward<qual_type>(qual));
1230 
1231  write_tag_fields(stream, std::forward<tag_dict_type>(tag_dict), separator);
1232 
1233  detail::write_eol(stream_it, options.add_carriage_return);
1234  }
1235 
1236 protected:
1239  static constexpr char format_version[4] = "1.6";
1241  bool written_header{false};
1242 
1250  template <typename stream_it_t, typename field_type>
1254  void write_range(stream_it_t & stream_it, field_type && field_value)
1255  {
1256  if (std::ranges::empty(field_value))
1257  stream_it = '*';
1258  else
1259  std::ranges::copy(field_value | view::to_char, stream_it);
1260  }
1261 
1268  template <typename stream_it_t>
1269  void write_range(stream_it_t & stream_it, char const * const field_value)
1270  {
1271  write_range(stream_it, std::string_view{field_value});
1272  }
1273 
1279  template <typename stream_t, Arithmetic field_type>
1280  void write_field(stream_t & stream, field_type field_value)
1281  {
1282  // TODO: replace this with to_chars for efficiency
1284  stream << static_cast<int16_t>(field_value);
1285  else
1286  stream << field_value;
1287  }
1288 
1296  template <typename stream_t>
1297  void write_tag_fields(stream_t & stream, sam_tag_dictionary const & tag_dict, char const separator)
1298  {
1299  auto stream_variant_fn = [this, &stream] (auto && arg) // helper to print an std::variant
1300  {
1301  using T = remove_cvref_t<decltype(arg)>;
1302 
1303  if constexpr (!Container<T> || std::Same<T, std::string>)
1304  {
1305  stream << arg;
1306  }
1307  else
1308  {
1309  if (arg.begin() != arg.end())
1310  {
1311  for (auto it = arg.begin(); it != (arg.end() - 1); ++it)
1312  {
1313  write_field(stream, *it);
1314  stream << ',';
1315  }
1316 
1317  write_field(stream, *(arg.end() - 1)); // write last value without trailing ','
1318  }
1319  }
1320  };
1321 
1322  for (auto & [tag, variant] : tag_dict)
1323  {
1324  stream << separator;
1325 
1326  char char0 = tag / 256;
1327  char char1 = tag % 256;
1328 
1329  stream << char0 << char1 << ':' << detail::sam_tag_type_char[variant.index()] << ':';
1330 
1331  if (detail::sam_tag_type_char_extra[variant.index()] != '\0')
1332  stream << detail::sam_tag_type_char_extra[variant.index()] << ',';
1333 
1334  std::visit(stream_variant_fn, variant);
1335  }
1336  }
1337 
1354  template <typename stream_t, typename ref_ids_type>
1355  void write_header(stream_t & stream,
1356  alignment_file_output_options const & options,
1357  alignment_file_header<ref_ids_type> & header)
1358  {
1359  // -----------------------------------------------------------------
1360  // Check Header
1361  // -----------------------------------------------------------------
1362 
1363  // (@HD) Check header line
1364  // The format version string will be taken from the local member variable
1365  if (!header.sorting.empty() &&
1366  !(header.sorting == "unknown" ||
1367  header.sorting == "unsorted" ||
1368  header.sorting == "queryname" ||
1369  header.sorting == "coordinate" ))
1370  throw format_error{"SAM format error: The header.sorting member must be "
1371  "one of [unknown, unsorted, queryname, coordinate]."};
1372 
1373  if (!header.grouping.empty() &&
1374  !(header.grouping == "none" ||
1375  header.grouping == "query" ||
1376  header.grouping == "reference"))
1377  throw format_error{"SAM format error: The header.grouping member must be "
1378  "one of [none, query, reference]."};
1379 
1380  // (@SQ) Check Reference Sequence Dictionary lines
1381 
1382  // TODO
1383 
1384  // - sorting order be one of ...
1385  // - grouping can be one of ...
1386  // - reference names must be unique
1387  // - ids of read groups must be unique
1388  // - program ids need to be unique
1389  // many more small semantic things, like fits REGEX
1390 
1391  // -----------------------------------------------------------------
1392  // Write Header
1393  // -----------------------------------------------------------------
1394  seqan3::ostreambuf_iterator stream_it{stream};
1395 
1396  // (@HD) Write header line [required].
1397  stream << "@HD\tVN:";
1398  stream << format_sam::format_version;
1399 
1400  if (!header.sorting.empty())
1401  stream << "\tSO:" << header.sorting;
1402 
1403  if (!header.subsorting.empty())
1404  stream << "\tSS:" << header.subsorting;
1405 
1406  if (!header.grouping.empty())
1407  stream << "\tGO:" << header.grouping;
1408 
1409  detail::write_eol(stream_it, options.add_carriage_return);
1410 
1411  // (@SQ) Write Reference Sequence Dictionary lines [required].
1412  for (auto const & [ref_name, ref_info] : std::view::zip(header.ref_ids(), header.ref_id_info))
1413  {
1414  stream << "@SQ\tSN:";
1415 
1416  std::ranges::copy(ref_name, stream_it);
1417 
1418  stream << "\tLN:" << get<0>(ref_info);
1419 
1420  if (!get<1>(ref_info).empty())
1421  stream << "\t" << get<1>(ref_info);
1422 
1423  detail::write_eol(stream_it, options.add_carriage_return);
1424  }
1425 
1426  // Write read group (@RG) lines if specified.
1427  for (auto const & read_group : header.read_groups)
1428  {
1429  stream << "@RG"
1430  << "\tID:" << get<0>(read_group);
1431 
1432  if (!get<1>(read_group).empty())
1433  stream << "\t" << get<1>(read_group);
1434 
1435  detail::write_eol(stream_it, options.add_carriage_return);
1436  }
1437 
1438  // Write program (@PG) lines if specified.
1439  for (auto const & program : header.program_infos)
1440  {
1441  stream << "@PG"
1442  << "\tID:" << program.id;
1443 
1444  if (!program.name.empty())
1445  stream << "\tPN:" << program.name;
1446 
1447  if (!program.command_line_call.empty())
1448  stream << "\tCL:" << program.command_line_call;
1449 
1450  if (!program.previous.empty())
1451  stream << "\tPP:" << program.previous;
1452 
1453  if (!program.description.empty())
1454  stream << "\tDS:" << program.description;
1455 
1456  if (!program.version.empty())
1457  stream << "\tVN:" << program.version;
1458 
1459  detail::write_eol(stream_it, options.add_carriage_return);
1460  }
1461 
1462  // Write comment (@CO) lines if specified.
1463  for (auto const & comment : header.comments)
1464  {
1465  stream << "@CO\t" << comment;
1466  detail::write_eol(stream_it, options.add_carriage_return);
1467  }
1468  }
1469 };
1470 
1471 } // namespace seqan3::detail
Provides concepts for core language types and relations that don&#39;t have concepts in C++20 (yet)...
::ranges::distance distance
Alias for ranges::distance. Returns the number of hops from first to last.
Definition: iterator:321
T visit(T... args)
void assign_unaligned(aligned_seq_t &aligned_seq, unaligned_sequence_type &&unaligned_seq)
An implementation of seqan3::AlignedSequence::assign_unaligned_sequence for sequence containers...
Definition: aligned_sequence_concept.hpp:345
typename value_type< t >::type value_type_t
Shortcut for seqan3::value_type (TransformationTrait shortcut).
Definition: pre.hpp:48
::ranges::next next
Alias for ranges::next. Returns the nth successor of the given iterator.
Definition: iterator:331
Provides seqan3::view::istreambuf.
auto constexpr take_until
A view adaptor that returns elements from the underlying range until the functor evaluates to true (o...
Definition: take_until.hpp:599
constexpr sequenced_policy seq
Global execution policy object for sequenced execution policy.
Definition: execution.hpp:54
The SAM format (tag).
Definition: format_sam.hpp:126
T tie(T... args)
Provides the seqan3::sam_tag_dictionary class and auxiliaries.
constexpr auto zip
A range adaptor that transforms a tuple of range into a range of tuples.
Definition: ranges:948
::ranges::_to_::to to
Alias for ranges::to.
Definition: ranges:225
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:130
T index(T... args)
Result type of std::from_chars.
Definition: charconv:47
::ranges::ostreambuf_iterator ostreambuf_iterator
Alias for ranges::ostreambuf_iterator. Writes successive characters onto the output stream from which...
Definition: iterator.hpp:56
Provides seqan3::detail::ignore_output_iterator for writing to null stream.
Provides seqan3::type_list and auxiliary type traits.
T to_string(T... args)
std::remove_cv_t< std::remove_reference_t< t > > remove_cvref_t
Return the input type with const, volatile and references removed (type trait).
Definition: basic.hpp:35
auto search(queries_t &&queries, index_t const &index, configuration_t const &cfg)
Search a query or a range of queries in an index.
Definition: search.hpp:56
SeqAn specific customisations in the standard namespace.
constexpr auto istreambuf
A view factory that returns a view over the stream buffer of an input stream.
Definition: istreambuf.hpp:245
constexpr auto const & get(configuration< configs_t... > const &config) noexcept
Definition: configuration.hpp:578
::ranges::size size
Alias for ranges::size. Obtains the size of a range whose size can be calculated in constant time...
Definition: ranges:189
The main SeqAn3 namespace.
auto constexpr 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.hpp:641
Auxiliary for pretty printing of exception messages.
Auxiliary functions for the alignment IO.
Provides std::from_chars and std::to_chars if not defined in the stl <charconv> header.
constexpr auto slice
A view adaptor that returns a half-open interval on the underlying range.
Definition: slice.hpp:144
Provides seqan3::alignment_file_output_options.
static constexpr char format_version[4]
The format version string.
Definition: format_sam.hpp:135
T push_back(T... args)
Provides seqan3::AlignmentFileInputFormat and auxiliary classes.
Provides seqan3::view::take_until and seqan3::view::take_until_or_throw.
Provides seqan3::TupleLike.
Provides the seqan3::alignment_file_header class.
Provides seqan3::view::repeat_n.
Provides various utility functions.
Provides various utility functions.
Provides seqan3::view::char_to.
The Concepts library.
Adaptations of concepts from the Ranges TS.
::ranges::begin begin
Alias for ranges::begin. Returns an iterator to the beginning of a range.
Definition: ranges:174
::ranges::advance advance
Alias for ranges::advance. Advances the iterator by the given distance.
Definition: iterator:316
Provides seqan3::alignment_file_input_options.
T addressof(T... args)
T align(T... args)
::ranges::copy copy
Alias for ranges::copy. Copies a range of elements to a new location.
Definition: algorithm:44
auto const to_char
A view that calls seqan3::to_char() on each element in the input range.
Definition: to_char.hpp:66
Definition: aligned_sequence_concept.hpp:35
Provides character predicates for tokenisation.
Provides seqan3::ostream and seqan3::ostreambuf iterator.
auto constexpr 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:613
Provides seqan3::AlignmentFileOutputFormat and auxiliary classes.
T back_inserter(T... args)
Adaptations of algorithms from the Ranges TS.
Provides seqan3::view::slice.
Definition: ranges:240
::ranges::empty empty
Alias for ranges::empty. Checks whether a range is empty.
Definition: ranges:194
Provides seqan3::view::to_char.
constexpr auto repeat_n
A view factory that repeats a given value n times.
Definition: repeat_n.hpp:97
Provides various transformation traits used by the range module.
typename reference< t >::type reference_t
Shortcut for seqan3::reference (TransformationTrait shortcut).
Definition: pre.hpp:77
Specifies requirements of a Range type for which begin returns a type that models std::ForwardIterato...
Static reflection for arbitrary types.
The concept std::Same<T, U> is satisfied if and only if T and U denote the same type.
T from_chars(T... args)
Provides SeqAn version macros and global variables.
::ranges::end end
Alias for ranges::end. Returns an iterator to the end of a range.
Definition: ranges:179
Provides seqan3::gap_decorator.
constexpr auto transform
A range adaptor that takes a invocable and returns a view of the elements with the invocable applied...
Definition: ranges:911
The concept Integral is satisfied if and only if T is an integral type.
auto const char_to
A view over an alphabet, given a range of characters.
Definition: char_to.hpp:70