mirror of
https://github.com/red0124/ssp.git
synced 2025-04-20 10:37:57 +02:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
53c5b779d4 | |||
107a122718 | |||
![]() |
55d0a4e598 | ||
![]() |
1b9a01f787 | ||
![]() |
f5b750dd93 | ||
7f53b585f9 | |||
67ef6651c1 | |||
fa4ec324de | |||
![]() |
f229de61d6 | ||
![]() |
df2beab6c3 | ||
![]() |
27bd60b5ce | ||
![]() |
c5b50f2b47 | ||
![]() |
d8dcce7f2a | ||
![]() |
126329608c |
7
.github/fuzz/makefile
vendored
Normal file
7
.github/fuzz/makefile
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
EXE=ssp_fuzz
|
||||
|
||||
all:
|
||||
clang++ ${CXXFLAGS} ssp_fuzz.cpp -fsanitize=fuzzer -std=c++17 -o ${EXE}
|
||||
|
||||
run:
|
||||
./${EXE} -max_total_time=900
|
81
.github/fuzz/ssp_fuzz.cpp
vendored
Normal file
81
.github/fuzz/ssp_fuzz.cpp
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
#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;
|
||||
}
|
43
.github/workflows/fuzz.yml
vendored
Normal file
43
.github/workflows/fuzz.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
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
|
55
.github/workflows/macos-apple-clang.yml
vendored
Normal file
55
.github/workflows/macos-apple-clang.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: macos-apple-clang-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]')
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['13.4.1', '14.1']
|
||||
type: [Release, Debug]
|
||||
|
||||
runs-on: macos-12
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer
|
||||
|
||||
name: "Xcode ${{matrix.xcode}}: ${{matrix.type}}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: friendlyanon/fetch-core-count@v1
|
||||
id: cores
|
||||
|
||||
- name: Install dependencies
|
||||
run: script/ci_install_deps.sh
|
||||
|
||||
- name: Configure
|
||||
run: cmake -S test -B build -DCMAKE_BUILD_TYPE=${{matrix.type}}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j ${{steps.cores.outputs.count}}
|
||||
|
||||
- name: Run
|
||||
working-directory: build
|
||||
run: ctest --output-on-failure
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,6 +1,8 @@
|
||||
compile_commands.json
|
||||
.clang-format
|
||||
.ccls-cache/*
|
||||
.clang-tidy
|
||||
.ccls-cache/
|
||||
.cache/
|
||||
experiment/
|
||||
build/
|
||||
hbuild/
|
||||
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(
|
||||
ssp
|
||||
VERSION 1.7.0
|
||||
VERSION 1.8.0
|
||||
DESCRIPTION "csv parser"
|
||||
HOMEPAGE_URL "https://github.com/red0124/ssp"
|
||||
LANGUAGES CXX
|
||||
|
48
README.md
48
README.md
@ -9,6 +9,7 @@
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://coveralls.io/github/red0124/ssp?branch=master)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
|
||||
@ -16,6 +17,7 @@
|
||||
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/win-msvc.yml)
|
||||
[](https://github.com/red0124/ssp/actions/workflows/macos-apple-clang.yml)
|
||||
|
||||
A header only CSV parser which is fast and versatile with modern C++ API. Requires compiler with C++17 support. [Can also be used to efficiently convert strings to specific types.](#the-converter)
|
||||
|
||||
@ -72,7 +74,7 @@ Bill (Heath) Gates 65 3.3
|
||||
|
||||
# Single header
|
||||
|
||||
The library can be used with a single header file **`ssp.hpp`**, but it sufferes a slight 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 significant performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
|
||||
|
||||
# Installation
|
||||
|
||||
@ -90,7 +92,7 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
||||
|
||||
## Headers
|
||||
|
||||
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.
|
||||
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.
|
||||
```shell
|
||||
$ cat students_with_header.csv
|
||||
Id,Age,Grade
|
||||
@ -114,18 +116,18 @@ James Bailey 2.5
|
||||
Brian S. Wolfe 1.9
|
||||
Bill (Heath) Gates 3.3
|
||||
```
|
||||
The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) option or by calling the **`ignore_next`** metod after the parser has been constructed.
|
||||
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.
|
||||
```cpp
|
||||
ss::parser<ss::ignore_header> p{file_name};
|
||||
```
|
||||
The fields with which the parser works with can be modified at any given time. The praser can also check if a field is present within the header by using the **`field_exists`** method.
|
||||
The fields with which the parser works with can be modified at any given time. The parser can also check if a field is present within the header by using the **`field_exists`** method.
|
||||
```cpp
|
||||
// ...
|
||||
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||
p.use_fields("Id", "Grade");
|
||||
p.use_fields("Grade");
|
||||
|
||||
const auto& [id, grade] = p.get_next<std::string, float>();
|
||||
std::cout << id << ' ' << grade << std::endl;
|
||||
const auto& grade = p.get_next<std::string>();
|
||||
std::cout << grade << std::endl;
|
||||
|
||||
if (p.field_exists("Id")) {
|
||||
p.use_fields("Grade", "Id");
|
||||
@ -137,10 +139,32 @@ The fields with which the parser works with can be modified at any given time. T
|
||||
```
|
||||
```shell
|
||||
$ ./a.out
|
||||
James Bailey 2.5
|
||||
40 Brian S. Wolfe
|
||||
65 Bill (Heath) Gates
|
||||
2.5
|
||||
1.9 Brian S. Wolfe
|
||||
3.3 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
|
||||
An alternate loop to the example above would look like:
|
||||
```cpp
|
||||
@ -248,7 +272,7 @@ By default, **`,`** is used as the delimiter, a custom delimiter can be specifie
|
||||
```cpp
|
||||
ss::parser p{file_name, "--"};
|
||||
```
|
||||
*Note, the delimiter can consist of multiple characters but the parser is slightliy faster when using single character delimiters.*
|
||||
*Note, the delimiter can consist of multiple characters but the parser is slightly faster when using single character delimiters.*
|
||||
|
||||
### Empty lines
|
||||
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
|
||||
@ -397,7 +421,7 @@ if (std::holds_alternative<float>(grade)) {
|
||||
// grade set as char
|
||||
}
|
||||
```
|
||||
Passing **`char`** and types that are aliases to it such as **`uint8_t`** and **`int8_t`** make the parser interpret the input data as a single character in a similar way to how **`std::cin`** does it. To read numeric values into something like **`uint8_t`** the **`ss::uint8`** and **`ss::int8`** types can be used. These are wrappers arround the corresponding char aliases and can be implicitly converted to and from them. When these types are given to the parser he will try to read the given data and store it in the underlying element, but this time as a numeric value instead of a single character.
|
||||
Passing **`char`** and types that are aliases to it such as **`uint8_t`** and **`int8_t`** make the parser interpret the input data as a single character in a similar way to how **`std::cin`** does it. To read numeric values into something like **`uint8_t`** the **`ss::uint8`** and **`ss::int8`** types can be used. These are wrappers around the corresponding char aliases and can be implicitly converted to and from them. When these types are given to the parser he will try to read the given data and store it in the underlying element, but this time as a numeric value instead of a single character.
|
||||
```cpp
|
||||
// returns std::tuple<std::string, ss::uint8, float>
|
||||
auto [id, age, grade] = p.get_next<std::string, ss::uint8, float>();
|
||||
|
@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#if !__unix__
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#endif
|
||||
|
||||
namespace ss {
|
||||
|
||||
struct none {};
|
||||
@ -17,18 +21,18 @@ constexpr inline auto default_delimiter = ",";
|
||||
constexpr inline auto get_line_initial_buffer_size = 128;
|
||||
|
||||
template <bool StringError>
|
||||
inline void assert_string_error_defined() {
|
||||
void assert_string_error_defined() {
|
||||
static_assert(StringError,
|
||||
"'string_error' needs to be enabled to use 'error_msg'");
|
||||
}
|
||||
|
||||
template <bool ThrowOnError>
|
||||
inline void assert_throw_on_error_not_defined() {
|
||||
void assert_throw_on_error_not_defined() {
|
||||
static_assert(!ThrowOnError, "cannot handle errors manually if "
|
||||
"'throw_on_error' is enabled");
|
||||
}
|
||||
|
||||
inline void* strict_realloc(void* ptr, size_t size) {
|
||||
[[nodiscard]] inline void* strict_realloc(void* ptr, size_t size) {
|
||||
ptr = std::realloc(ptr, size);
|
||||
if (!ptr) {
|
||||
throw std::bad_alloc{};
|
||||
@ -38,45 +42,42 @@ inline void* strict_realloc(void* ptr, size_t size) {
|
||||
}
|
||||
|
||||
#if __unix__
|
||||
inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
|
||||
return getline(lineptr, n, stream);
|
||||
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
|
||||
FILE* file) {
|
||||
return getline(&lineptr, &n, file);
|
||||
}
|
||||
#else
|
||||
|
||||
using ssize_t = intptr_t;
|
||||
|
||||
ssize_t get_line_file(char** lineptr, size_t* n, FILE* fp) {
|
||||
if (lineptr == nullptr || n == nullptr || fp == nullptr) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
|
||||
FILE* file) {
|
||||
std::array<char, get_line_initial_buffer_size> buff;
|
||||
|
||||
if (lineptr == nullptr || n < sizeof(buff)) {
|
||||
const size_t new_n = sizeof(buff);
|
||||
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||
n = new_n;
|
||||
}
|
||||
|
||||
char buff[get_line_initial_buffer_size];
|
||||
|
||||
if (*lineptr == nullptr || *n < sizeof(buff)) {
|
||||
size_t new_n = sizeof(buff);
|
||||
*lineptr = static_cast<char*>(strict_realloc(*lineptr, new_n));
|
||||
*n = new_n;
|
||||
}
|
||||
|
||||
(*lineptr)[0] = '\0';
|
||||
lineptr[0] = '\0';
|
||||
|
||||
size_t line_used = 0;
|
||||
while (std::fgets(buff, sizeof(buff), fp) != nullptr) {
|
||||
line_used = std::strlen(*lineptr);
|
||||
size_t buff_used = std::strlen(buff);
|
||||
while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) {
|
||||
line_used = std::strlen(lineptr);
|
||||
size_t buff_used = std::strlen(buff.data());
|
||||
|
||||
if (*n <= buff_used + line_used) {
|
||||
size_t new_n = *n * 2;
|
||||
*lineptr = static_cast<char*>(strict_realloc(*lineptr, new_n));
|
||||
*n = new_n;
|
||||
if (n <= buff_used + line_used) {
|
||||
const size_t new_n = n * 2;
|
||||
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||
n = new_n;
|
||||
}
|
||||
|
||||
std::memcpy(*lineptr + line_used, buff, buff_used);
|
||||
std::memcpy(lineptr + line_used, buff.data(), buff_used);
|
||||
line_used += buff_used;
|
||||
(*lineptr)[line_used] = '\0';
|
||||
lineptr[line_used] = '\0';
|
||||
|
||||
if ((*lineptr)[line_used - 1] == '\n') {
|
||||
if (lineptr[line_used - 1] == '\n') {
|
||||
return line_used;
|
||||
}
|
||||
}
|
||||
@ -86,4 +87,65 @@ ssize_t get_line_file(char** lineptr, size_t* n, FILE* fp) {
|
||||
|
||||
#endif
|
||||
|
||||
} /* ss */
|
||||
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
|
||||
const char* const csv_data_buffer,
|
||||
size_t csv_data_size,
|
||||
size_t& curr_char) {
|
||||
if (curr_char >= csv_data_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lineptr == nullptr || n < get_line_initial_buffer_size) {
|
||||
auto* new_lineptr = static_cast<char*>(
|
||||
strict_realloc(lineptr, get_line_initial_buffer_size));
|
||||
lineptr = new_lineptr;
|
||||
n = get_line_initial_buffer_size;
|
||||
}
|
||||
|
||||
size_t line_used = 0;
|
||||
while (curr_char < csv_data_size) {
|
||||
if (line_used + 1 >= n) {
|
||||
const size_t new_n = n * 2;
|
||||
|
||||
char* new_lineptr =
|
||||
static_cast<char*>(strict_realloc(lineptr, new_n));
|
||||
n = new_n;
|
||||
lineptr = new_lineptr;
|
||||
}
|
||||
|
||||
auto c = csv_data_buffer[curr_char++];
|
||||
lineptr[line_used++] = c;
|
||||
if (c == '\n') {
|
||||
lineptr[line_used] = '\0';
|
||||
return line_used;
|
||||
}
|
||||
}
|
||||
|
||||
lineptr[line_used] = '\0';
|
||||
return line_used;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line(
|
||||
char*& buffer, size_t& buffer_size, FILE* file,
|
||||
const char* const csv_data_buffer, size_t csv_data_size,
|
||||
size_t& curr_char) {
|
||||
ssize_t ssize = 0;
|
||||
if (file) {
|
||||
ssize = get_line_file(buffer, buffer_size, file);
|
||||
curr_char += ssize;
|
||||
} else {
|
||||
ssize = get_line_buffer(buffer, buffer_size, csv_data_buffer,
|
||||
csv_data_size, curr_char);
|
||||
}
|
||||
|
||||
if (ssize == -1) {
|
||||
if (errno == ENOMEM) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
return {ssize, true};
|
||||
}
|
||||
|
||||
return {ssize, false};
|
||||
}
|
||||
|
||||
} /* namespace ss */
|
||||
|
@ -110,19 +110,19 @@ public:
|
||||
// parses line with given delimiter, returns a 'T' object created with
|
||||
// extracted values of type 'Ts'
|
||||
template <typename T, typename... Ts>
|
||||
T convert_object(line_ptr_type line,
|
||||
const std::string& delim = default_delimiter) {
|
||||
[[nodiscard]] T convert_object(
|
||||
line_ptr_type line, const std::string& delim = default_delimiter) {
|
||||
return to_object<T>(convert<Ts...>(line, delim));
|
||||
}
|
||||
|
||||
// parses line with given delimiter, returns tuple of objects with
|
||||
// extracted values of type 'Ts'
|
||||
template <typename... Ts>
|
||||
no_void_validator_tup_t<Ts...> convert(
|
||||
[[nodiscard]] no_void_validator_tup_t<Ts...> convert(
|
||||
line_ptr_type line, const std::string& delim = default_delimiter) {
|
||||
split(line, delim);
|
||||
if (splitter_.valid()) {
|
||||
return convert<Ts...>(splitter_.split_data_);
|
||||
return convert<Ts...>(splitter_.get_split_data());
|
||||
} else {
|
||||
handle_error_bad_split();
|
||||
return {};
|
||||
@ -131,13 +131,13 @@ public:
|
||||
|
||||
// parses already split line, returns 'T' object with extracted values
|
||||
template <typename T, typename... Ts>
|
||||
T convert_object(const split_data& elems) {
|
||||
[[nodiscard]] T convert_object(const split_data& elems) {
|
||||
return to_object<T>(convert<Ts...>(elems));
|
||||
}
|
||||
|
||||
// same as above, but uses cached split line
|
||||
template <typename T, typename... Ts>
|
||||
T convert_object() {
|
||||
[[nodiscard]] T convert_object() {
|
||||
return to_object<T>(convert<Ts...>());
|
||||
}
|
||||
|
||||
@ -146,11 +146,12 @@ public:
|
||||
// one argument is given which is a class which has a tied
|
||||
// method which returns a tuple, returns that type
|
||||
template <typename T, typename... Ts>
|
||||
no_void_validator_tup_t<T, Ts...> convert(const split_data& elems) {
|
||||
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert(
|
||||
const split_data& elems) {
|
||||
if constexpr (sizeof...(Ts) == 0 && is_instance_of_v<std::tuple, T>) {
|
||||
return convert_impl(elems, static_cast<T*>(nullptr));
|
||||
} else if constexpr (tied_class_v<T, Ts...>) {
|
||||
using arg_ref_tuple = std::result_of_t<decltype (&T::tied)(T)>;
|
||||
using arg_ref_tuple = std::invoke_result_t<decltype(&T::tied), T>;
|
||||
using arg_tuple = apply_trait_t<std::decay, arg_ref_tuple>;
|
||||
|
||||
return to_object<T>(
|
||||
@ -162,11 +163,11 @@ public:
|
||||
|
||||
// same as above, but uses cached split line
|
||||
template <typename T, typename... Ts>
|
||||
no_void_validator_tup_t<T, Ts...> convert() {
|
||||
return convert<T, Ts...>(splitter_.split_data_);
|
||||
[[nodiscard]] no_void_validator_tup_t<T, Ts...> convert() {
|
||||
return convert<T, Ts...>(splitter_.get_split_data());
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
[[nodiscard]] bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
@ -176,12 +177,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& error_msg() const {
|
||||
[[nodiscard]] const std::string& error_msg() const {
|
||||
assert_string_error_defined<string_error>();
|
||||
return error_;
|
||||
}
|
||||
|
||||
bool unterminated_quote() const {
|
||||
[[nodiscard]] bool unterminated_quote() const {
|
||||
return splitter_.unterminated_quote();
|
||||
}
|
||||
|
||||
@ -189,9 +190,9 @@ public:
|
||||
// contain the beginnings and the ends of each column of the string
|
||||
const split_data& split(line_ptr_type line,
|
||||
const std::string& delim = default_delimiter) {
|
||||
splitter_.split_data_.clear();
|
||||
splitter_.clear_split_data();
|
||||
if (line[0] == '\0') {
|
||||
return splitter_.split_data_;
|
||||
return splitter_.get_split_data();
|
||||
}
|
||||
|
||||
return splitter_.split(line, delim);
|
||||
@ -207,7 +208,7 @@ private:
|
||||
return splitter_.resplit(new_line, new_size, delim);
|
||||
}
|
||||
|
||||
size_t size_shifted() {
|
||||
[[nodiscard]] size_t size_shifted() {
|
||||
return splitter_.size_shifted();
|
||||
}
|
||||
|
||||
@ -223,9 +224,11 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
std::string error_sufix(const string_range msg, size_t pos) const {
|
||||
[[nodiscard]] std::string error_sufix(const string_range msg,
|
||||
size_t pos) const {
|
||||
constexpr static auto reserve_size = 32;
|
||||
std::string error;
|
||||
error.reserve(32);
|
||||
error.reserve(reserve_size);
|
||||
error.append("at column ")
|
||||
.append(std::to_string(pos + 1))
|
||||
.append(": \'")
|
||||
@ -269,6 +272,7 @@ private:
|
||||
|
||||
void handle_error_multiline_limit_reached() {
|
||||
constexpr static auto error_msg = "multiline limit reached";
|
||||
splitter_.unterminated_quote_ = false;
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
@ -349,7 +353,8 @@ private:
|
||||
////////////////
|
||||
|
||||
template <typename... Ts>
|
||||
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
|
||||
[[nodiscard]] no_void_validator_tup_t<Ts...> convert_impl(
|
||||
const split_data& elems) {
|
||||
clear_error();
|
||||
|
||||
if (!splitter_.valid()) {
|
||||
@ -380,7 +385,7 @@ private:
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
|
||||
[[nodiscard]] no_void_validator_tup_t<std::tuple<Ts...>> convert_impl(
|
||||
const split_data& elems, const std::tuple<Ts...>*) {
|
||||
return convert_impl<Ts...>(elems);
|
||||
}
|
||||
@ -389,11 +394,11 @@ private:
|
||||
// column mapping
|
||||
////////////////
|
||||
|
||||
bool columns_mapped() const {
|
||||
return column_mappings_.size() != 0;
|
||||
[[nodiscard]] bool columns_mapped() const {
|
||||
return !column_mappings_.empty();
|
||||
}
|
||||
|
||||
size_t column_position(size_t tuple_position) const {
|
||||
[[nodiscard]] size_t column_position(size_t tuple_position) const {
|
||||
if (!columns_mapped()) {
|
||||
return tuple_position;
|
||||
}
|
||||
@ -403,7 +408,7 @@ private:
|
||||
// assumes positions are valid and the vector is not empty
|
||||
void set_column_mapping(std::vector<size_t> positions,
|
||||
size_t number_of_columns) {
|
||||
column_mappings_ = positions;
|
||||
column_mappings_ = std::move(positions);
|
||||
number_of_columns_ = number_of_columns;
|
||||
}
|
||||
|
||||
@ -424,7 +429,7 @@ private:
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
extract(msg.first, msg.second, dst);
|
||||
static_cast<void>(extract(msg.first, msg.second, dst));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -470,7 +475,8 @@ private:
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
no_void_validator_tup_t<Ts...> extract_tuple(const split_data& elems) {
|
||||
[[nodiscard]] no_void_validator_tup_t<Ts...> extract_tuple(
|
||||
const split_data& elems) {
|
||||
static_assert(!all_of_v<std::is_void, Ts...>,
|
||||
"at least one parameter must be non void");
|
||||
no_void_validator_tup_t<Ts...> ret{};
|
||||
@ -489,7 +495,7 @@ private:
|
||||
friend class parser;
|
||||
|
||||
std::vector<size_t> column_mappings_;
|
||||
size_t number_of_columns_;
|
||||
size_t number_of_columns_{0};
|
||||
};
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -12,12 +12,12 @@ class exception : public std::exception {
|
||||
std::string msg_;
|
||||
|
||||
public:
|
||||
exception(const std::string& msg): msg_{msg} {
|
||||
exception(std::string msg) : msg_{std::move(msg)} {
|
||||
}
|
||||
|
||||
virtual char const* what() const noexcept {
|
||||
[[nodiscard]] char const* what() const noexcept override {
|
||||
return msg_.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
#include "type_traits.hpp"
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@ -13,6 +13,7 @@
|
||||
#include <fast_float/fast_float.h>
|
||||
#else
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
@ -25,8 +26,8 @@ namespace ss {
|
||||
#ifndef SSP_DISABLE_FAST_FLOAT
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||
const char* const begin, const char* const end) {
|
||||
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
|
||||
to_num(const char* const begin, const char* const end) {
|
||||
T ret;
|
||||
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
|
||||
|
||||
@ -39,22 +40,23 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||
#else
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||
const char* const begin, const char* const end) {
|
||||
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
|
||||
to_num(const char* const begin, const char* const end) {
|
||||
static_assert(!std::is_same_v<T, long double>,
|
||||
"Conversion to long double is disabled");
|
||||
|
||||
constexpr static auto buff_max = 64;
|
||||
char short_buff[buff_max];
|
||||
size_t string_range = std::distance(begin, end);
|
||||
std::array<char, buff_max> short_buff;
|
||||
|
||||
const size_t string_range = std::distance(begin, end);
|
||||
std::string long_buff;
|
||||
|
||||
char* buff;
|
||||
char* buff = nullptr;
|
||||
if (string_range > buff_max) {
|
||||
long_buff = std::string{begin, end};
|
||||
buff = long_buff.data();
|
||||
} else {
|
||||
buff = short_buff;
|
||||
buff = short_buff.data();
|
||||
buff[string_range] = '\0';
|
||||
std::copy_n(begin, string_range, buff);
|
||||
}
|
||||
@ -86,12 +88,14 @@ struct numeric_wrapper {
|
||||
using type = T;
|
||||
|
||||
numeric_wrapper() = default;
|
||||
numeric_wrapper(numeric_wrapper&&) = default;
|
||||
numeric_wrapper(numeric_wrapper&&) noexcept = default;
|
||||
numeric_wrapper(const numeric_wrapper&) = default;
|
||||
|
||||
numeric_wrapper& operator=(numeric_wrapper&&) = default;
|
||||
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default;
|
||||
numeric_wrapper& operator=(const numeric_wrapper&) = default;
|
||||
|
||||
~numeric_wrapper() = default;
|
||||
|
||||
numeric_wrapper(T other) : value{other} {
|
||||
}
|
||||
|
||||
@ -110,7 +114,7 @@ using int8 = numeric_wrapper<int8_t>;
|
||||
using uint8 = numeric_wrapper<uint8_t>;
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
||||
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
||||
const char* const begin, const char* const end) {
|
||||
T ret;
|
||||
auto [ptr, ec] = std::from_chars(begin, end, ret);
|
||||
@ -122,8 +126,9 @@ std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<is_instance_of_v<numeric_wrapper, T>, std::optional<T>> to_num(
|
||||
const char* const begin, const char* const end) {
|
||||
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>,
|
||||
std::optional<T>>
|
||||
to_num(const char* const begin, const char* const end) {
|
||||
T ret;
|
||||
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
|
||||
|
||||
@ -142,10 +147,11 @@ template <typename T>
|
||||
struct unsupported_type {
|
||||
constexpr static bool value = false;
|
||||
};
|
||||
} /* namespace */
|
||||
} /* namespace errors */
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
|
||||
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> &&
|
||||
!std::is_floating_point_v<T> &&
|
||||
!is_instance_of_v<std::optional, T> &&
|
||||
!is_instance_of_v<std::variant, T> &&
|
||||
!is_instance_of_v<numeric_wrapper, T>,
|
||||
@ -157,7 +163,8 @@ extract(const char*, const char*, T&) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> ||
|
||||
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> ||
|
||||
std::is_floating_point_v<T> ||
|
||||
is_instance_of_v<numeric_wrapper, T>,
|
||||
bool>
|
||||
extract(const char* begin, const char* end, T& value) {
|
||||
@ -170,8 +177,8 @@ extract(const char* begin, const char* end, T& value) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
|
||||
const char* begin, const char* end, T& value) {
|
||||
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool>
|
||||
extract(const char* begin, const char* end, T& value) {
|
||||
typename T::value_type raw_value;
|
||||
if (extract(begin, end, raw_value)) {
|
||||
value = raw_value;
|
||||
@ -182,7 +189,8 @@ std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
|
||||
}
|
||||
|
||||
template <typename T, size_t I>
|
||||
bool extract_variant(const char* begin, const char* end, T& value) {
|
||||
[[nodiscard]] bool extract_variant(const char* begin, const char* end,
|
||||
T& value) {
|
||||
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
|
||||
IthType ithValue;
|
||||
if (extract<IthType>(begin, end, ithValue)) {
|
||||
@ -195,7 +203,7 @@ bool extract_variant(const char* begin, const char* end, T& value) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
||||
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
||||
const char* begin, const char* end, T& value) {
|
||||
return extract_variant<T, 0>(begin, end, value);
|
||||
}
|
||||
@ -205,7 +213,8 @@ std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
|
||||
////////////////
|
||||
|
||||
template <>
|
||||
inline bool extract(const char* begin, const char* end, bool& value) {
|
||||
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||
bool& value) {
|
||||
if (end == begin + 1) {
|
||||
if (*begin == '1') {
|
||||
value = true;
|
||||
@ -215,10 +224,13 @@ inline bool extract(const char* begin, const char* end, bool& value) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
size_t size = end - begin;
|
||||
if (size == 4 && std::strncmp(begin, "true", size) == 0) {
|
||||
constexpr static auto true_size = 4;
|
||||
constexpr static auto false_size = 5;
|
||||
const size_t size = end - begin;
|
||||
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
|
||||
value = true;
|
||||
} else if (size == 5 && std::strncmp(begin, "false", size) == 0) {
|
||||
} else if (size == false_size &&
|
||||
std::strncmp(begin, "false", size) == 0) {
|
||||
value = false;
|
||||
} else {
|
||||
return false;
|
||||
@ -229,22 +241,24 @@ inline bool extract(const char* begin, const char* end, bool& value) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool extract(const char* begin, const char* end, char& value) {
|
||||
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||
char& value) {
|
||||
value = *begin;
|
||||
return (end == begin + 1);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool extract(const char* begin, const char* end, std::string& value) {
|
||||
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||
std::string& value) {
|
||||
value = std::string{begin, end};
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool extract(const char* begin, const char* end,
|
||||
[[nodiscard]] inline bool extract(const char* begin, const char* end,
|
||||
std::string_view& value) {
|
||||
value = std::string_view{begin, static_cast<size_t>(end - begin)};
|
||||
return true;
|
||||
}
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
|
||||
namespace ss {
|
||||
|
||||
@ -77,4 +76,4 @@ struct member_wrapper<R T::*> {
|
||||
template <typename T> \
|
||||
constexpr bool has_m_##method##_t = has_m_##method<T>::value;
|
||||
|
||||
} /* trait */
|
||||
} /* namespace ss */
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "exception.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "restrictions.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
@ -32,10 +31,12 @@ class parser {
|
||||
|
||||
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:
|
||||
parser(const std::string& file_name,
|
||||
const std::string& delim = ss::default_delimiter)
|
||||
: file_name_{file_name}, reader_{file_name_, delim} {
|
||||
parser(std::string file_name, std::string delim = ss::default_delimiter)
|
||||
: file_name_{std::move(file_name)}, reader_{file_name_, delim} {
|
||||
if (reader_.file_) {
|
||||
read_line();
|
||||
if constexpr (ignore_header) {
|
||||
@ -51,7 +52,7 @@ public:
|
||||
|
||||
parser(const char* const csv_data_buffer, size_t csv_data_size,
|
||||
const std::string& delim = ss::default_delimiter)
|
||||
: file_name_{"buffer line"},
|
||||
: file_name_{"CSV data buffer"},
|
||||
reader_{csv_data_buffer, csv_data_size, delim} {
|
||||
if (csv_data_buffer) {
|
||||
read_line();
|
||||
@ -66,14 +67,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
parser(parser&& other) = default;
|
||||
parser& operator=(parser&& other) = default;
|
||||
parser(parser&& other) noexcept = default;
|
||||
parser& operator=(parser&& other) noexcept = default;
|
||||
~parser() = default;
|
||||
|
||||
parser() = delete;
|
||||
parser(const parser& other) = delete;
|
||||
parser& operator=(const parser& other) = delete;
|
||||
|
||||
bool valid() const {
|
||||
[[nodiscard]] bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
@ -83,12 +85,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& error_msg() const {
|
||||
[[nodiscard]] const std::string& error_msg() const {
|
||||
assert_string_error_defined<string_error>();
|
||||
return error_;
|
||||
}
|
||||
|
||||
bool eof() const {
|
||||
[[nodiscard]] bool eof() const {
|
||||
return eof_;
|
||||
}
|
||||
|
||||
@ -97,23 +99,21 @@ public:
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
T get_object() {
|
||||
[[nodiscard]] T get_object() {
|
||||
return to_object<T>(get_next<Ts...>());
|
||||
}
|
||||
|
||||
size_t line() const {
|
||||
[[nodiscard]] size_t line() const {
|
||||
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
|
||||
: reader_.line_number_;
|
||||
}
|
||||
|
||||
size_t position() const {
|
||||
[[nodiscard]] size_t position() const {
|
||||
return reader_.chars_read_;
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
no_void_validator_tup_t<T, Ts...> get_next() {
|
||||
std::optional<std::string> error;
|
||||
|
||||
[[nodiscard]] no_void_validator_tup_t<T, Ts...> get_next() {
|
||||
if (!eof_) {
|
||||
if constexpr (throw_on_error) {
|
||||
try {
|
||||
@ -162,20 +162,49 @@ public:
|
||||
return value;
|
||||
}
|
||||
|
||||
bool field_exists(const std::string& field) {
|
||||
[[nodiscard]] std::string raw_header() const {
|
||||
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()) {
|
||||
split_header_data();
|
||||
}
|
||||
|
||||
if (!valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return header_index(field).has_value();
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void use_fields(const Ts&... fields_args) {
|
||||
if constexpr (ignore_header) {
|
||||
handle_error_header_ignored();
|
||||
return;
|
||||
}
|
||||
assert_ignore_header_not_defined();
|
||||
clear_error();
|
||||
|
||||
if (header_.empty() && !eof()) {
|
||||
split_header_data();
|
||||
@ -188,7 +217,7 @@ public:
|
||||
auto fields = std::vector<std::string>{fields_args...};
|
||||
|
||||
if (fields.empty()) {
|
||||
handle_error_empty_mapping();
|
||||
handle_error_invalid_use_fields_argument();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -236,13 +265,17 @@ public:
|
||||
}
|
||||
|
||||
iterator(const iterator& other) = default;
|
||||
iterator(iterator&& other) = default;
|
||||
iterator(iterator&& other) noexcept = default;
|
||||
~iterator() = default;
|
||||
|
||||
value& operator*() {
|
||||
iterator& operator=(const iterator& other) = delete;
|
||||
iterator& operator=(iterator&& other) noexcept = delete;
|
||||
|
||||
[[nodiscard]] value& operator*() {
|
||||
return value_;
|
||||
}
|
||||
|
||||
value* operator->() {
|
||||
[[nodiscard]] value* operator->() {
|
||||
return &value_;
|
||||
}
|
||||
|
||||
@ -261,17 +294,21 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator& operator++(int) {
|
||||
return ++*this;
|
||||
iterator operator++(int) {
|
||||
auto result = *this;
|
||||
++*this;
|
||||
return result;
|
||||
}
|
||||
|
||||
friend bool operator==(const iterator& lhs, const iterator& rhs) {
|
||||
[[nodiscard]] friend bool operator==(const iterator& lhs,
|
||||
const iterator& rhs) {
|
||||
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
|
||||
(lhs.parser_ == rhs.parser_ &&
|
||||
&lhs.value_ == &rhs.value_);
|
||||
}
|
||||
|
||||
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
|
||||
[[nodiscard]] friend bool operator!=(const iterator& lhs,
|
||||
const iterator& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
@ -283,11 +320,11 @@ public:
|
||||
iterable(parser<Options...>* parser) : parser_{parser} {
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
[[nodiscard]] iterator begin() {
|
||||
return ++iterator{parser_};
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
[[nodiscard]] iterator end() {
|
||||
return iterator{};
|
||||
}
|
||||
|
||||
@ -296,12 +333,12 @@ public:
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
auto iterate() {
|
||||
[[nodiscard]] auto iterate() {
|
||||
return iterable<false, Ts...>{this};
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
auto iterate_object() {
|
||||
[[nodiscard]] auto iterate_object() {
|
||||
return iterable<true, Ts...>{this};
|
||||
}
|
||||
|
||||
@ -326,7 +363,7 @@ public:
|
||||
Fun&& fun = none{}) {
|
||||
using Value = no_void_validator_tup_t<Us...>;
|
||||
std::optional<Value> value;
|
||||
try_convert_and_invoke<Value, Us...>(value, fun);
|
||||
try_convert_and_invoke<Value, Us...>(value, std::forward<Fun>(fun));
|
||||
return composite_with(std::move(value));
|
||||
}
|
||||
|
||||
@ -335,11 +372,11 @@ public:
|
||||
template <typename U, typename... Us, typename Fun = none>
|
||||
composite<Ts..., std::optional<U>> or_object(Fun&& fun = none{}) {
|
||||
std::optional<U> value;
|
||||
try_convert_and_invoke<U, Us...>(value, fun);
|
||||
try_convert_and_invoke<U, Us...>(value, std::forward<Fun>(fun));
|
||||
return composite_with(std::move(value));
|
||||
}
|
||||
|
||||
std::tuple<Ts...> values() {
|
||||
[[nodiscard]] std::tuple<Ts...> values() {
|
||||
return values_;
|
||||
}
|
||||
|
||||
@ -362,7 +399,7 @@ public:
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
composite<Ts..., T> composite_with(T&& new_value) {
|
||||
[[nodiscard]] composite<Ts..., T> composite_with(T&& new_value) {
|
||||
auto merged_values =
|
||||
std::tuple_cat(std::move(values_),
|
||||
std::tuple<T>{parser_.valid()
|
||||
@ -392,7 +429,7 @@ public:
|
||||
}
|
||||
|
||||
template <typename U, typename... Us>
|
||||
no_void_validator_tup_t<U, Us...> try_same() {
|
||||
[[nodiscard]] no_void_validator_tup_t<U, Us...> try_same() {
|
||||
parser_.clear_error();
|
||||
auto value =
|
||||
parser_.reader_.converter_.template convert<U, Us...>();
|
||||
@ -413,8 +450,8 @@ public:
|
||||
// tries to convert a line and returns a composite which is
|
||||
// able to try additional conversions in case of failure
|
||||
template <typename... Ts, typename Fun = none>
|
||||
composite<std::optional<no_void_validator_tup_t<Ts...>>> try_next(
|
||||
Fun&& fun = none{}) {
|
||||
[[nodiscard]] composite<std::optional<no_void_validator_tup_t<Ts...>>>
|
||||
try_next(Fun&& fun = none{}) {
|
||||
assert_throw_on_error_not_defined<throw_on_error>();
|
||||
using Ret = no_void_validator_tup_t<Ts...>;
|
||||
return try_invoke_and_make_composite<
|
||||
@ -424,7 +461,7 @@ public:
|
||||
// identical to try_next but returns composite with object instead of a
|
||||
// tuple
|
||||
template <typename T, typename... Ts, typename Fun = none>
|
||||
composite<std::optional<T>> try_object(Fun&& fun = none{}) {
|
||||
[[nodiscard]] composite<std::optional<T>> try_object(Fun&& fun = none{}) {
|
||||
assert_throw_on_error_not_defined<throw_on_error>();
|
||||
return try_invoke_and_make_composite<
|
||||
std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun));
|
||||
@ -443,7 +480,8 @@ private:
|
||||
using Ret = decltype(try_invoke_impl(arg, std::forward<Fun>(fun)));
|
||||
constexpr bool returns_void = std::is_same_v<Ret, void>;
|
||||
if constexpr (!returns_void) {
|
||||
if (!try_invoke_impl(arg, std::forward<Fun>(fun))) {
|
||||
if (!try_invoke_impl(std::forward<Arg>(arg),
|
||||
std::forward<Fun>(fun))) {
|
||||
handle_error_failed_check();
|
||||
}
|
||||
} else {
|
||||
@ -474,26 +512,55 @@ private:
|
||||
}
|
||||
|
||||
template <typename T, typename Fun = none>
|
||||
composite<T> try_invoke_and_make_composite(T&& value, Fun&& fun) {
|
||||
[[nodiscard]] composite<T> try_invoke_and_make_composite(T&& value,
|
||||
Fun&& fun) {
|
||||
if (valid()) {
|
||||
try_invoke(*value, std::forward<Fun>(fun));
|
||||
}
|
||||
return {valid() ? std::move(value) : std::nullopt, *this};
|
||||
return {valid() ? std::forward<T>(value) : std::nullopt, *this};
|
||||
}
|
||||
|
||||
////////////////
|
||||
// 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() {
|
||||
ss::splitter<Options...> splitter;
|
||||
header_splitter splitter;
|
||||
std::string raw_header_copy = raw_header_;
|
||||
splitter.split(raw_header_copy.data(), reader_.delim_);
|
||||
for (const auto& [begin, end] : splitter.split_data_) {
|
||||
|
||||
if (!strict_split(splitter, raw_header_copy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [begin, end] : splitter.get_split_data()) {
|
||||
std::string field{begin, end};
|
||||
if (std::find(header_.begin(), header_.end(), field) !=
|
||||
header_.end()) {
|
||||
handle_error_invalid_header(field);
|
||||
handle_error_duplicate_header_field(field);
|
||||
header_.clear();
|
||||
return;
|
||||
}
|
||||
@ -501,7 +568,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<size_t> header_index(const std::string& field) {
|
||||
[[nodiscard]] std::optional<size_t> header_index(const std::string& field) {
|
||||
auto it = std::find(header_.begin(), header_.end(), field);
|
||||
|
||||
if (it == header_.end()) {
|
||||
@ -524,7 +591,7 @@ private:
|
||||
}
|
||||
|
||||
void handle_error_failed_check() {
|
||||
constexpr static auto error_msg = " failed check";
|
||||
constexpr static auto error_msg = ": failed check";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
@ -537,7 +604,7 @@ private:
|
||||
}
|
||||
|
||||
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) {
|
||||
error_.clear();
|
||||
@ -550,7 +617,7 @@ private:
|
||||
}
|
||||
|
||||
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) {
|
||||
error_.clear();
|
||||
@ -563,7 +630,7 @@ private:
|
||||
}
|
||||
|
||||
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) {
|
||||
error_.clear();
|
||||
@ -588,20 +655,6 @@ 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) {
|
||||
constexpr static auto error_msg =
|
||||
": header does not contain given field: ";
|
||||
@ -629,8 +682,9 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void handle_error_empty_mapping() {
|
||||
constexpr static auto error_msg = "received empty mapping";
|
||||
void handle_error_invalid_use_fields_argument() {
|
||||
constexpr static auto error_msg =
|
||||
"received invalid argument for 'use_fields'";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
@ -642,19 +696,53 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void handle_error_invalid_header(const std::string& field) {
|
||||
constexpr static auto error_msg = "header contains duplicates: ";
|
||||
void handle_error_invalid_header_field() {
|
||||
constexpr static auto error_msg = ": header contains empty field";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(error_msg).append(error_msg);
|
||||
error_.append(file_name_).append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg + field};
|
||||
throw ss::exception{file_name_ + error_msg};
|
||||
} else {
|
||||
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 {
|
||||
static_assert(throw_on_error,
|
||||
"throw_on_error needs to be enabled to use this method");
|
||||
@ -674,17 +762,18 @@ private:
|
||||
}
|
||||
|
||||
struct reader {
|
||||
reader(const std::string& file_name_, const std::string& delim)
|
||||
: delim_{delim}, file_{std::fopen(file_name_.c_str(), "rb")} {
|
||||
reader(const std::string& file_name_, std::string delim)
|
||||
: delim_{std::move(delim)},
|
||||
file_{std::fopen(file_name_.c_str(), "rb")} {
|
||||
}
|
||||
|
||||
reader(const char* const buffer, size_t csv_data_size,
|
||||
const std::string& delim)
|
||||
: delim_{delim}, csv_data_buffer_{buffer},
|
||||
std::string delim)
|
||||
: delim_{std::move(delim)}, csv_data_buffer_{buffer},
|
||||
csv_data_size_{csv_data_size} {
|
||||
}
|
||||
|
||||
reader(reader&& other)
|
||||
reader(reader&& other) noexcept
|
||||
: buffer_{other.buffer_},
|
||||
next_line_buffer_{other.next_line_buffer_},
|
||||
helper_buffer_{other.helper_buffer_},
|
||||
@ -705,7 +794,7 @@ private:
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
reader& operator=(reader&& other) {
|
||||
reader& operator=(reader&& other) noexcept {
|
||||
if (this != &other) {
|
||||
buffer_ = other.buffer_;
|
||||
next_line_buffer_ = other.next_line_buffer_;
|
||||
@ -741,7 +830,7 @@ private:
|
||||
std::free(helper_buffer_);
|
||||
|
||||
if (file_) {
|
||||
std::fclose(file_);
|
||||
std::ignore = std::fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,46 +838,9 @@ private:
|
||||
reader(const reader& other) = delete;
|
||||
reader& operator=(const reader& other) = delete;
|
||||
|
||||
ssize_t get_line_buffer(char** lineptr, size_t* n,
|
||||
const char* const csv_data_buffer,
|
||||
size_t csv_data_size, size_t& curr_char) {
|
||||
if (curr_char >= csv_data_size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (*lineptr == nullptr || *n < get_line_initial_buffer_size) {
|
||||
auto new_lineptr = static_cast<char*>(
|
||||
strict_realloc(*lineptr, get_line_initial_buffer_size));
|
||||
*lineptr = new_lineptr;
|
||||
*n = get_line_initial_buffer_size;
|
||||
}
|
||||
|
||||
size_t line_used = 0;
|
||||
while (curr_char <= csv_data_size) {
|
||||
if (line_used + 1 >= *n) {
|
||||
size_t new_n = *n * 2;
|
||||
|
||||
char* new_lineptr =
|
||||
static_cast<char*>(strict_realloc(*lineptr, new_n));
|
||||
*n = new_n;
|
||||
*lineptr = new_lineptr;
|
||||
}
|
||||
|
||||
auto c = csv_data_buffer[curr_char++];
|
||||
(*lineptr)[line_used++] = c;
|
||||
if (c == '\n') {
|
||||
(*lineptr)[line_used] = '\0';
|
||||
return line_used;
|
||||
}
|
||||
}
|
||||
|
||||
return (line_used != 0) ? line_used : -1;
|
||||
}
|
||||
|
||||
// read next line each time in order to set eof_
|
||||
bool read_next() {
|
||||
[[nodiscard]] bool read_next() {
|
||||
next_line_converter_.clear_error();
|
||||
ssize_t ssize = 0;
|
||||
size_t size = 0;
|
||||
while (size == 0) {
|
||||
++line_number_;
|
||||
@ -797,21 +849,11 @@ private:
|
||||
}
|
||||
|
||||
chars_read_ = curr_char_;
|
||||
if (file_) {
|
||||
ssize = get_line_file(&next_line_buffer_,
|
||||
&next_line_buffer_size_, file_);
|
||||
curr_char_ = std::ftell(file_);
|
||||
} else {
|
||||
ssize = get_line_buffer(&next_line_buffer_,
|
||||
&next_line_buffer_size_,
|
||||
csv_data_buffer_, csv_data_size_,
|
||||
curr_char_);
|
||||
}
|
||||
auto [ssize, eof] =
|
||||
get_line(next_line_buffer_, next_line_buffer_size_, file_,
|
||||
csv_data_buffer_, csv_data_size_, curr_char_);
|
||||
|
||||
if (ssize == -1) {
|
||||
if (errno == ENOMEM) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
if (eof) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -836,7 +878,8 @@ private:
|
||||
}
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
next_line_size_)) {
|
||||
next_line_size_,
|
||||
next_line_buffer_size_)) {
|
||||
next_line_converter_.handle_error_unterminated_escape();
|
||||
return;
|
||||
}
|
||||
@ -854,7 +897,8 @@ private:
|
||||
}
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
next_line_size_)) {
|
||||
next_line_size_,
|
||||
next_line_buffer_size_)) {
|
||||
next_line_converter_.handle_error_unterminated_quote();
|
||||
return;
|
||||
}
|
||||
@ -865,8 +909,9 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
next_line_size_)) {
|
||||
if (!append_next_line_to_buffer(
|
||||
next_line_buffer_, next_line_size_,
|
||||
next_line_buffer_size_)) {
|
||||
next_line_converter_
|
||||
.handle_error_unterminated_escape();
|
||||
return;
|
||||
@ -886,7 +931,7 @@ private:
|
||||
std::swap(converter_, next_line_converter_);
|
||||
}
|
||||
|
||||
bool multiline_limit_reached(size_t& limit) {
|
||||
[[nodiscard]] bool multiline_limit_reached(size_t& limit) {
|
||||
if constexpr (multiline::size > 0) {
|
||||
if (limit++ >= multiline::size) {
|
||||
next_line_converter_.handle_error_multiline_limit_reached();
|
||||
@ -896,8 +941,8 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool escaped_eol(size_t size) {
|
||||
const char* curr;
|
||||
[[nodiscard]] bool escaped_eol(size_t size) {
|
||||
const char* curr = nullptr;
|
||||
for (curr = next_line_buffer_ + size - 1;
|
||||
curr >= next_line_buffer_ &&
|
||||
setup<Options...>::escape::match(*curr);
|
||||
@ -906,22 +951,24 @@ private:
|
||||
return (next_line_buffer_ - curr + size) % 2 == 0;
|
||||
}
|
||||
|
||||
bool unterminated_quote() {
|
||||
[[nodiscard]] bool unterminated_quote() {
|
||||
return next_line_converter_.unterminated_quote();
|
||||
}
|
||||
|
||||
void undo_remove_eol(char* buffer, size_t& string_end) {
|
||||
if (crlf_) {
|
||||
std::copy_n("\r\n\0", 3, buffer + string_end);
|
||||
string_end += 2;
|
||||
} else {
|
||||
std::copy_n("\n\0", 2, buffer + string_end);
|
||||
string_end += 1;
|
||||
void undo_remove_eol(char* buffer, size_t& line_size,
|
||||
size_t buffer_size) {
|
||||
if (crlf_ && buffer_size >= line_size + 2) {
|
||||
std::copy_n("\r\n", 2, buffer + line_size);
|
||||
line_size += 2;
|
||||
} else if (buffer_size > line_size) {
|
||||
std::copy_n("\n", 1, buffer + line_size);
|
||||
line_size += 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t remove_eol(char*& buffer, size_t ssize) {
|
||||
[[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) {
|
||||
if (buffer[ssize - 1] != '\n') {
|
||||
crlf_ = false;
|
||||
return ssize;
|
||||
}
|
||||
|
||||
@ -941,7 +988,7 @@ private:
|
||||
size_t& buffer_size, const char* const second,
|
||||
size_t second_size) {
|
||||
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));
|
||||
|
||||
first = new_first;
|
||||
@ -949,33 +996,29 @@ private:
|
||||
first_size += second_size;
|
||||
}
|
||||
|
||||
bool append_next_line_to_buffer(char*& buffer, size_t& size) {
|
||||
undo_remove_eol(buffer, size);
|
||||
[[nodiscard]] bool append_next_line_to_buffer(char*& buffer,
|
||||
size_t& line_size,
|
||||
size_t buffer_size) {
|
||||
undo_remove_eol(buffer, line_size, buffer_size);
|
||||
|
||||
ssize_t next_ssize;
|
||||
if (file_) {
|
||||
next_ssize =
|
||||
get_line_file(&helper_buffer_, &helper_buffer_size, file_);
|
||||
} else {
|
||||
next_ssize =
|
||||
get_line_buffer(&helper_buffer_, &helper_buffer_size,
|
||||
csv_data_buffer_, csv_data_size_,
|
||||
curr_char_);
|
||||
}
|
||||
chars_read_ = curr_char_;
|
||||
auto [next_ssize, eof] =
|
||||
get_line(helper_buffer_, helper_buffer_size, file_,
|
||||
csv_data_buffer_, csv_data_size_, curr_char_);
|
||||
|
||||
if (next_ssize == -1) {
|
||||
if (eof) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++line_number_;
|
||||
size_t next_size = remove_eol(helper_buffer_, next_ssize);
|
||||
realloc_concat(buffer, size, next_line_buffer_size_, helper_buffer_,
|
||||
next_size);
|
||||
realloc_concat(buffer, line_size, next_line_buffer_size_,
|
||||
helper_buffer_, next_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_buffer() {
|
||||
return std::string{next_line_buffer_, next_line_buffer_size_};
|
||||
[[nodiscard]] std::string get_buffer() {
|
||||
return std::string{next_line_buffer_, next_line_size_};
|
||||
}
|
||||
|
||||
////////////////
|
||||
@ -1018,4 +1061,4 @@ private:
|
||||
bool eof_{false};
|
||||
};
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -10,7 +10,7 @@ template <typename T, auto... Values>
|
||||
struct ax {
|
||||
private:
|
||||
template <auto X, auto... Xs>
|
||||
bool ss_valid_impl(const T& x) const {
|
||||
[[nodiscard]] bool ss_valid_impl(const T& x) const {
|
||||
if constexpr (sizeof...(Xs) != 0) {
|
||||
return x != X && ss_valid_impl<Xs...>(x);
|
||||
}
|
||||
@ -18,11 +18,11 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return ss_valid_impl<Values...>(value);
|
||||
}
|
||||
|
||||
const char* error() const {
|
||||
[[nodiscard]] const char* error() const {
|
||||
return "value excluded";
|
||||
}
|
||||
};
|
||||
@ -35,7 +35,7 @@ template <typename T, auto... Values>
|
||||
struct nx {
|
||||
private:
|
||||
template <auto X, auto... Xs>
|
||||
bool ss_valid_impl(const T& x) const {
|
||||
[[nodiscard]] bool ss_valid_impl(const T& x) const {
|
||||
if constexpr (sizeof...(Xs) != 0) {
|
||||
return x == X || ss_valid_impl<Xs...>(x);
|
||||
}
|
||||
@ -43,11 +43,11 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return ss_valid_impl<Values...>(value);
|
||||
}
|
||||
|
||||
const char* error() const {
|
||||
[[nodiscard]] const char* error() const {
|
||||
return "value excluded";
|
||||
}
|
||||
};
|
||||
@ -61,28 +61,28 @@ public:
|
||||
|
||||
template <typename T, auto N>
|
||||
struct gt {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value > N;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, auto N>
|
||||
struct gte {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value >= N;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, auto N>
|
||||
struct lt {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value < N;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, auto N>
|
||||
struct lte {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value <= N;
|
||||
}
|
||||
};
|
||||
@ -93,7 +93,7 @@ struct lte {
|
||||
|
||||
template <typename T, auto Min, auto Max>
|
||||
struct ir {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value >= Min && value <= Max;
|
||||
}
|
||||
};
|
||||
@ -104,7 +104,7 @@ struct ir {
|
||||
|
||||
template <typename T, auto Min, auto Max>
|
||||
struct oor {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return value < Min || value > Max;
|
||||
}
|
||||
};
|
||||
@ -115,13 +115,13 @@ struct oor {
|
||||
|
||||
template <typename T>
|
||||
struct ne {
|
||||
bool ss_valid(const T& value) const {
|
||||
[[nodiscard]] bool ss_valid(const T& value) const {
|
||||
return !value.empty();
|
||||
}
|
||||
|
||||
const char* error() const {
|
||||
[[nodiscard]] const char* error() const {
|
||||
return "empty field";
|
||||
}
|
||||
};
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -293,4 +293,7 @@ private:
|
||||
template <typename... Options>
|
||||
struct setup<setup<Options...>> : setup<Options...> {};
|
||||
|
||||
} /* ss */
|
||||
template <typename... Options>
|
||||
struct setup<std::tuple<Options...>> : setup<Options...> {};
|
||||
|
||||
} /* namespace ss */
|
||||
|
@ -2,11 +2,9 @@
|
||||
#include "common.hpp"
|
||||
#include "exception.hpp"
|
||||
#include "setup.hpp"
|
||||
#include "type_traits.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -30,7 +28,7 @@ private:
|
||||
public:
|
||||
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
|
||||
|
||||
bool valid() const {
|
||||
[[nodiscard]] bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
@ -40,12 +38,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& error_msg() const {
|
||||
[[nodiscard]] const std::string& error_msg() const {
|
||||
assert_string_error_defined<string_error>();
|
||||
return error_;
|
||||
}
|
||||
|
||||
bool unterminated_quote() const {
|
||||
[[nodiscard]] bool unterminated_quote() const {
|
||||
return unterminated_quote_;
|
||||
}
|
||||
|
||||
@ -57,13 +55,21 @@ public:
|
||||
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:
|
||||
////////////////
|
||||
// resplit
|
||||
////////////////
|
||||
|
||||
// number of characters the end of line is shifted backwards
|
||||
size_t size_shifted() const {
|
||||
[[nodiscard]] size_t size_shifted() const {
|
||||
return escaped_;
|
||||
}
|
||||
|
||||
@ -86,7 +92,7 @@ private:
|
||||
}
|
||||
|
||||
const auto [old_line, old_begin] = *std::prev(split_data_.end());
|
||||
size_t begin = old_begin - old_line - 1;
|
||||
const size_t begin = old_begin - old_line - 1;
|
||||
|
||||
// safety measure
|
||||
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
|
||||
@ -194,19 +200,19 @@ private:
|
||||
// matching
|
||||
////////////////
|
||||
|
||||
bool match(const char* const curr, char delim) {
|
||||
[[nodiscard]] bool match(const char* const curr, char delim) {
|
||||
return *curr == delim;
|
||||
};
|
||||
|
||||
bool match(const char* const curr, const std::string& delim) {
|
||||
[[nodiscard]] bool match(const char* const curr, const std::string& delim) {
|
||||
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
|
||||
};
|
||||
|
||||
size_t delimiter_size(char) {
|
||||
[[nodiscard]] size_t delimiter_size(char) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t delimiter_size(const std::string& delim) {
|
||||
[[nodiscard]] size_t delimiter_size(const std::string& delim) {
|
||||
return delim.size();
|
||||
}
|
||||
|
||||
@ -227,7 +233,7 @@ private:
|
||||
}
|
||||
|
||||
template <typename Delim>
|
||||
std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
|
||||
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
|
||||
const Delim& delim) {
|
||||
line_ptr_type end = begin;
|
||||
|
||||
@ -322,8 +328,9 @@ private:
|
||||
|
||||
trim_left_if_enabled(begin_);
|
||||
|
||||
for (done_ = false; !done_; read(delim))
|
||||
;
|
||||
for (done_ = false; !done_;) {
|
||||
read(delim);
|
||||
}
|
||||
|
||||
return split_data_;
|
||||
}
|
||||
@ -462,7 +469,6 @@ private:
|
||||
// members
|
||||
////////////////
|
||||
|
||||
public:
|
||||
error_type error_{};
|
||||
bool unterminated_quote_{false};
|
||||
bool done_{true};
|
||||
@ -479,4 +485,4 @@ public:
|
||||
friend class converter;
|
||||
};
|
||||
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
@ -34,7 +34,11 @@ struct left_of_impl;
|
||||
|
||||
template <size_t N, typename T, typename... Ts>
|
||||
struct left_of_impl {
|
||||
static_assert(N < 128, "recursion limit reached");
|
||||
private:
|
||||
constexpr static auto recursion_limit = 128;
|
||||
|
||||
public:
|
||||
static_assert(N < recursion_limit, "recursion limit reached");
|
||||
static_assert(N != 0, "cannot take the whole tuple");
|
||||
using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>;
|
||||
};
|
||||
@ -362,12 +366,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
|
||||
////////////////
|
||||
|
||||
template <class T, std::size_t... Is, class U>
|
||||
T to_object_impl(std::index_sequence<Is...>, U&& data) {
|
||||
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) {
|
||||
return {std::get<Is>(std::forward<U>(data))...};
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
T to_object(U&& data) {
|
||||
[[nodiscard]] T to_object(U&& data) {
|
||||
using NoRefU = std::decay_t<U>;
|
||||
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
|
||||
return to_object_impl<
|
||||
@ -378,4 +382,4 @@ T to_object(U&& data) {
|
||||
}
|
||||
}
|
||||
|
||||
} /* trait */
|
||||
} /* namespace ss */
|
||||
|
@ -6,7 +6,7 @@ project(
|
||||
'cpp_std=c++17',
|
||||
'buildtype=debugoptimized',
|
||||
'wrap_mode=forcefallback'],
|
||||
version: '1.7.0',
|
||||
version: '1.8.0',
|
||||
meson_version:'>=0.54.0')
|
||||
|
||||
fast_float_dep = dependency('fast_float')
|
||||
|
@ -14,14 +14,21 @@ headers = ['type_traits.hpp',
|
||||
|
||||
combined_file = []
|
||||
includes = []
|
||||
in_pp_block = False
|
||||
|
||||
for header in headers:
|
||||
with open(headers_dir + header) as f:
|
||||
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:
|
||||
continue
|
||||
|
||||
if '#include <' in line:
|
||||
if '#include <' in line and not in_pp_block:
|
||||
includes.append(line)
|
||||
continue
|
||||
|
||||
@ -30,6 +37,7 @@ for header in headers:
|
||||
|
||||
includes = sorted(set(includes))
|
||||
|
||||
print('#pragma once')
|
||||
print('\n'.join(includes))
|
||||
print('#define SSP_DISABLE_FAST_FLOAT')
|
||||
print('\n'.join(combined_file))
|
||||
|
@ -33,10 +33,11 @@ set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
|
||||
enable_testing()
|
||||
|
||||
foreach(name IN ITEMS test_splitter test_parser1_1 test_parser1_2
|
||||
test_parser1_3 test_parser1_4 test_converter
|
||||
test_extractions test_parser2_1 test_parser2_2
|
||||
test_parser2_3 test_parser2_4 test_parser2_5
|
||||
test_parser2_6 test_extractions_without_fast_float)
|
||||
test_parser1_3 test_parser1_4 test_parser1_5
|
||||
test_converter test_extractions test_parser2_1
|
||||
test_parser2_2 test_parser2_3 test_parser2_4
|
||||
test_parser2_5 test_parser2_6
|
||||
test_extractions_without_fast_float)
|
||||
add_executable("${name}" "${name}.cpp")
|
||||
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float
|
||||
doctest::doctest)
|
||||
|
@ -6,6 +6,7 @@ tests = [
|
||||
'parser1_2',
|
||||
'parser1_3',
|
||||
'parser1_4',
|
||||
'parser1_5',
|
||||
'splitter',
|
||||
'converter',
|
||||
'extractions',
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "test_helpers.hpp"
|
||||
#include <algorithm>
|
||||
#include <ss/converter.hpp>
|
||||
|
||||
TEST_CASE("converter test split") {
|
||||
@ -11,7 +10,8 @@ TEST_CASE("converter test split") {
|
||||
{" x x x x | x ", {" x x x x ", " x "}, "|"},
|
||||
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
|
||||
{"x\t-\ty", {"x", "y"}, "\t-\t"},
|
||||
{"x", {"x"}, ","}} // clang-format on
|
||||
{"x", {"x"}, ","}}
|
||||
// clang-format on
|
||||
) {
|
||||
auto split = c.split(s, delim);
|
||||
CHECK_EQ(split.size(), expected.size());
|
||||
@ -278,36 +278,37 @@ TEST_CASE_TEMPLATE("converter test valid conversions with exceptions", T, int,
|
||||
TEST_CASE_TEMPLATE("converter test invalid conversions", T, int, ss::uint8) {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<T>("");
|
||||
std::ignore = c.convert<T>("");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T>("1", "");
|
||||
std::ignore = c.convert<T>("1", "");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T>("10", "");
|
||||
std::ignore = c.convert<T>("10", "");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T, void>("");
|
||||
std::ignore = c.convert<T, void>("");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T, void>(",junk");
|
||||
std::ignore = c.convert<T, void>(",junk");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<void, T>("junk,");
|
||||
std::ignore = c.convert<void, T>("junk,");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T>("x");
|
||||
std::ignore = c.convert<T>("x");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T, void>("x");
|
||||
std::ignore = c.convert<T, void>("x");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<T, void>("x,junk");
|
||||
std::ignore = c.convert<T, void>("x,junk");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<void, T>("junk,x");
|
||||
std::ignore = c.convert<void, T>("junk,x");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
std::ignore =
|
||||
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
}
|
||||
@ -316,34 +317,36 @@ TEST_CASE_TEMPLATE("converter test invalid conversions with exceptions", T, int,
|
||||
ss::uint8) {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<T>(""));
|
||||
REQUIRE_EXCEPTION(c.convert<T>("1", ""));
|
||||
REQUIRE_EXCEPTION(c.convert<T>("10", ""));
|
||||
REQUIRE_EXCEPTION(c.convert<T, void>(""));
|
||||
REQUIRE_EXCEPTION(c.convert<T, void>(",junk"));
|
||||
REQUIRE_EXCEPTION(c.convert<void, T>("junk,"));
|
||||
REQUIRE_EXCEPTION(c.convert<T>("x"));
|
||||
REQUIRE_EXCEPTION(c.convert<T, void>("x"));
|
||||
REQUIRE_EXCEPTION(c.convert<T, void>("x,junk"));
|
||||
REQUIRE_EXCEPTION(c.convert<void, T>("junk,x"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T>(""));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("1", ""));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("10", ""));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(""));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(",junk"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("x"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x,junk"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,x"));
|
||||
REQUIRE_EXCEPTION(
|
||||
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6", ";"));
|
||||
std::ignore =
|
||||
c.convert<void, std::variant<T, double>, double>("junk;.5.5;6",
|
||||
";"));
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("converter test ss:ax restriction (all except)", T, int,
|
||||
ss::uint8) {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::ax<T, 0>>("0");
|
||||
std::ignore = c.convert<ss::ax<T, 0>>("0");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::ax<T, 0, 1, 2>>("1");
|
||||
std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
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_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::ax<T, 1>, char>("1,c");
|
||||
std::ignore = c.convert<ss::ax<T, 1>, char>("1,c");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
{
|
||||
T tup = c.convert<ss::ax<T, 1>>("3");
|
||||
@ -367,10 +370,11 @@ TEST_CASE_TEMPLATE(
|
||||
ss::uint8) {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0>>("0"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ax<T, 0, 1, 2>>("1"));
|
||||
REQUIRE_EXCEPTION(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, 0>>("0"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1"));
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -393,13 +397,13 @@ TEST_CASE_TEMPLATE(
|
||||
TEST_CASE("converter test ss:nx restriction (none except)") {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::nx<int, 1>>("3");
|
||||
std::ignore = c.convert<ss::nx<int, 1>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
|
||||
std::ignore = c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::nx<int, 1>, char>("3,c");
|
||||
std::ignore = c.convert<ss::nx<int, 1>, char>("3,c");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
{
|
||||
@ -427,9 +431,10 @@ TEST_CASE("converter test ss:nx restriction (none except)") {
|
||||
TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>>("3"));
|
||||
REQUIRE_EXCEPTION(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>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore =
|
||||
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -461,13 +466,13 @@ TEST_CASE_TEMPLATE("converter test ss:ir restriction (in range)", T, int,
|
||||
ss::uint8) {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::ir<T, 0, 2>>("3");
|
||||
std::ignore = c.convert<ss::ir<T, 0, 2>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<char, ss::ir<T, 4, 69>>("c,3");
|
||||
std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::ir<T, 1, 2>, char>("3,c");
|
||||
std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
{
|
||||
@ -497,9 +502,9 @@ TEST_CASE_TEMPLATE(
|
||||
ss::uint8) {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 0, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<char, ss::ir<T, 4, 69>>("c,3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ir<T, 1, 2>, char>("3,c"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 0, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -530,16 +535,16 @@ TEST_CASE_TEMPLATE(
|
||||
TEST_CASE("converter test ss:oor restriction (out of range)") {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::oor<int, 1, 5>>("3");
|
||||
std::ignore = c.convert<ss::oor<int, 1, 5>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::oor<int, 0, 2>>("2");
|
||||
std::ignore = c.convert<ss::oor<int, 0, 2>>("2");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
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_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::oor<int, 1, 20>, char>("1,c");
|
||||
std::ignore = c.convert<ss::oor<int, 1, 20>, char>("1,c");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
{
|
||||
@ -564,10 +569,12 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
|
||||
TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 5>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 0, 2>>("2"));
|
||||
REQUIRE_EXCEPTION(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, 5>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 0, 2>>("2"));
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
|
||||
REQUIRE_EXCEPTION(std::ignore =
|
||||
c.convert<ss::oor<int, 1, 20>, char>("1,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -608,19 +615,19 @@ inline bool ss::extract(const char* begin, const char* end,
|
||||
TEST_CASE("converter test ss:ne restriction (not empty)") {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::ne<std::string>>("");
|
||||
std::ignore = c.convert<ss::ne<std::string>>("");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<int, ss::ne<std::string>>("3,");
|
||||
std::ignore = c.convert<int, ss::ne<std::string>>("3,");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::ne<std::string>, int>(",3");
|
||||
std::ignore = c.convert<ss::ne<std::string>, int>(",3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<void, ss::ne<std::string>, int>("junk,,3");
|
||||
std::ignore = c.convert<void, ss::ne<std::string>, int>("junk,,3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::ne<std::vector<int>>>("");
|
||||
std::ignore = c.convert<ss::ne<std::vector<int>>>("");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
{
|
||||
@ -643,11 +650,12 @@ TEST_CASE("converter test ss:ne restriction (not empty)") {
|
||||
TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>>(""));
|
||||
REQUIRE_EXCEPTION(c.convert<int, ss::ne<std::string>>("3,"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>, int>(",3"));
|
||||
REQUIRE_EXCEPTION(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::string>>(""));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<int, ss::ne<std::string>>("3,"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>, int>(",3"));
|
||||
REQUIRE_EXCEPTION(std::ignore =
|
||||
c.convert<void, ss::ne<std::string>, int>("junk,,3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::vector<int>>>(""));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -675,22 +683,22 @@ TEST_CASE(
|
||||
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<ss::lt<int, 3>>("3");
|
||||
std::ignore = c.convert<ss::lt<int, 3>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::lt<int, 2>>("3");
|
||||
std::ignore = c.convert<ss::lt<int, 2>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::gt<int, 3>>("3");
|
||||
std::ignore = c.convert<ss::gt<int, 3>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::gt<int, 4>>("3");
|
||||
std::ignore = c.convert<ss::gt<int, 4>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::lte<int, 2>>("3");
|
||||
std::ignore = c.convert<ss::lte<int, 2>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<ss::gte<int, 4>>("3");
|
||||
std::ignore = c.convert<ss::gte<int, 4>>("3");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
{
|
||||
@ -734,12 +742,12 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
|
||||
"with exception") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 3>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 3>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 4>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::lte<int, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::gte<int, 4>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 3>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 3>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 4>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lte<int, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gte<int, 4>>("3"));
|
||||
|
||||
try {
|
||||
{
|
||||
@ -784,14 +792,14 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
|
||||
|
||||
TEST_CASE("converter test error mode") {
|
||||
ss::converter<ss::string_error> c;
|
||||
c.convert<int>("junk");
|
||||
std::ignore = c.convert<int>("junk");
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.error_msg().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("converter test throw on error mode") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
REQUIRE_EXCEPTION(c.convert<int>("junk"));
|
||||
REQUIRE_EXCEPTION(std::ignore = c.convert<int>("junk"));
|
||||
}
|
||||
|
||||
TEST_CASE("converter test converter with quotes spacing and escaping") {
|
||||
@ -908,7 +916,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// mismatched quote
|
||||
c.convert<std::string, std::string, double, char>(
|
||||
std::ignore = c.convert<std::string, std::string, double, char>(
|
||||
buff(R"( "just , some , "12.3","a" )"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -917,7 +925,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated quote
|
||||
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)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK(c.unterminated_quote());
|
||||
@ -926,7 +934,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escape
|
||||
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\)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -935,7 +943,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escape while quoting
|
||||
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\)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -944,7 +952,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escaped quote
|
||||
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\")"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK(c.unterminated_quote());
|
||||
@ -958,27 +966,32 @@ TEST_CASE("converter test invalid split conversions with exceptions") {
|
||||
c;
|
||||
|
||||
// mismatched quote
|
||||
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, char>(
|
||||
REQUIRE_EXCEPTION(std::ignore =
|
||||
c.convert<std::string, std::string, double, char>(
|
||||
buff(R"( "just , some , "12.3","a" )")));
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
|
||||
// unterminated quote
|
||||
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")));
|
||||
CHECK(c.unterminated_quote());
|
||||
|
||||
// unterminated escape
|
||||
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,strings\)")));
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
|
||||
// unterminated escape while quoting
|
||||
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,"strings\)")));
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
|
||||
// unterminated escaped quote
|
||||
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
|
||||
REQUIRE_EXCEPTION(
|
||||
std::ignore = c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,"strings\")")));
|
||||
CHECK(c.unterminated_quote());
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ struct is_unsigned : public std::is_unsigned<T> {};
|
||||
template <>
|
||||
struct is_unsigned<ss::uint8> : public std::true_type {};
|
||||
|
||||
} /* namespace */
|
||||
} /* anonymous namespace */
|
||||
|
||||
static_assert(is_signed<ss::int8>::value);
|
||||
static_assert(is_unsigned<ss::uint8>::value);
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "test_helpers.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#define SSP_DISABLE_FAST_FLOAT
|
||||
#include <ss/extract.hpp>
|
||||
|
@ -19,7 +19,7 @@
|
||||
namespace ss {
|
||||
template <typename... Ts>
|
||||
class parser;
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
||||
namespace {
|
||||
|
||||
@ -145,6 +145,17 @@ struct unique_file_name {
|
||||
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>
|
||||
[[maybe_unused]] std::vector<std::vector<T>> vector_combinations(
|
||||
const std::vector<T>& v, size_t n) {
|
||||
@ -166,6 +177,22 @@ template <typename T>
|
||||
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) {
|
||||
std::ifstream in{file_name, std::ios::binary};
|
||||
std::string tmp;
|
||||
@ -185,6 +212,7 @@ template <typename T>
|
||||
}
|
||||
};
|
||||
|
||||
// Evade small string optimization
|
||||
out.reserve(sizeof(out) + 1);
|
||||
|
||||
copy_if_whitespaces();
|
||||
@ -224,4 +252,4 @@ make_parser(const std::string& file_name,
|
||||
return make_parser_impl<buffer_mode, Ts...>(file_name, delim);
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
} /* anonymous namespace */
|
||||
|
@ -12,7 +12,8 @@
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
[[maybe_unused]] void replace_all(std::string& s, const std::string& from,
|
||||
#ifdef _WIN32
|
||||
void replace_all(std::string& s, const std::string& from,
|
||||
const std::string& to) {
|
||||
if (from.empty()) return;
|
||||
size_t start_pos = 0;
|
||||
@ -21,6 +22,7 @@ namespace {
|
||||
start_pos += to.length();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename... Ts>
|
||||
void expect_error_on_command(ss::parser<Ts...>& p,
|
||||
@ -28,6 +30,7 @@ void expect_error_on_command(ss::parser<Ts...>& p,
|
||||
if (ss::setup<Ts...>::throw_on_error) {
|
||||
try {
|
||||
command();
|
||||
FAIL("expected exception");
|
||||
} catch (const std::exception& e) {
|
||||
CHECK_FALSE(std::string{e.what()}.empty());
|
||||
}
|
||||
@ -55,7 +58,7 @@ struct X {
|
||||
double d;
|
||||
std::string s;
|
||||
|
||||
std::string to_string() const {
|
||||
[[nodiscard]] std::string to_string() const {
|
||||
if (s == empty) {
|
||||
return "";
|
||||
}
|
||||
@ -66,14 +69,15 @@ struct X {
|
||||
.append(delim)
|
||||
.append(s);
|
||||
}
|
||||
auto tied() const {
|
||||
|
||||
[[nodiscard]] auto tied() const {
|
||||
return std::tie(i, d, s);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(const T& lhs,
|
||||
const T& rhs) {
|
||||
[[nodiscard]] std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(
|
||||
const T& lhs, const T& rhs) {
|
||||
return lhs.tied() == rhs.tied();
|
||||
}
|
||||
|
||||
@ -109,4 +113,4 @@ static void make_and_write(const std::string& file_name,
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
} /* anonymous namespace */
|
||||
|
@ -57,7 +57,7 @@ struct Y {
|
||||
.append(s3);
|
||||
}
|
||||
|
||||
auto tied() const {
|
||||
[[nodiscard]] auto tied() const {
|
||||
return std::tie(s1, s2, s3);
|
||||
}
|
||||
};
|
||||
@ -115,7 +115,8 @@ TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) {
|
||||
CHECK_EQ(p.line(), expected_line);
|
||||
|
||||
while (!p.eof()) {
|
||||
auto _ = p.template get_next<std::string, std::string, std::string>();
|
||||
std::ignore =
|
||||
p.template get_next<std::string, std::string, std::string>();
|
||||
++expected_line;
|
||||
CHECK_EQ(p.line(), expected_line);
|
||||
}
|
||||
|
@ -51,12 +51,14 @@ TEST_CASE_TEMPLATE("test moving of parsed composite values", T,
|
||||
// to compile is enough
|
||||
return;
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>("", "");
|
||||
std::ignore =
|
||||
p.template try_next<my_string, my_string, my_string>()
|
||||
.template or_else<my_string, my_string, my_string, my_string>(
|
||||
[](auto&&) {})
|
||||
.template or_else<my_string>([](auto&) {})
|
||||
.template or_else<xyz>([](auto&&) {})
|
||||
.template or_object<xyz, my_string, my_string, my_string>([](auto&&) {})
|
||||
.template or_object<xyz, my_string, my_string, my_string>(
|
||||
[](auto&&) {})
|
||||
.template or_else<std::tuple<my_string, my_string, my_string>>(
|
||||
[](auto&, auto&, auto&) {});
|
||||
}
|
||||
@ -73,7 +75,7 @@ TEST_CASE_TEMPLATE("parser test string error mode", BufferMode, std::true_type,
|
||||
auto [p, _] = make_parser<BufferMode::value, ss::string_error>(f.name, ",");
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
p.template get_next<int>();
|
||||
std::ignore = p.template get_next<int>();
|
||||
CHECK_FALSE(p.valid());
|
||||
CHECK_FALSE(p.error_msg().empty());
|
||||
}
|
||||
@ -92,7 +94,7 @@ TEST_CASE_TEMPLATE("parser throw on error mode", BufferMode, std::true_type,
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
try {
|
||||
p.template get_next<int>();
|
||||
std::ignore = p.template get_next<int>();
|
||||
FAIL("Expected exception...");
|
||||
} catch (const std::exception& e) {
|
||||
CHECK_FALSE(std::string{e.what()}.empty());
|
||||
@ -148,6 +150,7 @@ TEST_CASE_TEMPLATE("test quote multiline", T, ParserOptionCombinations) {
|
||||
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
|
||||
while (!p.eof()) {
|
||||
auto command = [&p_no_multiline = p_no_multiline] {
|
||||
std::ignore =
|
||||
p_no_multiline.template get_next<int, double, std::string>();
|
||||
};
|
||||
expect_error_on_command(p_no_multiline, command);
|
||||
|
@ -16,13 +16,14 @@ TEST_CASE_TEMPLATE("test multiline restricted", T, ParserOptionCombinations) {
|
||||
out << "5,6,just\\\n\\\nstrings" << std::endl;
|
||||
#endif
|
||||
out << "7,8,ju\\\n\\\n\\\nnk" << std::endl;
|
||||
out << "99,100,\"\n\n\n\n" << std::endl;
|
||||
out << "9,10,\"just\\\n\nstrings\"" << std::endl;
|
||||
out << "11,12,\"ju\\\n|\n\n\n\n\nk\"" << std::endl;
|
||||
out << "13,14,\"ju\\\n\\\n15,16\"\\\n\\\\\n\nnk\"" << std::endl;
|
||||
out << "17,18,\"ju\\\n\\\n\\\n\\\\\n\nnk\"" << std::endl;
|
||||
out << "19,20,just strings" << std::endl;
|
||||
}
|
||||
auto bad_lines = 15;
|
||||
auto bad_lines = 20;
|
||||
auto num_errors = 0;
|
||||
|
||||
auto [p, _] =
|
||||
@ -82,7 +83,7 @@ void test_unterminated_line(const std::vector<std::string>& lines,
|
||||
size_t line = 0;
|
||||
while (!p.eof()) {
|
||||
auto command = [&p = p] {
|
||||
p.template get_next<int, double, std::string>();
|
||||
std::ignore = p.template get_next<int, double, std::string>();
|
||||
};
|
||||
|
||||
if (line == bad_line) {
|
||||
|
@ -9,6 +9,7 @@ struct has_type<T, std::tuple<Us...>>
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
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) {
|
||||
constexpr auto buffer_mode = T::BufferMode::value;
|
||||
using ErrorMode = typename T::ErrorMode;
|
||||
@ -17,9 +18,14 @@ static void test_fields(const std::string file_name, const std::vector<X>& data,
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>(file_name, ",");
|
||||
CHECK_FALSE(p.field_exists("Unknown"));
|
||||
p.use_fields(fields);
|
||||
|
||||
CHECK_EQ_ARRAY(header, p.header());
|
||||
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||
std::vector<CaseType> i;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -40,12 +46,12 @@ static void test_fields(const std::string file_name, const std::vector<X>& data,
|
||||
TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||
ParserOptionCombinations) {
|
||||
unique_file_name f{"various_cases_with_header"};
|
||||
using str = std::string;
|
||||
|
||||
constexpr static auto Int = "Int";
|
||||
constexpr static auto Dbl = "Double";
|
||||
constexpr static auto Str = "String";
|
||||
using str = std::string;
|
||||
|
||||
std::vector<std::string> header{Int, Dbl, Str};
|
||||
const std::vector<std::string> header{Int, Dbl, Str};
|
||||
|
||||
std::vector<X> data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"},
|
||||
{7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}};
|
||||
@ -59,6 +65,8 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||
std::vector<X> i;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -71,46 +79,22 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||
|
||||
p.ignore_next();
|
||||
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));
|
||||
}
|
||||
|
||||
CHECK_EQ(i, data);
|
||||
}
|
||||
|
||||
{
|
||||
ss::parser<ss::ignore_header> p{f.name, ","};
|
||||
std::vector<X> i;
|
||||
|
||||
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, ","};
|
||||
CHECK_EQ(header, p.header());
|
||||
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
|
||||
|
||||
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>();
|
||||
@ -119,6 +103,8 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||
}
|
||||
|
||||
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>();
|
||||
@ -163,25 +149,25 @@ TEST_CASE_TEMPLATE("test various cases with header", T,
|
||||
template_params.append(type)
|
||||
arg_params.append(header[type])
|
||||
call = 'testFields<' + ', '.join(template_params) + \
|
||||
'>(o, d, {' + ', '.join(arg_params) + '});'
|
||||
'>(o, d, header, {' + ', '.join(arg_params) + '});'
|
||||
print(call)
|
||||
*/
|
||||
|
||||
test_fields<T, str>(o, d, {Str});
|
||||
test_fields<T, int>(o, d, {Int});
|
||||
test_fields<T, double>(o, d, {Dbl});
|
||||
test_fields<T, str, int>(o, d, {Str, Int});
|
||||
test_fields<T, str, double>(o, d, {Str, Dbl});
|
||||
test_fields<T, int, str>(o, d, {Int, Str});
|
||||
test_fields<T, int, double>(o, d, {Int, Dbl});
|
||||
test_fields<T, double, str>(o, d, {Dbl, Str});
|
||||
test_fields<T, double, int>(o, d, {Dbl, Int});
|
||||
test_fields<T, str, int, double>(o, d, {Str, Int, Dbl});
|
||||
test_fields<T, str, double, int>(o, d, {Str, Dbl, Int});
|
||||
test_fields<T, int, str, double>(o, d, {Int, Str, Dbl});
|
||||
test_fields<T, int, double, str>(o, d, {Int, Dbl, Str});
|
||||
test_fields<T, double, str, int>(o, d, {Dbl, Str, Int});
|
||||
test_fields<T, double, int, str>(o, d, {Dbl, Int, Str});
|
||||
test_fields<T, str>(o, d, header, {Str});
|
||||
test_fields<T, int>(o, d, header, {Int});
|
||||
test_fields<T, double>(o, d, header, {Dbl});
|
||||
test_fields<T, str, int>(o, d, header, {Str, Int});
|
||||
test_fields<T, str, double>(o, d, header, {Str, Dbl});
|
||||
test_fields<T, int, str>(o, d, header, {Int, Str});
|
||||
test_fields<T, int, double>(o, d, header, {Int, Dbl});
|
||||
test_fields<T, double, str>(o, d, header, {Dbl, Str});
|
||||
test_fields<T, double, int>(o, d, header, {Dbl, Int});
|
||||
test_fields<T, str, int, double>(o, d, header, {Str, Int, Dbl});
|
||||
test_fields<T, str, double, int>(o, d, header, {Str, Dbl, Int});
|
||||
test_fields<T, int, str, double>(o, d, header, {Int, Str, Dbl});
|
||||
test_fields<T, int, double, str>(o, d, header, {Int, Dbl, Str});
|
||||
test_fields<T, double, str, int>(o, d, header, {Dbl, Str, Int});
|
||||
test_fields<T, double, int, str>(o, d, header, {Dbl, Int, Str});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -190,6 +176,18 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
constexpr auto buffer_mode = T::BufferMode::value;
|
||||
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"};
|
||||
{
|
||||
std::ofstream out{f.name};
|
||||
@ -203,6 +201,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||
auto command = [&p = p] { p.use_fields(); };
|
||||
expect_error_on_command(p, command);
|
||||
check_header(p);
|
||||
}
|
||||
|
||||
{
|
||||
@ -210,6 +209,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||
auto command = [&p = p] { p.use_fields("Unknown"); };
|
||||
expect_error_on_command(p, command);
|
||||
check_header(p);
|
||||
}
|
||||
|
||||
{
|
||||
@ -221,6 +221,7 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
if (!fields.empty()) {
|
||||
expect_error_on_command(p, command);
|
||||
}
|
||||
check_header(p);
|
||||
}
|
||||
|
||||
{
|
||||
@ -228,17 +229,21 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||
auto command = [&p = p, &fields = fields] {
|
||||
p.use_fields(fields.at(0));
|
||||
p.template get_next<std::string, std::string>();
|
||||
std::ignore = p.template get_next<std::string, std::string>();
|
||||
};
|
||||
check_header(p);
|
||||
|
||||
if (!fields.empty()) {
|
||||
expect_error_on_command(p, command);
|
||||
}
|
||||
check_header(p);
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid header
|
||||
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
|
||||
auto command = [&p = p, &fields = fields] { p.use_fields(fields); };
|
||||
check_header(p);
|
||||
|
||||
if (!fields.empty()) {
|
||||
// Pass if there are no duplicates, fail otherwise
|
||||
@ -255,10 +260,11 @@ void test_invalid_fields(const std::vector<std::string>& lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
check_header(p);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("test invalid fheader fields usage", T,
|
||||
TEST_CASE_TEMPLATE("test invalid header fields usage", T,
|
||||
ParserOptionCombinations) {
|
||||
test_invalid_fields<T>({}, {});
|
||||
|
||||
@ -289,7 +295,7 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||
constexpr auto buffer_mode = T::BufferMode::value;
|
||||
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};
|
||||
out << "Int,String,Double" << std::endl;
|
||||
@ -301,8 +307,12 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||
out << "six,line6,10.11" << std::endl;
|
||||
}
|
||||
|
||||
std::vector<std::string> header = {"Int", "String", "Double"};
|
||||
|
||||
{
|
||||
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");
|
||||
using data = std::tuple<int, std::string, double>;
|
||||
@ -325,10 +335,14 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||
{3, "line3", 67.8},
|
||||
{5, "line5", 9.10}};
|
||||
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);
|
||||
CHECK_EQ_ARRAY(header, p.header());
|
||||
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||
|
||||
p.use_fields("Double", "Int");
|
||||
using data = std::tuple<double, int>;
|
||||
@ -349,10 +363,14 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||
|
||||
std::vector<data> expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}};
|
||||
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);
|
||||
CHECK_EQ_ARRAY(header, p.header());
|
||||
CHECK_EQ(merge_header(p.header()), p.raw_header());
|
||||
|
||||
p.use_fields("String", "Double");
|
||||
using data = std::tuple<std::string, double>;
|
||||
@ -376,96 +394,7 @@ TEST_CASE_TEMPLATE("test invalid rows with header", T,
|
||||
{"line5", 9.10},
|
||||
{"line6", 10.11}};
|
||||
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>({});
|
||||
}
|
||||
|
301
test/test_parser1_5.cpp
Normal file
301
test/test_parser1_5.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
#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>({});
|
||||
}
|
@ -1,13 +1,7 @@
|
||||
#include "test_helpers.hpp"
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <ss/parser.hpp>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#ifndef SEGMENT_NAME
|
||||
@ -91,7 +85,7 @@ struct column {
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
column make_column(const std::string& input_header,
|
||||
[[nodiscard]] column make_column(const std::string& input_header,
|
||||
const std::vector<field>& input_fields) {
|
||||
using setup = ss::setup<Ts...>;
|
||||
std::vector<field> filtered_fields;
|
||||
@ -133,8 +127,8 @@ column make_column(const std::string& input_header,
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
std::vector<std::string> generate_csv_data(const std::vector<field>& data,
|
||||
const std::string& delim) {
|
||||
[[nodiscard]] std::vector<std::string> generate_csv_data(
|
||||
const std::vector<field>& data, const std::string& delim) {
|
||||
(void)delim;
|
||||
using setup = ss::setup<Ts...>;
|
||||
constexpr static auto escape = '\\';
|
||||
@ -333,8 +327,10 @@ void test_data_combinations(const std::vector<column>& input_data,
|
||||
field_header.push_back(field{el.header});
|
||||
}
|
||||
|
||||
std::string header_line;
|
||||
if (include_header) {
|
||||
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) {
|
||||
write_to_file(header_data, delim, f.name, false);
|
||||
} else {
|
||||
@ -403,7 +399,9 @@ void test_data_combinations(const std::vector<column>& input_data,
|
||||
fields.push_back(header[index]);
|
||||
}
|
||||
|
||||
if constexpr (!setup::ignore_header) {
|
||||
p.use_fields(fields);
|
||||
}
|
||||
|
||||
if (!p.valid()) {
|
||||
if constexpr (setup::string_error) {
|
||||
@ -425,8 +423,19 @@ 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();
|
||||
for (size_t i = 0; i < n + 1; ++i) {
|
||||
check_header();
|
||||
try {
|
||||
switch (num_columns) {
|
||||
case 1: {
|
||||
@ -616,7 +625,7 @@ void test_option_combinations3() {
|
||||
test_option_combinations2<Ts..., trim>();
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
} /* anonymous namespace */
|
||||
|
||||
// Tests split into multiple compilation units
|
||||
#if 0
|
||||
|
@ -9,4 +9,3 @@ TEST_CASE("parser test various cases version 2 segment 1") {
|
||||
test_option_combinations3<escape>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -10,4 +10,3 @@ TEST_CASE("parser test various cases version 2 segment 2") {
|
||||
test_option_combinations3<escape, quote>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -11,4 +11,3 @@ TEST_CASE("parser test various cases version 2 segment 3") {
|
||||
test_option_combinations3<quote, multiline>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -12,4 +12,3 @@ TEST_CASE("parser test various cases version 2 segment 4") {
|
||||
test_option_combinations3<escape, quote, multiline_r>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -13,4 +13,3 @@ TEST_CASE("parser test various cases version 2 segment 5") {
|
||||
test_option_combinations<escape, quote, multiline, trimr>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -8,4 +8,3 @@ TEST_CASE("parser test various cases version 2 segment 6") {
|
||||
|
||||
test_option_combinations3<escape, quote, multiline>();
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,14 @@
|
||||
set -x
|
||||
set -e
|
||||
|
||||
python3 script/single_header_generator.py > ssp.cpp
|
||||
TMP_HDR=test_single_header.hpp
|
||||
TMP_SRC=test_single_header.cpp
|
||||
TMP_BIN=test_single_header
|
||||
|
||||
echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
|
||||
>> ssp.cpp
|
||||
python3 script/single_header_generator.py > ${TMP_HDR}
|
||||
cat ${TMP_HDR} test/test_single_header_main.txt > ${TMP_SRC}
|
||||
|
||||
g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
|
||||
./ssp.bin
|
||||
g++ -std=c++17 ${TMP_SRC} -o ${TMP_BIN} -Wall -Wextra
|
||||
./${TMP_BIN}
|
||||
|
||||
rm ssp.cpp ssp.bin
|
||||
rm ${TMP_HDR} ${TMP_SRC} ${TMP_BIN}
|
||||
|
12
test/test_single_header_main.txt
Normal file
12
test/test_single_header_main.txt
Normal file
@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
@ -145,7 +145,7 @@ make_combinations(const std::vector<std::string>& input,
|
||||
|
||||
return {std::move(lines), std::move(expectations)};
|
||||
}
|
||||
} /* namespace */
|
||||
} /* anonymous namespace */
|
||||
|
||||
/* ********************************** */
|
||||
/* ********************************** */
|
||||
@ -548,7 +548,7 @@ public:
|
||||
return splitter.size_shifted();
|
||||
}
|
||||
};
|
||||
} /* ss */
|
||||
} /* namespace ss */
|
||||
|
||||
TEST_CASE("splitter test resplit unterminated quote") {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user