#pragma once #include #include #include #include #include #include #include #include #include #include #ifdef CMAKE_GITHUB_CI #include #else #include #endif namespace ss { template class parser; } /* namespace ss */ namespace { struct bool_error {}; template struct config { using BufferMode = T; using ErrorMode = U; constexpr static auto ThrowOnError = std::is_same_v; constexpr static auto StringError = std::is_same_v; }; #define ParserOptionCombinations \ config, config, \ config, config, \ config, \ config struct buffer { std::string data_; char* operator()(const std::string& data) { data_ = data; return data_.data(); } char* append(const std::string& data) { data_ += data; return data_.data(); } char* append_overwrite_last(const std::string& data, size_t size) { data_.resize(data_.size() - size); return append(data); } }; [[maybe_unused]] inline buffer buff; [[maybe_unused]] std::string time_now_rand() { std::srand(std::time(nullptr)); std::stringstream ss; auto t = std::time(nullptr); auto tm = *std::localtime(&t); ss << std::put_time(&tm, "%d%m%Y%H%M%S"); std::srand(std::time(nullptr)); return ss.str() + std::to_string(rand()); } struct unique_file_name { static inline int i = 0; std::string name; unique_file_name(const std::string& test) { do { name = "ssp_test_" + test + "_" + std::to_string(i++) + "_" + time_now_rand() + "_file.csv"; } while (std::filesystem::exists(name)); } ~unique_file_name() { try { std::filesystem::remove(name); } catch (const std::filesystem::filesystem_error& e) { std::cerr << e.what() << std::endl; } } }; #define CHECK_FLOATING_CONVERSION(input, type) \ { \ auto eps = std::numeric_limits::min(); \ std::string s = #input; \ auto t = ss::to_num(s.c_str(), s.c_str() + s.size()); \ REQUIRE(t.has_value()); \ CHECK_LT(std::abs(t.value() - type(input)), eps); \ } \ { \ /* check negative too */ \ auto eps = std::numeric_limits::min(); \ auto s = std::string("-") + #input; \ auto t = ss::to_num(s.c_str(), s.c_str() + s.size()); \ REQUIRE(t.has_value()); \ CHECK_LT(std::abs(t.value() - type(-input)), eps); \ } #define CHECK_FLOATING_CONVERSION_LONG_NUMBER(STRING_NUMBER, TYPE, CONVERTER) \ { \ auto begin = STRING_NUMBER.c_str(); \ auto end = begin + STRING_NUMBER.size(); \ \ auto number = ss::to_num(begin, end); \ REQUIRE(number.has_value()); \ \ auto expected_number = CONVERTER(STRING_NUMBER); \ CHECK_EQ(number.value(), expected_number); \ } #define CHECK_INVALID_CONVERSION(input, type) \ { \ std::string s = input; \ auto t = ss::to_num(s.c_str(), s.c_str() + s.size()); \ CHECK_FALSE(t.has_value()); \ } #define REQUIRE_VARIANT(var, el, type) \ { \ auto ptr = std::get_if(&var); \ REQUIRE(ptr); \ REQUIRE_EQ(el, *ptr); \ } #define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative(var)); #define REQUIRE_EXCEPTION(...) \ try { \ __VA_ARGS__; \ FAIL("Expected exception"); \ } catch (ss::exception & e) { \ CHECK_FALSE(std::string{e.what()}.empty()); \ } #define CHECK_EQ_ARRAY(first, second) \ { \ const auto& first_ = (first); \ const auto& second_ = (second); \ CHECK_EQ(first_.size(), second_.size()); \ for (size_t i_ = 0; i_ < std::min(first_.size(), second_.size()); \ ++i_) { \ CHECK_EQ(first_[i_], second_[i_]); \ } \ } template [[maybe_unused]] std::vector> vector_combinations( const std::vector& v, size_t n) { std::vector> ret; if (n <= 1) { for (const auto& i : v) { ret.push_back({i}); } return ret; } auto inner_combinations = vector_combinations(v, n - 1); for (const auto& i : v) { for (auto j : inner_combinations) { j.insert(j.begin(), i); ret.push_back(std::move(j)); } } return ret; } [[maybe_unused]] std::string merge_header( const std::vector& header, const std::string& delimiter = ss::default_delimiter) { std::string s; if (!header.empty()) { for (const auto& i : header) { s.append(i); s.append(delimiter); } for (size_t i = 0; i < delimiter.size(); ++i) { s.pop_back(); } } return s; }; [[maybe_unused]] std::string make_buffer(const std::string& file_name) { std::ifstream in{file_name, std::ios::binary}; std::string tmp; std::string out; auto copy_if_whitespaces = [&] { std::string matches = "\n\r\t "; while (std::any_of(matches.begin(), matches.end(), [&](auto c) { return in.peek() == c; })) { if (in.peek() == '\r') { out += "\r\n"; in.ignore(2); } else { out += std::string{static_cast(in.peek())}; in.ignore(1); } } }; out.reserve(sizeof(out) + 1); copy_if_whitespaces(); while (in >> tmp) { out += tmp; copy_if_whitespaces(); } return out; } template std::tuple, std::string> make_parser_impl( const std::string& file_name, std::string delim = ss::default_delimiter) { if (buffer_mode) { auto buffer = make_buffer(file_name); return {ss::parser{buffer.data(), buffer.size(), delim}, std::move(buffer)}; } else { return {ss::parser{file_name, delim}, std::string{}}; } } template [[maybe_unused]] std::enable_if_t< !std::is_same_v, std::tuple, std::string>> make_parser(const std::string& file_name, std::string delim = ss::default_delimiter) { return make_parser_impl(file_name, delim); } template [[maybe_unused]] std::enable_if_t, std::tuple, std::string>> make_parser(const std::string& file_name, std::string delim = ss::default_delimiter) { return make_parser_impl(file_name, delim); } } /* anonymous namespace */