Vince's CSV Parser
Loading...
Searching...
No Matches
csv_writer.hpp
Go to the documentation of this file.
1
5#pragma once
6#include "common.hpp"
7#include <cmath>
8#include <fstream>
9#include <iostream>
10#include <memory>
11#ifdef CSV_HAS_CXX20
12#include <ranges>
13#endif
14#include <stdexcept>
15#include <string>
16#include <tuple>
17#include <type_traits>
18#include <vector>
19
21#include "common.hpp"
22#include "csv_exceptions.hpp"
23#include "data_frame/fwd.hpp"
24
25namespace csv {
26namespace internals {
27 static int DECIMAL_PLACES = 5;
28
30 template<typename T>
31 CSV_CONST CONSTEXPR_14
32 long double pow10(const T& n) noexcept {
33 static_assert(std::is_integral<T>::value, "pow10 only supports integral exponents");
34
35 long double multiplicand = n > 0 ? 10 : 0.1,
36 ret = 1;
37 T iterations = n > 0 ? n : -n;
38
39 for (T i = 0; i < iterations; i++) {
40 ret *= multiplicand;
41 }
42
43 return ret;
44 }
45
46 template<>
47 CSV_CONST CONSTEXPR_14
48 long double pow10(const unsigned& n) noexcept {
49 long double multiplicand = n > 0 ? 10 : 0.1,
50 ret = 1;
51
52 for (unsigned i = 0; i < n; i++) {
53 ret *= multiplicand;
54 }
55
56 return ret;
57 }
58
62 template<typename T = int>
63 inline T csv_abs(T x) {
64 return abs(x);
65 }
66
67 template<>
68 inline int csv_abs(int x) {
69 return abs(x);
70 }
71
72 template<>
73 inline long int csv_abs(long int x) {
74 return labs(x);
75 }
76
77 template<>
78 inline long long int csv_abs(long long int x) {
79 return llabs(x);
80 }
81
82 template<>
83 inline float csv_abs(float x) {
84 return fabsf(x);
85 }
86
87 template<>
88 inline double csv_abs(double x) {
89 return fabs(x);
90 }
91
92 template<>
93 inline long double csv_abs(long double x) {
94 return fabsl(x);
95 }
96
100 template<
101 typename T,
102 csv::enable_if_t<std::is_arithmetic<T>::value, int> = 0
103 >
104 int num_digits(T x)
105 {
106 x = csv_abs(x);
107 int digits = 0;
108 for (; x >= 1; digits++)
109 x /= 10;
110
111 return (x == 0) ? 1 : digits;
112 }
113
115 template<typename T,
116 csv::enable_if_t<std::is_unsigned<T>::value, int> = 0>
117 inline std::string to_string(T value) {
118 std::string digits_reverse = "";
119 if (value == 0) return "0";
120
121 for (; value > 0; value /= 10)
122 digits_reverse += (char)('0' + (value % 10));
123
124 return std::string(digits_reverse.rbegin(), digits_reverse.rend());
125 }
126
128 template<
129 typename T,
130 csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0
131 >
132 inline std::string to_string(T value) {
133 return (value >= 0) ? to_string(static_cast<unsigned long long>(value))
134 : "-" + to_string(0ULL - static_cast<unsigned long long>(value));
135 }
136
138 template<
139 typename T,
140 csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
141 >
142 inline std::string to_string(T value) {
143 std::string result = "";
144
145 long double integral_part;
146 long double fractional_part = csv_abs(std::modf((long double)value, &integral_part));
147
148 const long double scale = pow10(DECIMAL_PLACES);
149 long double rounded_fractional = std::round(fractional_part * scale);
150
151 // Work with the absolute value of the integral part so digit extraction
152 // and carry both work correctly for negative numbers.
153 long double abs_integral = csv_abs(integral_part);
154
155 // Carry rounding overflow from fractional digits into integral digits.
156 if (rounded_fractional >= scale) {
157 abs_integral += 1;
158 rounded_fractional = 0;
159 }
160
161 // Integral part
162 if (value < 0) result = "-";
163
164 if (abs_integral == 0) {
165 result += "0";
166 }
167 else {
168 for (int n_digits = num_digits(abs_integral); n_digits > 0; n_digits --) {
169 int digit = (int)(std::fmod(abs_integral, pow10(n_digits)) / pow10(n_digits - 1));
170 result += (char)('0' + digit);
171 }
172 }
173
174 // Decimal part
175 result += ".";
176
177 if (rounded_fractional > 0) {
178 for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
179 int digit = (int)(std::fmod(rounded_fractional, pow10(n_digits)) / pow10(n_digits - 1));
180 result += (char)('0' + digit);
181 }
182 }
183 else {
184 result += "0";
185 }
186
187 return result;
188 }
189}
190
192inline static void set_decimal_places(int precision) {
193 internals::DECIMAL_PLACES = precision;
194}
195
196namespace internals {
197 template<typename T, typename = void>
198 struct has_checked_csv_fields : std::false_type {};
199
200 template<typename T>
201 struct has_checked_csv_fields<T, csv::void_t<
202 decltype(std::declval<const typename std::remove_reference<T>::type&>().size()),
203 decltype(std::declval<const typename std::remove_reference<T>::type&>()
204 .try_field(size_t{0}, std::declval<CSVError&>()).get_sv())>> : std::true_type {};
205
206 template<typename T>
207 struct is_data_frame : std::false_type {};
208
209 template<typename KeyType>
210 struct is_data_frame<DataFrame<KeyType>> : std::true_type {};
211
213 template<typename T, typename = void>
214 struct is_iterable : std::false_type {};
215
216 template<typename T>
217 struct is_iterable<T, typename std::enable_if<true>::type> {
218 private:
219 template<typename U>
220 static auto test(int) -> decltype(
221 std::begin(std::declval<const U&>()),
222 std::end(std::declval<const U&>()),
223 std::true_type{}
224 );
225 template<typename>
226 static std::false_type test(...);
227 public:
228 static constexpr bool value = decltype(test<T>(0))::value;
229 };
230
232 template<typename T, typename = void>
233 struct is_tuple : std::false_type {};
234
235 template<typename T>
236 struct is_tuple<T, typename std::enable_if<true>::type> {
237 private:
238 template<typename U>
239 static auto test(int) -> decltype(std::tuple_size<U>::value, std::true_type{});
240 template<typename>
241 static std::false_type test(...);
242 public:
243 static constexpr bool value = decltype(test<T>(0))::value;
244 };
245
246}
247
250
271 template<class OutputStream, char Delim, char Quote>
273 public:
276 DelimWriter(OutputStream& _out, bool _quote_minimal = true)
277 : out(&_out), quote_minimal(_quote_minimal) {}
278
280#if CSV_EXCEPTIONS_ENABLED
281 template<typename T = OutputStream,
282 csv::enable_if_t<std::is_same<T, std::ofstream>::value, int> = 0>
283 DelimWriter(const std::string& filename, bool _quote_minimal = true)
284 : DelimWriter(std::unique_ptr<OutputStream>(new OutputStream(filename, std::ios::out)),
285 _quote_minimal) {
286 if (!owned_out->is_open())
287 internals::throw_failed_open_for_writing(filename);
288 }
289#endif
290
294 template<typename T = OutputStream,
295 csv::enable_if_t<std::is_same<T, std::ofstream>::value, int> = 0>
296 static std::unique_ptr<DelimWriter> try_open(
297 const std::string& filename, CSVError& error, bool quote_minimal = true
298 ) {
299 error.clear();
300 std::unique_ptr<OutputStream> stream(new OutputStream(filename, std::ios::out));
301 if (!stream->is_open()) {
302 internals::fail(error, CSVErrorCode::OpenFailed,
303 std::string(internals::ERROR_FAILED_OPEN_WRITE) + filename);
304 return nullptr;
305 }
306 return std::unique_ptr<DelimWriter>(new DelimWriter(std::move(stream), quote_minimal));
307 }
308
309 DelimWriter(const DelimWriter&) = delete;
310 DelimWriter& operator=(const DelimWriter&) = delete;
311
312 DelimWriter(DelimWriter&& other) noexcept
313 : owned_out(std::move(other.owned_out)),
314 out(other.out),
315 quote_minimal(other.quote_minimal),
316 auto_flush_(other.auto_flush_),
317 batch_buffer_(std::move(other.batch_buffer_)) {
318 if (owned_out && out) {
319 out = owned_out.get();
320 }
321 other.out = nullptr;
322 other.quote_minimal = true;
323 }
324
325 DelimWriter& operator=(DelimWriter&& other) noexcept {
326 if (this == &other) return *this;
327
328 owned_out = std::move(other.owned_out);
329 out = other.out;
330 quote_minimal = other.quote_minimal;
331 auto_flush_ = other.auto_flush_;
332 batch_buffer_ = std::move(other.batch_buffer_);
333
334 if (owned_out && out) {
335 out = owned_out.get();
336 }
337
338 other.out = nullptr;
339 other.quote_minimal = true;
340 return *this;
341 }
342
344 DelimWriter& set_auto_flush(bool value) & noexcept {
345 this->auto_flush_ = value;
346 return *this;
347 }
348
350 DelimWriter&& set_auto_flush(bool value) && noexcept {
351 this->auto_flush_ = value;
352 return std::move(*this);
353 }
354
356 bool get_auto_flush() const noexcept {
357 return this->auto_flush_;
358 }
359
362 // A failed write can have emitted a prefix. Never retry its buffered bytes
363 // during destruction; the caller has already received the I/O failure.
364 if (!out || !ready_for_destruction(*out, 0)) return;
365 CSV_TRY {
366 flush_batch();
367 out->flush();
368 }
369 CSV_CATCH((...), {
370 // Destruction must not replace an active exception or terminate a caller
371 // that already handled a checked I/O failure.
372 })
373 }
374
376 template<typename T, size_t N>
377 DelimWriter& operator<<(const T (&record)[N]) {
378 write_range_impl(record);
379 return *this;
380 }
381
383 template<typename T, size_t N>
384 DelimWriter& operator<<(const std::array<T, N>& record) {
385 write_range_impl(record);
386 return *this;
387 }
388
394#if CSV_EXCEPTIONS_ENABLED
395 template<typename KeyType>
396 DelimWriter& operator<<(const DataFrame<KeyType>& frame) {
397 append_range_fields(frame.columns());
398 end_record();
399 flush_batch_if_needed();
400
401#ifdef CSV_HAS_CXX20
402 return write_rows(frame);
403#else
404 // Older standards use the existing visible-row conversion path.
405 for (const auto& row : frame) {
406 append_range_fields(std::vector<std::string>(row));
407 end_record();
408 flush_batch_if_needed();
409 }
410 finish_write_call();
411 return *this;
412#endif
413 }
414
415#endif
416
428 template<typename T>
429 auto write_row(T&& record) -> decltype(*this << std::forward<T>(record)) {
430 return *this << std::forward<T>(record);
431 }
432
442 template<typename T, typename U, typename... Rest>
443 DelimWriter& write_row(T&& first, U&& second, Rest&&... rest) {
444 this->write_tuple<0>(std::forward_as_tuple(
445 std::forward<T>(first), std::forward<U>(second), std::forward<Rest>(rest)...));
446 return *this;
447 }
448
449#ifdef CSV_HAS_CXX20
465 template<std::ranges::input_range Rows>
467 DelimWriter& write_rows(Rows&& rows) {
468 for (auto&& row : rows) {
469 append_row_like(row);
470 flush_batch_if_needed();
471 }
472
473 finish_write_call();
474 return *this;
475 }
476
483 template<std::ranges::input_range Range>
484 DelimWriter& operator<<(Range&& container)
485 requires std::ranges::input_range<Range>
486 && std::convertible_to<std::ranges::range_reference_t<Range>, csv::string_view> {
487 write_range_impl(container);
488 return *this;
489 }
490
492 template<typename RowLike>
493 DelimWriter& operator<<(const RowLike& row)
496 append_row_like(row);
497 finish_write_call();
498 return *this;
499 }
500#else
509 template<typename Range>
510 typename std::enable_if<
513 && !std::is_same<Range, std::string>::value
514 && !std::is_same<Range, csv::string_view>::value,
516 >::type operator<<(const Range& record) {
517 write_range_impl(record);
518 return *this;
519 }
520#endif
521
523 template<typename... T>
524 DelimWriter& operator<<(const std::tuple<T...>& record) {
525 this->write_tuple<0, T...>(record);
526 return *this;
527 }
528
530 void flush() {
531 flush_batch();
532 out->flush();
533 }
534
541 template<typename T,
542 csv::enable_if_t<internals::has_checked_csv_fields<T>::value
543 || internals::is_data_frame<typename std::decay<T>::type>::value, int> = 0>
544 bool try_write_row(CSVError& error, T&& record) {
545 return this->checked_write(error, std::forward<T>(record));
546 }
547
548 template<typename... Args>
549 auto try_write_row(CSVError& error, Args&&... args)
550 -> decltype(this->write_row(std::forward<Args>(args)...), bool()) {
551 return this->checked_write(error, std::forward<Args>(args)...);
552 }
553
554#ifdef CSV_HAS_CXX20
556 template<std::ranges::input_range Rows>
557 requires (internals::csv_write_rows_input_range<Rows>
558 || internals::has_checked_csv_fields<std::ranges::range_reference_t<Rows>>::value)
559 bool try_write_rows(Rows&& rows, CSVError& error) {
560 return this->checked_operation(error, [this, &rows, &error]() {
561 for (auto&& row : rows) {
562 if (!this->append_checked_row(row, error, 0)) return false;
563 this->flush_batch_if_needed();
564 if (!this->check_write_status(error)) return false;
565 }
566 this->finish_write_call();
567 return true;
568 });
569 }
570#endif
571
573 bool try_flush(CSVError& error) {
574 return this->checked_operation(error, [this]() {
575 this->flush();
576 return true;
577 });
578 }
579
584 template<typename T = OutputStream,
585 csv::enable_if_t<std::is_same<T, std::ofstream>::value, int> = 0>
586 bool try_close(CSVError& error) {
587 error.clear();
588 if (!owned_out || !out) {
589 return internals::fail_static(error, CSVErrorCode::InvalidArgument,
590 "try_close() requires an open, owned output file.");
591 }
592
593 const bool flushed = this->try_flush(error);
594 // Detach before closing so neither close errors nor a failed flush can
595 // trigger a second write from the writer's destructor.
596 out = nullptr;
597 CSV_TRY {
598 owned_out->close();
599 }
600 CSV_CATCH((const std::ios_base::failure& failure), {
601 if (flushed) internals::fail(error, CSVErrorCode::WriteFailed, failure.what());
602 return false;
603 })
604 if (!flushed) return false;
605 if (owned_out->fail()) {
606 return internals::fail_static(error, CSVErrorCode::WriteFailed, "Failed to close CSV output.");
607 }
608 return true;
609 }
610
611 private:
612 DelimWriter(std::unique_ptr<OutputStream> stream, bool quote_minimal)
613 : owned_out(std::move(stream)), out(owned_out.get()), quote_minimal(quote_minimal) {}
614
615 // These probes preserve support for lightweight custom output streams in
616 // the existing unchecked API. Only checked calls require fail().
617 template<typename Stream>
618 static auto exceptions_disabled(const Stream& stream, int)
619 -> decltype(stream.exceptions(), bool()) {
620 return stream.exceptions() == std::ios_base::goodbit;
621 }
622
623 template<typename Stream>
624 static bool exceptions_disabled(const Stream&, long) { return true; }
625
626 template<typename Stream>
627 static auto ready_for_destruction(const Stream& stream, int)
628 -> decltype(stream.fail(), bool()) {
629#if CSV_EXCEPTIONS_ENABLED
630 return !stream.fail();
631#else
632 return !stream.fail() && exceptions_disabled(stream, 0);
633#endif
634 }
635
636 template<typename Stream>
637 static bool ready_for_destruction(const Stream&, long) { return true; }
638
639 template<typename Fn>
640 bool checked_operation(CSVError& error, Fn&& operation) {
641 error.clear();
642 if (!out) {
643 return internals::fail_static(error, CSVErrorCode::InvalidArgument, "CSV output is closed.");
644 }
645#if !CSV_EXCEPTIONS_ENABLED
646 if (!exceptions_disabled(*out, 0)) {
647 return internals::fail_static(error, CSVErrorCode::InvalidArgument,
648 "Checked CSV output requires a stream with exceptions disabled.");
649 }
650#endif
651 if (out->fail()) {
652 return internals::fail_static(error, CSVErrorCode::WriteFailed, "CSV output stream has failed.");
653 }
654 CSV_TRY {
655 if (!std::forward<Fn>(operation)()) return false;
656 }
657 CSV_CATCH((const std::ios_base::failure& failure), {
658 return internals::fail(error, CSVErrorCode::WriteFailed, failure.what());
659 })
660 return this->check_write_status(error);
661 }
662
663 bool check_write_status(CSVError& error) const {
664 return !out->fail() || internals::fail_static(error, CSVErrorCode::WriteFailed,
665 "CSV output stream has failed.");
666 }
667
668 template<typename... Args>
669 bool checked_write(CSVError& error, Args&&... args) {
670 return this->checked_operation(error, [&]() {
671 return this->checked_write_call(error, std::forward<Args>(args)...);
672 });
673 }
674
675 template<typename Row>
676 static auto checked_record_valid(const Row& row, int)
677 -> decltype(row.is_valid(), bool()) {
678 return row.is_valid();
679 }
680
681 template<typename Row>
682 static bool checked_record_valid(const Row&, long) { return true; }
683
684 template<typename T>
685 bool checked_write_call(CSVError& error, T&& record) {
686 return this->checked_write_record(std::forward<T>(record), error, 0);
687 }
688
689 template<typename T, typename U, typename... Rest>
690 bool checked_write_call(CSVError&, T&& first, U&& second, Rest&&... rest) {
691 this->write_row(std::forward<T>(first), std::forward<U>(second), std::forward<Rest>(rest)...);
692 return true;
693 }
694
695 template<typename KeyType>
696 bool checked_write_record(DataFrame<KeyType>& frame, CSVError& error, int) {
697 return this->checked_write_record(static_cast<const DataFrame<KeyType>&>(frame), error, 0);
698 }
699
700 template<typename KeyType>
701 bool checked_write_record(DataFrame<KeyType>&& frame, CSVError& error, int) {
702 return this->checked_write_record(static_cast<const DataFrame<KeyType>&>(frame), error, 0);
703 }
704
705 template<typename KeyType>
706 bool checked_write_record(const DataFrame<KeyType>& frame, CSVError& error, int) {
707 append_range_fields(frame.columns());
708 end_record();
709 flush_batch_if_needed();
710 if (!this->check_write_status(error)) return false;
711 for (const auto& row : frame) {
712 if (!this->append_checked_row(row, error, 0)) return false;
713 flush_batch_if_needed();
714 if (!this->check_write_status(error)) return false;
715 }
716 finish_write_call();
717 return true;
718 }
719
720 template<typename Row>
721 auto checked_write_record(Row&& row, CSVError& error, int)
722 -> decltype(row.try_field(size_t{0}, error).get_sv(), bool()) {
723 if (!this->append_checked_row(row, error, 0)) return false;
724 finish_write_call();
725 return true;
726 }
727
728 template<typename Row>
729 bool checked_write_record(Row&& row, CSVError&, long) {
730 this->write_row(std::forward<Row>(row));
731 return true;
732 }
733
734 // Logical rows need checked physical-width access: KEEP permits native
735 // rows shorter than the schema. Generic string ranges use their iterator
736 // contract instead, so this indexed validation cannot share that loop.
737 template<typename Row>
738 auto append_checked_row(Row&& row, CSVError& error, int)
739 -> decltype(row.try_field(size_t{0}, error).get_sv(), bool()) {
740 if (!checked_record_valid(row, 0)) {
741 return internals::fail_static(error, CSVErrorCode::InvalidProxy, "CSV row or column proxy is invalid.");
742 }
743 const size_t row_start = batch_buffer_.size();
744 for (size_t i = 0; i < row.size(); ++i) {
745 const auto field = row.try_field(i, error);
746 if (error) {
747 // No bytes from this row have been sent yet. Discard its
748 // incomplete buffered record while retaining earlier rows.
749 batch_buffer_.resize(row_start);
750 return false;
751 }
752 if (i != 0) batch_buffer_.push_back(Delim);
753 write_escaped_field(field.get_sv());
754 }
755 end_record();
756 return true;
757 }
758
759#ifdef CSV_HAS_CXX20
760 template<typename Row>
761 bool append_checked_row(Row&& row, CSVError&, long) {
762 append_row_like(std::forward<Row>(row));
763 return true;
764 }
765#endif
766
772 template<typename Range>
773 inline void append_range_fields(Range&& record) {
774 auto it = std::begin(record);
775 auto end = std::end(record);
776
777 if (it != end) {
778 write_field(*it);
779 ++it;
780 }
781
782 for (; it != end; ++it) {
783 batch_buffer_.push_back(Delim);
784 write_field(*it);
785 }
786 }
787
789 size_t find_first_special_for_writer(csv::string_view in) const {
790 size_t pos = internals::find_next_non_special(in, 0, simd_sentinels_);
791
792 for (; pos < in.size(); ++pos) {
793 char ch = in[pos];
794 if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n')
795 return pos;
796 }
797
798 return in.size();
799 }
800
804 template<typename Range>
805 inline void write_range_impl(const Range& record) {
806 append_range_fields(record);
807
808 end_record();
809 finish_write_call();
810 }
811
812#ifdef CSV_HAS_CXX20
813 template<typename Row>
814 void append_row_like(Row&& row) {
815 IF_CONSTEXPR(internals::csv_string_field_range<Row>) {
816 append_range_fields(std::forward<Row>(row));
817 }
818 else {
819 append_range_fields(row.to_sv_range());
820 }
821
822 end_record();
823 }
824#endif
825
826 template<
827 typename T,
828 csv::enable_if_t<
829 !std::is_convertible<T, std::string>::value
830 && !std::is_convertible<T, csv::string_view>::value
831 , int> = 0
832 >
833 void write_field(T in) {
834 const std::string serialized = internals::to_string(in);
835 write_raw(serialized);
836 }
837
838 template<
839 typename T,
840 csv::enable_if_t<
841 std::is_convertible<T, std::string>::value
842 || std::is_convertible<T, csv::string_view>::value
843 , int> = 0
844 >
845 void write_field(T in) {
846 IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
847 write_escaped_field(in);
848 }
849 else {
850 const std::string serialized(in);
851 write_escaped_field(serialized);
852 }
853 }
854
855 void write_raw(csv::string_view in) {
856 if (!in.empty())
857 batch_buffer_.append(in.data(), in.size());
858 }
859
860 void write_escaped_field(csv::string_view in) {
861 const size_t first_special = find_first_special_for_writer(in);
862
863 if (first_special == in.size()) {
864 if (!quote_minimal) {
865 batch_buffer_.push_back(Quote);
866 write_raw(in);
867 batch_buffer_.push_back(Quote);
868 } else {
869 write_raw(in);
870 }
871 return;
872 }
873
874 write_quoted_field(in, first_special);
875 }
876
877 void write_quoted_field(csv::string_view in, size_t first_special) {
878 batch_buffer_.push_back(Quote);
879
880 size_t chunk_start = 0;
881 size_t pos = first_special;
882 while (pos < in.size()) {
883 if (in[pos] == Quote) {
884 write_raw(in.substr(chunk_start, pos - chunk_start));
885 batch_buffer_.push_back(Quote);
886 batch_buffer_.push_back(Quote);
887 chunk_start = pos + 1;
888 }
889
890 ++pos;
891 }
892
893 write_raw(in.substr(chunk_start));
894 batch_buffer_.push_back(Quote);
895 }
896
898 template<size_t Index = 0, typename... T>
899 typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
900 write_field(std::get<Index>(record));
901
902 CSV_MSVC_PUSH_DISABLE(4127)
903 IF_CONSTEXPR (Index + 1 < sizeof...(T)) batch_buffer_.push_back(Delim);
904 CSV_MSVC_POP
905
906 this->write_tuple<Index + 1>(record);
907 }
908
910 template<size_t Index = 0, typename... T>
911 typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
912 (void)record;
913 end_record();
914 finish_write_call();
915 }
916
918 void end_record() {
919 batch_buffer_.push_back('\n');
920 }
921
922 void finish_write_call() {
923 if (this->auto_flush_) {
924 flush_batch();
925 out->flush();
926 return;
927 }
928
929 flush_batch_if_needed();
930 }
931
932 void flush_batch() {
933 if (batch_buffer_.empty()) return;
934
935 out->write(batch_buffer_.data(), static_cast<std::streamsize>(batch_buffer_.size()));
936 batch_buffer_.clear();
937 }
938
939 void flush_batch_if_needed() {
940 if (batch_buffer_.size() >= batch_flush_threshold_)
941 flush_batch();
942 }
943
949 std::unique_ptr<OutputStream> owned_out;
950
952 OutputStream* out;
953
954 bool quote_minimal;
955 bool auto_flush_ = true;
956 static constexpr size_t batch_flush_threshold_ = 64 * 1024;
957 std::string batch_buffer_;
958 internals::SentinelVecs simd_sentinels_{Delim, Quote};
959 };
960
968 template<class OutputStream>
969 using CSVWriter = DelimWriter<OutputStream, ',', '"'>;
970
979 template<class OutputStream>
980 using TSVWriter = DelimWriter<OutputStream, '\t', '"'>;
981
983 template<class OutputStream>
984 inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) {
985 return CSVWriter<OutputStream>(out, quote_minimal);
986 }
987
989 template<class OutputStream>
990 inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) {
991 return TSVWriter<OutputStream>(out, quote_minimal);
992 }
994}
SIMD-accelerated skip for runs of non-special CSV bytes.
Class for writing delimiter separated values files.
DelimWriter & write_row(T &&first, U &&second, Rest &&... rest)
Write a row from a variadic list of mixed-type values.
~DelimWriter()
Best-effort flush.
DelimWriter & operator<<(const DataFrame< KeyType > &frame)
Write a complete DataFrame: visible column names followed by visible rows.
DelimWriter & operator<<(const std::array< T, N > &record)
Write a std::array of strings as one delimited row.
static std::unique_ptr< DelimWriter > try_open(const std::string &filename, CSVError &error, bool quote_minimal=true)
Open an owned output file, returning null on failure.
DelimWriter && set_auto_flush(bool value) &&noexcept
Configure whether each write operation flushes the underlying stream.
bool try_write_row(CSVError &error, T &&record)
Write the same inputs accepted by write_row(), reporting stream failures.
DelimWriter & set_auto_flush(bool value) &noexcept
Configure whether each write operation flushes the underlying stream.
DelimWriter(const std::string &filename, bool _quote_minimal=true)
Construct a DelimWriter that owns an output file stream.
auto write_row(T &&record) -> decltype(*this<< std::forward< T >(record))
Write a row from any single argument accepted by operator<< (std::vector, std::array,...
bool try_flush(CSVError &error)
Flush buffered data and the stream, reporting I/O errors.
DelimWriter & write_rows(Rows &&rows)
Write many rows using a shared batch buffer.
DelimWriter & operator<<(const T(&record)[N])
Write a C-style array of strings as one delimited row.
DelimWriter & operator<<(const RowLike &row)
Write a row-like object that exposes to_sv_range().
bool get_auto_flush() const noexcept
Return whether each write operation flushes the underlying stream.
DelimWriter & operator<<(const std::tuple< T... > &record)
Write a C-style array of strings as one delimited row.
bool try_close(CSVError &error)
Flush and close an owned output file, including after a flush failure.
void flush()
Flushes the written data.
bool try_write_rows(Rows &&rows, CSVError &error)
Write a batch using the same accepted row ranges and buffering policy as write_rows().
DelimWriter(OutputStream &_out, bool _quote_minimal=true)
Construct a DelimWriter over the specified output stream.
DelimWriter & operator<<(Range &&container)
Write a range of string-like fields as one delimited row.
A standalone header file containing shared code.
#define IF_CONSTEXPR
Expands to if constexpr in C++17 and if otherwise.
Definition common.hpp:191
Shared exception message templates and throw helpers.
T csv_abs(T x)
Calculate the absolute value of a number.
CSV_CONST CONSTEXPR_14 long double pow10(const T &n) noexcept
Compute 10 to the power of an integral exponent.
int num_digits(T x)
Calculate the number of digits in a number.
std::string to_string(T value)
to_string() for unsigned integers
The all encompassing namespace.
TSVWriter< OutputStream > make_tsv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::TSVWriter over the output stream.
@ InvalidArgument
Invalid operation input, such as a null stream or zero batch size.
@ WriteFailed
Writing, flushing, or closing the output failed.
@ InvalidProxy
A proxy is unbound or a column proxy was invalidated by structural mutation.
@ OpenFailed
A native file could not be opened, including initial input mapping.
CSVWriter< OutputStream > make_csv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::CSVWriter over the output stream.
DelimWriter< OutputStream, ',', '"'> CSVWriter
An alias for csv::DelimWriter for writing standard CSV files.
DelimWriter< OutputStream, '\t', '"'> TSVWriter
Class for writing tab-separated values files.
std::string_view string_view
The string_view class used by this library.
Definition common.hpp:182
A diagnostic whose text remains valid for the lifetime of the error.
Definition csv_error.hpp:94
SFINAE trait: detects if a type is iterable (has std::begin/end).
SFINAE trait: detects if a type is a std::tuple.