Line data Source code
1 : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : #include "tablecsv.hpp"
5 : #include "utils/filesystem.hpp"
6 :
7 : #include <algorithm>
8 : #include <fstream>
9 : #include <iterator>
10 : #include <utility>
11 : #include <fmt/format.h>
12 : #include <fmt/os.h>
13 : #include <fmt/ranges.h>
14 : #include <scn/scan.h>
15 : #include <mfem/general/error.hpp>
16 :
17 : namespace palace
18 : {
19 :
20 264 : [[nodiscard]] size_t Column::col_width(const ColumnOptions &defaults) const
21 : {
22 : // Quickfix to specify full column width in integer case to match current formatting.
23 264 : if (print_as_int)
24 : {
25 : return std::max(min_left_padding.value_or(defaults.min_left_padding),
26 : header_text.size());
27 : }
28 : size_t pad = min_left_padding.value_or(defaults.min_left_padding);
29 : size_t prec = float_precision.value_or(defaults.float_precision);
30 :
31 : // Normal float in our exponent format needs float_precision + 7 ("+" , leading digit,
32 : // ".", "e", "+", +2 exponent. Sometimes exponent maybe +3 if very small or large; see
33 : // std::numeric_limits<double>::max_exponent. We pick +7 for consistency, but
34 : // min_left_padding should be at least 1, which is not currently enforced.
35 260 : return std::max(pad + prec + 7, header_text.size());
36 : }
37 :
38 132 : [[nodiscard]] auto Column::format_header(const ColumnOptions &defaults,
39 : const std::optional<size_t> &width) const
40 : {
41 132 : auto w = width.value_or(col_width(defaults));
42 132 : return fmt::format("{0:>{1}s}", header_text, w);
43 : }
44 :
45 132 : [[nodiscard]] auto Column::format_row(size_t i, const ColumnOptions &defaults,
46 : const std::optional<size_t> &width) const
47 : {
48 264 : auto width_ = width.value_or(col_width(defaults));
49 : // If data available format double.
50 132 : if ((i >= 0) && (i < data.size()))
51 : {
52 120 : auto val = data[i];
53 120 : if (print_as_int)
54 : { // Quick-fix to force int printing
55 : auto fmt_str = fmt::format("{{:>{width}d}}", fmt::arg("width", width_));
56 2 : return fmt::format(fmt::runtime(fmt_str), int(val));
57 : }
58 : else
59 : {
60 118 : auto sign = fmt_sign.value_or(defaults.fmt_sign);
61 118 : auto prec = float_precision.value_or(defaults.float_precision);
62 : auto fmt_str = fmt::format("{{:>{sign:s}{width}.{prec}e}}", fmt::arg("sign", sign),
63 : fmt::arg("width", width_), fmt::arg("prec", prec));
64 : return fmt::format(fmt::runtime(fmt_str), val);
65 : }
66 : }
67 : return fmt::format("{0:>{1}s}", defaults.empty_cell_val, width_);
68 : }
69 :
70 689 : Column::Column(std::string name_, std::string header_text_, long column_group_idx_,
71 : std::optional<size_t> min_left_padding_,
72 689 : std::optional<size_t> float_precision_, std::optional<std::string> fmt_sign_)
73 689 : : name(std::move(name_)), header_text(std::move(header_text_)),
74 689 : column_group_idx(column_group_idx_), min_left_padding(min_left_padding_),
75 689 : float_precision(float_precision_), fmt_sign(std::move(fmt_sign_))
76 : {
77 689 : }
78 :
79 70 : [[nodiscard]] size_t Table::n_rows() const
80 : {
81 70 : if (n_cols() == 0)
82 : {
83 : return 0;
84 : }
85 : auto max_col = std::max_element(cols.begin(), cols.end(), [](const auto &a, const auto &b)
86 : { return a.n_rows() < b.n_rows(); });
87 69 : return max_col->n_rows();
88 : }
89 :
90 56 : void Table::reserve(size_t n_rows, size_t n_cols)
91 : {
92 56 : reserve_n_rows = n_rows;
93 56 : cols.reserve(n_cols);
94 58 : for (auto &col : cols)
95 : {
96 2 : col.data.reserve(n_rows);
97 : }
98 56 : }
99 :
100 : // Insert columns: map like interface.
101 689 : bool Table::insert(Column &&column)
102 : {
103 689 : auto it = std::find_if(cols.begin(), cols.end(),
104 1547 : [&column](auto &c) { return c.name == column.name; });
105 689 : if (it != cols.end())
106 : {
107 : return false;
108 : }
109 689 : auto &col = cols.emplace_back(std::move(column));
110 689 : if (reserve_n_rows > 0)
111 : {
112 439 : col.data.reserve(reserve_n_rows);
113 : }
114 : return true;
115 : }
116 :
117 112 : Column &Table::operator[](std::string_view name)
118 : {
119 : auto it =
120 415 : std::find_if(cols.begin(), cols.end(), [&name](auto &c) { return c.name == name; });
121 112 : if (it == cols.end())
122 : {
123 3 : throw std::out_of_range(fmt::format("Column {} not found in table", name).c_str());
124 : }
125 111 : return *it;
126 : }
127 :
128 : // TODO: Improve all the functions below with ranges in C++20.
129 : template <typename T>
130 30 : void Table::append_header(T &buf) const
131 : {
132 162 : for (size_t i = 0; i < n_cols(); i++)
133 : {
134 132 : if (i > 0)
135 : {
136 : fmt::format_to(std::back_inserter(buf), "{:s}", print_col_separator);
137 : }
138 264 : fmt::format_to(std::back_inserter(buf), "{:s}", cols[i].format_header(col_options));
139 : }
140 : fmt::format_to(std::back_inserter(buf), "{:s}", print_row_separator);
141 30 : }
142 :
143 : template <typename T>
144 32 : void Table::append_row(T &buf, size_t row_j) const
145 : {
146 164 : for (size_t i = 0; i < n_cols(); i++)
147 : {
148 132 : if (i > 0)
149 : {
150 : fmt::format_to(std::back_inserter(buf), "{:s}", print_col_separator);
151 : }
152 264 : fmt::format_to(std::back_inserter(buf), "{:s}", cols[i].format_row(row_j, col_options));
153 : }
154 : fmt::format_to(std::back_inserter(buf), "{:s}", print_row_separator);
155 32 : }
156 :
157 0 : [[nodiscard]] std::string Table::format_header() const
158 : {
159 : fmt::memory_buffer buf{};
160 0 : append_header(buf);
161 0 : return {buf.data(), buf.size()};
162 : }
163 :
164 0 : [[nodiscard]] std::string Table::format_row(size_t j) const
165 : {
166 : fmt::memory_buffer buf{};
167 0 : append_row(buf, j);
168 0 : return {buf.data(), buf.size()};
169 : }
170 :
171 30 : [[nodiscard]] std::string Table::format_table() const
172 : {
173 : fmt::memory_buffer buf{};
174 30 : append_header(buf);
175 62 : for (size_t j = 0; j < n_rows(); j++)
176 : {
177 32 : append_row(buf, j);
178 : }
179 30 : return {buf.data(), buf.size()};
180 : }
181 :
182 : // Helper class to parse split view between delimiters. Not a proper view, but same idea.
183 : class sv_split_r
184 : {
185 : std::string_view full_view;
186 : std::string_view delimiters;
187 : std::size_t row_cursor_start = 0;
188 : std::size_t row_cursor_end = 0;
189 :
190 : public:
191 : sv_split_r(std::string_view full_view_, std::string_view delimiters_)
192 119 : : full_view(full_view_), delimiters(delimiters_)
193 : {
194 : }
195 :
196 1170 : std::string_view next()
197 : {
198 1170 : row_cursor_end = full_view.find_first_of(delimiters, row_cursor_start);
199 1170 : auto out = full_view.substr(row_cursor_start, row_cursor_end - row_cursor_start);
200 1170 : row_cursor_start =
201 1170 : (row_cursor_end == std::string_view::npos) ? row_cursor_end : row_cursor_end + 1;
202 1170 : return out;
203 : }
204 :
205 : bool at_end() const
206 : {
207 1267 : return (row_cursor_start == full_view.size()) ||
208 : (row_cursor_start == std::string_view::npos);
209 : }
210 : };
211 :
212 1073 : std::string_view trim_space(std::string_view str_v)
213 : {
214 : auto prefix = str_v.find_first_not_of(" \t\f\v");
215 : str_v.remove_prefix(std::min(prefix, str_v.size()));
216 : auto suffix = str_v.find_last_not_of(" \t\f\v"); // suffix also counts from 0
217 1073 : str_v.remove_suffix(std::min(str_v.size() - 1 - suffix, str_v.size()));
218 1073 : return str_v;
219 : }
220 :
221 22 : Table::Table(std::string_view table_str,
222 : std::optional<std::string_view> print_col_separator_,
223 22 : std::optional<std::string_view> print_row_separator_)
224 : {
225 : using namespace std::literals;
226 22 : if (table_str.empty())
227 : {
228 : return;
229 : }
230 : try
231 : {
232 22 : print_col_separator = print_col_separator_.value_or(print_col_separator);
233 22 : print_row_separator = print_row_separator_.value_or(print_row_separator);
234 :
235 22 : std::string_view table_full_view{table_str};
236 : sv_split_r row_split(table_full_view, print_row_separator);
237 : // Handle first row separately since it defines header & cols.
238 : {
239 22 : sv_split_r entries(row_split.next(), print_col_separator);
240 : std::size_t col_i = 0;
241 22 : while (!entries.at_end())
242 : {
243 452 : std::string entry_trim_str{trim_space(entries.next())};
244 226 : this->insert(fmt::format("col_{}", col_i), entry_trim_str);
245 226 : col_i++;
246 : }
247 : }
248 : // Loop over other rows
249 22 : while (!row_split.at_end())
250 : {
251 75 : sv_split_r entries(row_split.next(), print_col_separator);
252 : std::size_t col_i = 0;
253 75 : while (!entries.at_end())
254 : {
255 847 : auto entry_trim = trim_space(entries.next());
256 847 : if ((entry_trim == col_options.empty_cell_val) || (entry_trim.size() == 0))
257 : {
258 : }
259 : else
260 : {
261 647 : auto result = scn::scan<double>(entry_trim, "{}");
262 647 : MFEM_VERIFY(result, fmt::format("Could not parse CSV entry \"{}\" as double",
263 : entry_trim));
264 1294 : (*this)[col_i] << result->value();
265 : }
266 847 : col_i++;
267 : }
268 : }
269 : }
270 0 : catch (const std::exception &e)
271 : {
272 0 : MFEM_ABORT("Could not parse CSV table from string!\n " << e.what());
273 0 : }
274 0 : }
275 :
276 : // explicit instantiation to avoid fmt inclusion.
277 : template void Table::append_header(fmt::memory_buffer &) const;
278 : template void Table::append_row(fmt::memory_buffer &, size_t) const;
279 :
280 56 : TableWithCSVFile::TableWithCSVFile(std::string csv_file_fullpath, bool load_existing_file)
281 56 : : csv_file_fullpath_{std::move(csv_file_fullpath)}
282 : {
283 56 : if (!load_existing_file)
284 : {
285 38 : return;
286 : }
287 48 : if (!fs::exists(csv_file_fullpath_))
288 : {
289 : return;
290 : }
291 :
292 18 : std::ifstream file_buffer(csv_file_fullpath_, std::ios_base::in);
293 18 : if (!file_buffer.good())
294 : {
295 : return;
296 : }
297 18 : std::stringstream file_buffer_str;
298 18 : file_buffer_str << file_buffer.rdbuf();
299 18 : file_buffer.close();
300 18 : table = Table(file_buffer_str.str());
301 18 : }
302 :
303 24 : void TableWithCSVFile::WriteFullTableTrunc()
304 : {
305 : auto file_buffer = fmt::output_file(
306 : csv_file_fullpath_, fmt::file::WRONLY | fmt::file::CREATE | fmt::file::TRUNC);
307 24 : file_buffer.print("{}", table.format_table());
308 24 : }
309 :
310 : } // namespace palace
|