LCOV - code coverage report
Current view: top level - utils - tablecsv.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 91.1 % 112 102
Test Date: 2025-10-23 22:45:05 Functions: 89.5 % 19 17
Legend: Lines: hit not hit

            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
        

Generated by: LCOV version 2.0-1