13 std::string extension_of(
const std::string& filepath)
15 auto extension = std::filesystem::path(filepath).extension().string();
17 std::ranges::transform(extension, extension.begin(),
18 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
24 const Kakshya::CompositeLayout& left,
25 const Kakshya::CompositeLayout& right)
27 if (left.stride_bytes() != right.stride_bytes()
28 || left.presence_bytes() != right.presence_bytes()
29 || left.fields().size() != right.fields().size())
32 for (
size_t i = 0; i < left.fields().
size(); ++i) {
33 const auto&
a = left.fields()[i];
34 const auto&
b = right.fields()[i];
36 if (
a.name !=
b.name ||
a.type !=
b.type
37 ||
a.offset_bytes !=
b.offset_bytes
38 ||
a.size_bytes !=
b.size_bytes)
46 std::optional<std::string> format_number(
47 const Kakshya::Composite& element,
48 std::string_view
name)
54 if constexpr (std::same_as<T, bool>) {
55 return *
value ?
"true" :
"false";
57 std::array<char, 128>
buffer {};
59 if constexpr (std::integral<T>) {
60 using Formatted = std::conditional_t<
61 std::is_signed_v<T>,
long long,
unsigned long long>;
63 const auto [end,
error] = std::to_chars(
65 static_cast<Formatted
>(*
value));
67 if (error != std::errc {})
70 return std::string(
buffer.data(), end);
72 if (!std::isfinite(*
value))
75 const auto [end,
error] = std::to_chars(
78 if (error != std::errc {})
81 return std::string(
buffer.data(), end);
86 std::optional<std::string> format_field(
87 const Kakshya::Composite& element,
88 const Kakshya::CompositeField& field)
90 const auto name = std::string_view(field.name);
91 const auto type = field.type;
93 if (
type ==
typeid(
bool))
94 return format_number<bool>(element,
name);
96 if (
type ==
typeid(
char))
97 return format_number<char>(element,
name);
98 if (
type ==
typeid(
signed char))
99 return format_number<signed char>(element,
name);
100 if (
type ==
typeid(
unsigned char))
101 return format_number<unsigned char>(element,
name);
103 if (
type ==
typeid(
short))
104 return format_number<short>(element,
name);
105 if (
type ==
typeid(
unsigned short))
106 return format_number<unsigned short>(element,
name);
108 if (
type ==
typeid(
int))
109 return format_number<int>(element,
name);
110 if (
type ==
typeid(
unsigned int))
111 return format_number<unsigned int>(element,
name);
113 if (
type ==
typeid(
long))
114 return format_number<long>(element,
name);
115 if (
type ==
typeid(
unsigned long))
116 return format_number<unsigned long>(element,
name);
118 if (
type ==
typeid(
long long))
119 return format_number<long long>(element,
name);
120 if (
type ==
typeid(
unsigned long long))
121 return format_number<unsigned long long>(element,
name);
123 if (
type ==
typeid(
float))
124 return format_number<float>(element,
name);
125 if (
type ==
typeid(
double))
126 return format_number<double>(element,
name);
127 if (
type ==
typeid(
long double))
128 return format_number<long double>(element,
name);
133 bool supported_type(std::type_index
type)
135 return type ==
typeid(std::string)
136 ||
type ==
typeid(
bool)
137 ||
type ==
typeid(char)
138 ||
type ==
typeid(
signed char)
139 ||
type ==
typeid(
unsigned char)
140 ||
type ==
typeid(
short)
141 ||
type ==
typeid(
unsigned short)
142 ||
type ==
typeid(
int)
143 ||
type ==
typeid(
unsigned int)
144 ||
type ==
typeid(
long)
145 ||
type ==
typeid(
unsigned long)
146 ||
type ==
typeid(
long long)
147 ||
type ==
typeid(
unsigned long long)
148 ||
type ==
typeid(
float)
149 ||
type ==
typeid(double)
150 ||
type ==
typeid(
long double);
166 m_write_header = enabled;
172 if (is_open() || delimiter ==
'\0' || delimiter ==
'"'
173 || delimiter ==
'\r' || delimiter ==
'\n')
176 m_delimiter = delimiter;
182 const auto extension = extension_of(filepath);
184 return extension ==
".csv" || extension ==
".tsv";
188 const std::string& filepath,
192 m_last_error =
"Close the current file before opening another";
203 if (layout.
fields().empty()) {
204 m_last_error =
"Composite output requires at least one field";
208 for (
const auto& field : layout.
fields()) {
209 if (!supported_type(field.type)) {
210 m_last_error =
"Unsupported Composite field: " + field.name;
219 : (extension_of(filepath) ==
".tsv" ?
'\t' :
',');
222 std::ios::binary | std::ios::out | std::ios::trunc);
235 for (
size_t i = 0; i < fields.size(); ++i) {
266 m_last_error =
"Composite slice layout differs from the open file";
270 for (
size_t i = 0; i <
rows.size(); ++i) {
271 const auto element =
rows.at(i);
307 const bool finished = !
m_file.fail();
309 m_last_error =
"Failed to finalize delimited text output";
316 std::string_view text,
bool force_quote)
318 const bool needs_quote = force_quote
320 || text.find(
'"') != std::string_view::npos
321 || text.find(
'\r') != std::string_view::npos
322 || text.find(
'\n') != std::string_view::npos;
328 for (
const char c : text) {
335 if (text.size() >
static_cast<size_t>(
336 std::numeric_limits<std::streamsize>::max())) {
337 m_last_error =
"Delimited text cell exceeds stream size";
341 m_file.write(text.data(),
static_cast<std::streamsize
>(text.size()));
358 const auto& fields =
m_layout->fields();
360 for (
size_t i = 0; i < fields.size(); ++i) {
364 const auto& field = fields[i];
365 if (!element.
has(field.name))
368 if (field.type ==
typeid(std::string)) {
369 const auto text = element.
text(field.name);
371 if (!text || !
write_cell(*text, text->empty())) {
377 const auto text = format_field(element, field);
390 m_last_error =
"Failed to write delimited text element";
std::shared_ptr< NetworkGeometryBuffer > buffer
vk::PhysicalDeviceType type
bool set_delimiter(char delimiter) noexcept
Override the extension-selected delimiter.
bool write_cell(std::string_view text, bool force_quote)
bool write_rows(const Kakshya::CompositeSlice &rows) override
Encode a bounded slice using the open file's layout.
bool flush() override
Flush the current file without closing it.
bool set_write_header(bool enabled) noexcept
Select whether a header record is written.
bool write_element(const Kakshya::Composite &element)
bool close() override
Finalize and close the current file.
bool is_open() const override
Whether an output file is open.
bool can_write(const std::string &filepath) const override
Check for a .csv or .tsv output path.
~DelimitedTextWriter() override
std::optional< Kakshya::CompositeLayout > m_layout
bool open(const std::string &filepath, const Kakshya::CompositeLayout &layout) override
Create or truncate a CSV or TSV file.
const CompositeLayout & layout() const noexcept
Finalized layout shared by all elements.
Owns a sequence of values that share a CompositeLayout.
const std::vector< CompositeField > & fields() const noexcept
Declared fields in element order, with finalized byte offsets.
Shared field description and byte layout for a CompositeArray.
Borrowed consecutive elements, optionally selected by a Region.
std::optional< std::string_view > text(std::string_view field_name) const noexcept
Read a present std::string field as borrowed UTF-8 bytes.
bool has(std::string_view field_name) const noexcept
Whether a named field exists and is present in this element.
Read view of one value in a CompositeArray.
std::string resolve_write_path(const std::string &filepath, std::string_view default_directory=Config::OUT_DIR)
Resolve a writable path against the configured output directory.
void error(Component component, Context context, std::source_location location, std::string_view message)
Log an error message and optionally throw an exception.