Compare commits

..

No commits in common. "master" and "v1.7.1" have entirely different histories.

41 changed files with 757 additions and 1470 deletions

View File

@ -1,7 +0,0 @@
EXE=ssp_fuzz
all:
clang++ ${CXXFLAGS} ssp_fuzz.cpp -fsanitize=fuzzer -std=c++17 -o ${EXE}
run:
./${EXE} -max_total_time=900

View File

@ -1,81 +0,0 @@
#include "../../ssp.hpp"
#include <filesystem>
#include <iostream>
#include <unistd.h>
template <typename... Ts>
void test_ssp_file_mode(const uint8_t* data, size_t size,
std::string delim = ss::default_delimiter) {
std::string file_name = std::filesystem::temp_directory_path().append(
"ss_fuzzer" + std::to_string(getpid()) + ".csv");
FILE* file = std::fopen(file_name.c_str(), "wb");
if (!file) {
std::exit(1);
}
std::fwrite(data, size, 1, file);
std::fclose(file);
ss::parser<Ts...> p{file_name.c_str(), delim};
while (!p.eof()) {
try {
const auto& [s0, s1] =
p.template get_next<std::string, std::string>();
if (s0.size() == 10000) {
std::cout << s0.size() << std::endl;
}
} catch (ss::exception& e) {
continue;
}
}
std::remove(file_name.c_str());
}
template <typename... Ts>
void test_ssp_buffer_mode(const uint8_t* data, size_t size,
std::string delim = ss::default_delimiter) {
ss::parser<Ts...> p{(const char*)data, size, delim};
while (!p.eof()) {
try {
const auto& [s0, s1] =
p.template get_next<std::string, std::string>();
if (s0.size() == 10000) {
std::cout << s0.size() << std::endl;
}
} catch (ss::exception& e) {
continue;
}
}
}
template <typename... Ts>
void test_ssp(const uint8_t* data, size_t size) {
test_ssp_file_mode<Ts...>(data, size);
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size);
test_ssp_file_mode<Ts...>(data, size, ":::");
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size, ":::");
test_ssp_buffer_mode<Ts...>(data, size);
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size);
test_ssp_buffer_mode<Ts...>(data, size, ":::");
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size, ":::");
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
using escape = ss::escape<'\\'>;
using quote = ss::quote<'"'>;
using trim = ss::trim<' ', '\t'>;
using multiline_r = ss::multiline_restricted<5>;
test_ssp<>(data, size);
test_ssp<escape>(data, size);
test_ssp<quote>(data, size);
test_ssp<trim>(data, size);
test_ssp<quote, escape>(data, size);
test_ssp<escape, quote, multiline_r, trim>(data, size);
test_ssp<escape, quote, multiline_r, trim, ss::ignore_empty>(data, size);
return 0;
}

View File

@ -1,43 +0,0 @@
name: fuzz-ci
on:
workflow_dispatch:
push:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
pull_request:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
jobs:
clang_tests:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
runs-on: ubuntu-latest
name: "Fuzzing"
container:
image: silkeh/clang:15
options: -v /usr/local:/host_usr_local
steps:
- uses: actions/checkout@v1
- name: Build
working-directory: .github/fuzz
run: make
- name: Run
working-directory: .github/fuzz
run: make run

4
.gitignore vendored
View File

@ -1,8 +1,6 @@
compile_commands.json compile_commands.json
.clang-format .clang-format
.clang-tidy .ccls-cache/*
.ccls-cache/
.cache/
experiment/ experiment/
build/ build/
hbuild/ hbuild/

View File

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14)
project( project(
ssp ssp
VERSION 1.8.0 VERSION 1.7.1
DESCRIPTION "csv parser" DESCRIPTION "csv parser"
HOMEPAGE_URL "https://github.com/red0124/ssp" HOMEPAGE_URL "https://github.com/red0124/ssp"
LANGUAGES CXX LANGUAGES CXX

View File

@ -9,7 +9,6 @@
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![coverage](https://coveralls.io/repos/github/red0124/ssp/badge.svg?branch=master)](https://coveralls.io/github/red0124/ssp?branch=master) [![coverage](https://coveralls.io/repos/github/red0124/ssp/badge.svg?branch=master)](https://coveralls.io/github/red0124/ssp?branch=master)
[![fuzz](https://github.com/red0124/ssp/workflows/fuzz-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
[![single-header](https://github.com/red0124/ssp/workflows/single-header-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/single-header.yml) [![single-header](https://github.com/red0124/ssp/workflows/single-header-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
[![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml) [![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
[![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml) [![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
@ -74,7 +73,7 @@ Bill (Heath) Gates 65 3.3
# Single header # Single header
The library can be used with a single header file **`ssp.hpp`**, but it suffers a significant performance loss when converting floating point values since the **`fast_float`** library is not present within the file. The library can be used with a single header file **`ssp.hpp`**, but it suffers a slight performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
# Installation # Installation
@ -92,7 +91,7 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
## Headers ## Headers
The parser can be told to use only certain columns by parsing the header. This can be done with the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error. The parser can be told to use only certain columns by parsing the header. This can be done by using the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
```shell ```shell
$ cat students_with_header.csv $ cat students_with_header.csv
Id,Age,Grade Id,Age,Grade
@ -116,7 +115,7 @@ James Bailey 2.5
Brian S. Wolfe 1.9 Brian S. Wolfe 1.9
Bill (Heath) Gates 3.3 Bill (Heath) Gates 3.3
``` ```
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** method after the parser has been constructed. If the header has been ignored calling any method related to header usage will result in a compilation error. The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** method after the parser has been constructed.
```cpp ```cpp
ss::parser<ss::ignore_header> p{file_name}; ss::parser<ss::ignore_header> p{file_name};
``` ```
@ -124,10 +123,10 @@ The fields with which the parser works with can be modified at any given time. T
```cpp ```cpp
// ... // ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"}; ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
p.use_fields("Grade"); p.use_fields("Id", "Grade");
const auto& grade = p.get_next<std::string>(); const auto& [id, grade] = p.get_next<std::string, float>();
std::cout << grade << std::endl; std::cout << id << ' ' << grade << std::endl;
if (p.field_exists("Id")) { if (p.field_exists("Id")) {
p.use_fields("Grade", "Id"); p.use_fields("Grade", "Id");
@ -139,32 +138,10 @@ The fields with which the parser works with can be modified at any given time. T
``` ```
```shell ```shell
$ ./a.out $ ./a.out
2.5 James Bailey 2.5
1.9 Brian S. Wolfe 40 Brian S. Wolfe
3.3 Bill (Heath) Gates 65 Bill (Heath) Gates
``` ```
The header is parsed with the same rules as other rows, the only difference is that **`multiline`** will be disabled when parsing the header. To get the data that is
present in the header as a **`std::vector<std::string>`**, the **`header`** method can be used, and to get the header line before it has been parsed, the **`raw_header`** method can be used:
```cpp
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
std::cout << p.raw_header() << std::endl;
for (const auto& field: p.header()) {
std::cout << "> " << field << std::endl;
}
// ...
```
```shell
$ ./a.out
Id,Age,Grade
> Id
> Age
> Grade
```
Methods related to headers can also fail, the error handling of these is done in the same way as for other methods.
## Conversions ## Conversions
An alternate loop to the example above would look like: An alternate loop to the example above would look like:
```cpp ```cpp

View File

@ -1,15 +1,11 @@
#pragma once #pragma once
#include <cerrno> #include <cerrno>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#if !__unix__
#include <array>
#include <cstdint>
#endif
namespace ss { namespace ss {
struct none {}; struct none {};
@ -21,18 +17,18 @@ constexpr inline auto default_delimiter = ",";
constexpr inline auto get_line_initial_buffer_size = 128; constexpr inline auto get_line_initial_buffer_size = 128;
template <bool StringError> template <bool StringError>
void assert_string_error_defined() { inline void assert_string_error_defined() {
static_assert(StringError, static_assert(StringError,
"'string_error' needs to be enabled to use 'error_msg'"); "'string_error' needs to be enabled to use 'error_msg'");
} }
template <bool ThrowOnError> template <bool ThrowOnError>
void assert_throw_on_error_not_defined() { inline void assert_throw_on_error_not_defined() {
static_assert(!ThrowOnError, "cannot handle errors manually if " static_assert(!ThrowOnError, "cannot handle errors manually if "
"'throw_on_error' is enabled"); "'throw_on_error' is enabled");
} }
[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) { inline void* strict_realloc(void* ptr, size_t size) {
ptr = std::realloc(ptr, size); ptr = std::realloc(ptr, size);
if (!ptr) { if (!ptr) {
throw std::bad_alloc{}; throw std::bad_alloc{};
@ -42,20 +38,18 @@ void assert_throw_on_error_not_defined() {
} }
#if __unix__ #if __unix__
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, inline ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
FILE* file) {
return getline(&lineptr, &n, file); return getline(&lineptr, &n, file);
} }
#else #else
using ssize_t = intptr_t; using ssize_t = intptr_t;
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n, ssize_t get_line_file(char*& lineptr, size_t& n, FILE* file) {
FILE* file) { char buff[get_line_initial_buffer_size];
std::array<char, get_line_initial_buffer_size> buff;
if (lineptr == nullptr || n < sizeof(buff)) { if (lineptr == nullptr || n < sizeof(buff)) {
const size_t new_n = sizeof(buff); size_t new_n = sizeof(buff);
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n)); lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n; n = new_n;
} }
@ -63,17 +57,17 @@ using ssize_t = intptr_t;
lineptr[0] = '\0'; lineptr[0] = '\0';
size_t line_used = 0; size_t line_used = 0;
while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) { while (std::fgets(buff, sizeof(buff), file) != nullptr) {
line_used = std::strlen(lineptr); line_used = std::strlen(lineptr);
size_t buff_used = std::strlen(buff.data()); size_t buff_used = std::strlen(buff);
if (n <= buff_used + line_used) { if (n <= buff_used + line_used) {
const size_t new_n = n * 2; size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n)); lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n; n = new_n;
} }
std::memcpy(lineptr + line_used, buff.data(), buff_used); std::memcpy(lineptr + line_used, buff, buff_used);
line_used += buff_used; line_used += buff_used;
lineptr[line_used] = '\0'; lineptr[line_used] = '\0';
@ -87,16 +81,15 @@ using ssize_t = intptr_t;
#endif #endif
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n, ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer, const char* const csv_data_buffer, size_t csv_data_size,
size_t csv_data_size,
size_t& curr_char) { size_t& curr_char) {
if (curr_char >= csv_data_size) { if (curr_char >= csv_data_size) {
return -1; return -1;
} }
if (lineptr == nullptr || n < get_line_initial_buffer_size) { if (lineptr == nullptr || n < get_line_initial_buffer_size) {
auto* new_lineptr = static_cast<char*>( auto new_lineptr = static_cast<char*>(
strict_realloc(lineptr, get_line_initial_buffer_size)); strict_realloc(lineptr, get_line_initial_buffer_size));
lineptr = new_lineptr; lineptr = new_lineptr;
n = get_line_initial_buffer_size; n = get_line_initial_buffer_size;
@ -105,7 +98,7 @@ using ssize_t = intptr_t;
size_t line_used = 0; size_t line_used = 0;
while (curr_char < csv_data_size) { while (curr_char < csv_data_size) {
if (line_used + 1 >= n) { if (line_used + 1 >= n) {
const size_t new_n = n * 2; size_t new_n = n * 2;
char* new_lineptr = char* new_lineptr =
static_cast<char*>(strict_realloc(lineptr, new_n)); static_cast<char*>(strict_realloc(lineptr, new_n));
@ -121,18 +114,22 @@ using ssize_t = intptr_t;
} }
} }
if (line_used != 0) {
lineptr[line_used] = '\0'; lineptr[line_used] = '\0';
return line_used; return line_used;
}
return -1;
} }
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line( std::tuple<ssize_t, bool> get_line(char*& buffer, size_t& buffer_size,
char*& buffer, size_t& buffer_size, FILE* file, FILE* file,
const char* const csv_data_buffer, size_t csv_data_size, const char* const csv_data_buffer,
size_t& curr_char) { size_t csv_data_size, size_t& curr_char) {
ssize_t ssize = 0; ssize_t ssize;
if (file) { if (file) {
ssize = get_line_file(buffer, buffer_size, file); ssize = get_line_file(buffer, buffer_size, file);
curr_char += ssize; curr_char = std::ftell(file);
} else { } else {
ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer, ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer,
csv_data_size, curr_char); csv_data_size, curr_char);
@ -148,4 +145,4 @@ using ssize_t = intptr_t;
return {ssize, false}; return {ssize, false};
} }
} /* namespace ss */ } /* ss */

View File

@ -110,19 +110,19 @@ public:
// parses line with given delimiter, returns a 'T' object created with // parses line with given delimiter, returns a 'T' object created with
// extracted values of type 'Ts' // extracted values of type 'Ts'
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T convert_object( T convert_object(line_ptr_type line,
line_ptr_type line, const std::string& delim = default_delimiter) { const std::string& delim = default_delimiter) {
return to_object<T>(convert<Ts...>(line, delim)); return to_object<T>(convert<Ts...>(line, delim));
} }
// parses line with given delimiter, returns tuple of objects with // parses line with given delimiter, returns tuple of objects with
// extracted values of type 'Ts' // extracted values of type 'Ts'
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert( no_void_validator_tup_t<Ts...> convert(
line_ptr_type line, const std::string& delim = default_delimiter) { line_ptr_type line, const std::string& delim = default_delimiter) {
split(line, delim); split(line, delim);
if (splitter_.valid()) { if (splitter_.valid()) {
return convert<Ts...>(splitter_.get_split_data()); return convert<Ts...>(splitter_.split_data_);
} else { } else {
handle_error_bad_split(); handle_error_bad_split();
return {}; return {};
@ -131,13 +131,13 @@ public:
// parses already split line, returns 'T' object with extracted values // parses already split line, returns 'T' object with extracted values
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T convert_object(const split_data& elems) { T convert_object(const split_data& elems) {
return to_object<T>(convert<Ts...>(elems)); return to_object<T>(convert<Ts...>(elems));
} }
// same as above, but uses cached split line // same as above, but uses cached split line
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T convert_object() { T convert_object() {
return to_object<T>(convert<Ts...>()); return to_object<T>(convert<Ts...>());
} }
@ -146,8 +146,7 @@ public:
// one argument is given which is a class which has a tied // one argument is given which is a class which has a tied
// method which returns a tuple, returns that type // method which returns a tuple, returns that type
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert( no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
const split_data& elems) {
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) { if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
return convert_impl(elems, static_cast<T*>(nullptr)); return convert_impl(elems, static_cast<T*>(nullptr));
} else if constexpr (tied_class_v<T, Ts...>) { } else if constexpr (tied_class_v<T, Ts...>) {
@ -163,11 +162,11 @@ public:
// same as above, but uses cached split line // same as above, but uses cached split line
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() { no_void_validator_tup_t<T, Ts...> convert() {
return convert<T, Ts...>(splitter_.get_split_data()); return convert<T, Ts...>(splitter_.split_data_);
} }
[[nodiscard]] bool valid() const { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {
return error_.empty(); return error_.empty();
} else if constexpr (throw_on_error) { } else if constexpr (throw_on_error) {
@ -177,12 +176,12 @@ public:
} }
} }
[[nodiscard]] const std::string& error_msg() const { const std::string& error_msg() const {
assert_string_error_defined<string_error>(); assert_string_error_defined<string_error>();
return error_; return error_;
} }
[[nodiscard]] bool unterminated_quote() const { bool unterminated_quote() const {
return splitter_.unterminated_quote(); return splitter_.unterminated_quote();
} }
@ -190,9 +189,9 @@ public:
// contain the beginnings and the ends of each column of the string // contain the beginnings and the ends of each column of the string
const split_data& split(line_ptr_type line, const split_data& split(line_ptr_type line,
const std::string& delim = default_delimiter) { const std::string& delim = default_delimiter) {
splitter_.clear_split_data(); splitter_.split_data_.clear();
if (line[0] == '\0') { if (line[0] == '\0') {
return splitter_.get_split_data(); return splitter_.split_data_;
} }
return splitter_.split(line, delim); return splitter_.split(line, delim);
@ -208,7 +207,7 @@ private:
return splitter_.resplit(new_line, new_size, delim); return splitter_.resplit(new_line, new_size, delim);
} }
[[nodiscard]] size_t size_shifted() { size_t size_shifted() {
return splitter_.size_shifted(); return splitter_.size_shifted();
} }
@ -224,11 +223,9 @@ private:
} }
} }
[[nodiscard]] std::string error_sufix(const string_range msg, std::string error_sufix(const string_range msg, size_t pos) const {
size_t pos) const {
constexpr static auto reserve_size = 32;
std::string error; std::string error;
error.reserve(reserve_size); error.reserve(32);
error.append("at column ") error.append("at column ")
.append(std::to_string(pos + 1)) .append(std::to_string(pos + 1))
.append(": \'") .append(": \'")
@ -353,8 +350,7 @@ private:
//////////////// ////////////////
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl( no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
const split_data& elems) {
clear_error(); clear_error();
if (!splitter_.valid()) { if (!splitter_.valid()) {
@ -385,7 +381,7 @@ private:
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<std::tuple<Ts...>> convert_impl( no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
const split_data& elems, const std::tuple<Ts...>*) { const split_data& elems, const std::tuple<Ts...>*) {
return convert_impl<Ts...>(elems); return convert_impl<Ts...>(elems);
} }
@ -394,11 +390,11 @@ private:
// column mapping // column mapping
//////////////// ////////////////
[[nodiscard]] bool columns_mapped() const { bool columns_mapped() const {
return !column_mappings_.empty(); return column_mappings_.size() != 0;
} }
[[nodiscard]] size_t column_position(size_t tuple_position) const { size_t column_position(size_t tuple_position) const {
if (!columns_mapped()) { if (!columns_mapped()) {
return tuple_position; return tuple_position;
} }
@ -408,7 +404,7 @@ private:
// assumes positions are valid and the vector is not empty // assumes positions are valid and the vector is not empty
void set_column_mapping(std::vector<size_t> positions, void set_column_mapping(std::vector<size_t> positions,
size_t number_of_columns) { size_t number_of_columns) {
column_mappings_ = std::move(positions); column_mappings_ = positions;
number_of_columns_ = number_of_columns; number_of_columns_ = number_of_columns;
} }
@ -429,7 +425,7 @@ private:
} }
if constexpr (std::is_same_v<T, std::string>) { if constexpr (std::is_same_v<T, std::string>) {
static_cast<void>(extract(msg.first, msg.second, dst)); extract(msg.first, msg.second, dst);
return; return;
} }
@ -475,8 +471,7 @@ private:
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple( no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
const split_data& elems) {
static_assert(!all_of_v<std::is_void, Ts...>, static_assert(!all_of_v<std::is_void, Ts...>,
"at least one parameter must be non void"); "at least one parameter must be non void");
no_void_validator_tup_t<Ts...> ret{}; no_void_validator_tup_t<Ts...> ret{};
@ -495,7 +490,7 @@ private:
friend class parser; friend class parser;
std::vector<size_t> column_mappings_; std::vector<size_t> column_mappings_;
size_t number_of_columns_{0}; size_t number_of_columns_;
}; };
} /* namespace ss */ } /* ss */

View File

@ -12,12 +12,12 @@ class exception : public std::exception {
std::string msg_; std::string msg_;
public: public:
exception(std::string msg) : msg_{std::move(msg)} { exception(const std::string& msg): msg_{msg} {
} }
[[nodiscard]] char const* what() const noexcept override { virtual char const* what() const noexcept {
return msg_.c_str(); return msg_.c_str();
} }
}; };
} /* namespace ss */ } /* ss */

View File

@ -2,8 +2,8 @@
#include "type_traits.hpp" #include "type_traits.hpp"
#include <charconv> #include <charconv>
#include <cstdint>
#include <cstring> #include <cstring>
#include <functional>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -13,7 +13,6 @@
#include <fast_float/fast_float.h> #include <fast_float/fast_float.h>
#else #else
#include <algorithm> #include <algorithm>
#include <array>
#include <cstdlib> #include <cstdlib>
#endif #endif
@ -26,8 +25,8 @@ namespace ss {
#ifndef SSP_DISABLE_FAST_FLOAT #ifndef SSP_DISABLE_FAST_FLOAT
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
to_num(const char* const begin, const char* const end) { const char* const begin, const char* const end) {
T ret; T ret;
auto [ptr, ec] = fast_float::from_chars(begin, end, ret); auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
@ -40,23 +39,22 @@ to_num(const char* const begin, const char* const end) {
#else #else
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
to_num(const char* const begin, const char* const end) { const char* const begin, const char* const end) {
static_assert(!std::is_same_v<T, long double>, static_assert(!std::is_same_v<T, long double>,
"Conversion to long double is disabled"); "Conversion to long double is disabled");
constexpr static auto buff_max = 64; constexpr static auto buff_max = 64;
std::array<char, buff_max> short_buff; char short_buff[buff_max];
size_t string_range = std::distance(begin, end);
const size_t string_range = std::distance(begin, end);
std::string long_buff; std::string long_buff;
char* buff = nullptr; char* buff;
if (string_range > buff_max) { if (string_range > buff_max) {
long_buff = std::string{begin, end}; long_buff = std::string{begin, end};
buff = long_buff.data(); buff = long_buff.data();
} else { } else {
buff = short_buff.data(); buff = short_buff;
buff[string_range] = '\0'; buff[string_range] = '\0';
std::copy_n(begin, string_range, buff); std::copy_n(begin, string_range, buff);
} }
@ -88,14 +86,12 @@ struct numeric_wrapper {
using type = T; using type = T;
numeric_wrapper() = default; numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) noexcept = default; numeric_wrapper(numeric_wrapper&&) = default;
numeric_wrapper(const numeric_wrapper&) = default; numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default; numeric_wrapper& operator=(numeric_wrapper&&) = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default; numeric_wrapper& operator=(const numeric_wrapper&) = default;
~numeric_wrapper() = default;
numeric_wrapper(T other) : value{other} { numeric_wrapper(T other) : value{other} {
} }
@ -114,7 +110,7 @@ using int8 = numeric_wrapper<int8_t>;
using uint8 = numeric_wrapper<uint8_t>; using uint8 = numeric_wrapper<uint8_t>;
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num( std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) { const char* const begin, const char* const end) {
T ret; T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret); auto [ptr, ec] = std::from_chars(begin, end, ret);
@ -126,9 +122,8 @@ template <typename T>
} }
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>, std::enable_if_t<is_instance_of_v<numeric_wrapper, T>, std::optional<T>> to_num(
std::optional<T>> const char* const begin, const char* const end) {
to_num(const char* const begin, const char* const end) {
T ret; T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value); auto [ptr, ec] = std::from_chars(begin, end, ret.value);
@ -147,11 +142,10 @@ template <typename T>
struct unsupported_type { struct unsupported_type {
constexpr static bool value = false; constexpr static bool value = false;
}; };
} /* namespace errors */ } /* namespace */
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> && std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
!std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> && !is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T> && !is_instance_of_v<std::variant, T> &&
!is_instance_of_v<numeric_wrapper, T>, !is_instance_of_v<numeric_wrapper, T>,
@ -163,8 +157,7 @@ extract(const char*, const char*, T&) {
} }
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> || std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> ||
std::is_floating_point_v<T> ||
is_instance_of_v<numeric_wrapper, T>, is_instance_of_v<numeric_wrapper, T>,
bool> bool>
extract(const char* begin, const char* end, T& value) { extract(const char* begin, const char* end, T& value) {
@ -177,8 +170,8 @@ extract(const char* begin, const char* end, T& value) {
} }
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool> std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
extract(const char* begin, const char* end, T& value) { const char* begin, const char* end, T& value) {
typename T::value_type raw_value; typename T::value_type raw_value;
if (extract(begin, end, raw_value)) { if (extract(begin, end, raw_value)) {
value = raw_value; value = raw_value;
@ -189,8 +182,7 @@ extract(const char* begin, const char* end, T& value) {
} }
template <typename T, size_t I> template <typename T, size_t I>
[[nodiscard]] bool extract_variant(const char* begin, const char* end, bool extract_variant(const char* begin, const char* end, T& value) {
T& value) {
using IthType = std::variant_alternative_t<I, std::decay_t<T>>; using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
IthType ithValue; IthType ithValue;
if (extract<IthType>(begin, end, ithValue)) { if (extract<IthType>(begin, end, ithValue)) {
@ -203,7 +195,7 @@ template <typename T, size_t I>
} }
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract( std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
const char* begin, const char* end, T& value) { const char* begin, const char* end, T& value) {
return extract_variant<T, 0>(begin, end, value); return extract_variant<T, 0>(begin, end, value);
} }
@ -213,8 +205,7 @@ template <typename T>
//////////////// ////////////////
template <> template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end, inline bool extract(const char* begin, const char* end, bool& value) {
bool& value) {
if (end == begin + 1) { if (end == begin + 1) {
if (*begin == '1') { if (*begin == '1') {
value = true; value = true;
@ -224,13 +215,10 @@ template <>
return false; return false;
} }
} else { } else {
constexpr static auto true_size = 4; size_t size = end - begin;
constexpr static auto false_size = 5; if (size == 4 && std::strncmp(begin, "true", size) == 0) {
const size_t size = end - begin;
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
value = true; value = true;
} else if (size == false_size && } else if (size == 5 && std::strncmp(begin, "false", size) == 0) {
std::strncmp(begin, "false", size) == 0) {
value = false; value = false;
} else { } else {
return false; return false;
@ -241,24 +229,22 @@ template <>
} }
template <> template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end, inline bool extract(const char* begin, const char* end, char& value) {
char& value) {
value = *begin; value = *begin;
return (end == begin + 1); return (end == begin + 1);
} }
template <> template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end, inline bool extract(const char* begin, const char* end, std::string& value) {
std::string& value) {
value = std::string{begin, end}; value = std::string{begin, end};
return true; return true;
} }
template <> template <>
[[nodiscard]] inline bool extract(const char* begin, const char* end, inline bool extract(const char* begin, const char* end,
std::string_view& value) { std::string_view& value) {
value = std::string_view{begin, static_cast<size_t>(end - begin)}; value = std::string_view{begin, static_cast<size_t>(end - begin)};
return true; return true;
} }
} /* namespace ss */ } /* ss */

View File

@ -2,6 +2,7 @@
#include <cstdlib> #include <cstdlib>
#include <functional> #include <functional>
#include <tuple>
namespace ss { namespace ss {
@ -76,4 +77,4 @@ struct member_wrapper<R T::*> {
template <typename T> \ template <typename T> \
constexpr bool has_m_##method##_t = has_m_##method<T>::value; constexpr bool has_m_##method##_t = has_m_##method<T>::value;
} /* namespace ss */ } /* trait */

View File

@ -5,6 +5,7 @@
#include "exception.hpp" #include "exception.hpp"
#include "extract.hpp" #include "extract.hpp"
#include "restrictions.hpp" #include "restrictions.hpp"
#include <cerrno>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <optional> #include <optional>
@ -31,12 +32,10 @@ class parser {
constexpr static bool ignore_empty = setup<Options...>::ignore_empty; constexpr static bool ignore_empty = setup<Options...>::ignore_empty;
using header_splitter = ss::splitter<
ss::filter_not_t<ss::is_instance_of_multiline, Options...>>;
public: public:
parser(std::string file_name, std::string delim = ss::default_delimiter) parser(const std::string& file_name,
: file_name_{std::move(file_name)}, reader_{file_name_, delim} { const std::string& delim = ss::default_delimiter)
: file_name_{file_name}, reader_{file_name_, delim} {
if (reader_.file_) { if (reader_.file_) {
read_line(); read_line();
if constexpr (ignore_header) { if constexpr (ignore_header) {
@ -52,7 +51,7 @@ public:
parser(const char* const csv_data_buffer, size_t csv_data_size, parser(const char* const csv_data_buffer, size_t csv_data_size,
const std::string& delim = ss::default_delimiter) const std::string& delim = ss::default_delimiter)
: file_name_{"CSV data buffer"}, : file_name_{"buffer line"},
reader_{csv_data_buffer, csv_data_size, delim} { reader_{csv_data_buffer, csv_data_size, delim} {
if (csv_data_buffer) { if (csv_data_buffer) {
read_line(); read_line();
@ -67,15 +66,14 @@ public:
} }
} }
parser(parser&& other) noexcept = default; parser(parser&& other) = default;
parser& operator=(parser&& other) noexcept = default; parser& operator=(parser&& other) = default;
~parser() = default;
parser() = delete; parser() = delete;
parser(const parser& other) = delete; parser(const parser& other) = delete;
parser& operator=(const parser& other) = delete; parser& operator=(const parser& other) = delete;
[[nodiscard]] bool valid() const { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {
return error_.empty(); return error_.empty();
} else if constexpr (throw_on_error) { } else if constexpr (throw_on_error) {
@ -85,12 +83,12 @@ public:
} }
} }
[[nodiscard]] const std::string& error_msg() const { const std::string& error_msg() const {
assert_string_error_defined<string_error>(); assert_string_error_defined<string_error>();
return error_; return error_;
} }
[[nodiscard]] bool eof() const { bool eof() const {
return eof_; return eof_;
} }
@ -99,21 +97,23 @@ public:
} }
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] T get_object() { T get_object() {
return to_object<T>(get_next<Ts...>()); return to_object<T>(get_next<Ts...>());
} }
[[nodiscard]] size_t line() const { size_t line() const {
return reader_.line_number_ > 0 ? reader_.line_number_ - 1 return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_; : reader_.line_number_;
} }
[[nodiscard]] size_t position() const { size_t position() const {
return reader_.chars_read_; return reader_.chars_read_;
} }
template <typename T, typename... Ts> template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> get_next() { no_void_validator_tup_t<T, Ts...> get_next() {
std::optional<std::string> error;
if (!eof_) { if (!eof_) {
if constexpr (throw_on_error) { if constexpr (throw_on_error) {
try { try {
@ -162,49 +162,20 @@ public:
return value; return value;
} }
[[nodiscard]] std::string raw_header() const { bool field_exists(const std::string& field) {
assert_ignore_header_not_defined();
return raw_header_;
}
[[nodiscard]] std::vector<std::string> header() {
assert_ignore_header_not_defined();
clear_error();
header_splitter splitter;
std::string raw_header_copy = raw_header_;
if (!strict_split(splitter, raw_header_copy)) {
return {};
}
std::vector<std::string> split_header;
for (const auto& [begin, end] : splitter.get_split_data()) {
split_header.emplace_back(begin, end);
}
return split_header;
}
[[nodiscard]] bool field_exists(const std::string& field) {
assert_ignore_header_not_defined();
clear_error();
if (header_.empty()) { if (header_.empty()) {
split_header_data(); split_header_data();
} }
if (!valid()) {
return false;
}
return header_index(field).has_value(); return header_index(field).has_value();
} }
template <typename... Ts> template <typename... Ts>
void use_fields(const Ts&... fields_args) { void use_fields(const Ts&... fields_args) {
assert_ignore_header_not_defined(); if constexpr (ignore_header) {
clear_error(); handle_error_header_ignored();
return;
}
if (header_.empty() && !eof()) { if (header_.empty() && !eof()) {
split_header_data(); split_header_data();
@ -217,7 +188,7 @@ public:
auto fields = std::vector<std::string>{fields_args...}; auto fields = std::vector<std::string>{fields_args...};
if (fields.empty()) { if (fields.empty()) {
handle_error_invalid_use_fields_argument(); handle_error_empty_mapping();
return; return;
} }
@ -265,17 +236,13 @@ public:
} }
iterator(const iterator& other) = default; iterator(const iterator& other) = default;
iterator(iterator&& other) noexcept = default; iterator(iterator&& other) = default;
~iterator() = default;
iterator& operator=(const iterator& other) = delete; value& operator*() {
iterator& operator=(iterator&& other) noexcept = delete;
[[nodiscard]] value& operator*() {
return value_; return value_;
} }
[[nodiscard]] value* operator->() { value* operator->() {
return &value_; return &value_;
} }
@ -294,21 +261,17 @@ public:
return *this; return *this;
} }
iterator operator++(int) { iterator& operator++(int) {
auto result = *this; return ++*this;
++*this;
return result;
} }
[[nodiscard]] friend bool operator==(const iterator& lhs, friend bool operator==(const iterator& lhs, const iterator& rhs) {
const iterator& rhs) {
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) || return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
(lhs.parser_ == rhs.parser_ && (lhs.parser_ == rhs.parser_ &&
&lhs.value_ == &rhs.value_); &lhs.value_ == &rhs.value_);
} }
[[nodiscard]] friend bool operator!=(const iterator& lhs, friend bool operator!=(const iterator& lhs, const iterator& rhs) {
const iterator& rhs) {
return !(lhs == rhs); return !(lhs == rhs);
} }
@ -320,11 +283,11 @@ public:
iterable(parser<Options...>* parser) : parser_{parser} { iterable(parser<Options...>* parser) : parser_{parser} {
} }
[[nodiscard]] iterator begin() { iterator begin() {
return ++iterator{parser_}; return ++iterator{parser_};
} }
[[nodiscard]] iterator end() { iterator end() {
return iterator{}; return iterator{};
} }
@ -333,12 +296,12 @@ public:
}; };
template <typename... Ts> template <typename... Ts>
[[nodiscard]] auto iterate() { auto iterate() {
return iterable<false, Ts...>{this}; return iterable<false, Ts...>{this};
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] auto iterate_object() { auto iterate_object() {
return iterable<true, Ts...>{this}; return iterable<true, Ts...>{this};
} }
@ -363,7 +326,7 @@ public:
Fun&& fun = none{}) { Fun&& fun = none{}) {
using Value = no_void_validator_tup_t<Us...>; using Value = no_void_validator_tup_t<Us...>;
std::optional<Value> value; std::optional<Value> value;
try_convert_and_invoke<Value, Us...>(value, std::forward<Fun>(fun)); try_convert_and_invoke<Value, Us...>(value, fun);
return composite_with(std::move(value)); return composite_with(std::move(value));
} }
@ -372,11 +335,11 @@ public:
template <typename U, typename... Us, typename Fun = none> template <typename U, typename... Us, typename Fun = none>
composite<Ts..., std::optional<U>> or_object(Fun&& fun = none{}) { composite<Ts..., std::optional<U>> or_object(Fun&& fun = none{}) {
std::optional<U> value; std::optional<U> value;
try_convert_and_invoke<U, Us...>(value, std::forward<Fun>(fun)); try_convert_and_invoke<U, Us...>(value, fun);
return composite_with(std::move(value)); return composite_with(std::move(value));
} }
[[nodiscard]] std::tuple<Ts...> values() { std::tuple<Ts...> values() {
return values_; return values_;
} }
@ -399,7 +362,7 @@ public:
private: private:
template <typename T> template <typename T>
[[nodiscard]] composite<Ts..., T> composite_with(T&& new_value) { composite<Ts..., T> composite_with(T&& new_value) {
auto merged_values = auto merged_values =
std::tuple_cat(std::move(values_), std::tuple_cat(std::move(values_),
std::tuple<T>{parser_.valid() std::tuple<T>{parser_.valid()
@ -429,7 +392,7 @@ public:
} }
template <typename U, typename... Us> template <typename U, typename... Us>
[[nodiscard]] no_void_validator_tup_t<U, Us...> try_same() { no_void_validator_tup_t<U, Us...> try_same() {
parser_.clear_error(); parser_.clear_error();
auto value = auto value =
parser_.reader_.converter_.template convert<U, Us...>(); parser_.reader_.converter_.template convert<U, Us...>();
@ -450,8 +413,8 @@ public:
// tries to convert a line and returns a composite which is // tries to convert a line and returns a composite which is
// able to try additional conversions in case of failure // able to try additional conversions in case of failure
template <typename... Ts, typename Fun = none> template <typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<no_void_validator_tup_t<Ts...>>> composite<std::optional<no_void_validator_tup_t<Ts...>>> try_next(
try_next(Fun&& fun = none{}) { Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>(); assert_throw_on_error_not_defined<throw_on_error>();
using Ret = no_void_validator_tup_t<Ts...>; using Ret = no_void_validator_tup_t<Ts...>;
return try_invoke_and_make_composite< return try_invoke_and_make_composite<
@ -461,7 +424,7 @@ public:
// identical to try_next but returns composite with object instead of a // identical to try_next but returns composite with object instead of a
// tuple // tuple
template <typename T, typename... Ts, typename Fun = none> template <typename T, typename... Ts, typename Fun = none>
[[nodiscard]] composite<std::optional<T>> try_object(Fun&& fun = none{}) { composite<std::optional<T>> try_object(Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>(); assert_throw_on_error_not_defined<throw_on_error>();
return try_invoke_and_make_composite< return try_invoke_and_make_composite<
std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun)); std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun));
@ -480,8 +443,7 @@ private:
using Ret = decltype(try_invoke_impl(arg, std::forward<Fun>(fun))); using Ret = decltype(try_invoke_impl(arg, std::forward<Fun>(fun)));
constexpr bool returns_void = std::is_same_v<Ret, void>; constexpr bool returns_void = std::is_same_v<Ret, void>;
if constexpr (!returns_void) { if constexpr (!returns_void) {
if (!try_invoke_impl(std::forward<Arg>(arg), if (!try_invoke_impl(arg, std::forward<Fun>(fun))) {
std::forward<Fun>(fun))) {
handle_error_failed_check(); handle_error_failed_check();
} }
} else { } else {
@ -512,55 +474,26 @@ private:
} }
template <typename T, typename Fun = none> template <typename T, typename Fun = none>
[[nodiscard]] composite<T> try_invoke_and_make_composite(T&& value, composite<T> try_invoke_and_make_composite(T&& value, Fun&& fun) {
Fun&& fun) {
if (valid()) { if (valid()) {
try_invoke(*value, std::forward<Fun>(fun)); try_invoke(*value, std::forward<Fun>(fun));
} }
return {valid() ? std::forward<T>(value) : std::nullopt, *this}; return {valid() ? std::move(value) : std::nullopt, *this};
} }
//////////////// ////////////////
// header // header
//////////////// ////////////////
void assert_ignore_header_not_defined() const {
static_assert(!ignore_header,
"cannot use this method when 'ignore_header' is defined");
}
[[nodiscard]] bool strict_split(header_splitter& splitter,
std::string& header) {
if constexpr (throw_on_error) {
try {
splitter.split(header.data(), reader_.delim_);
} catch (const ss::exception& e) {
decorate_rethrow_invalid_header_split(e);
}
} else {
splitter.split(header.data(), reader_.delim_);
if (!splitter.valid()) {
handle_error_invalid_header_split(splitter);
return false;
}
}
return true;
}
void split_header_data() { void split_header_data() {
header_splitter splitter; ss::splitter<Options...> splitter;
std::string raw_header_copy = raw_header_; std::string raw_header_copy = raw_header_;
splitter.split(raw_header_copy.data(), reader_.delim_);
if (!strict_split(splitter, raw_header_copy)) { for (const auto& [begin, end] : splitter.split_data_) {
return;
}
for (const auto& [begin, end] : splitter.get_split_data()) {
std::string field{begin, end}; std::string field{begin, end};
if (std::find(header_.begin(), header_.end(), field) != if (std::find(header_.begin(), header_.end(), field) !=
header_.end()) { header_.end()) {
handle_error_duplicate_header_field(field); handle_error_invalid_header(field);
header_.clear(); header_.clear();
return; return;
} }
@ -568,7 +501,7 @@ private:
} }
} }
[[nodiscard]] std::optional<size_t> header_index(const std::string& field) { std::optional<size_t> header_index(const std::string& field) {
auto it = std::find(header_.begin(), header_.end(), field); auto it = std::find(header_.begin(), header_.end(), field);
if (it == header_.end()) { if (it == header_.end()) {
@ -591,7 +524,7 @@ private:
} }
void handle_error_failed_check() { void handle_error_failed_check() {
constexpr static auto error_msg = ": failed check"; constexpr static auto error_msg = " failed check";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
@ -604,7 +537,7 @@ private:
} }
void handle_error_null_buffer() { void handle_error_null_buffer() {
constexpr static auto error_msg = ": received null data buffer"; constexpr static auto error_msg = " received null data buffer";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
@ -617,7 +550,7 @@ private:
} }
void handle_error_file_not_open() { void handle_error_file_not_open() {
constexpr static auto error_msg = ": could not be opened"; constexpr static auto error_msg = " could not be opened";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
@ -630,7 +563,7 @@ private:
} }
void handle_error_eof_reached() { void handle_error_eof_reached() {
constexpr static auto error_msg = ": read on end of file"; constexpr static auto error_msg = " read on end of file";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
@ -655,6 +588,20 @@ private:
} }
} }
void handle_error_header_ignored() {
constexpr static auto error_msg =
": the header row is ignored within the setup it cannot be used";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_).append(error_msg);
} else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg};
} else {
error_ = true;
}
}
void handle_error_invalid_field(const std::string& field) { void handle_error_invalid_field(const std::string& field) {
constexpr static auto error_msg = constexpr static auto error_msg =
": header does not contain given field: "; ": header does not contain given field: ";
@ -682,9 +629,8 @@ private:
} }
} }
void handle_error_invalid_use_fields_argument() { void handle_error_empty_mapping() {
constexpr static auto error_msg = constexpr static auto error_msg = "received empty mapping";
"received invalid argument for 'use_fields'";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
@ -696,53 +642,19 @@ private:
} }
} }
void handle_error_invalid_header_field() { void handle_error_invalid_header(const std::string& field) {
constexpr static auto error_msg = ": header contains empty field"; constexpr static auto error_msg = "header contains duplicates: ";
if constexpr (string_error) { if constexpr (string_error) {
error_.clear(); error_.clear();
error_.append(file_name_).append(error_msg); error_.append(error_msg).append(error_msg);
} else if constexpr (throw_on_error) { } else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg}; throw ss::exception{error_msg + field};
} else { } else {
error_ = true; error_ = true;
} }
} }
void handle_error_duplicate_header_field(const std::string& field) {
constexpr static auto error_msg = ": header contains duplicate: ";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_).append(error_msg).append(field);
} else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg + field};
} else {
error_ = true;
}
}
void handle_error_invalid_header_split(const header_splitter& splitter) {
constexpr static auto error_msg = ": failed header parsing: ";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_)
.append(error_msg)
.append(splitter.error_msg());
} else {
error_ = true;
}
}
void decorate_rethrow_invalid_header_split(const ss::exception& e) const {
static_assert(throw_on_error,
"throw_on_error needs to be enabled to use this method");
throw ss::exception{std::string{file_name_}
.append(": failed header parsing: ")
.append(e.what())};
}
void decorate_rethrow(const ss::exception& e) const { void decorate_rethrow(const ss::exception& e) const {
static_assert(throw_on_error, static_assert(throw_on_error,
"throw_on_error needs to be enabled to use this method"); "throw_on_error needs to be enabled to use this method");
@ -762,18 +674,17 @@ private:
} }
struct reader { struct reader {
reader(const std::string& file_name_, std::string delim) reader(const std::string& file_name_, const std::string& delim)
: delim_{std::move(delim)}, : delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} {
file_{std::fopen(file_name_.c_str(), "rb")} {
} }
reader(const char* const buffer, size_t csv_data_size, reader(const char* const buffer, size_t csv_data_size,
std::string delim) const std::string& delim)
: delim_{std::move(delim)}, csv_data_buffer_{buffer}, : delim_{delim}, csv_data_buffer_{buffer},
csv_data_size_{csv_data_size} { csv_data_size_{csv_data_size} {
} }
reader(reader&& other) noexcept reader(reader&& other)
: buffer_{other.buffer_}, : buffer_{other.buffer_},
next_line_buffer_{other.next_line_buffer_}, next_line_buffer_{other.next_line_buffer_},
helper_buffer_{other.helper_buffer_}, helper_buffer_{other.helper_buffer_},
@ -794,7 +705,7 @@ private:
other.file_ = nullptr; other.file_ = nullptr;
} }
reader& operator=(reader&& other) noexcept { reader& operator=(reader&& other) {
if (this != &other) { if (this != &other) {
buffer_ = other.buffer_; buffer_ = other.buffer_;
next_line_buffer_ = other.next_line_buffer_; next_line_buffer_ = other.next_line_buffer_;
@ -830,7 +741,7 @@ private:
std::free(helper_buffer_); std::free(helper_buffer_);
if (file_) { if (file_) {
std::ignore = std::fclose(file_); std::fclose(file_);
} }
} }
@ -839,7 +750,7 @@ private:
reader& operator=(const reader& other) = delete; reader& operator=(const reader& other) = delete;
// read next line each time in order to set eof_ // read next line each time in order to set eof_
[[nodiscard]] bool read_next() { bool read_next() {
next_line_converter_.clear_error(); next_line_converter_.clear_error();
size_t size = 0; size_t size = 0;
while (size == 0) { while (size == 0) {
@ -931,7 +842,7 @@ private:
std::swap(converter_, next_line_converter_); std::swap(converter_, next_line_converter_);
} }
[[nodiscard]] bool multiline_limit_reached(size_t& limit) { bool multiline_limit_reached(size_t& limit) {
if constexpr (multiline::size > 0) { if constexpr (multiline::size > 0) {
if (limit++ >= multiline::size) { if (limit++ >= multiline::size) {
next_line_converter_.handle_error_multiline_limit_reached(); next_line_converter_.handle_error_multiline_limit_reached();
@ -941,8 +852,8 @@ private:
return false; return false;
} }
[[nodiscard]] bool escaped_eol(size_t size) { bool escaped_eol(size_t size) {
const char* curr = nullptr; const char* curr;
for (curr = next_line_buffer_ + size - 1; for (curr = next_line_buffer_ + size - 1;
curr >= next_line_buffer_ && curr >= next_line_buffer_ &&
setup<Options...>::escape::match(*curr); setup<Options...>::escape::match(*curr);
@ -951,7 +862,7 @@ private:
return (next_line_buffer_ - curr + size) % 2 == 0; return (next_line_buffer_ - curr + size) % 2 == 0;
} }
[[nodiscard]] bool unterminated_quote() { bool unterminated_quote() {
return next_line_converter_.unterminated_quote(); return next_line_converter_.unterminated_quote();
} }
@ -966,7 +877,7 @@ private:
} }
} }
[[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) { size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') { if (buffer[ssize - 1] != '\n') {
crlf_ = false; crlf_ = false;
return ssize; return ssize;
@ -988,7 +899,7 @@ private:
size_t& buffer_size, const char* const second, size_t& buffer_size, const char* const second,
size_t second_size) { size_t second_size) {
buffer_size = first_size + second_size + 3; buffer_size = first_size + second_size + 3;
auto* new_first = static_cast<char*>( auto new_first = static_cast<char*>(
strict_realloc(static_cast<void*>(first), buffer_size)); strict_realloc(static_cast<void*>(first), buffer_size));
first = new_first; first = new_first;
@ -996,8 +907,7 @@ private:
first_size += second_size; first_size += second_size;
} }
[[nodiscard]] bool append_next_line_to_buffer(char*& buffer, bool append_next_line_to_buffer(char*& buffer, size_t& line_size,
size_t& line_size,
size_t buffer_size) { size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size); undo_remove_eol(buffer, line_size, buffer_size);
@ -1017,8 +927,8 @@ private:
return true; return true;
} }
[[nodiscard]] std::string get_buffer() { std::string get_buffer() {
return std::string{next_line_buffer_, next_line_size_}; return std::string{next_line_buffer_, next_line_buffer_size_};
} }
//////////////// ////////////////
@ -1061,4 +971,4 @@ private:
bool eof_{false}; bool eof_{false};
}; };
} /* namespace ss */ } /* ss */

View File

@ -10,7 +10,7 @@ template <typename T, auto... Values>
struct ax { struct ax {
private: private:
template <auto X, auto... Xs> template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const { bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) { if constexpr (sizeof...(Xs) != 0) {
return x != X && ss_valid_impl<Xs...>(x); return x != X && ss_valid_impl<Xs...>(x);
} }
@ -18,11 +18,11 @@ private:
} }
public: public:
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value); return ss_valid_impl<Values...>(value);
} }
[[nodiscard]] const char* error() const { const char* error() const {
return "value excluded"; return "value excluded";
} }
}; };
@ -35,7 +35,7 @@ template <typename T, auto... Values>
struct nx { struct nx {
private: private:
template <auto X, auto... Xs> template <auto X, auto... Xs>
[[nodiscard]] bool ss_valid_impl(const T& x) const { bool ss_valid_impl(const T& x) const {
if constexpr (sizeof...(Xs) != 0) { if constexpr (sizeof...(Xs) != 0) {
return x == X || ss_valid_impl<Xs...>(x); return x == X || ss_valid_impl<Xs...>(x);
} }
@ -43,11 +43,11 @@ private:
} }
public: public:
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return ss_valid_impl<Values...>(value); return ss_valid_impl<Values...>(value);
} }
[[nodiscard]] const char* error() const { const char* error() const {
return "value excluded"; return "value excluded";
} }
}; };
@ -61,28 +61,28 @@ public:
template <typename T, auto N> template <typename T, auto N>
struct gt { struct gt {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value > N; return value > N;
} }
}; };
template <typename T, auto N> template <typename T, auto N>
struct gte { struct gte {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value >= N; return value >= N;
} }
}; };
template <typename T, auto N> template <typename T, auto N>
struct lt { struct lt {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value < N; return value < N;
} }
}; };
template <typename T, auto N> template <typename T, auto N>
struct lte { struct lte {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value <= N; return value <= N;
} }
}; };
@ -93,7 +93,7 @@ struct lte {
template <typename T, auto Min, auto Max> template <typename T, auto Min, auto Max>
struct ir { struct ir {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value >= Min && value <= Max; return value >= Min && value <= Max;
} }
}; };
@ -104,7 +104,7 @@ struct ir {
template <typename T, auto Min, auto Max> template <typename T, auto Min, auto Max>
struct oor { struct oor {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value < Min || value > Max; return value < Min || value > Max;
} }
}; };
@ -115,13 +115,13 @@ struct oor {
template <typename T> template <typename T>
struct ne { struct ne {
[[nodiscard]] bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return !value.empty(); return !value.empty();
} }
[[nodiscard]] const char* error() const { const char* error() const {
return "empty field"; return "empty field";
} }
}; };
} /* namespace ss */ } /* ss */

View File

@ -293,7 +293,4 @@ private:
template <typename... Options> template <typename... Options>
struct setup<setup<Options...>> : setup<Options...> {}; struct setup<setup<Options...>> : setup<Options...> {};
template <typename... Options> } /* ss */
struct setup<std::tuple<Options...>> : setup<Options...> {};
} /* namespace ss */

View File

@ -2,9 +2,11 @@
#include "common.hpp" #include "common.hpp"
#include "exception.hpp" #include "exception.hpp"
#include "setup.hpp" #include "setup.hpp"
#include "type_traits.hpp"
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -28,7 +30,7 @@ private:
public: public:
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>; using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
[[nodiscard]] bool valid() const { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {
return error_.empty(); return error_.empty();
} else if constexpr (throw_on_error) { } else if constexpr (throw_on_error) {
@ -38,12 +40,12 @@ public:
} }
} }
[[nodiscard]] const std::string& error_msg() const { const std::string& error_msg() const {
assert_string_error_defined<string_error>(); assert_string_error_defined<string_error>();
return error_; return error_;
} }
[[nodiscard]] bool unterminated_quote() const { bool unterminated_quote() const {
return unterminated_quote_; return unterminated_quote_;
} }
@ -55,21 +57,13 @@ public:
return split_impl_select_delim(delimiter); return split_impl_select_delim(delimiter);
} }
[[nodiscard]] const split_data& get_split_data() const {
return split_data_;
}
void clear_split_data() {
split_data_.clear();
}
private: private:
//////////////// ////////////////
// resplit // resplit
//////////////// ////////////////
// number of characters the end of line is shifted backwards // number of characters the end of line is shifted backwards
[[nodiscard]] size_t size_shifted() const { size_t size_shifted() const {
return escaped_; return escaped_;
} }
@ -92,7 +86,7 @@ private:
} }
const auto [old_line, old_begin] = *std::prev(split_data_.end()); const auto [old_line, old_begin] = *std::prev(split_data_.end());
const size_t begin = old_begin - old_line - 1; size_t begin = old_begin - old_line - 1;
// safety measure // safety measure
if (new_size != -1 && static_cast<size_t>(new_size) < begin) { if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
@ -200,19 +194,19 @@ private:
// matching // matching
//////////////// ////////////////
[[nodiscard]] bool match(const char* const curr, char delim) { bool match(const char* const curr, char delim) {
return *curr == delim; return *curr == delim;
}; };
[[nodiscard]] bool match(const char* const curr, const std::string& delim) { bool match(const char* const curr, const std::string& delim) {
return std::strncmp(curr, delim.c_str(), delim.size()) == 0; return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
}; };
[[nodiscard]] size_t delimiter_size(char) { size_t delimiter_size(char) {
return 1; return 1;
} }
[[nodiscard]] size_t delimiter_size(const std::string& delim) { size_t delimiter_size(const std::string& delim) {
return delim.size(); return delim.size();
} }
@ -233,7 +227,7 @@ private:
} }
template <typename Delim> template <typename Delim>
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin, std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
const Delim& delim) { const Delim& delim) {
line_ptr_type end = begin; line_ptr_type end = begin;
@ -328,9 +322,8 @@ private:
trim_left_if_enabled(begin_); trim_left_if_enabled(begin_);
for (done_ = false; !done_;) { for (done_ = false; !done_; read(delim))
read(delim); ;
}
return split_data_; return split_data_;
} }
@ -469,6 +462,7 @@ private:
// members // members
//////////////// ////////////////
public:
error_type error_{}; error_type error_{};
bool unterminated_quote_{false}; bool unterminated_quote_{false};
bool done_{true}; bool done_{true};
@ -485,4 +479,4 @@ private:
friend class converter; friend class converter;
}; };
} /* namespace ss */ } /* ss */

View File

@ -34,11 +34,7 @@ struct left_of_impl;
template <size_t N, typename T, typename... Ts> template <size_t N, typename T, typename... Ts>
struct left_of_impl { struct left_of_impl {
private: static_assert(N < 128, "recursion limit reached");
constexpr static auto recursion_limit = 128;
public:
static_assert(N < recursion_limit, "recursion limit reached");
static_assert(N != 0, "cannot take the whole tuple"); static_assert(N != 0, "cannot take the whole tuple");
using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>; using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>;
}; };
@ -366,12 +362,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
//////////////// ////////////////
template <class T, std::size_t... Is, class U> template <class T, std::size_t... Is, class U>
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) { T to_object_impl(std::index_sequence<Is...>, U&& data) {
return {std::get<Is>(std::forward<U>(data))...}; return {std::get<Is>(std::forward<U>(data))...};
} }
template <class T, class U> template <class T, class U>
[[nodiscard]] T to_object(U&& data) { T to_object(U&& data) {
using NoRefU = std::decay_t<U>; using NoRefU = std::decay_t<U>;
if constexpr (is_instance_of_v<std::tuple, NoRefU>) { if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
return to_object_impl< return to_object_impl<
@ -382,4 +378,4 @@ template <class T, class U>
} }
} }
} /* namespace ss */ } /* trait */

View File

@ -6,7 +6,7 @@ project(
'cpp_std=c++17', 'cpp_std=c++17',
'buildtype=debugoptimized', 'buildtype=debugoptimized',
'wrap_mode=forcefallback'], 'wrap_mode=forcefallback'],
version: '1.8.0', version: '1.7.1',
meson_version:'>=0.54.0') meson_version:'>=0.54.0')
fast_float_dep = dependency('fast_float') fast_float_dep = dependency('fast_float')

View File

@ -14,21 +14,14 @@ headers = ['type_traits.hpp',
combined_file = [] combined_file = []
includes = [] includes = []
in_pp_block = False
for header in headers: for header in headers:
with open(headers_dir + header) as f: with open(headers_dir + header) as f:
for line in f.read().splitlines(): for line in f.read().splitlines():
if '#if ' in line:
in_pp_block = True
if '#endif' in line:
in_pp_block = False
if '#include "' in line or '#include <fast_float' in line: if '#include "' in line or '#include <fast_float' in line:
continue continue
if '#include <' in line and not in_pp_block: if '#include <' in line:
includes.append(line) includes.append(line)
continue continue
@ -37,7 +30,6 @@ for header in headers:
includes = sorted(set(includes)) includes = sorted(set(includes))
print('#pragma once')
print('\n'.join(includes)) print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT') print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file)) print('\n'.join(combined_file))

548
ssp.hpp

File diff suppressed because it is too large Load Diff

View File

@ -33,11 +33,10 @@ set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
enable_testing() enable_testing()
foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2 foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2
test_parser1_3 test_parser1_4 test_parser1_5 test_parser1_3 test_parser1_4 test_converter
test_converter test_extractions test_parser2_1 test_extractions test_parser2_1 test_parser2_2
test_parser2_2 test_parser2_3 test_parser2_4 test_parser2_3 test_parser2_4 test_parser2_5
test_parser2_5 test_parser2_6 test_parser2_6 test_extractions_without_fast_float)
test_extractions_without_fast_float)
add_executable("${name}" "${name}.cpp") add_executable("${name}" "${name}.cpp")
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float target_link_libraries("${name}" PRIVATE ssp::ssp fast_float
doctest::doctest) doctest::doctest)

View File

@ -6,7 +6,6 @@ tests = [
'parser1_2', 'parser1_2',
'parser1_3', 'parser1_3',
'parser1_4', 'parser1_4',
'parser1_5',
'splitter', 'splitter',
'converter', 'converter',
'extractions', 'extractions',

View File

@ -1,4 +1,5 @@
#include "test_helpers.hpp" #include "test_helpers.hpp"
#include <algorithm>
#include <ss/converter.hpp> #include <ss/converter.hpp>
TEST_CASE("converter test split") { TEST_CASE("converter test split") {
@ -10,8 +11,7 @@ TEST_CASE("converter test split") {
{" x x x x | x ", {" x x x x ", " x "}, "|"}, {" x x x x | x ", {" x x x x ", " x "}, "|"},
{"a::b::c::d", {"a", "b", "c", "d"}, "::"}, {"a::b::c::d", {"a", "b", "c", "d"}, "::"},
{"x\t-\ty", {"x", "y"}, "\t-\t"}, {"x\t-\ty", {"x", "y"}, "\t-\t"},
{"x", {"x"}, ","}} {"x", {"x"}, ","}} // clang-format on
// clang-format on
) { ) {
auto split = c.split(s, delim); auto split = c.split(s, delim);
CHECK_EQ(split.size(), expected.size()); CHECK_EQ(split.size(), expected.size());
@ -278,37 +278,36 @@ TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int,
TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) { TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) {
ss::converter c; ss::converter c;
std::ignore = c.convert<T>(""); c.convert<T>("");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("1", ""); c.convert<T>("1", "");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("10", ""); c.convert<T>("10", "");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>(""); c.convert<T, void>("");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>(",junk"); c.convert<T, void>(",junk");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, T>("junk,"); c.convert<void, T>("junk,");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T>("x"); c.convert<T>("x");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>("x"); c.convert<T, void>("x");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<T, void>("x,junk"); c.convert<T, void>("x,junk");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, T>("junk,x"); c.convert<void, T>("junk,x");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore =
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";"); c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
} }
@ -317,36 +316,34 @@ TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int,
ss::uint8) { ss::uint8) {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("")); REQUIRE_EXCEPTION(c.convert<T>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("1", "")); REQUIRE_EXCEPTION(c.convert<T>("1", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("10", "")); REQUIRE_EXCEPTION(c.convert<T>("10", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("")); REQUIRE_EXCEPTION(c.convert<T, void>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(",junk")); REQUIRE_EXCEPTION(c.convert<T, void>(",junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,")); REQUIRE_EXCEPTION(c.convert<void, T>("junk,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("x")); REQUIRE_EXCEPTION(c.convert<T>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x")); REQUIRE_EXCEPTION(c.convert<T, void>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x,junk")); REQUIRE_EXCEPTION(c.convert<T, void>("x,junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,x")); REQUIRE_EXCEPTION(c.convert<void, T>("junk,x"));
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(
std::ignore = c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";"));
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6",
";"));
} }
TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int, TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int,
ss::uint8) { ss::uint8) {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::ax<T, 0>>("0"); c.convert<ss::ax<T, 0>>("0");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1"); c.convert<ss::ax<T, 0, 1, 2>>("1");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"); c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"); c.convert<ss::ax<T, 1>, char>("1,c");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
T tup = c.convert<ss::ax<T, 1>>("3"); T tup = c.convert<ss::ax<T, 1>>("3");
@ -370,11 +367,10 @@ TEST_CASE_TEMPLATE(
ss::uint8) { ss::uint8) {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0>>("0")); REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0>>("0"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1")); REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1")); REQUIRE_EXCEPTION(c.convert<ss::ax<T, 1>, char>("1,c"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"));
try { try {
{ {
@ -397,13 +393,13 @@ TEST_CASE_TEMPLATE(
TEST_CASE("converter test ss:nx restriction (none except)") { TEST_CASE("converter test ss:nx restriction (none except)") {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::nx<int, 1>>("3"); c.convert<ss::nx<int, 1>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"); c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"); c.convert<ss::nx<int, 1>, char>("3,c");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
@ -431,10 +427,9 @@ TEST_CASE("converter test ss:nx restriction (none except)") {
TEST_CASE("converter test ss:nx restriction (none except) with exceptions") { TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>>("3")); REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>>("3"));
REQUIRE_EXCEPTION(std::ignore = REQUIRE_EXCEPTION(c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3")); REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>, char>("3,c"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"));
try { try {
{ {
@ -466,13 +461,13 @@ TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int,
ss::uint8) { ss::uint8) {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::ir<T, 0, 2>>("3"); c.convert<ss::ir<T, 0, 2>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3"); c.convert<char, ss::ir<T, 4, 69>>("c,3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c"); c.convert<ss::ir<T, 1, 2>, char>("3,c");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
@ -502,9 +497,9 @@ TEST_CASE_TEMPLATE(
ss::uint8) { ss::uint8) {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 0, 2>>("3")); REQUIRE_EXCEPTION(c.convert<ss::ir<T, 0, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3")); REQUIRE_EXCEPTION(c.convert<char, ss::ir<T, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c")); REQUIRE_EXCEPTION(c.convert<ss::ir<T, 1, 2>, char>("3,c"));
try { try {
{ {
@ -535,16 +530,16 @@ TEST_CASE_TEMPLATE(
TEST_CASE("converter test ss:oor restriction (out of range)") { TEST_CASE("converter test ss:oor restriction (out of range)") {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::oor<int, 1, 5>>("3"); c.convert<ss::oor<int, 1, 5>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::oor<int, 0, 2>>("2"); c.convert<ss::oor<int, 0, 2>>("2");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"); c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::oor<int, 1, 20>, char>("1,c"); c.convert<ss::oor<int, 1, 20>, char>("1,c");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
@ -569,12 +564,10 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") { TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 1, 5>>("3")); REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 5>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 0, 2>>("2")); REQUIRE_EXCEPTION(c.convert<ss::oor<int, 0, 2>>("2"));
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk")); REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 20>, char>("1,c"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<ss::oor<int, 1, 20>, char>("1,c"));
try { try {
{ {
@ -615,19 +608,19 @@ inline bool ss::extract(const char* begin, const char* end,
TEST_CASE("converter test ss:ne restriction (not empty)") { TEST_CASE("converter test ss:ne restriction (not empty)") {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::ne<std::string>>(""); c.convert<ss::ne<std::string>>("");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<int, ss::ne<std::string>>("3,"); c.convert<int, ss::ne<std::string>>("3,");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ne<std::string>, int>(",3"); c.convert<ss::ne<std::string>, int>(",3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<void, ss::ne<std::string>, int>("junk,,3"); c.convert<void, ss::ne<std::string>, int>("junk,,3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::ne<std::vector<int>>>(""); c.convert<ss::ne<std::vector<int>>>("");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
@ -650,12 +643,11 @@ TEST_CASE("converter test ss:ne restriction (not empty)") {
TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") { TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>>("")); REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<int, ss::ne<std::string>>("3,")); REQUIRE_EXCEPTION(c.convert<int, ss::ne<std::string>>("3,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>, int>(",3")); REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>, int>(",3"));
REQUIRE_EXCEPTION(std::ignore = REQUIRE_EXCEPTION(c.convert<void, ss::ne<std::string>, int>("junk,,3"));
c.convert<void, ss::ne<std::string>, int>("junk,,3")); REQUIRE_EXCEPTION(c.convert<ss::ne<std::vector<int>>>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::vector<int>>>(""));
try { try {
{ {
@ -683,22 +675,22 @@ TEST_CASE(
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") { "converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
ss::converter c; ss::converter c;
std::ignore = c.convert<ss::lt<int, 3>>("3"); c.convert<ss::lt<int, 3>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::lt<int, 2>>("3"); c.convert<ss::lt<int, 2>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gt<int, 3>>("3"); c.convert<ss::gt<int, 3>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gt<int, 4>>("3"); c.convert<ss::gt<int, 4>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::lte<int, 2>>("3"); c.convert<ss::lte<int, 2>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
std::ignore = c.convert<ss::gte<int, 4>>("3"); c.convert<ss::gte<int, 4>>("3");
REQUIRE_FALSE(c.valid()); REQUIRE_FALSE(c.valid());
{ {
@ -742,12 +734,12 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
"with exception") { "with exception") {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 3>>("3")); REQUIRE_EXCEPTION(c.convert<ss::lt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 2>>("3")); REQUIRE_EXCEPTION(c.convert<ss::lt<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 3>>("3")); REQUIRE_EXCEPTION(c.convert<ss::gt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 4>>("3")); REQUIRE_EXCEPTION(c.convert<ss::gt<int, 4>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lte<int, 2>>("3")); REQUIRE_EXCEPTION(c.convert<ss::lte<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gte<int, 4>>("3")); REQUIRE_EXCEPTION(c.convert<ss::gte<int, 4>>("3"));
try { try {
{ {
@ -792,14 +784,14 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
TEST_CASE("converter test error mode") { TEST_CASE("converter test error mode") {
ss::converter<ss::string_error> c; ss::converter<ss::string_error> c;
std::ignore = c.convert<int>("junk"); c.convert<int>("junk");
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK_FALSE(c.error_msg().empty()); CHECK_FALSE(c.error_msg().empty());
} }
TEST_CASE("converter test throw on error mode") { TEST_CASE("converter test throw on error mode") {
ss::converter<ss::throw_on_error> c; ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(std::ignore = c.convert<int>("junk")); REQUIRE_EXCEPTION(c.convert<int>("junk"));
} }
TEST_CASE("converter test converter with quotes spacing and escaping") { TEST_CASE("converter test converter with quotes spacing and escaping") {
@ -916,7 +908,7 @@ TEST_CASE("converter test invalid split conversions") {
{ {
// mismatched quote // mismatched quote
std::ignore = c.convert<std::string, std::string, double, char>( c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )")); buff(R"( "just , some , "12.3","a" )"));
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
@ -925,7 +917,7 @@ TEST_CASE("converter test invalid split conversions") {
{ {
// unterminated quote // unterminated quote
std::ignore = c.convert<std::string, std::string, double, std::string>( c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")); buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote()); CHECK(c.unterminated_quote());
@ -934,7 +926,7 @@ TEST_CASE("converter test invalid split conversions") {
{ {
// unterminated escape // unterminated escape
std::ignore = c.convert<std::string, std::string, double, std::string>( c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)")); buff(R"(just,some,2,strings\)"));
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
@ -943,7 +935,7 @@ TEST_CASE("converter test invalid split conversions") {
{ {
// unterminated escape while quoting // unterminated escape while quoting
std::ignore = c.convert<std::string, std::string, double, std::string>( c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)")); buff(R"(just,some,2,"strings\)"));
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
@ -952,7 +944,7 @@ TEST_CASE("converter test invalid split conversions") {
{ {
// unterminated escaped quote // unterminated escaped quote
std::ignore = c.convert<std::string, std::string, double, std::string>( c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")")); buff(R"(just,some,2,"strings\")"));
CHECK_FALSE(c.valid()); CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote()); CHECK(c.unterminated_quote());
@ -966,32 +958,27 @@ TEST_CASE("converter test invalid split conversions with exceptions") {
c; c;
// mismatched quote // mismatched quote
REQUIRE_EXCEPTION(std::ignore = REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, char>(
c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )"))); buff(R"( "just , some , "12.3","a" )")));
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
// unterminated quote // unterminated quote
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"))); buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")));
CHECK(c.unterminated_quote()); CHECK(c.unterminated_quote());
// unterminated escape // unterminated escape
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)"))); buff(R"(just,some,2,strings\)")));
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
// unterminated escape while quoting // unterminated escape while quoting
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)"))); buff(R"(just,some,2,"strings\)")));
CHECK_FALSE(c.unterminated_quote()); CHECK_FALSE(c.unterminated_quote());
// unterminated escaped quote // unterminated escaped quote
REQUIRE_EXCEPTION( REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")"))); buff(R"(just,some,2,"strings\")")));
CHECK(c.unterminated_quote()); CHECK(c.unterminated_quote());
} }

View File

@ -23,7 +23,7 @@ struct is_unsigned : public std::is_unsigned<T> {};
template <> template <>
struct is_unsigned<ss::uint8> : public std::true_type {}; struct is_unsigned<ss::uint8> : public std::true_type {};
} /* anonymous namespace */ } /* namespace */
static_assert(is_signed<ss::int8>::value); static_assert(is_signed<ss::int8>::value);
static_assert(is_unsigned<ss::uint8>::value); static_assert(is_unsigned<ss::uint8>::value);

View File

@ -1,4 +1,5 @@
#include "test_helpers.hpp" #include "test_helpers.hpp"
#include <algorithm>
#define SSP_DISABLE_FAST_FLOAT #define SSP_DISABLE_FAST_FLOAT
#include <ss/extract.hpp> #include <ss/extract.hpp>

View File

@ -19,7 +19,7 @@
namespace ss { namespace ss {
template <typename... Ts> template <typename... Ts>
class parser; class parser;
} /* namespace ss */ } /* ss */
namespace { namespace {
@ -145,17 +145,6 @@ struct unique_file_name {
CHECK_FALSE(std::string{e.what()}.empty()); \ 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 <typename T> template <typename T>
[[maybe_unused]] std::vector<std::vector<T>> vector_combinations( [[maybe_unused]] std::vector<std::vector<T>> vector_combinations(
const std::vector<T>& v, size_t n) { const std::vector<T>& v, size_t n) {
@ -177,22 +166,6 @@ template <typename T>
return ret; return ret;
} }
[[maybe_unused]] std::string merge_header(
const std::vector<std::string>& 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) { [[maybe_unused]] std::string make_buffer(const std::string& file_name) {
std::ifstream in{file_name, std::ios::binary}; std::ifstream in{file_name, std::ios::binary};
std::string tmp; std::string tmp;
@ -212,7 +185,6 @@ template <typename T>
} }
}; };
// Evade small string optimization
out.reserve(sizeof(out) + 1); out.reserve(sizeof(out) + 1);
copy_if_whitespaces(); copy_if_whitespaces();
@ -252,4 +224,4 @@ make_parser(const std::string& file_name,
return make_parser_impl<buffer_mode, Ts...>(file_name, delim); return make_parser_impl<buffer_mode, Ts...>(file_name, delim);
} }
} /* anonymous namespace */ } /* namespace */

View File

@ -12,8 +12,7 @@
#include <unordered_set> #include <unordered_set>
namespace { namespace {
#ifdef _WIN32 [[maybe_unused]] void replace_all(std::string& s, const std::string& from,
void replace_all(std::string& s, const std::string& from,
const std::string& to) { const std::string& to) {
if (from.empty()) return; if (from.empty()) return;
size_t start_pos = 0; size_t start_pos = 0;
@ -22,7 +21,6 @@ void replace_all(std::string& s, const std::string& from,
start_pos += to.length(); start_pos += to.length();
} }
} }
#endif
template <typename... Ts> template <typename... Ts>
void expect_error_on_command(ss::parser<Ts...>& p, void expect_error_on_command(ss::parser<Ts...>& p,
@ -30,7 +28,6 @@ void expect_error_on_command(ss::parser<Ts...>& p,
if (ss::setup<Ts...>::throw_on_error) { if (ss::setup<Ts...>::throw_on_error) {
try { try {
command(); command();
FAIL("expected exception");
} catch (const std::exception& e) { } catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty()); CHECK_FALSE(std::string{e.what()}.empty());
} }
@ -58,7 +55,7 @@ struct X {
double d; double d;
std::string s; std::string s;
[[nodiscard]] std::string to_string() const { std::string to_string() const {
if (s == empty) { if (s == empty) {
return ""; return "";
} }
@ -69,15 +66,14 @@ struct X {
.append(delim) .append(delim)
.append(s); .append(s);
} }
auto tied() const {
[[nodiscard]] auto tied() const {
return std::tie(i, d, s); return std::tie(i, d, s);
} }
}; };
template <typename T> template <typename T>
[[nodiscard]] std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==( std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(const T& lhs,
const T& lhs, const T& rhs) { const T& rhs) {
return lhs.tied() == rhs.tied(); return lhs.tied() == rhs.tied();
} }
@ -113,4 +109,4 @@ static void make_and_write(const std::string& file_name,
} }
} }
} /* anonymous namespace */ } /* namespace */

View File

@ -57,7 +57,7 @@ struct Y {
.append(s3); .append(s3);
} }
[[nodiscard]] auto tied() const { auto tied() const {
return std::tie(s1, s2, s3); return std::tie(s1, s2, s3);
} }
}; };
@ -115,8 +115,7 @@ TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) {
CHECK_EQ(p.line(), expected_line); CHECK_EQ(p.line(), expected_line);
while (!p.eof()) { while (!p.eof()) {
std::ignore = auto _ = p.template get_next<std::string, std::string, std::string>();
p.template get_next<std::string, std::string, std::string>();
++expected_line; ++expected_line;
CHECK_EQ(p.line(), expected_line); CHECK_EQ(p.line(), expected_line);
} }

View File

@ -51,14 +51,12 @@ TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
// to compile is enough // to compile is enough
return; return;
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", ""); auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
std::ignore =
p.template try_next<my_string, my_string, my_string>() p.template try_next<my_string, my_string, my_string>()
.template or_else<my_string, my_string, my_string, my_string>( .template or_else<my_string, my_string, my_string, my_string>(
[](auto&&) {}) [](auto&&) {})
.template or_else<my_string>([](auto&) {}) .template or_else<my_string>([](auto&) {})
.template or_else<xyz>([](auto&&) {}) .template or_else<xyz>([](auto&&) {})
.template or_object<xyz, my_string, my_string, my_string>( .template or_object<xyz, my_string, my_string, my_string>([](auto&&) {})
[](auto&&) {})
.template or_else<std::tuple<my_string, my_string, my_string>>( .template or_else<std::tuple<my_string, my_string, my_string>>(
[](auto&, auto&, auto&) {}); [](auto&, auto&, auto&) {});
} }
@ -75,7 +73,7 @@ TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ","); auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof()); REQUIRE_FALSE(p.eof());
std::ignore = p.template get_next<int>(); p.template get_next<int>();
CHECK_FALSE(p.valid()); CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty()); CHECK_FALSE(p.error_msg().empty());
} }
@ -94,7 +92,7 @@ TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type,
REQUIRE_FALSE(p.eof()); REQUIRE_FALSE(p.eof());
try { try {
std::ignore = p.template get_next<int>(); p.template get_next<int>();
FAIL("Expected exception..."); FAIL("Expected exception...");
} catch (const std::exception& e) { } catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty()); CHECK_FALSE(std::string{e.what()}.empty());
@ -150,7 +148,6 @@ TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ","); make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
while (!p.eof()) { while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] { auto command = [&p_no_multiline = p_no_multiline] {
std::ignore =
p_no_multiline.template get_next<int, double, std::string>(); p_no_multiline.template get_next<int, double, std::string>();
}; };
expect_error_on_command(p_no_multiline, command); expect_error_on_command(p_no_multiline, command);

View File

@ -83,7 +83,7 @@ void test_unterminated_line(const std::vector<std::string>& lines,
size_t line = 0; size_t line = 0;
while (!p.eof()) { while (!p.eof()) {
auto command = [&p = p] { auto command = [&p = p] {
std::ignore = p.template get_next<int, double, std::string>(); p.template get_next<int, double, std::string>();
}; };
if (line == bad_line) { if (line == bad_line) {

View File

@ -9,7 +9,6 @@ struct has_type<T, std::tuple<Us...>>
template <typename T, typename... Ts> template <typename T, typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data, static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& header,
const std::vector<std::string>& fields) { const std::vector<std::string>& fields) {
constexpr auto buffer_mode = T::BufferMode::value; constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode; using ErrorMode = typename T::ErrorMode;
@ -18,14 +17,9 @@ static void test_fields(const std::string file_name, const std::vector<X>& data,
auto [p, _] = make_parser<buffer_mode, ErrorMode>(file_name, ","); auto [p, _] = make_parser<buffer_mode, ErrorMode>(file_name, ",");
CHECK_FALSE(p.field_exists("Unknown")); CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(fields); p.use_fields(fields);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
std::vector<CaseType> i; std::vector<CaseType> i;
for (const auto& a : p.template iterate<CaseType>()) { for (const auto& a : p.template iterate<CaseType>()) {
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.push_back(a); i.push_back(a);
} }
@ -46,12 +40,12 @@ static void test_fields(const std::string file_name, const std::vector<X>& data,
TEST_CASE_TEMPLATE("test various cases with header", T, TEST_CASE_TEMPLATE("test various cases with header", T,
ParserOptionCombinations) { ParserOptionCombinations) {
unique_file_name f{"various_cases_with_header"}; unique_file_name f{"various_cases_with_header"};
using str = std::string;
constexpr static auto Int = "Int"; constexpr static auto Int = "Int";
constexpr static auto Dbl = "Double"; constexpr static auto Dbl = "Double";
constexpr static auto Str = "String"; constexpr static auto Str = "String";
const std::vector<std::string> header{Int, Dbl, Str}; using str = std::string;
std::vector<std::string> header{Int, Dbl, Str};
std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"}, std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"},
{7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}}; {7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}};
@ -65,8 +59,6 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
std::vector<X> i; std::vector<X> i;
for (const auto& a : p.iterate<int, double, std::string>()) { for (const auto& a : p.iterate<int, double, std::string>()) {
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.emplace_back(ss::to_object<X>(a)); i.emplace_back(ss::to_object<X>(a));
} }
@ -79,8 +71,6 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
p.ignore_next(); p.ignore_next();
for (const auto& a : p.iterate<int, double, std::string>()) { for (const auto& a : p.iterate<int, double, std::string>()) {
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.emplace_back(ss::to_object<X>(a)); i.emplace_back(ss::to_object<X>(a));
} }
@ -88,13 +78,39 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
} }
{ {
ss::parser<ss::string_error> p{f.name, ","}; ss::parser<ss::ignore_header> p{f.name, ","};
CHECK_EQ(header, p.header()); std::vector<X> i;
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
for (const auto& a : p.iterate<int, double, std::string>()) {
i.emplace_back(ss::to_object<X>(a));
}
CHECK_EQ(i, data);
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
p.use_fields(Int, Dbl, Str);
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(Int, "Unknown");
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
p.use_fields(Int, Int);
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::string_error> p{f.name, ","};
p.use_fields(Int, Dbl); p.use_fields(Int, Dbl);
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
{ {
auto [int_, double_] = p.get_next<int, double>(); auto [int_, double_] = p.get_next<int, double>();
@ -103,8 +119,6 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
} }
p.use_fields(Dbl, Int); p.use_fields(Dbl, Int);
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
{ {
auto [double_, int_] = p.get_next<double, int>(); auto [double_, int_] = p.get_next<double, int>();
@ -149,25 +163,25 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
template_params.append(type) template_params.append(type)
arg_params.append(header[type]) arg_params.append(header[type])
call = 'testFields<' + ', '.join(template_params) + \ call = 'testFields<' + ', '.join(template_params) + \
'>(o, d, header, {' + ', '.join(arg_params) + '});' '>(o, d, {' + ', '.join(arg_params) + '});'
print(call) print(call)
*/ */
test_fields<T, str>(o, d, header, {Str}); test_fields<T, str>(o, d, {Str});
test_fields<T, int>(o, d, header, {Int}); test_fields<T, int>(o, d, {Int});
test_fields<T, double>(o, d, header, {Dbl}); test_fields<T, double>(o, d, {Dbl});
test_fields<T, str, int>(o, d, header, {Str, Int}); test_fields<T, str, int>(o, d, {Str, Int});
test_fields<T, str, double>(o, d, header, {Str, Dbl}); test_fields<T, str, double>(o, d, {Str, Dbl});
test_fields<T, int, str>(o, d, header, {Int, Str}); test_fields<T, int, str>(o, d, {Int, Str});
test_fields<T, int, double>(o, d, header, {Int, Dbl}); test_fields<T, int, double>(o, d, {Int, Dbl});
test_fields<T, double, str>(o, d, header, {Dbl, Str}); test_fields<T, double, str>(o, d, {Dbl, Str});
test_fields<T, double, int>(o, d, header, {Dbl, Int}); test_fields<T, double, int>(o, d, {Dbl, Int});
test_fields<T, str, int, double>(o, d, header, {Str, Int, Dbl}); test_fields<T, str, int, double>(o, d, {Str, Int, Dbl});
test_fields<T, str, double, int>(o, d, header, {Str, Dbl, Int}); test_fields<T, str, double, int>(o, d, {Str, Dbl, Int});
test_fields<T, int, str, double>(o, d, header, {Int, Str, Dbl}); test_fields<T, int, str, double>(o, d, {Int, Str, Dbl});
test_fields<T, int, double, str>(o, d, header, {Int, Dbl, Str}); test_fields<T, int, double, str>(o, d, {Int, Dbl, Str});
test_fields<T, double, str, int>(o, d, header, {Dbl, Str, Int}); test_fields<T, double, str, int>(o, d, {Dbl, Str, Int});
test_fields<T, double, int, str>(o, d, header, {Dbl, Int, Str}); test_fields<T, double, int, str>(o, d, {Dbl, Int, Str});
} }
template <typename T> template <typename T>
@ -176,18 +190,6 @@ void test_invalid_fields(const std::vector<std::string>& lines,
constexpr auto buffer_mode = T::BufferMode::value; constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode; using ErrorMode = typename T::ErrorMode;
auto check_header = [&lines](auto& p) {
if (lines.empty()) {
CHECK_EQ(p.header().size(), 1);
CHECK_EQ(p.header().at(0), "");
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
} else {
CHECK_EQ(lines[0], merge_header(p.header()));
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
}
CHECK(p.valid());
};
unique_file_name f{"invalid_fields"}; unique_file_name f{"invalid_fields"};
{ {
std::ofstream out{f.name}; std::ofstream out{f.name};
@ -201,7 +203,6 @@ void test_invalid_fields(const std::vector<std::string>& lines,
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ","); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields(); }; auto command = [&p = p] { p.use_fields(); };
expect_error_on_command(p, command); expect_error_on_command(p, command);
check_header(p);
} }
{ {
@ -209,7 +210,6 @@ void test_invalid_fields(const std::vector<std::string>& lines,
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ","); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields("Unknown"); }; auto command = [&p = p] { p.use_fields("Unknown"); };
expect_error_on_command(p, command); expect_error_on_command(p, command);
check_header(p);
} }
{ {
@ -221,7 +221,6 @@ void test_invalid_fields(const std::vector<std::string>& lines,
if (!fields.empty()) { if (!fields.empty()) {
expect_error_on_command(p, command); expect_error_on_command(p, command);
} }
check_header(p);
} }
{ {
@ -229,21 +228,17 @@ void test_invalid_fields(const std::vector<std::string>& lines,
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ","); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] { auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0)); p.use_fields(fields.at(0));
std::ignore = p.template get_next<std::string, std::string>(); p.template get_next<std::string, std::string>();
}; };
check_header(p);
if (!fields.empty()) { if (!fields.empty()) {
expect_error_on_command(p, command); expect_error_on_command(p, command);
} }
check_header(p);
} }
{ {
// Invalid header // Invalid header
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ","); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] { p.use_fields(fields); }; auto command = [&p = p, &fields = fields] { p.use_fields(fields); };
check_header(p);
if (!fields.empty()) { if (!fields.empty()) {
// Pass if there are no duplicates, fail otherwise // Pass if there are no duplicates, fail otherwise
@ -260,11 +255,10 @@ void test_invalid_fields(const std::vector<std::string>& lines,
} }
} }
} }
check_header(p);
} }
} }
TEST_CASE_TEMPLATE("test invalid header fields usage", T, TEST_CASE_TEMPLATE("test invalid fheader fields usage", T,
ParserOptionCombinations) { ParserOptionCombinations) {
test_invalid_fields<T>({}, {}); test_invalid_fields<T>({}, {});
@ -295,7 +289,7 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
constexpr auto buffer_mode = T::BufferMode::value; constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode; using ErrorMode = typename T::ErrorMode;
unique_file_name f{"invalid_rows_with_header"}; unique_file_name f{"invalid rows with header"};
{ {
std::ofstream out{f.name}; std::ofstream out{f.name};
out << "Int,String,Double" << std::endl; out << "Int,String,Double" << std::endl;
@ -307,12 +301,8 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
out << "six,line6,10.11" << std::endl; out << "six,line6,10.11" << std::endl;
} }
std::vector<std::string> header = {"Int", "String", "Double"};
{ {
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("Int", "String", "Double"); p.use_fields("Int", "String", "Double");
using data = std::tuple<int, std::string, double>; using data = std::tuple<int, std::string, double>;
@ -335,14 +325,10 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
{3, "line3", 67.8}, {3, "line3", 67.8},
{5, "line5", 9.10}}; {5, "line5", 9.10}};
CHECK_EQ(i, expected); CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
} }
{ {
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("Double", "Int"); p.use_fields("Double", "Int");
using data = std::tuple<double, int>; using data = std::tuple<double, int>;
@ -363,14 +349,10 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
std::vector<data> expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}}; std::vector<data> expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}};
CHECK_EQ(i, expected); CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
} }
{ {
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name); auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("String", "Double"); p.use_fields("String", "Double");
using data = std::tuple<std::string, double>; using data = std::tuple<std::string, double>;
@ -394,7 +376,96 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
{"line5", 9.10}, {"line5", 9.10},
{"line6", 10.11}}; {"line6", 10.11}};
CHECK_EQ(i, expected); CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
} }
} }
template <typename T>
void test_ignore_empty(const std::vector<X>& data) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"ignore_empty"};
make_and_write(f.name, data);
std::vector<X> expected;
for (const auto& d : data) {
if (d.s != X::empty) {
expected.push_back(d);
}
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
std::vector<X> i;
for (const auto& a : p.template iterate<X>()) {
i.push_back(a);
}
CHECK_EQ(i, expected);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
size_t n = 0;
while (!p.eof()) {
try {
++n;
const auto& a = p.template get_next<X>();
if (data.at(n - 1).s == X::empty) {
CHECK_FALSE(p.valid());
continue;
}
i.push_back(a);
} catch (...) {
CHECK_EQ(data.at(n - 1).s, X::empty);
}
}
CHECK_EQ(i, expected);
}
}
TEST_CASE_TEMPLATE("test various cases with empty lines", T,
ParserOptionCombinations) {
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>({{1, 2, X::empty},
{3, 4, X::empty},
{9, 10, X::empty},
{11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
test_ignore_empty<T>({{11, 12, X::empty}});
test_ignore_empty<T>({});
}

View File

@ -1,301 +0,0 @@
#include "test_parser1.hpp"
TEST_CASE_TEMPLATE("test empty fields header", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"empty_fields_header"};
// Empty header
{
std::ofstream out{f.name};
out << "" << std::endl;
out << "1" << std::endl;
}
{
std::vector<std::string> expected_header = {""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("", p.raw_header());
CHECK(p.valid());
}
// All empty header fields
{
std::ofstream out{f.name};
out << ",," << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"", "", ""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ(",,", p.raw_header());
CHECK(p.valid());
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
// One empty field
const std::vector<std::string> valid_fields = {"Int0", "Int1", ""};
using svec = std::vector<std::string>;
const std::vector<std::vector<std::string>> valid_field_combinations =
{svec{"Int0"},
svec{"Int1"},
svec{""},
svec{"", "Int0"},
svec{"Int0", "Int1"},
svec{"Int1", ""},
svec{"Int0", "", "Int1"},
svec{"", "Int1", "Int0"}};
// Last header field empty
{
std::ofstream out{f.name};
out << "Int0,Int1," << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"Int0", "Int1", ""};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("Int0,Int1,", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
// First header field empty
{
std::ofstream out{f.name};
out << ",Int0,Int1" << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"", "Int0", "Int1"};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ(",Int0,Int1", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
// Middle header field empty
{
std::ofstream out{f.name};
out << "Int0,,Int1" << std::endl;
out << "1,2,3" << std::endl;
}
{
std::vector<std::string> expected_header = {"Int0", "", "Int1"};
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(expected_header, p.header());
CHECK_EQ("Int0,,Int1", p.raw_header());
CHECK(p.valid());
for (const auto& field : valid_fields) {
CHECK(p.field_exists(field));
CHECK(p.valid());
}
for (const auto& fields : valid_field_combinations) {
p.use_fields(fields);
CHECK(p.valid());
}
}
}
template <typename T, typename... Ts>
void test_unterminated_quote_header() {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"unterminated_quote_header"};
{
std::ofstream out{f.name};
out << "\"Int" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
auto command0 = [&p = p] { std::ignore = p.header(); };
expect_error_on_command(p, command0);
CHECK_EQ(p.raw_header(), "\"Int");
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
}
TEST_CASE_TEMPLATE("test unterminated quote header", T,
ParserOptionCombinations) {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
test_unterminated_quote_header<T, quote>();
test_unterminated_quote_header<T, quote, ss::multiline>();
test_unterminated_quote_header<T, quote, escape>();
test_unterminated_quote_header<T, quote, escape, ss::multiline>();
}
template <typename T, typename... Ts>
void test_unterminated_escape_header() {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"unterminated_escape_header"};
// Unterminated escape in header
{
std::ofstream out{f.name};
out << "Int\\" << std::endl;
out << "1" << std::endl;
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode, Ts...>(f.name);
auto command0 = [&p = p] { std::ignore = p.header(); };
expect_error_on_command(p, command0);
CHECK_EQ(p.raw_header(), "Int\\");
auto command1 = [&p = p] { std::ignore = p.field_exists("Int"); };
expect_error_on_command(p, command1);
auto command2 = [&p = p] { p.use_fields("Int"); };
expect_error_on_command(p, command2);
}
}
TEST_CASE_TEMPLATE("test unterminated escape header", T,
ParserOptionCombinations) {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
test_unterminated_escape_header<T, escape>();
test_unterminated_escape_header<T, escape, ss::multiline>();
test_unterminated_escape_header<T, escape, quote>();
test_unterminated_escape_header<T, escape, quote, ss::multiline>();
}
template <typename T>
void test_ignore_empty(const std::vector<X>& data) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"ignore_empty"};
make_and_write(f.name, data);
std::vector<X> expected;
for (const auto& d : data) {
if (d.s != X::empty) {
expected.push_back(d);
}
}
{
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::ignore_empty>(f.name, ",");
std::vector<X> i;
for (const auto& a : p.template iterate<X>()) {
i.push_back(a);
}
CHECK_EQ(i, expected);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
size_t n = 0;
while (!p.eof()) {
try {
++n;
const auto& a = p.template get_next<X>();
if (data.at(n - 1).s == X::empty) {
CHECK_FALSE(p.valid());
continue;
}
i.push_back(a);
} catch (...) {
CHECK_EQ(data.at(n - 1).s, X::empty);
}
}
CHECK_EQ(i, expected);
}
}
TEST_CASE_TEMPLATE("test various cases with empty lines", T,
ParserOptionCombinations) {
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {5, 6, X::empty}, {9, 10, "v"}, {11, 12, "w"}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, "v"}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, "y"}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>({{1, 2, X::empty},
{3, 4, X::empty},
{9, 10, X::empty},
{11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, "x"}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, X::empty}});
test_ignore_empty<T>(
{{1, 2, X::empty}, {3, 4, X::empty}, {9, 10, X::empty}, {11, 12, "w"}});
test_ignore_empty<T>({{11, 12, X::empty}});
test_ignore_empty<T>({});
}

View File

@ -1,7 +1,13 @@
#include "test_helpers.hpp" #include "test_helpers.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream> #include <fstream>
#include <iomanip>
#include <iostream> #include <iostream>
#include <regex>
#include <ss/parser.hpp> #include <ss/parser.hpp>
#include <sstream>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#ifndef SEGMENT_NAME #ifndef SEGMENT_NAME
@ -85,7 +91,7 @@ struct column {
}; };
template <typename... Ts> template <typename... Ts>
[[nodiscard]] column make_column(const std::string& input_header, column make_column(const std::string& input_header,
const std::vector<field>& input_fields) { const std::vector<field>& input_fields) {
using setup = ss::setup<Ts...>; using setup = ss::setup<Ts...>;
std::vector<field> filtered_fields; std::vector<field> filtered_fields;
@ -127,8 +133,8 @@ template <typename... Ts>
} }
template <typename... Ts> template <typename... Ts>
[[nodiscard]] std::vector<std::string> generate_csv_data( std::vector<std::string> generate_csv_data(const std::vector<field>& data,
const std::vector<field>& data, const std::string& delim) { const std::string& delim) {
(void)delim; (void)delim;
using setup = ss::setup<Ts...>; using setup = ss::setup<Ts...>;
constexpr static auto escape = '\\'; constexpr static auto escape = '\\';
@ -327,10 +333,8 @@ void test_data_combinations(const std::vector<column>& input_data,
field_header.push_back(field{el.header}); field_header.push_back(field{el.header});
} }
std::string header_line;
if (include_header) { if (include_header) {
auto header_data = generate_csv_data<Ts...>(field_header, delim); auto header_data = generate_csv_data<Ts...>(field_header, delim);
header_line = merge_header(header_data, delim);
if (input_data.size() == 0 && rand() % 10 == 0) { if (input_data.size() == 0 && rand() % 10 == 0) {
write_to_file(header_data, delim, f.name, false); write_to_file(header_data, delim, f.name, false);
} else { } else {
@ -399,9 +403,7 @@ void test_data_combinations(const std::vector<column>& input_data,
fields.push_back(header[index]); fields.push_back(header[index]);
} }
if constexpr (!setup::ignore_header) {
p.use_fields(fields); p.use_fields(fields);
}
if (!p.valid()) { if (!p.valid()) {
if constexpr (setup::string_error) { if constexpr (setup::string_error) {
@ -423,19 +425,8 @@ void test_data_combinations(const std::vector<column>& input_data,
} }
}; };
auto check_header = [&p = p, &header = header, include_header,
header_line] {
if (include_header) {
if constexpr (!setup::ignore_header) {
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(header_line, p.raw_header());
}
}
};
int num_columns = layout.size(); int num_columns = layout.size();
for (size_t i = 0; i < n + 1; ++i) { for (size_t i = 0; i < n + 1; ++i) {
check_header();
try { try {
switch (num_columns) { switch (num_columns) {
case 1: { case 1: {
@ -625,7 +616,7 @@ void test_option_combinations3() {
test_option_combinations2<Ts..., trim>(); test_option_combinations2<Ts..., trim>();
} }
} /* anonymous namespace */ } /* namespace */
// Tests split into multiple compilation units // Tests split into multiple compilation units
#if 0 #if 0

View File

@ -9,3 +9,4 @@ TEST_CASE("parser test various cases version 2 segment 1") {
test_option_combinations3<escape>(); test_option_combinations3<escape>();
#endif #endif
} }

View File

@ -10,3 +10,4 @@ TEST_CASE("parser test various cases version 2 segment 2") {
test_option_combinations3<escape, quote>(); test_option_combinations3<escape, quote>();
#endif #endif
} }

View File

@ -11,3 +11,4 @@ TEST_CASE("parser test various cases version 2 segment 3") {
test_option_combinations3<quote, multiline>(); test_option_combinations3<quote, multiline>();
#endif #endif
} }

View File

@ -12,3 +12,4 @@ TEST_CASE("parser test various cases version 2 segment 4") {
test_option_combinations3<escape, quote, multiline_r>(); test_option_combinations3<escape, quote, multiline_r>();
#endif #endif
} }

View File

@ -13,3 +13,4 @@ TEST_CASE("parser test various cases version 2 segment 5") {
test_option_combinations<escape, quote, multiline, trimr>(); test_option_combinations<escape, quote, multiline, trimr>();
#endif #endif
} }

View File

@ -8,3 +8,4 @@ TEST_CASE("parser test various cases version 2 segment 6") {
test_option_combinations3<escape, quote, multiline>(); test_option_combinations3<escape, quote, multiline>();
} }

View File

@ -3,14 +3,12 @@
set -x set -x
set -e set -e
TMP_HDR=test_single_header.hpp python3 script/single_header_generator.py > ssp.cpp
TMP_SRC=test_single_header.cpp
TMP_BIN=test_single_header
python3 script/single_header_generator.py > ${TMP_HDR} echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
cat ${TMP_HDR} test/test_single_header_main.txt > ${TMP_SRC} >> ssp.cpp
g++ -std=c++17 ${TMP_SRC} -o ${TMP_BIN} -Wall -Wextra g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
./${TMP_BIN} ./ssp.bin
rm ${TMP_HDR} ${TMP_SRC} ${TMP_BIN} rm ssp.cpp ssp.bin

View File

@ -1,12 +0,0 @@
int main() {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using trim = ss::trim<' '>;
std::string data = "1,string,2.34,c";
ss::parser<quote, escape, trim, ss::multiline> p{data.c_str(), data.size()};
auto tup = p.get_next<int, std::string, float, std::optional<char>>();
return 0;
}

View File

@ -145,7 +145,7 @@ make_combinations(const std::vector<std::string>& input,
return {std::move(lines), std::move(expectations)}; return {std::move(lines), std::move(expectations)};
} }
} /* anonymous namespace */ } /* namespace */
/* ********************************** */ /* ********************************** */
/* ********************************** */ /* ********************************** */
@ -548,7 +548,7 @@ public:
return splitter.size_shifted(); return splitter.size_shifted();
} }
}; };
} /* namespace ss */ } /* ss */
TEST_CASE("splitter test resplit unterminated quote") { TEST_CASE("splitter test resplit unterminated quote") {