mirror of
https://github.com/red0124/ssp.git
synced 2025-01-23 13:05:20 +01:00
Merge pull request #27 from red0124/improvement/throw_on_error
Improvement/throw_on_error
This commit is contained in:
commit
e3c7db53f7
211
README.md
211
README.md
@ -17,17 +17,16 @@
|
||||
|
||||
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 convert strings to specific types.](#The-converter)
|
||||
|
||||
Conversion for floating point values invoked using [fast-float](https://github.com/fastfloat/fast_float) .
|
||||
Function traits taken from [qt-creator](https://code.woboq.org/qt5/qt-creator/src/libs/utils/functiontraits.h.html) .
|
||||
Conversion for floating point values invoked using [fast-float](https://github.com/fastfloat/fast_float) . \
|
||||
Function traits taken from *qt-creator* .
|
||||
|
||||
# Example
|
||||
Lets say we have a csv file containing students in a given format '$name,$age,$grade' and we want to parse and print all the valid values:
|
||||
Lets say we have a csv file containing students in a given format |Id,Age,Grade| and we want to parse and print all the valid values:
|
||||
|
||||
```shell
|
||||
$ cat students.csv
|
||||
James Bailey,65,2.5
|
||||
Brian S. Wolfe,40,1.9
|
||||
Nathan Fielder,37,Really good grades
|
||||
Bill (Heath) Gates,65,3.3
|
||||
```
|
||||
```cpp
|
||||
@ -35,12 +34,10 @@ Bill (Heath) Gates,65,3.3
|
||||
#include <ss/parser.hpp>
|
||||
|
||||
int main() {
|
||||
ss::parser p{"students.csv", ","};
|
||||
ss::parser<ss::throw_on_error> p{"students.csv"};
|
||||
|
||||
for(const auto& [name, age, grade] : p.iterate<std::string, int, float>()) {
|
||||
if (p.valid()) {
|
||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
||||
}
|
||||
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
|
||||
std::cout << id << ' ' << age << ' ' << grade << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -56,14 +53,14 @@ Bill (Heath) Gates 65 3.3
|
||||
# Features
|
||||
* [Works on any type](#custom-conversions)
|
||||
* Easy to use
|
||||
* No exceptions
|
||||
* Can work without exceptions
|
||||
* [Works with headers](#headers)
|
||||
* [Works with quotes, escapes and spacings](#setup)
|
||||
* [Works with values containing new lines](#multiline)
|
||||
* [Columns and rows can be ignored](#special-types)
|
||||
* Works with any type of delimiter
|
||||
* [Works with any type of delimiter](#delimiter)
|
||||
* Can return whole objects composed of converted values
|
||||
* [Descriptive error handling can be enabled](#error-handling)
|
||||
* [Error handling can be configured](#error-handling)
|
||||
* [Restrictions can be added for each column](#restrictions)
|
||||
* [Works with `std::optional` and `std::variant`](#special-types)
|
||||
* Works with **`CRLF`** and **`LF`**
|
||||
@ -83,7 +80,7 @@ $ cmake --configure .
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
*Note, this will also install the fast_float library*
|
||||
*Note, this will also install the fast_float library.*\
|
||||
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
||||
|
||||
# Usage
|
||||
@ -93,20 +90,20 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
||||
The parser can be told to use only certain columns by parsing the header. This can be done by using the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
|
||||
```shell
|
||||
$ cat students_with_header.csv
|
||||
Name,Age,Grade
|
||||
Id,Age,Grade
|
||||
James Bailey,65,2.5
|
||||
Brian S. Wolfe,40,1.9
|
||||
Bill (Heath) Gates,65,3.3
|
||||
```
|
||||
```cpp
|
||||
// ...
|
||||
ss::parser p{"students.csv", ","};
|
||||
p.use_fields("Name", "Grade");
|
||||
// ...
|
||||
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||
p.use_fields("Id", "Grade");
|
||||
|
||||
for(const auto& [name, grade] : p.iterate<std::string, float>()) {
|
||||
std::cout << name << ' ' << grade << std::endl;
|
||||
}
|
||||
// ...
|
||||
for(const auto& [id, grade] : p.iterate<std::string, float>()) {
|
||||
std::cout << id << ' ' << grade << std::endl;
|
||||
}
|
||||
// ...
|
||||
```
|
||||
```shell
|
||||
$ ./a.out
|
||||
@ -121,17 +118,16 @@ 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.
|
||||
```cpp
|
||||
// ...
|
||||
ss::parser p{"students.csv", ","};
|
||||
p.use_fields("Name", "Grade");
|
||||
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
|
||||
p.use_fields("Id", "Grade");
|
||||
|
||||
const auto& [name, grade] = p.get_next<std::string, float>();
|
||||
std::cout << name << ' ' << grade << std::endl;
|
||||
const auto& [id, grade] = p.get_next<std::string, float>();
|
||||
std::cout << id << ' ' << grade << std::endl;
|
||||
|
||||
if (p.field_exists("Age")) {
|
||||
p.use_fields("Grade", "Name", "Age");
|
||||
for (const auto& [grade, name, age] :
|
||||
p.iterate<float, std::string, int>()) {
|
||||
std::cout << grade << ' ' << name << ' ' << age << std::endl;
|
||||
if (p.field_exists("Id")) {
|
||||
p.use_fields("Grade", "Id");
|
||||
for (const auto& [grade, id] : p.iterate<float, std::string>()) {
|
||||
std::cout << grade << ' ' << id << std::endl;
|
||||
}
|
||||
}
|
||||
// ...
|
||||
@ -139,21 +135,26 @@ The fields with which the parser works with can be modified at any given time. T
|
||||
```shell
|
||||
$ ./a.out
|
||||
James Bailey 2.5
|
||||
1.9 Brian S. Wolfe 40
|
||||
3.3 Bill (Heath) Gates 65
|
||||
40 Brian S. Wolfe
|
||||
65 Bill (Heath) Gates
|
||||
```
|
||||
## Conversions
|
||||
An alternate loop to the example above would look like:
|
||||
```cpp
|
||||
while(!p.eof()) {
|
||||
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
||||
// ...
|
||||
ss::parser p{"students.csv"};
|
||||
|
||||
while (!p.eof()) {
|
||||
const auto& [id, age, grade] = p.get_next<std::string, int, float>();
|
||||
|
||||
if (p.valid()) {
|
||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
||||
std::cout << id << ' ' << age << ' ' << grade << std::endl;
|
||||
}
|
||||
}
|
||||
// ...
|
||||
```
|
||||
|
||||
The alternate example will be used to show some of the features of the library. The **`get_next`** method returns a tuple of objects specified inside the template type list.
|
||||
The alternate example with exceptions disabled will be used to show some of the features of the library. The **`get_next`** method returns a tuple of objects specified inside the template type list.
|
||||
|
||||
If a conversion could not be applied, the method would return a tuple of default constructed objects, and the **`valid`** method would return **`false`**, for example if the third (grade) column in our csv could not be converted to a float the conversion would fail.
|
||||
|
||||
@ -162,14 +163,14 @@ If **`get_next`** is called with a **`tuple`** as template parameter it would be
|
||||
using student = std::tuple<std::string, int, float>;
|
||||
|
||||
// returns std::tuple<std::string, int, float>
|
||||
auto [name, age, grade] = p.get_next<student>();
|
||||
auto [id, age, grade] = p.get_next<student>();
|
||||
```
|
||||
*Note, it does not always return a student tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)*
|
||||
*Note, it does not always return the specified tuple since the returned tuples parameters may be altered as explained below (no void, no restrictions, ...)*
|
||||
|
||||
Whole objects can be returned using the **`get_object`** function which takes the tuple, created in a similar way as **`get_next`** does it, and creates an object out of it:
|
||||
```cpp
|
||||
struct student {
|
||||
std::string name;
|
||||
std::string id;
|
||||
int age;
|
||||
float grade;
|
||||
};
|
||||
@ -180,24 +181,23 @@ auto student = p.get_object<student, std::string, int, float>();
|
||||
```
|
||||
This works with any object if the constructor could be invoked using the template arguments given to **`get_object`**:
|
||||
```cpp
|
||||
// returns std::vector<std::string> containing 3 elements
|
||||
auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
|
||||
std::string>();
|
||||
// returns std::vector<std::string> containing 2 elements
|
||||
auto vec = p.get_object<std::vector<std::string>, std::string, std::string>();
|
||||
```
|
||||
An iteration loop as in the first example which returns objects would look like:
|
||||
An iterator loop as in the first example which returns objects would look like:
|
||||
```cpp
|
||||
for(const auto& student : p.iterate_object<student, std::string, int, float>()) {
|
||||
// ...
|
||||
for (const student& s : p.iterate_object<student, std::string, int, float>()) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
And finally, using something I personally like to do, a struct (class) with a **`tied`** method which returns a tuple of references to to the members of the struct.
|
||||
```cpp
|
||||
struct student {
|
||||
std::string name;
|
||||
std::string id;
|
||||
int age;
|
||||
float grade;
|
||||
|
||||
auto tied() { return std::tie(name, age, grade); }
|
||||
auto tied() { return std::tie(id, age, grade); }
|
||||
};
|
||||
```
|
||||
The method can be used to compare the object, serialize it, deserialize it, etc. Now **`get_next`** can accept such a struct and deduce the types to which to convert the csv.
|
||||
@ -225,7 +225,14 @@ using my_setup = ss::setup<ss::escape<'\\'>, ss::quote<'"'>>;
|
||||
ss::parser<my_setup> p2{file_name};
|
||||
```
|
||||
Invalid setups will be met with **`static_asserts`**.
|
||||
*Note, each setup parameter defined comes with a slight performance loss, so use them only if needed.*
|
||||
*Note, most setup parameters defined come with a slight performance loss, so use them only if needed.*
|
||||
|
||||
### Delimiter
|
||||
By default, **`,`** is used as the delimiter, a custom delimiter can be specified as the second constructor parameter.
|
||||
```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.*
|
||||
|
||||
### Empty lines
|
||||
Empty lines can be ignored by defining **`ss::ignore_empty`** within the setup parameters:
|
||||
@ -307,14 +314,11 @@ ss::parser<ss::escape<'\\'>,
|
||||
ss::trim<' ', '\t'>,
|
||||
ss::multiline_restricted<5>> p{file_name};
|
||||
|
||||
while(!p.eof()) {
|
||||
auto [name, age, grade] = p.get_next<std::string, int, float>();
|
||||
if(!p.valid()) {
|
||||
continue;
|
||||
for (const auto& [id, age, grade] : p.iterate<std::string, int, float>()) {
|
||||
if (p.valid()) {
|
||||
std::cout << "'" << id << ' ' << age << ' ' << grade << "'\n";
|
||||
}
|
||||
std::cout << "'" << name << ' ' << age << ' ' << grade << "'" << std::endl;
|
||||
}
|
||||
|
||||
```
|
||||
input:
|
||||
```
|
||||
@ -335,22 +339,22 @@ Gates 65 3.3'
|
||||
```
|
||||
## Special types
|
||||
|
||||
Passing **`void`** makes the parser ignore a column. In the given example **`void`** could be given as the second template parameter to ignore the second (age) column in the csv, a tuple of only 2 parameters would be retuned:
|
||||
Passing **`void`** makes the parser ignore a column. In the initial example **`void`** could be given as the second template parameter to ignore the second (age) column in the csv, a tuple of only 2 parameters would be retuned:
|
||||
```cpp
|
||||
// returns std::tuple<std::string, float>
|
||||
auto [name, grade] = p.get_next<std::string, void, float>();
|
||||
auto [id, grade] = p.get_next<std::string, void, float>();
|
||||
```
|
||||
Works with different types of conversions too:
|
||||
```cpp
|
||||
using student = std::tuple<std::string, void, float>;
|
||||
|
||||
// returns std::tuple<std::string, float>
|
||||
auto [name, grade] = p.get_next<student>();
|
||||
auto [id, grade] = p.get_next<student>();
|
||||
```
|
||||
Values can also be converted to **`std::string_view`**. It is more efficient then converting values to **`std::string`** but one must be careful with the lifetime of it.
|
||||
```cpp
|
||||
// string_view name stays valid until the next line is read
|
||||
auto [name, age, grade] = p.get_next<std::string_view, int, float>();
|
||||
// string_view id stays valid until the next line is read
|
||||
auto [id, age, grade] = p.get_next<std::string_view, int, float>();
|
||||
```
|
||||
|
||||
To ignore a whole row, **`ignore_next`** could be used, returns **`false`** if **`eof`**:
|
||||
@ -361,19 +365,19 @@ bool parser::ignore_next();
|
||||
|
||||
```cpp
|
||||
// returns std::tuple<std::string, int, std::optional<float>>
|
||||
auto [name, age, grade] = p.get_next<std::string, int, std::optional<float>();
|
||||
if(grade) {
|
||||
// do something with grade
|
||||
auto [id, age, grade] = p.get_next<std::string, int, std::optional<float>>();
|
||||
if (grade) {
|
||||
std::cout << grade.value() << std::endl;
|
||||
}
|
||||
```
|
||||
Similar to **`std::optional`**, **`std::variant`** could be used to try other conversions if the previous failed _(Note, conversion to std::string will always pass)_:
|
||||
```cpp
|
||||
// returns std::tuple<std::string, int, std::variant<float, char>>
|
||||
auto [name, age, grade] =
|
||||
p.get_next<std::string, int, std::variant<float, char>();
|
||||
if(std::holds_alternative<float>(grade)) {
|
||||
auto [id, age, grade] =
|
||||
p.get_next<std::string, int, std::variant<float, char>>();
|
||||
if (std::holds_alternative<float>(grade)) {
|
||||
// grade set as float
|
||||
} else if(std::holds_alternative<char>(grade)) {
|
||||
} else if (std::holds_alternative<char>(grade)) {
|
||||
// grade set as char
|
||||
}
|
||||
```
|
||||
@ -381,10 +385,10 @@ if(std::holds_alternative<float>(grade)) {
|
||||
|
||||
Custom **`restrictions`** can be used to narrow down the conversions of unwanted values. **`ss::ir`** (in range) and **`ss::ne`** (none empty) are some of those:
|
||||
```cpp
|
||||
// ss::ne makes sure that the name is not empty
|
||||
// ss::ne makes sure that the id is not empty
|
||||
// ss::ir makes sure that the grade will be in range [0, 10]
|
||||
// returns std::tuple<std::string, int, float>
|
||||
auto [name, age, grade] =
|
||||
auto [id, age, grade] =
|
||||
p.get_next<ss::ne<std::string>, int, ss::ir<float, 0, 10>>();
|
||||
```
|
||||
If the restrictions are not met, the conversion will fail. Other predefined restrictions are **`ss::ax`** (all except), **`ss::nx`** (none except) and **`ss::oor`** (out of range), **`ss::lt`** (less than), ...(see *restrictions.hpp*):
|
||||
@ -411,9 +415,11 @@ struct even {
|
||||
};
|
||||
```
|
||||
```cpp
|
||||
// only even numbers will pass
|
||||
// ...
|
||||
// only even numbers will pass without invoking error handling
|
||||
// returns std::tuple<std::string, int>
|
||||
auto [name, age] = p.get_next<std::string, even<int>, void>();
|
||||
const auto& [id, age] = p.get_next<std::string, even<int>, void>();
|
||||
// ...
|
||||
```
|
||||
## Custom conversions
|
||||
|
||||
@ -440,21 +446,34 @@ The shape enum will be used in an example below. The **`inline`** is there just
|
||||
|
||||
## Error handling
|
||||
|
||||
By default, the parser handles errors only using the **`valid`** method which would return **`false`** if the file could not be opened, or if the conversion could not be made (invalid types, invalid number of columns, ...).\
|
||||
The **`eof`** method can be used to detect if the end of the file was reached.
|
||||
|
||||
Detailed error messages can be accessed via the **`error_msg`** method, and to enable them **`ss::string_error`** needs to be included in the setup. If **`ss::string_error`** is not defined, the **`error_msg`** method will not be defined either.
|
||||
|
||||
The line number can be fetched using the **`line`** method.
|
||||
|
||||
```cpp
|
||||
const std::string& parser::error_msg();
|
||||
bool parser::valid();
|
||||
bool parser::eof();
|
||||
size_t parser::line();
|
||||
|
||||
// ...
|
||||
ss::parser<ss::string_error> parser;
|
||||
```
|
||||
An error can be detected using the **`valid`** method which would return **`false`** if the file could not be opened, or if the conversion could not be made (invalid types, invalid number of columns, ...). The **`eof`** method can be used to detect if the end of the file was reached.
|
||||
|
||||
The above two methods are preferable if invalid inputs are expected and allows for fast handling, but the parser can also be forced to throw an exception in case of an invalid input using the **`ss::throw_on_error`** setup option. Enabling exceptions also makes the **`valid`** method always return **`true`**.
|
||||
|
||||
```cpp
|
||||
ss::parser<ss::throw_on_error> parser;
|
||||
```
|
||||
*Note, enabling this option will also make the parser throw if the constructor fails.*
|
||||
|
||||
## Substitute conversions
|
||||
|
||||
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario.
|
||||
The parser can also be used to effectively parse files whose rows are not always in the same format (not a classical csv but still csv-like). A more complicated example would be the best way to demonstrate such a scenario.\
|
||||
***Important, substitute conversions do not work when throw_on_error is enabled.***
|
||||
|
||||
Supposing we have a file containing different shapes in given formats:
|
||||
* circle RADIUS
|
||||
@ -483,11 +502,16 @@ while (!p.eof()) {
|
||||
using udbl = ss::gte<double, 0>;
|
||||
|
||||
auto [circle_or_square, rectangle, triangle] =
|
||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>()
|
||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>()
|
||||
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>()
|
||||
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>()
|
||||
.values();
|
||||
|
||||
if (!p.valid()) {
|
||||
// handle error
|
||||
continue;
|
||||
}
|
||||
|
||||
if (circle_or_square) {
|
||||
auto& [s, x] = circle_or_square.value();
|
||||
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
||||
@ -531,31 +555,34 @@ Each of those **`composite`** conversions can accept a lambda (or anything calla
|
||||
// non negative double
|
||||
using udbl = ss::gte<double, 0>;
|
||||
|
||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>(
|
||||
[&](const auto& data) {
|
||||
const auto& [s, x] = data;
|
||||
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
||||
shapes.emplace_back(s, area);
|
||||
})
|
||||
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>(
|
||||
[&](const shape s, const double a, const double b) {
|
||||
shapes.emplace_back(s, a * b);
|
||||
})
|
||||
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>(
|
||||
[&](auto&& s, auto& a, const double& b, double& c) {
|
||||
double sh = (a + b + c) / 2;
|
||||
if (sh >= a && sh >= b && sh >= c) {
|
||||
double area = sqrt(sh * (sh - a) * (sh - b) * (sh - c));
|
||||
shapes.emplace_back(s, area);
|
||||
}
|
||||
while (!p.eof()) {
|
||||
p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>(
|
||||
[&](const auto& data) {
|
||||
const auto& [s, x] = data;
|
||||
double area = (s == shape::circle) ? x * x * M_PI : x * x;
|
||||
shapes.emplace_back(s, area);
|
||||
})
|
||||
.or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>(
|
||||
[&](shape s, double a, double b) { shapes.emplace_back(s, a * b); })
|
||||
.or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>(
|
||||
[&](auto s, auto a, auto b, auto c) {
|
||||
double sh = (a + b + c) / 2;
|
||||
if (sh >= a && sh >= b && sh >= c) {
|
||||
double area = sqrt(sh * (sh - a) * (sh - b) * (sh - c));
|
||||
shapes.emplace_back(s, area);
|
||||
}
|
||||
})
|
||||
.on_error([] {
|
||||
// handle error
|
||||
});
|
||||
}
|
||||
```
|
||||
It is a bit less readable, but it removes the need to check which conversion was invoked. The **`composite`** also has an **`on_error`** method which accepts a lambda which will be invoked if no previous conversions were successful. The lambda can take no arguments or just one argument, an **`std::string`**, in which the error message is stored if **`string_error`** is enabled:
|
||||
```cpp
|
||||
p.try_next<int>()
|
||||
.on_error([](const std::string& e) { /* int conversion failed */ })
|
||||
.or_object<x, double>()
|
||||
.on_error([] { /* int and x (all) conversions failed */ });
|
||||
.on_error([] { /* int and x conversions failed (all previous failed) */ });
|
||||
```
|
||||
*See unit tests for more examples.*
|
||||
|
||||
@ -584,7 +611,7 @@ if (c.valid()) {
|
||||
All setup parameters, special types and restrictions work on the converter too.
|
||||
Error handling is also identical to error handling of the parser.
|
||||
|
||||
The converter has also the ability to just split the line, ~~tho it does not change it (kinda statically), hence the name of the library~~ and depending if either quoting or escaping are enabled it may change the line, rather than creating a copy, for performance reasons (the name of the library does not apply anymore, I may change it). It returns an **`std::vector`** of **`std::pair`**s of pointers, begin and end, each pair representing a split segment (column) of the whole string. The vector can then be used in a overloaded **`convert`** method. This allows the reuse of the same line without splitting it on every conversion.
|
||||
The converter has also the ability to just split the line, and depending if either quoting or escaping are enabled it may change the line, rather than creating a copy, for performance reasons. It returns an **`std::vector`** of **`std::pair`**s of pointers, begin and end, each pair representing a split segment (column) of the whole string. The vector can then be used in a overloaded **`convert`** method. This allows the reuse of the same line without splitting it on every conversion.
|
||||
```cpp
|
||||
ss::converter c;
|
||||
auto split_line = c.split("circle 10", " ");
|
||||
|
@ -19,6 +19,12 @@ inline void assert_string_error_defined() {
|
||||
"'string_error' needs to be enabled to use 'error_msg'");
|
||||
}
|
||||
|
||||
template <bool ThrowOnError>
|
||||
inline void assert_throw_on_error_not_defined() {
|
||||
static_assert(!ThrowOnError, "cannot handle errors manually if "
|
||||
"'throw_on_error' is enabled");
|
||||
}
|
||||
|
||||
#if __unix__
|
||||
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
||||
return getline(lineptr, n, stream);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "exception.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "function_traits.hpp"
|
||||
#include "restrictions.hpp"
|
||||
@ -95,11 +96,12 @@ constexpr bool tied_class_v = tied_class<Ts...>::value;
|
||||
// converter
|
||||
////////////////
|
||||
|
||||
template <typename... Matchers>
|
||||
template <typename... Options>
|
||||
class converter {
|
||||
using line_ptr_type = typename splitter<Matchers...>::line_ptr_type;
|
||||
using line_ptr_type = typename splitter<Options...>::line_ptr_type;
|
||||
|
||||
constexpr static auto string_error = setup<Matchers...>::string_error;
|
||||
constexpr static auto string_error = setup<Options...>::string_error;
|
||||
constexpr static auto throw_on_error = setup<Options...>::throw_on_error;
|
||||
constexpr static auto default_delimiter = ",";
|
||||
|
||||
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||
@ -119,7 +121,12 @@ public:
|
||||
no_void_validator_tup_t<Ts...> convert(
|
||||
line_ptr_type line, const std::string& delim = default_delimiter) {
|
||||
split(line, delim);
|
||||
return convert<Ts...>(splitter_.split_data_);
|
||||
if (splitter_.valid()) {
|
||||
return convert<Ts...>(splitter_.split_data_);
|
||||
} else {
|
||||
handle_error_bad_split();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// parses already split line, returns 'T' object with extracted values
|
||||
@ -162,6 +169,8 @@ public:
|
||||
bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
return true;
|
||||
} else {
|
||||
return !error_;
|
||||
}
|
||||
@ -225,98 +234,111 @@ private:
|
||||
return error;
|
||||
}
|
||||
|
||||
void set_error_unterminated_quote() {
|
||||
void handle_error_bad_split() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(splitter_.error_msg());
|
||||
} else {
|
||||
} else if constexpr (!throw_on_error) {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_unterminated_escape() {
|
||||
void handle_error_unterminated_escape() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
splitter_.set_error_unterminated_escape();
|
||||
splitter_.handle_error_unterminated_escape();
|
||||
error_.append(splitter_.error_msg());
|
||||
} else if constexpr (throw_on_error) {
|
||||
splitter_.handle_error_unterminated_escape();
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_multiline_limit_reached() {
|
||||
void handle_error_unterminated_quote() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("multiline limit reached.");
|
||||
splitter_.handle_error_unterminated_quote();
|
||||
error_.append(splitter_.error_msg());
|
||||
} else if constexpr (throw_on_error) {
|
||||
splitter_.handle_error_unterminated_quote();
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_invalid_conversion(const string_range msg, size_t pos) {
|
||||
void handle_error_multiline_limit_reached() {
|
||||
constexpr static auto error_msg = "multiline limit reached";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("invalid conversion for parameter ")
|
||||
.append(error_sufix(msg, pos));
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_validate(const char* const error, const string_range msg,
|
||||
size_t pos) {
|
||||
void handle_error_invalid_conversion(const string_range msg, size_t pos) {
|
||||
constexpr static auto error_msg = "invalid conversion for parameter ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(error_msg).append(error_sufix(msg, pos));
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg + error_sufix(msg, pos)};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_error_validation_failed(const char* const error,
|
||||
const string_range msg, size_t pos) {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(error).append(" ").append(error_sufix(msg, pos));
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error + (" " + error_sufix(msg, pos))};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_number_of_columns(size_t expected_pos, size_t pos) {
|
||||
void handle_error_number_of_columns(size_t expected_pos, size_t pos) {
|
||||
constexpr static auto error_msg1 =
|
||||
"invalid number of columns, expected: ";
|
||||
constexpr static auto error_msg2 = ", got: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("invalid number of columns, expected: ")
|
||||
error_.append(error_msg1)
|
||||
.append(std::to_string(expected_pos))
|
||||
.append(", got: ")
|
||||
.append(error_msg2)
|
||||
.append(std::to_string(pos));
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg1 + std::to_string(expected_pos) +
|
||||
error_msg2 + std::to_string(pos)};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_incompatible_mapping(size_t argument_size,
|
||||
size_t mapping_size) {
|
||||
void handle_error_incompatible_mapping(size_t argument_size,
|
||||
size_t mapping_size) {
|
||||
constexpr static auto error_msg1 =
|
||||
"number of arguments does not match mapping, expected: ";
|
||||
constexpr static auto error_msg2 = ", got: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_
|
||||
.append(
|
||||
"number of arguments does not match mapping, expected: ")
|
||||
error_.append(error_msg1)
|
||||
.append(std::to_string(mapping_size))
|
||||
.append(", got: ")
|
||||
.append(error_msg2)
|
||||
.append(std::to_string(argument_size));
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_invalid_mapping() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("received empty mapping");
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_mapping_out_of_range(size_t maximum_index,
|
||||
size_t number_of_columnts) {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("maximum index: ")
|
||||
.append(std::to_string(maximum_index))
|
||||
.append(", greater then number of columns: ")
|
||||
.append(std::to_string(number_of_columnts));
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg1 + std::to_string(mapping_size) +
|
||||
error_msg2 + std::to_string(argument_size)};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
@ -329,29 +351,28 @@ private:
|
||||
template <typename... Ts>
|
||||
no_void_validator_tup_t<Ts...> convert_impl(const split_data& elems) {
|
||||
clear_error();
|
||||
using return_type = no_void_validator_tup_t<Ts...>;
|
||||
|
||||
if (!splitter_.valid()) {
|
||||
set_error_unterminated_quote();
|
||||
no_void_validator_tup_t<Ts...> ret{};
|
||||
return ret;
|
||||
handle_error_bad_split();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!columns_mapped()) {
|
||||
if (sizeof...(Ts) != elems.size()) {
|
||||
set_error_number_of_columns(sizeof...(Ts), elems.size());
|
||||
return return_type{};
|
||||
handle_error_number_of_columns(sizeof...(Ts), elems.size());
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
if (sizeof...(Ts) != column_mappings_.size()) {
|
||||
set_error_incompatible_mapping(sizeof...(Ts),
|
||||
column_mappings_.size());
|
||||
return return_type{};
|
||||
handle_error_incompatible_mapping(sizeof...(Ts),
|
||||
column_mappings_.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
if (elems.size() != number_of_columns_) {
|
||||
set_error_number_of_columns(number_of_columns_, elems.size());
|
||||
return return_type{};
|
||||
handle_error_number_of_columns(number_of_columns_,
|
||||
elems.size());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,19 +401,9 @@ private:
|
||||
return column_mappings_[tuple_position];
|
||||
}
|
||||
|
||||
// assumes positions are valid and the vector is not empty
|
||||
void set_column_mapping(std::vector<size_t> positions,
|
||||
size_t number_of_columns) {
|
||||
if (positions.empty()) {
|
||||
set_error_invalid_mapping();
|
||||
return;
|
||||
}
|
||||
|
||||
auto max_index = *std::max_element(positions.begin(), positions.end());
|
||||
if (max_index >= number_of_columns) {
|
||||
set_error_mapping_out_of_range(max_index, number_of_columns);
|
||||
return;
|
||||
}
|
||||
|
||||
column_mappings_ = positions;
|
||||
number_of_columns_ = number_of_columns;
|
||||
}
|
||||
@ -419,16 +430,17 @@ private:
|
||||
}
|
||||
|
||||
if (!extract(msg.first, msg.second, dst)) {
|
||||
set_error_invalid_conversion(msg, pos);
|
||||
handle_error_invalid_conversion(msg, pos);
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (has_m_ss_valid_t<T>) {
|
||||
if (T validator; !validator.ss_valid(dst)) {
|
||||
if constexpr (has_m_error_t<T>) {
|
||||
set_error_validate(validator.error(), msg, pos);
|
||||
handle_error_validation_failed(validator.error(), msg, pos);
|
||||
} else {
|
||||
set_error_validate("validation error", msg, pos);
|
||||
handle_error_validation_failed("validation error", msg,
|
||||
pos);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -472,7 +484,7 @@ private:
|
||||
////////////////
|
||||
|
||||
error_type error_{};
|
||||
splitter<Matchers...> splitter_;
|
||||
splitter<Options...> splitter_;
|
||||
|
||||
template <typename...>
|
||||
friend class parser;
|
||||
|
23
include/ss/exception.hpp
Normal file
23
include/ss/exception.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace ss {
|
||||
|
||||
////////////////
|
||||
// exception
|
||||
////////////////
|
||||
|
||||
class exception : public std::exception {
|
||||
std::string msg_;
|
||||
|
||||
public:
|
||||
exception(const std::string& msg): msg_{msg} {
|
||||
}
|
||||
|
||||
virtual char const* what() const noexcept {
|
||||
return msg_.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
} /* ss */
|
@ -12,9 +12,11 @@
|
||||
#ifndef SSP_DISABLE_FAST_FLOAT
|
||||
#include <fast_float/fast_float.h>
|
||||
#else
|
||||
#include <charconv>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
// TODO try from_chars for integer conversions
|
||||
namespace ss {
|
||||
|
||||
////////////////
|
||||
@ -40,12 +42,32 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
|
||||
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) {
|
||||
T ret;
|
||||
auto [ptr, ec] = std::from_chars(begin, end, ret);
|
||||
constexpr static auto buff_max = 64;
|
||||
char buff[buff_max];
|
||||
size_t string_range = std::distance(begin, end);
|
||||
|
||||
if (ec != std::errc() || ptr != end) {
|
||||
if (string_range > buff_max) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::copy_n(begin, string_range, buff);
|
||||
buff[string_range] = '\0';
|
||||
|
||||
T ret;
|
||||
char* parse_end = nullptr;
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
ret = std::strtof(buff, &parse_end);
|
||||
} else if constexpr (std::is_same_v<T, double>) {
|
||||
ret = std::strtod(buff, &parse_end);
|
||||
} else if constexpr (std::is_same_v<T, long double>) {
|
||||
ret = std::strtold(buff, &parse_end);
|
||||
}
|
||||
|
||||
if (parse_end != buff + string_range) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -167,6 +189,7 @@ inline bool sub_overflow(long long& result, long long operand) {
|
||||
return __builtin_ssubll_overflow(result, operand, &result);
|
||||
}
|
||||
|
||||
// Note: sub_overflow function should be unreachable for unsigned values
|
||||
template <>
|
||||
inline bool sub_overflow(unsigned int& result, unsigned int operand) {
|
||||
return __builtin_usub_overflow(result, operand, &result);
|
||||
@ -184,8 +207,8 @@ inline bool sub_overflow(unsigned long long& result,
|
||||
}
|
||||
|
||||
template <typename T, typename F>
|
||||
bool shift_and_add_overflow(T& value, T digit, F add_last_digit_owerflow) {
|
||||
if (mul_overflow<T>(value, 10) || add_last_digit_owerflow(value, digit)) {
|
||||
bool shift_and_add_overflow(T& value, T digit, F add_last_digit_overflow) {
|
||||
if (mul_overflow<T>(value, 10) || add_last_digit_overflow(value, digit)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -223,17 +246,17 @@ std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
|
||||
|
||||
#if (defined(__clang__) || defined(__GNUC__) || defined(__GUNG__)) && \
|
||||
!defined(MINGW32_CLANG)
|
||||
auto add_last_digit_owerflow =
|
||||
auto add_last_digit_overflow =
|
||||
(is_negative) ? sub_overflow<T> : add_overflow<T>;
|
||||
#else
|
||||
auto add_last_digit_owerflow = is_negative;
|
||||
auto add_last_digit_overflow = is_negative;
|
||||
#endif
|
||||
|
||||
T value = 0;
|
||||
for (auto i = begin; i != end; ++i) {
|
||||
if (auto digit = from_char(*i);
|
||||
!digit || shift_and_add_overflow<T>(value, digit.value(),
|
||||
add_last_digit_owerflow)) {
|
||||
add_last_digit_overflow)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
// TODO add single header tests
|
||||
#include "common.hpp"
|
||||
#include "converter.hpp"
|
||||
#include "exception.hpp"
|
||||
#include "extract.hpp"
|
||||
#include "restrictions.hpp"
|
||||
#include <cstdlib>
|
||||
@ -12,22 +14,23 @@
|
||||
|
||||
namespace ss {
|
||||
|
||||
template <typename... Matchers>
|
||||
template <typename... Options>
|
||||
class parser {
|
||||
constexpr static auto string_error = setup<Matchers...>::string_error;
|
||||
constexpr static auto string_error = setup<Options...>::string_error;
|
||||
constexpr static auto throw_on_error = setup<Options...>::throw_on_error;
|
||||
|
||||
using multiline = typename setup<Matchers...>::multiline;
|
||||
using multiline = typename setup<Options...>::multiline;
|
||||
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||
|
||||
constexpr static bool escaped_multiline_enabled =
|
||||
multiline::enabled && setup<Matchers...>::escape::enabled;
|
||||
multiline::enabled && setup<Options...>::escape::enabled;
|
||||
|
||||
constexpr static bool quoted_multiline_enabled =
|
||||
multiline::enabled && setup<Matchers...>::quote::enabled;
|
||||
multiline::enabled && setup<Options...>::quote::enabled;
|
||||
|
||||
constexpr static bool ignore_header = setup<Matchers...>::ignore_header;
|
||||
constexpr static bool ignore_header = setup<Options...>::ignore_header;
|
||||
|
||||
constexpr static bool ignore_empty = setup<Matchers...>::ignore_empty;
|
||||
constexpr static bool ignore_empty = setup<Options...>::ignore_empty;
|
||||
|
||||
public:
|
||||
parser(const std::string& file_name,
|
||||
@ -38,10 +41,10 @@ public:
|
||||
if constexpr (ignore_header) {
|
||||
ignore_next();
|
||||
} else {
|
||||
header_ = reader_.get_next_row();
|
||||
raw_header_ = reader_.get_buffer();
|
||||
}
|
||||
} else {
|
||||
set_error_file_not_open();
|
||||
handle_error_file_not_open();
|
||||
eof_ = true;
|
||||
}
|
||||
}
|
||||
@ -56,6 +59,8 @@ public:
|
||||
bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
return true;
|
||||
} else {
|
||||
return !error_;
|
||||
}
|
||||
@ -80,22 +85,56 @@ public:
|
||||
}
|
||||
|
||||
size_t line() const {
|
||||
return valid() ? reader_.line_number_ - 1 : 0;
|
||||
return reader_.line_number_ > 1 ? reader_.line_number_ - 1
|
||||
: reader_.line_number_;
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
no_void_validator_tup_t<T, Ts...> get_next() {
|
||||
std::optional<std::string> error;
|
||||
|
||||
if (!eof_) {
|
||||
if constexpr (throw_on_error) {
|
||||
try {
|
||||
reader_.parse();
|
||||
} catch (const ss::exception& e) {
|
||||
read_line();
|
||||
decorate_rethrow(e);
|
||||
}
|
||||
} else {
|
||||
reader_.parse();
|
||||
}
|
||||
}
|
||||
|
||||
reader_.update();
|
||||
clear_error();
|
||||
if (eof_) {
|
||||
set_error_eof_reached();
|
||||
if (!reader_.converter_.valid()) {
|
||||
handle_error_invalid_conversion();
|
||||
read_line();
|
||||
return {};
|
||||
}
|
||||
|
||||
clear_error();
|
||||
|
||||
if (eof_) {
|
||||
handle_error_eof_reached();
|
||||
return {};
|
||||
}
|
||||
|
||||
if constexpr (throw_on_error) {
|
||||
try {
|
||||
auto value = reader_.converter_.template convert<T, Ts...>();
|
||||
read_line();
|
||||
return value;
|
||||
} catch (const ss::exception& e) {
|
||||
read_line();
|
||||
decorate_rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
auto value = reader_.converter_.template convert<T, Ts...>();
|
||||
|
||||
if (!reader_.converter_.valid()) {
|
||||
set_error_invalid_conversion();
|
||||
handle_error_invalid_conversion();
|
||||
}
|
||||
|
||||
read_line();
|
||||
@ -103,33 +142,47 @@ public:
|
||||
}
|
||||
|
||||
bool field_exists(const std::string& field) {
|
||||
if (header_.empty()) {
|
||||
split_header_data();
|
||||
}
|
||||
|
||||
return header_index(field).has_value();
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void use_fields(const Ts&... fields_args) {
|
||||
if constexpr (ignore_header) {
|
||||
set_error_header_ignored();
|
||||
handle_error_header_ignored();
|
||||
return;
|
||||
}
|
||||
|
||||
if (header_.empty()) {
|
||||
split_header_data();
|
||||
}
|
||||
|
||||
if (!valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto fields = std::vector<std::string>{fields_args...};
|
||||
|
||||
if (fields.empty()) {
|
||||
handle_error_empty_mapping();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<size_t> column_mappings;
|
||||
|
||||
for (const auto& field : fields) {
|
||||
if (std::count(fields.begin(), fields.end(), field) != 1) {
|
||||
set_error_field_used_multiple_times(field);
|
||||
handle_error_field_used_multiple_times(field);
|
||||
return;
|
||||
}
|
||||
|
||||
auto index = header_index(field);
|
||||
|
||||
if (!index) {
|
||||
set_error_invalid_field(field);
|
||||
handle_error_invalid_field(field);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -139,7 +192,8 @@ public:
|
||||
reader_.converter_.set_column_mapping(column_mappings, header_.size());
|
||||
reader_.next_line_converter_.set_column_mapping(column_mappings,
|
||||
header_.size());
|
||||
if (line() == 0) {
|
||||
|
||||
if (line() == 1) {
|
||||
ignore_next();
|
||||
}
|
||||
}
|
||||
@ -154,20 +208,25 @@ public:
|
||||
using value = std::conditional_t<get_object, T,
|
||||
no_void_validator_tup_t<T, Ts...>>;
|
||||
|
||||
iterator() : parser_{nullptr} {
|
||||
iterator() : parser_{nullptr}, value_{} {
|
||||
}
|
||||
iterator(parser<Matchers...>* parser) : parser_{parser} {
|
||||
|
||||
iterator(parser<Options...>* parser) : parser_{parser}, value_{} {
|
||||
}
|
||||
|
||||
iterator(const iterator& other) = default;
|
||||
iterator(iterator&& other) = default;
|
||||
|
||||
value& operator*() {
|
||||
return value_;
|
||||
}
|
||||
|
||||
value* operator->() {
|
||||
return &value_;
|
||||
}
|
||||
|
||||
iterator& operator++() {
|
||||
if (parser_->eof()) {
|
||||
if (!parser_ || parser_->eof()) {
|
||||
parser_ = nullptr;
|
||||
} else {
|
||||
if constexpr (get_object) {
|
||||
@ -196,22 +255,23 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
parser<Options...>* parser_;
|
||||
value value_;
|
||||
parser<Matchers...>* parser_;
|
||||
};
|
||||
|
||||
iterable(parser<Matchers...>* parser) : parser_{parser} {
|
||||
iterable(parser<Options...>* parser) : parser_{parser} {
|
||||
}
|
||||
|
||||
iterator begin() {
|
||||
return ++iterator{parser_};
|
||||
}
|
||||
|
||||
iterator end() {
|
||||
return iterator{};
|
||||
}
|
||||
|
||||
private:
|
||||
parser<Matchers...>* parser_;
|
||||
parser<Options...>* parser_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
@ -264,6 +324,7 @@ public:
|
||||
|
||||
template <typename Fun>
|
||||
auto on_error(Fun&& fun) {
|
||||
assert_throw_on_error_not_defined<throw_on_error>();
|
||||
if (!parser_.valid()) {
|
||||
if constexpr (std::is_invocable_v<Fun>) {
|
||||
fun();
|
||||
@ -313,7 +374,7 @@ public:
|
||||
auto value =
|
||||
parser_.reader_.converter_.template convert<U, Us...>();
|
||||
if (!parser_.reader_.converter_.valid()) {
|
||||
parser_.set_error_invalid_conversion();
|
||||
parser_.handle_error_invalid_conversion();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@ -331,6 +392,7 @@ public:
|
||||
template <typename... Ts, typename Fun = none>
|
||||
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<
|
||||
std::optional<Ret>>(get_next<Ts...>(), std::forward<Fun>(fun));
|
||||
@ -340,6 +402,7 @@ public:
|
||||
// tuple
|
||||
template <typename T, typename... Ts, typename Fun = none>
|
||||
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));
|
||||
}
|
||||
@ -358,7 +421,7 @@ private:
|
||||
constexpr bool returns_void = std::is_same_v<Ret, void>;
|
||||
if constexpr (!returns_void) {
|
||||
if (!try_invoke_impl(arg, std::forward<Fun>(fun))) {
|
||||
set_error_failed_check();
|
||||
handle_error_failed_check();
|
||||
}
|
||||
} else {
|
||||
try_invoke_impl(arg, std::forward<Fun>(fun));
|
||||
@ -399,6 +462,22 @@ private:
|
||||
// header
|
||||
////////////////
|
||||
|
||||
void split_header_data() {
|
||||
ss::splitter<Options...> splitter;
|
||||
std::string raw_header_copy = raw_header_;
|
||||
splitter.split(raw_header_copy.data(), reader_.delim_);
|
||||
for (const auto& [begin, end] : splitter.split_data_) {
|
||||
std::string field{begin, end};
|
||||
if (std::find(header_.begin(), header_.end(), field) !=
|
||||
header_.end()) {
|
||||
handle_error_invalid_header(field);
|
||||
header_.clear();
|
||||
return;
|
||||
}
|
||||
header_.push_back(std::move(field));
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<size_t> header_index(const std::string& field) {
|
||||
auto it = std::find(header_.begin(), header_.end(), field);
|
||||
|
||||
@ -421,77 +500,135 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_failed_check() {
|
||||
void handle_error_failed_check() {
|
||||
constexpr static auto error_msg = " failed check";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_).append(" failed check.");
|
||||
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 set_error_file_not_open() {
|
||||
void handle_error_file_not_open() {
|
||||
constexpr static auto error_msg = " could not be opened";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_).append(" could not be opened.");
|
||||
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 set_error_eof_reached() {
|
||||
void handle_error_eof_reached() {
|
||||
constexpr static auto error_msg = " read on end of file";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_).append(" reached end of file.");
|
||||
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 set_error_invalid_conversion() {
|
||||
void handle_error_invalid_conversion() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(file_name_)
|
||||
.append(" ")
|
||||
.append(std::to_string(reader_.line_number_))
|
||||
.append(": ")
|
||||
.append(reader_.converter_.error_msg())
|
||||
.append(": \"")
|
||||
.append(reader_.buffer_)
|
||||
.append("\"");
|
||||
.append(reader_.converter_.error_msg());
|
||||
} else if constexpr (!throw_on_error) {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
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 set_error_header_ignored() {
|
||||
void handle_error_invalid_field(const std::string& field) {
|
||||
constexpr static auto error_msg =
|
||||
": header does not contain given field: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_)
|
||||
.append(": \"")
|
||||
.append("the header row is ignored within the setup, it cannot "
|
||||
"be used")
|
||||
.append("\"");
|
||||
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 set_error_invalid_field(const std::string& field) {
|
||||
void handle_error_field_used_multiple_times(const std::string& field) {
|
||||
constexpr static auto error_msg = ": given field used multiple times: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_)
|
||||
.append(": header does not contain given field: ")
|
||||
.append(field);
|
||||
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 set_error_field_used_multiple_times(const std::string& field) {
|
||||
void handle_error_empty_mapping() {
|
||||
constexpr static auto error_msg = "received empty mapping";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.append(file_name_)
|
||||
.append(": given field used multiple times: ")
|
||||
.append(field);
|
||||
error_.clear();
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_error_invalid_header(const std::string& field) {
|
||||
constexpr static auto error_msg = "header contains duplicates: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append(error_msg).append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg + field};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void decorate_rethrow(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(" ")
|
||||
.append(std::to_string(line()))
|
||||
.append(": ")
|
||||
.append(e.what())};
|
||||
}
|
||||
|
||||
////////////////
|
||||
// line reading
|
||||
////////////////
|
||||
@ -511,10 +648,12 @@ private:
|
||||
helper_buffer_{other.helper_buffer_}, converter_{std::move(
|
||||
other.converter_)},
|
||||
next_line_converter_{std::move(other.next_line_converter_)},
|
||||
size_{other.size_}, next_line_size_{other.next_line_size_},
|
||||
buffer_size_{other.buffer_size_},
|
||||
next_line_buffer_size_{other.next_line_buffer_size_},
|
||||
helper_size_{other.helper_size_}, delim_{std::move(other.delim_)},
|
||||
file_{other.file_}, crlf_{other.crlf_}, line_number_{
|
||||
other.line_number_} {
|
||||
file_{other.file_}, crlf_{other.crlf_},
|
||||
line_number_{other.line_number_}, next_line_size_{
|
||||
other.next_line_size_} {
|
||||
other.buffer_ = nullptr;
|
||||
other.next_line_buffer_ = nullptr;
|
||||
other.helper_buffer_ = nullptr;
|
||||
@ -528,13 +667,14 @@ private:
|
||||
helper_buffer_ = other.helper_buffer_;
|
||||
converter_ = std::move(other.converter_);
|
||||
next_line_converter_ = std::move(other.next_line_converter_);
|
||||
size_ = other.size_;
|
||||
next_line_size_ = other.next_line_size_;
|
||||
buffer_size_ = other.buffer_size_;
|
||||
next_line_buffer_size_ = other.next_line_buffer_size_;
|
||||
helper_size_ = other.helper_size_;
|
||||
delim_ = std::move(other.delim_);
|
||||
file_ = other.file_;
|
||||
crlf_ = other.crlf_;
|
||||
line_number_ = other.line_number_;
|
||||
next_line_size_ = other.next_line_size_;
|
||||
|
||||
other.buffer_ = nullptr;
|
||||
other.next_line_buffer_ = nullptr;
|
||||
@ -559,16 +699,18 @@ private:
|
||||
reader(const reader& other) = delete;
|
||||
reader& operator=(const reader& other) = delete;
|
||||
|
||||
// read next line each time in order to set eof_
|
||||
bool read_next() {
|
||||
|
||||
ssize_t ssize;
|
||||
next_line_converter_.clear_error();
|
||||
ssize_t ssize = 0;
|
||||
size_t size = 0;
|
||||
while (size == 0) {
|
||||
++line_number_;
|
||||
if (next_line_size_ > 0) {
|
||||
if (next_line_buffer_size_ > 0) {
|
||||
next_line_buffer_[0] = '\0';
|
||||
}
|
||||
ssize = get_line(&next_line_buffer_, &next_line_size_, file_);
|
||||
ssize = get_line(&next_line_buffer_, &next_line_buffer_size_,
|
||||
file_);
|
||||
|
||||
if (ssize == -1) {
|
||||
return false;
|
||||
@ -581,17 +723,23 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
next_line_size_ = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
void parse() {
|
||||
size_t limit = 0;
|
||||
|
||||
if constexpr (escaped_multiline_enabled) {
|
||||
while (escaped_eol(size)) {
|
||||
while (escaped_eol(next_line_size_)) {
|
||||
if (multiline_limit_reached(limit)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (!append_next_line_to_buffer(next_line_buffer_, size)) {
|
||||
remove_eol(next_line_buffer_, ssize);
|
||||
next_line_converter_.set_error_unterminated_escape();
|
||||
return true;
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
next_line_size_)) {
|
||||
next_line_converter_.handle_error_unterminated_escape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -600,49 +748,49 @@ private:
|
||||
|
||||
if constexpr (quoted_multiline_enabled) {
|
||||
while (unterminated_quote()) {
|
||||
size -= next_line_converter_.size_shifted();
|
||||
next_line_size_ -= next_line_converter_.size_shifted();
|
||||
|
||||
if (multiline_limit_reached(limit)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (!append_next_line_to_buffer(next_line_buffer_, size)) {
|
||||
remove_eol(next_line_buffer_, ssize);
|
||||
return true;
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
next_line_size_)) {
|
||||
next_line_converter_.handle_error_unterminated_quote();
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (escaped_multiline_enabled) {
|
||||
while (escaped_eol(size)) {
|
||||
while (escaped_eol(next_line_size_)) {
|
||||
if (multiline_limit_reached(limit)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!append_next_line_to_buffer(next_line_buffer_,
|
||||
size)) {
|
||||
remove_eol(next_line_buffer_, ssize);
|
||||
next_line_size_)) {
|
||||
next_line_converter_
|
||||
.set_error_unterminated_escape();
|
||||
return true;
|
||||
.handle_error_unterminated_escape();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
next_line_converter_.resplit(next_line_buffer_, size,
|
||||
delim_);
|
||||
next_line_converter_.resplit(next_line_buffer_,
|
||||
next_line_size_, delim_);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void update() {
|
||||
std::swap(buffer_, next_line_buffer_);
|
||||
std::swap(size_, next_line_size_);
|
||||
std::swap(buffer_size_, next_line_buffer_size_);
|
||||
std::swap(converter_, next_line_converter_);
|
||||
}
|
||||
|
||||
bool multiline_limit_reached(size_t& limit) {
|
||||
if constexpr (multiline::size > 0) {
|
||||
if (limit++ >= multiline::size) {
|
||||
next_line_converter_.set_error_multiline_limit_reached();
|
||||
next_line_converter_.handle_error_multiline_limit_reached();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -653,17 +801,14 @@ private:
|
||||
const char* curr;
|
||||
for (curr = next_line_buffer_ + size - 1;
|
||||
curr >= next_line_buffer_ &&
|
||||
setup<Matchers...>::escape::match(*curr);
|
||||
setup<Options...>::escape::match(*curr);
|
||||
--curr) {
|
||||
}
|
||||
return (next_line_buffer_ - curr + size) % 2 == 0;
|
||||
}
|
||||
|
||||
bool unterminated_quote() {
|
||||
if (next_line_converter_.unterminated_quote()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return next_line_converter_.unterminated_quote();
|
||||
}
|
||||
|
||||
void undo_remove_eol(char* buffer, size_t& string_end) {
|
||||
@ -676,24 +821,30 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
size_t remove_eol(char*& buffer, size_t size) {
|
||||
size_t new_size = size - 1;
|
||||
if (size >= 2 && buffer[size - 2] == '\r') {
|
||||
size_t remove_eol(char*& buffer, size_t ssize) {
|
||||
size_t size = ssize - 1;
|
||||
if (ssize >= 2 && buffer[ssize - 2] == '\r') {
|
||||
crlf_ = true;
|
||||
new_size--;
|
||||
size--;
|
||||
} else {
|
||||
crlf_ = false;
|
||||
}
|
||||
|
||||
buffer[new_size] = '\0';
|
||||
return new_size;
|
||||
buffer[size] = '\0';
|
||||
return size;
|
||||
}
|
||||
|
||||
void realloc_concat(char*& first, size_t& first_size,
|
||||
const char* const second, size_t second_size) {
|
||||
next_line_size_ = first_size + second_size + 3;
|
||||
first = static_cast<char*>(
|
||||
realloc(static_cast<void*>(first), next_line_size_));
|
||||
// TODO make buffer_size an argument
|
||||
next_line_buffer_size_ = first_size + second_size + 3;
|
||||
auto new_first = static_cast<char*>(
|
||||
realloc(static_cast<void*>(first), next_line_buffer_size_));
|
||||
if (!first) {
|
||||
throw std::bad_alloc{};
|
||||
}
|
||||
|
||||
first = new_first;
|
||||
std::copy_n(second, second_size + 1, first + first_size);
|
||||
first_size += second_size;
|
||||
}
|
||||
@ -713,13 +864,8 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_next_row() const {
|
||||
std::vector<std::string> next_row;
|
||||
auto& next_row_raw = next_line_converter_.splitter_.split_data_;
|
||||
for (const auto& [begin, end] : next_row_raw) {
|
||||
next_row.emplace_back(begin, end);
|
||||
}
|
||||
return next_row;
|
||||
std::string get_buffer() {
|
||||
return std::string{next_line_buffer_, next_line_buffer_size_};
|
||||
}
|
||||
|
||||
////////////////
|
||||
@ -729,18 +875,20 @@ private:
|
||||
char* next_line_buffer_{nullptr};
|
||||
char* helper_buffer_{nullptr};
|
||||
|
||||
converter<Matchers...> converter_;
|
||||
converter<Matchers...> next_line_converter_;
|
||||
converter<Options...> converter_;
|
||||
converter<Options...> next_line_converter_;
|
||||
|
||||
size_t size_{0};
|
||||
size_t next_line_size_{0};
|
||||
size_t buffer_size_{0};
|
||||
size_t next_line_buffer_size_{0};
|
||||
size_t helper_size_{0};
|
||||
|
||||
std::string delim_;
|
||||
FILE* file_{nullptr};
|
||||
|
||||
bool crlf_;
|
||||
bool crlf_{false};
|
||||
size_t line_number_{0};
|
||||
|
||||
size_t next_line_size_{0};
|
||||
};
|
||||
|
||||
////////////////
|
||||
@ -751,6 +899,7 @@ private:
|
||||
error_type error_{};
|
||||
reader reader_;
|
||||
std::vector<std::string> header_;
|
||||
std::string raw_header_;
|
||||
bool eof_{false};
|
||||
};
|
||||
|
||||
|
@ -179,20 +179,26 @@ class ignore_header;
|
||||
|
||||
class ignore_empty;
|
||||
|
||||
////////////////
|
||||
// throw_on_error
|
||||
////////////////
|
||||
|
||||
class throw_on_error;
|
||||
|
||||
////////////////
|
||||
// setup implementation
|
||||
////////////////
|
||||
|
||||
template <typename... Ts>
|
||||
template <typename... Options>
|
||||
struct setup {
|
||||
private:
|
||||
template <typename T>
|
||||
template <typename Option>
|
||||
struct is_matcher
|
||||
: std::disjunction<is_instance_of_matcher_t<T, quote>,
|
||||
is_instance_of_matcher_t<T, escape>,
|
||||
is_instance_of_matcher_t<T, trim>,
|
||||
is_instance_of_matcher_t<T, trim_left>,
|
||||
is_instance_of_matcher_t<T, trim_right>> {};
|
||||
: std::disjunction<is_instance_of_matcher_t<Option, quote>,
|
||||
is_instance_of_matcher_t<Option, escape>,
|
||||
is_instance_of_matcher_t<Option, trim>,
|
||||
is_instance_of_matcher_t<Option, trim_left>,
|
||||
is_instance_of_matcher_t<Option, trim_right>> {};
|
||||
|
||||
template <typename T>
|
||||
struct is_string_error : std::is_same<T, string_error> {};
|
||||
@ -203,39 +209,48 @@ private:
|
||||
template <typename T>
|
||||
struct is_ignore_empty : std::is_same<T, ignore_empty> {};
|
||||
|
||||
constexpr static auto count_matcher = count_v<is_matcher, Ts...>;
|
||||
template <typename T>
|
||||
struct is_throw_on_error : std::is_same<T, throw_on_error> {};
|
||||
|
||||
constexpr static auto count_matcher = count_v<is_matcher, Options...>;
|
||||
|
||||
constexpr static auto count_multiline =
|
||||
count_v<is_instance_of_multiline, Ts...>;
|
||||
count_v<is_instance_of_multiline, Options...>;
|
||||
|
||||
constexpr static auto count_string_error = count_v<is_string_error, Ts...>;
|
||||
constexpr static auto count_string_error =
|
||||
count_v<is_string_error, Options...>;
|
||||
|
||||
constexpr static auto count_ignore_header =
|
||||
count_v<is_ignore_header, Ts...>;
|
||||
count_v<is_ignore_header, Options...>;
|
||||
|
||||
constexpr static auto count_ignore_empty = count_v<is_ignore_empty, Ts...>;
|
||||
constexpr static auto count_throw_on_error =
|
||||
count_v<is_throw_on_error, Options...>;
|
||||
|
||||
constexpr static auto count_ignore_empty =
|
||||
count_v<is_ignore_empty, Options...>;
|
||||
|
||||
constexpr static auto number_of_valid_setup_types =
|
||||
count_matcher + count_multiline + count_string_error +
|
||||
count_ignore_header + count_ignore_empty;
|
||||
count_ignore_header + count_ignore_empty + count_throw_on_error;
|
||||
|
||||
using trim_left_only = get_matcher_t<trim_left, Ts...>;
|
||||
using trim_right_only = get_matcher_t<trim_right, Ts...>;
|
||||
using trim_all = get_matcher_t<trim, Ts...>;
|
||||
using trim_left_only = get_matcher_t<trim_left, Options...>;
|
||||
using trim_right_only = get_matcher_t<trim_right, Options...>;
|
||||
using trim_all = get_matcher_t<trim, Options...>;
|
||||
|
||||
public:
|
||||
using quote = get_matcher_t<quote, Ts...>;
|
||||
using escape = get_matcher_t<escape, Ts...>;
|
||||
using quote = get_matcher_t<quote, Options...>;
|
||||
using escape = get_matcher_t<escape, Options...>;
|
||||
|
||||
using trim_left =
|
||||
std::conditional_t<trim_all::enabled, trim_all, trim_left_only>;
|
||||
using trim_right =
|
||||
std::conditional_t<trim_all::enabled, trim_all, trim_right_only>;
|
||||
|
||||
using multiline = get_multiline_t<Ts...>;
|
||||
using multiline = get_multiline_t<Options...>;
|
||||
constexpr static bool string_error = (count_string_error == 1);
|
||||
constexpr static bool ignore_header = (count_ignore_header == 1);
|
||||
constexpr static bool ignore_empty = (count_ignore_empty == 1);
|
||||
constexpr static bool throw_on_error = (count_throw_on_error == 1);
|
||||
|
||||
private:
|
||||
#define ASSERT_MSG "cannot have the same match character in multiple matchers"
|
||||
@ -254,21 +269,28 @@ private:
|
||||
static_assert(
|
||||
!multiline::enabled ||
|
||||
(multiline::enabled && (quote::enabled || escape::enabled)),
|
||||
"to enable multiline either quote or escape need to be enabled");
|
||||
"to enable multiline either quote or escape needs to be enabled");
|
||||
|
||||
static_assert(!(trim_all::enabled && trim_left_only::enabled) &&
|
||||
!(trim_all::enabled && trim_right_only::enabled),
|
||||
"ambiguous trim setup");
|
||||
|
||||
static_assert(count_multiline <= 1, "mutliline defined multiple times");
|
||||
|
||||
static_assert(count_string_error <= 1,
|
||||
"string_error defined multiple times");
|
||||
|
||||
static_assert(number_of_valid_setup_types == sizeof...(Ts),
|
||||
static_assert(count_throw_on_error <= 1,
|
||||
"throw_on_error defined multiple times");
|
||||
|
||||
static_assert(count_throw_on_error + count_string_error <= 1,
|
||||
"cannot define both throw_on_error and string_error");
|
||||
|
||||
static_assert(number_of_valid_setup_types == sizeof...(Options),
|
||||
"one or multiple invalid setup parameters defined");
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct setup<setup<Ts...>> : setup<Ts...> {};
|
||||
template <typename... Options>
|
||||
struct setup<setup<Options...>> : setup<Options...> {};
|
||||
|
||||
} /* ss */
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "common.hpp"
|
||||
#include "exception.hpp"
|
||||
#include "setup.hpp"
|
||||
#include "type_traits.hpp"
|
||||
#include <algorithm>
|
||||
@ -11,16 +12,17 @@
|
||||
|
||||
namespace ss {
|
||||
|
||||
template <typename... Ts>
|
||||
template <typename... Options>
|
||||
class splitter {
|
||||
private:
|
||||
using quote = typename setup<Ts...>::quote;
|
||||
using trim_left = typename setup<Ts...>::trim_left;
|
||||
using trim_right = typename setup<Ts...>::trim_right;
|
||||
using escape = typename setup<Ts...>::escape;
|
||||
using multiline = typename setup<Ts...>::multiline;
|
||||
using quote = typename setup<Options...>::quote;
|
||||
using trim_left = typename setup<Options...>::trim_left;
|
||||
using trim_right = typename setup<Options...>::trim_right;
|
||||
using escape = typename setup<Options...>::escape;
|
||||
using multiline = typename setup<Options...>::multiline;
|
||||
|
||||
constexpr static auto string_error = setup<Ts...>::string_error;
|
||||
constexpr static auto string_error = setup<Options...>::string_error;
|
||||
constexpr static auto throw_on_error = setup<Options...>::throw_on_error;
|
||||
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
|
||||
|
||||
using error_type = std::conditional_t<string_error, std::string, bool>;
|
||||
@ -31,6 +33,8 @@ public:
|
||||
bool valid() const {
|
||||
if constexpr (string_error) {
|
||||
return error_.empty();
|
||||
} else if constexpr (throw_on_error) {
|
||||
return true;
|
||||
} else {
|
||||
return !error_;
|
||||
}
|
||||
@ -77,7 +81,7 @@ private:
|
||||
// resplitting, continue from last slice
|
||||
if (!quote::enabled || !multiline::enabled || split_data_.empty() ||
|
||||
!unterminated_quote()) {
|
||||
set_error_invalid_resplit();
|
||||
handle_error_invalid_resplit();
|
||||
return split_data_;
|
||||
}
|
||||
|
||||
@ -86,7 +90,7 @@ private:
|
||||
|
||||
// safety measure
|
||||
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
|
||||
set_error_invalid_resplit();
|
||||
handle_error_invalid_resplit();
|
||||
return split_data_;
|
||||
}
|
||||
|
||||
@ -112,55 +116,75 @@ private:
|
||||
void clear_error() {
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
} else {
|
||||
} else if constexpr (!throw_on_error) {
|
||||
error_ = false;
|
||||
}
|
||||
unterminated_quote_ = false;
|
||||
}
|
||||
|
||||
void set_error_empty_delimiter() {
|
||||
void handle_error_empty_delimiter() {
|
||||
constexpr static auto error_msg = "empty delimiter";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("empt delimiter");
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_mismatched_quote(size_t n) {
|
||||
void handle_error_mismatched_quote(size_t n) {
|
||||
constexpr static auto error_msg = "mismatched quote at position: ";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("mismatched quote at position: " + std::to_string(n));
|
||||
error_.append(error_msg + std::to_string(n));
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg + std::to_string(n)};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_unterminated_escape() {
|
||||
void handle_error_unterminated_escape() {
|
||||
constexpr static auto error_msg =
|
||||
"unterminated escape at the end of the line";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("unterminated escape at the end of the line");
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_unterminated_quote() {
|
||||
unterminated_quote_ = true;
|
||||
void handle_error_unterminated_quote() {
|
||||
constexpr static auto error_msg = "unterminated quote";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("unterminated quote");
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_error_invalid_resplit() {
|
||||
unterminated_quote_ = false;
|
||||
void handle_error_invalid_resplit() {
|
||||
constexpr static auto error_msg =
|
||||
"invalid resplit, new line must be longer"
|
||||
"than the end of the last slice";
|
||||
|
||||
if constexpr (string_error) {
|
||||
error_.clear();
|
||||
error_.append("invalid resplit, new line must be longer"
|
||||
"than the end of the last slice");
|
||||
error_.append(error_msg);
|
||||
} else if constexpr (throw_on_error) {
|
||||
throw ss::exception{error_msg};
|
||||
} else {
|
||||
error_ = true;
|
||||
}
|
||||
@ -235,7 +259,9 @@ private:
|
||||
if constexpr (escape::enabled) {
|
||||
if (escape::match(*curr)) {
|
||||
if (curr[1] == '\0') {
|
||||
set_error_unterminated_escape();
|
||||
if constexpr (!multiline::enabled) {
|
||||
handle_error_unterminated_escape();
|
||||
}
|
||||
done_ = true;
|
||||
return;
|
||||
}
|
||||
@ -282,7 +308,7 @@ private:
|
||||
clear_error();
|
||||
switch (delimiter.size()) {
|
||||
case 0:
|
||||
set_error_empty_delimiter();
|
||||
handle_error_empty_delimiter();
|
||||
return split_data_;
|
||||
case 1:
|
||||
return split_impl(delimiter[0]);
|
||||
@ -362,7 +388,9 @@ private:
|
||||
if (end_[1] == '\0') {
|
||||
// eol, unterminated escape
|
||||
// eg: ... "hel\\0
|
||||
set_error_unterminated_escape();
|
||||
if constexpr (!multiline::enabled) {
|
||||
handle_error_unterminated_escape();
|
||||
}
|
||||
done_ = true;
|
||||
break;
|
||||
}
|
||||
@ -379,7 +407,10 @@ private:
|
||||
// eg: ..."hell\0 -> quote not terminated
|
||||
if (*end_ == '\0') {
|
||||
shift_and_set_current();
|
||||
set_error_unterminated_quote();
|
||||
unterminated_quote_ = true;
|
||||
if constexpr (!multiline::enabled) {
|
||||
handle_error_unterminated_quote();
|
||||
}
|
||||
split_data_.emplace_back(line_, begin_);
|
||||
done_ = true;
|
||||
break;
|
||||
@ -418,7 +449,7 @@ private:
|
||||
} else {
|
||||
// mismatched quote
|
||||
// eg: ...,"hel"lo,... -> error
|
||||
set_error_mismatched_quote(end_ - line_);
|
||||
handle_error_mismatched_quote(end_ - line_);
|
||||
split_data_.emplace_back(line_, begin_);
|
||||
}
|
||||
done_ = true;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
headers_dir = 'include/ss/'
|
||||
headers = ['type_traits.hpp',
|
||||
'exception.hpp',
|
||||
'function_traits.hpp',
|
||||
'restrictions.hpp',
|
||||
'common.hpp',
|
||||
|
@ -5,37 +5,40 @@ project(ssp_tests CXX)
|
||||
# ---- Dependencies ----
|
||||
|
||||
include(FetchContent)
|
||||
fetchcontent_declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
|
||||
fetchcontent_makeavailable(ssp)
|
||||
FetchContent_Declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
|
||||
FetchContent_MakeAvailable(ssp)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(ssp INTERFACE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
add_compile_options(/bigobj)
|
||||
elseif (MINGW)
|
||||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif ()
|
||||
|
||||
include(FetchContent)
|
||||
fetchcontent_declare(
|
||||
FetchContent_Declare(
|
||||
DOCTEST
|
||||
GIT_REPOSITORY https://github.com/red0124/doctest
|
||||
GIT_TAG origin/master
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
GIT_SHALLOW TRUE)
|
||||
|
||||
fetchcontent_makeavailable(DOCTEST)
|
||||
FetchContent_MakeAvailable(DOCTEST)
|
||||
set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
|
||||
|
||||
# ---- Test ----
|
||||
|
||||
enable_testing()
|
||||
|
||||
foreach(name IN ITEMS test_splitter test_parser test_converter test_extractions)
|
||||
foreach(name IN ITEMS test_splitter test_parser test_converter test_extractions
|
||||
test_parser2_1 test_parser2_2 test_parser2_3
|
||||
test_parser2_4 test_extractions_without_fast_float)
|
||||
add_executable("${name}" "${name}.cpp")
|
||||
target_link_libraries(
|
||||
"${name}"
|
||||
PRIVATE ssp::ssp fast_float doctest::doctest
|
||||
)
|
||||
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float
|
||||
doctest::doctest)
|
||||
target_compile_definitions(
|
||||
"${name}"
|
||||
PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI
|
||||
)
|
||||
"${name}" PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI)
|
||||
add_test(NAME "${name}" COMMAND "${name}")
|
||||
endforeach()
|
||||
|
@ -1,19 +1,27 @@
|
||||
test_sources = files([
|
||||
'test_main.cpp',
|
||||
'test_splitter.cpp',
|
||||
'test_converter.cpp',
|
||||
'test_parser.cpp',
|
||||
'test_extractions.cpp',
|
||||
'test_extractions_without_fast_float.cpp',
|
||||
])
|
||||
|
||||
doctest_dep = dependency('doctest')
|
||||
add_project_arguments('-DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN', language: 'cpp')
|
||||
|
||||
test_exe = executable(
|
||||
'test_ssp',
|
||||
sources: test_sources,
|
||||
dependencies: [doctest_dep, ssp_dep],
|
||||
cpp_args: '-lstdc++fs'
|
||||
)
|
||||
tests = [
|
||||
'parser',
|
||||
'splitter',
|
||||
'converter',
|
||||
'extractions',
|
||||
'parser2_1',
|
||||
'parser2_2',
|
||||
'parser2_3',
|
||||
'parser2_4',
|
||||
'extractions_without_fast_float',
|
||||
]
|
||||
|
||||
foreach name : tests
|
||||
test_name = 'test_' + name
|
||||
|
||||
exe = executable(
|
||||
test_name,
|
||||
test_name + '.cpp',
|
||||
dependencies: [doctest_dep, ssp_dep]
|
||||
)
|
||||
|
||||
test(test_name, exe, timeout: 60)
|
||||
endforeach
|
||||
|
||||
test('test_ssp', test_exe)
|
||||
|
@ -22,6 +22,30 @@ TEST_CASE("converter test split") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test split with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
try {
|
||||
for (const auto& [s, expected, delim] :
|
||||
// clang-format off
|
||||
{std::make_tuple("a,b,c,d", std::vector{"a", "b", "c", "d"}, ","),
|
||||
{"", {}, " "},
|
||||
{" 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
|
||||
) {
|
||||
auto split = c.split(s, delim);
|
||||
CHECK_EQ(split.size(), expected.size());
|
||||
for (size_t i = 0; i < split.size(); ++i) {
|
||||
auto s = std::string(split[i].first, split[i].second);
|
||||
CHECK_EQ(s, expected[i]);
|
||||
}
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test valid conversions") {
|
||||
ss::converter c;
|
||||
|
||||
@ -116,12 +140,153 @@ TEST_CASE("converter test valid conversions") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test valid conversions with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
try {
|
||||
auto tup = c.convert<int>("5");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<int, void>("5,junk");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<void, int>("junk,5");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<int, void, void>("5\njunk\njunk", "\n");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<void, int, void>("junk 5 junk", " ");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<void, void, int>("junk\tjunk\t5", "\t");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup =
|
||||
c.convert<void, void, std::optional<int>>("junk\tjunk\t5", "\t");
|
||||
REQUIRE(c.valid());
|
||||
REQUIRE(tup.has_value());
|
||||
CHECK_EQ(tup, 5);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<int, double, void>("5,6.6,junk");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<int, void, double>("5,junk,6.6");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<void, int, double>("junk;5;6.6", ";");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup =
|
||||
c.convert<void, std::optional<int>, double>("junk;5;6.6", ";");
|
||||
REQUIRE(c.valid());
|
||||
REQUIRE(std::get<0>(tup).has_value());
|
||||
CHECK_EQ(tup, std::make_tuple(5, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup =
|
||||
c.convert<void, std::optional<int>, double>("junk;5.4;6.6", ";");
|
||||
REQUIRE(c.valid());
|
||||
REQUIRE_FALSE(std::get<0>(tup).has_value());
|
||||
CHECK_EQ(tup, std::make_tuple(std::optional<int>{}, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup =
|
||||
c.convert<void, std::variant<int, double>, double>("junk;5;6.6",
|
||||
";");
|
||||
REQUIRE(c.valid());
|
||||
REQUIRE(std::holds_alternative<int>(std::get<0>(tup)));
|
||||
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5}, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup =
|
||||
c.convert<void, std::variant<int, double>, double>("junk;5.5;6.6",
|
||||
";");
|
||||
REQUIRE(c.valid());
|
||||
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
|
||||
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5.5}, 6.6));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
auto tup = c.convert<void, std::string_view, double,
|
||||
std::string_view>("junk;s1;6.6;s2", ";");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(std::string_view{"s1"}, 6.6,
|
||||
std::string_view{"s2"}));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test invalid conversions") {
|
||||
ss::converter c;
|
||||
|
||||
c.convert<int>("");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<int>("1", "");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
c.convert<int>("10", "");
|
||||
REQUIRE_FALSE(c.valid());
|
||||
|
||||
@ -150,6 +315,23 @@ TEST_CASE("converter test invalid conversions") {
|
||||
REQUIRE_FALSE(c.valid());
|
||||
}
|
||||
|
||||
TEST_CASE("converter test invalid conversions with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<int>(""));
|
||||
REQUIRE_EXCEPTION(c.convert<int>("1", ""));
|
||||
REQUIRE_EXCEPTION(c.convert<int>("10", ""));
|
||||
REQUIRE_EXCEPTION(c.convert<int, void>(""));
|
||||
REQUIRE_EXCEPTION(c.convert<int, void>(",junk"));
|
||||
REQUIRE_EXCEPTION(c.convert<void, int>("junk,"));
|
||||
REQUIRE_EXCEPTION(c.convert<int>("x"));
|
||||
REQUIRE_EXCEPTION(c.convert<int, void>("x"));
|
||||
REQUIRE_EXCEPTION(c.convert<int, void>("x,junk"));
|
||||
REQUIRE_EXCEPTION(c.convert<void, int>("junk,x"));
|
||||
REQUIRE_EXCEPTION(
|
||||
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";"));
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:ax restriction (all except)") {
|
||||
ss::converter c;
|
||||
|
||||
@ -181,6 +363,32 @@ TEST_CASE("converter test ss:ax restriction (all except)") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:ax restriction (all except) with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 0>>("0"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 0, 1, 2>>("1"));
|
||||
REQUIRE_EXCEPTION(c.convert<void, char, ss::ax<int, 0, 1, 2>>("junk,c,1"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ax<int, 1>, char>("1,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
int tup = c.convert<ss::ax<int, 1>>("3");
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
{
|
||||
std::tuple<char, int> tup = c.convert<char, ss::ax<int, 1>>("c,3");
|
||||
CHECK_EQ(tup, std::make_tuple('c', 3));
|
||||
}
|
||||
{
|
||||
std::tuple<int, char> tup = c.convert<ss::ax<int, 1>, char>("3,c");
|
||||
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:nx restriction (none except)") {
|
||||
ss::converter c;
|
||||
|
||||
@ -215,6 +423,39 @@ 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"));
|
||||
|
||||
try {
|
||||
{
|
||||
auto tup = c.convert<ss::nx<int, 3>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<ss::nx<int, 0, 1, 2>>("2");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 2);
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<char, void, ss::nx<int, 0, 1, 2>>("c,junk,1");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple('c', 1));
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<ss::nx<int, 1>, char>("1,c");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:ir restriction (in range)") {
|
||||
ss::converter c;
|
||||
|
||||
@ -249,6 +490,39 @@ TEST_CASE("converter test ss:ir restriction (in range)") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:ir restriction (in range) with exceptions") {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ir<int, 0, 2>>("3"));
|
||||
REQUIRE_EXCEPTION(c.convert<char, ss::ir<int, 4, 69>>("c,3"));
|
||||
REQUIRE_EXCEPTION(c.convert<ss::ir<int, 1, 2>, char>("3,c"));
|
||||
|
||||
try {
|
||||
{
|
||||
auto tup = c.convert<ss::ir<int, 1, 5>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<ss::ir<int, 0, 2>>("2");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 2);
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<char, void, ss::ir<int, 0, 1>>("c,junk,1");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple('c', 1));
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<ss::ir<int, 1, 20>, char>("1,c");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(1, 'c'));
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test ss:oor restriction (out of range)") {
|
||||
ss::converter c;
|
||||
|
||||
@ -283,7 +557,38 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<int> extracted_vector = {1, 2, 3};
|
||||
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"));
|
||||
|
||||
try {
|
||||
{
|
||||
auto tup = c.convert<ss::oor<int, 0, 2>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<char, void, ss::oor<int, 4, 69>>("c,junk,3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple('c', 3));
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::oor<int, 1, 2>, char>("3,c");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(3, 'c'));
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
const inline std::vector<int> extracted_vector = {1, 2, 3};
|
||||
|
||||
// custom extract
|
||||
template <>
|
||||
@ -331,6 +636,37 @@ 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>>>(""));
|
||||
|
||||
try {
|
||||
{
|
||||
auto tup = c.convert<ss::ne<std::string>>("s");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, "s");
|
||||
}
|
||||
{
|
||||
auto tup =
|
||||
c.convert<std::optional<int>, ss::ne<std::string>>("1,s");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple(1, "s"));
|
||||
}
|
||||
{
|
||||
auto tup = c.convert<ss::ne<std::vector<int>>>("{1 2 3}");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, extracted_vector);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
|
||||
ss::converter c;
|
||||
@ -390,6 +726,58 @@ TEST_CASE(
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
try {
|
||||
{
|
||||
auto tup = c.convert<ss::lt<int, 4>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::gt<int, 2>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::lte<int, 4>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::lte<int, 3>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::gte<int, 2>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto tup = c.convert<ss::gte<int, 3>>("3");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, 3);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test error mode") {
|
||||
ss::converter<ss::string_error> c;
|
||||
c.convert<int>("junk");
|
||||
@ -397,6 +785,11 @@ TEST_CASE("converter test error mode") {
|
||||
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"));
|
||||
}
|
||||
|
||||
TEST_CASE("converter test converter with quotes spacing and escaping") {
|
||||
{
|
||||
ss::converter c;
|
||||
@ -444,6 +837,66 @@ TEST_CASE("converter test converter with quotes spacing and escaping") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test converter with quotes spacing and escaping with "
|
||||
"exceptions") {
|
||||
try {
|
||||
ss::converter<ss::throw_on_error> c;
|
||||
|
||||
auto tup = c.convert<std::string, std::string, std::string>(
|
||||
R"("just","some","strings")");
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple("\"just\"", "\"some\"", "\"strings\""));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>> c;
|
||||
|
||||
auto tup = c.convert<std::string, std::string, double, char>(
|
||||
buff(R"("just",some,"12.3","a")"));
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple("just", "some", 12.3, 'a'));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::throw_on_error, ss::trim<' '>> c;
|
||||
|
||||
auto tup = c.convert<std::string, std::string, double, char>(
|
||||
buff(R"( just , some , 12.3 ,a )"));
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple("just", "some", 12.3, 'a'));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::throw_on_error, ss::escape<'\\'>> c;
|
||||
|
||||
auto tup =
|
||||
c.convert<std::string, std::string>(buff(R"(ju\,st,strings)"));
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple("ju,st", "strings"));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::throw_on_error, ss::escape<'\\'>, ss::trim<' '>,
|
||||
ss::quote<'"'>>
|
||||
c;
|
||||
|
||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings")"));
|
||||
REQUIRE(c.valid());
|
||||
CHECK_EQ(tup, std::make_tuple("ju,st", "so,me", 12.34, "str\"ings"));
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test invalid split conversions") {
|
||||
ss::converter<ss::string_error, ss::escape<'\\'>, ss::trim<' '>,
|
||||
ss::quote<'"'>>
|
||||
@ -451,7 +904,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// mismatched quote
|
||||
auto tup = c.convert<std::string, std::string, double, char>(
|
||||
c.convert<std::string, std::string, double, char>(
|
||||
buff(R"( "just , some , "12.3","a" )"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -460,7 +913,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated quote
|
||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||
c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK(c.unterminated_quote());
|
||||
@ -469,7 +922,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escape
|
||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||
c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,strings\)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -478,7 +931,7 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escape while quoting
|
||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||
c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,"strings\)"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK_FALSE(c.unterminated_quote());
|
||||
@ -487,10 +940,42 @@ TEST_CASE("converter test invalid split conversions") {
|
||||
|
||||
{
|
||||
// unterminated escaped quote
|
||||
auto tup = c.convert<std::string, std::string, double, std::string>(
|
||||
c.convert<std::string, std::string, double, std::string>(
|
||||
buff(R"(just,some,2,"strings\")"));
|
||||
CHECK_FALSE(c.valid());
|
||||
CHECK(c.unterminated_quote());
|
||||
CHECK_FALSE(c.error_msg().empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("converter test invalid split conversions with exceptions") {
|
||||
ss::converter<ss::escape<'\\'>, ss::trim<' '>, ss::quote<'"'>,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
|
||||
// mismatched quote
|
||||
REQUIRE_EXCEPTION(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>(
|
||||
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>(
|
||||
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>(
|
||||
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>(
|
||||
buff(R"(just,some,2,"strings\")")));
|
||||
CHECK(c.unterminated_quote());
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,11 @@ TEST_CASE("testing extract functions for floating point values") {
|
||||
CHECK_FLOATING_CONVERSION(123.456, float);
|
||||
CHECK_FLOATING_CONVERSION(123.456, double);
|
||||
|
||||
CHECK_FLOATING_CONVERSION(69, float);
|
||||
CHECK_FLOATING_CONVERSION(69, double);
|
||||
CHECK_FLOATING_CONVERSION(59, float);
|
||||
CHECK_FLOATING_CONVERSION(59, double);
|
||||
|
||||
CHECK_FLOATING_CONVERSION(420., float);
|
||||
CHECK_FLOATING_CONVERSION(420., double);
|
||||
CHECK_FLOATING_CONVERSION(4210., float);
|
||||
CHECK_FLOATING_CONVERSION(4210., double);
|
||||
|
||||
CHECK_FLOATING_CONVERSION(0.123, float);
|
||||
CHECK_FLOATING_CONVERSION(0.123, double);
|
||||
|
@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
#include <doctest/doctest.h>
|
||||
@ -8,56 +12,111 @@
|
||||
#include <doctest.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
struct buffer {
|
||||
std::string data_;
|
||||
std::string data_;
|
||||
|
||||
char *operator()(const std::string &data) {
|
||||
data_ = data;
|
||||
return data_.data();
|
||||
}
|
||||
char* operator()(const std::string& data) {
|
||||
data_ = data;
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
char *append(const std::string &data) {
|
||||
data_ += data;
|
||||
return data_.data();
|
||||
}
|
||||
char* append(const std::string& data) {
|
||||
data_ += data;
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
char *append_overwrite_last(const std::string &data, size_t size) {
|
||||
data_.resize(data_.size() - size);
|
||||
return append(data);
|
||||
}
|
||||
char* append_overwrite_last(const std::string& data, size_t size) {
|
||||
data_.resize(data_.size() - size);
|
||||
return append(data);
|
||||
}
|
||||
};
|
||||
|
||||
[[maybe_unused]] inline buffer buff;
|
||||
|
||||
std::string time_now_rand() {
|
||||
std::stringstream ss;
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
ss << std::put_time(&tm, "%d%m%Y%H%M%S");
|
||||
srand(time(nullptr));
|
||||
return ss.str() + std::to_string(rand());
|
||||
}
|
||||
|
||||
struct unique_file_name {
|
||||
static inline int i = 0;
|
||||
|
||||
const std::string name;
|
||||
|
||||
unique_file_name(const std::string& test)
|
||||
: name{"random_" + test + "_" + std::to_string(i++) + "_" +
|
||||
time_now_rand() + "_file.csv"} {
|
||||
}
|
||||
|
||||
~unique_file_name() {
|
||||
std::filesystem::remove(name);
|
||||
}
|
||||
};
|
||||
|
||||
#define CHECK_FLOATING_CONVERSION(input, type) \
|
||||
{ \
|
||||
auto eps = std::numeric_limits<type>::min(); \
|
||||
std::string s = #input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
REQUIRE(t.has_value()); \
|
||||
CHECK_LT(std::abs(t.value() - type(input)), eps); \
|
||||
} \
|
||||
{ \
|
||||
/* check negative too */ \
|
||||
auto eps = std::numeric_limits<type>::min(); \
|
||||
auto s = std::string("-") + #input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
REQUIRE(t.has_value()); \
|
||||
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
||||
}
|
||||
{ \
|
||||
auto eps = std::numeric_limits<type>::min(); \
|
||||
std::string s = #input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
REQUIRE(t.has_value()); \
|
||||
CHECK_LT(std::abs(t.value() - type(input)), eps); \
|
||||
} \
|
||||
{ \
|
||||
/* check negative too */ \
|
||||
auto eps = std::numeric_limits<type>::min(); \
|
||||
auto s = std::string("-") + #input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
REQUIRE(t.has_value()); \
|
||||
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
|
||||
}
|
||||
|
||||
#define CHECK_INVALID_CONVERSION(input, type) \
|
||||
{ \
|
||||
std::string s = input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
CHECK_FALSE(t.has_value()); \
|
||||
}
|
||||
{ \
|
||||
std::string s = input; \
|
||||
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
|
||||
CHECK_FALSE(t.has_value()); \
|
||||
}
|
||||
|
||||
#define REQUIRE_VARIANT(var, el, type) \
|
||||
{ \
|
||||
auto ptr = std::get_if<type>(&var); \
|
||||
REQUIRE(ptr); \
|
||||
REQUIRE_EQ(el, *ptr); \
|
||||
}
|
||||
{ \
|
||||
auto ptr = std::get_if<type>(&var); \
|
||||
REQUIRE(ptr); \
|
||||
REQUIRE_EQ(el, *ptr); \
|
||||
}
|
||||
|
||||
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
|
||||
|
||||
#define REQUIRE_EXCEPTION(...) \
|
||||
try { \
|
||||
__VA_ARGS__; \
|
||||
FAIL("Expected exception"); \
|
||||
} catch (ss::exception & e) { \
|
||||
CHECK_FALSE(std::string{e.what()}.empty()); \
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<std::vector<T>> vector_combinations(const std::vector<T>& v,
|
||||
size_t n) {
|
||||
std::vector<std::vector<T>> ret;
|
||||
if (n <= 1) {
|
||||
for (const auto& i : v) {
|
||||
ret.push_back({i});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto inner_combinations = vector_combinations(v, n - 1);
|
||||
for (const auto& i : v) {
|
||||
for (auto j : inner_combinations) {
|
||||
j.insert(j.begin(), i);
|
||||
ret.push_back(move(j));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} /* namespace */
|
||||
|
File diff suppressed because it is too large
Load Diff
653
test/test_parser2.hpp
Normal file
653
test/test_parser2.hpp
Normal file
@ -0,0 +1,653 @@
|
||||
#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
|
||||
#error "SEGMENT_NAME must be defined"
|
||||
#endif
|
||||
|
||||
// parser tests v2
|
||||
|
||||
namespace {
|
||||
struct random_number_generator {
|
||||
size_t z1 = 12341;
|
||||
size_t z2 = 12342;
|
||||
size_t z3 = 12343;
|
||||
size_t z4 = 12344;
|
||||
|
||||
size_t rand() {
|
||||
uint32_t b;
|
||||
b = ((z1 << 6) ^ z1) >> 13;
|
||||
z1 = ((z1 & 4294967294U) << 18) ^ b;
|
||||
b = ((z2 << 2) ^ z2) >> 27;
|
||||
z2 = ((z2 & 4294967288U) << 2) ^ b;
|
||||
b = ((z3 << 13) ^ z3) >> 21;
|
||||
z3 = ((z3 & 4294967280U) << 7) ^ b;
|
||||
b = ((z4 << 3) ^ z4) >> 12;
|
||||
z4 = ((z4 & 4294967168U) << 13) ^ b;
|
||||
return (z1 ^ z2 ^ z3 ^ z4);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t rand_index(const T& s) {
|
||||
REQUIRE(!s.empty());
|
||||
return rand() % s.size();
|
||||
}
|
||||
|
||||
bool rand_bool() {
|
||||
return (rand() % 100) > 50;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void rand_insert(std::string& dst, const T& src) {
|
||||
dst.insert(rand_index(dst), std::string{src});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void rand_insert_n(std::string& dst, const T& src, size_t n_max) {
|
||||
size_t n = rand() % n_max;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
rand_insert(dst, src);
|
||||
}
|
||||
}
|
||||
} rng;
|
||||
|
||||
struct field {
|
||||
std::string value;
|
||||
bool is_string = false;
|
||||
bool has_spaces_left = false;
|
||||
bool has_spaces_right = false;
|
||||
bool has_new_line = false;
|
||||
|
||||
field(const std::string& input) {
|
||||
value = input;
|
||||
is_string = true;
|
||||
|
||||
has_spaces_left = !input.empty() && input.front() == ' ';
|
||||
has_spaces_right = !input.empty() && input.back() == ' ';
|
||||
has_new_line = input.find_first_of('\n') != std::string::npos;
|
||||
}
|
||||
|
||||
field(int input) {
|
||||
value = std::to_string(input);
|
||||
}
|
||||
|
||||
field(double input) {
|
||||
value = std::to_string(input);
|
||||
}
|
||||
};
|
||||
|
||||
struct column {
|
||||
std::string header;
|
||||
std::vector<field> fields;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
column make_column(const std::string& input_header,
|
||||
const std::vector<field>& input_fields) {
|
||||
using setup = ss::setup<Ts...>;
|
||||
std::vector<field> filtered_fields;
|
||||
|
||||
for (const auto& el : input_fields) {
|
||||
if (!setup::multiline::enabled && el.has_new_line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!setup::escape::enabled && !setup::quote::enabled) {
|
||||
if (setup::trim_left::enabled && el.has_spaces_left) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (setup::trim_right::enabled && el.has_spaces_right) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
filtered_fields.push_back(el);
|
||||
}
|
||||
|
||||
column c;
|
||||
c.header = input_header;
|
||||
c.fields = filtered_fields;
|
||||
return c;
|
||||
}
|
||||
|
||||
[[maybe_unused]] void replace_all2(std::string& s, const std::string& old_value,
|
||||
const std::string& new_value) {
|
||||
for (size_t i = 0; i < 999; ++i) {
|
||||
size_t pos = s.find(old_value);
|
||||
if (pos == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
s.replace(pos, old_value.size(), new_value);
|
||||
}
|
||||
FAIL("bad replace");
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
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 = '\\';
|
||||
constexpr static auto quote = '"';
|
||||
constexpr static auto space = ' ';
|
||||
constexpr static auto new_line = '\n';
|
||||
constexpr static auto helper0 = '#';
|
||||
constexpr static auto helper1 = '$';
|
||||
// constexpr static auto helper3 = '&';
|
||||
|
||||
std::vector<std::string> output;
|
||||
|
||||
if (setup::escape::enabled && setup::quote::enabled) {
|
||||
for (const auto& el : data) {
|
||||
auto value = el.value;
|
||||
|
||||
replace_all2(value, {escape, quote}, {helper1});
|
||||
|
||||
bool quote_newline = rng.rand_bool();
|
||||
bool quote_spacings = rng.rand_bool();
|
||||
bool has_spaces = el.has_spaces_right || el.has_spaces_left;
|
||||
|
||||
// handle escape
|
||||
replace_all2(value, {escape}, {helper0});
|
||||
rng.rand_insert_n(value, escape, 2);
|
||||
if (!quote_newline) {
|
||||
replace_all2(value, {new_line}, {helper1});
|
||||
replace_all2(value, {helper1}, {escape, new_line});
|
||||
}
|
||||
replace_all2(value, {escape, escape}, {escape});
|
||||
replace_all2(value, {escape, helper0}, {helper0});
|
||||
replace_all2(value, {helper0, escape}, {helper0});
|
||||
replace_all2(value, {helper0}, {escape, escape});
|
||||
|
||||
replace_all2(value, {helper1}, {escape, quote});
|
||||
|
||||
replace_all2(value, {escape, quote}, {helper1});
|
||||
|
||||
if (rng.rand_bool() || quote_newline ||
|
||||
(quote_spacings && has_spaces)) {
|
||||
replace_all2(value, {quote}, {helper0});
|
||||
if (rng.rand_bool()) {
|
||||
replace_all2(value, {helper0}, {escape, quote});
|
||||
} else {
|
||||
replace_all2(value, {helper0}, {quote, quote});
|
||||
}
|
||||
value = std::string{quote} + value + std::string{quote};
|
||||
}
|
||||
|
||||
replace_all2(value, {helper1}, {escape, quote});
|
||||
|
||||
if (!quote_spacings && has_spaces) {
|
||||
replace_all2(value, {escape, space}, {helper0});
|
||||
replace_all2(value, {space}, {helper0});
|
||||
replace_all2(value, {helper0}, {escape, space});
|
||||
}
|
||||
|
||||
output.push_back(value);
|
||||
}
|
||||
} else if (setup::escape::enabled) {
|
||||
for (const auto& el : data) {
|
||||
auto value = el.value;
|
||||
|
||||
replace_all2(value, {escape}, {helper0});
|
||||
rng.rand_insert_n(value, escape, 3);
|
||||
replace_all2(value, {new_line}, {helper1});
|
||||
replace_all2(value, {helper1}, {escape, new_line});
|
||||
|
||||
replace_all2(value, {escape, escape}, {escape});
|
||||
replace_all2(value, {escape, helper0}, {helper0});
|
||||
|
||||
replace_all2(value, {helper0, escape}, {helper0});
|
||||
replace_all2(value, {helper0}, {escape, escape});
|
||||
|
||||
if (setup::trim_right::enabled || setup::trim_left::enabled) {
|
||||
// escape space
|
||||
replace_all2(value, {escape, space}, {helper0});
|
||||
replace_all2(value, {space}, {helper0});
|
||||
replace_all2(value, {helper0}, {escape, space});
|
||||
}
|
||||
|
||||
output.push_back(value);
|
||||
}
|
||||
} else if (setup::quote::enabled) {
|
||||
for (const auto& el : data) {
|
||||
auto value = el.value;
|
||||
if (rng.rand_bool() || el.has_new_line || el.has_spaces_left ||
|
||||
el.has_spaces_right) {
|
||||
replace_all2(value, {quote}, {helper0});
|
||||
replace_all2(value, {helper0}, {quote, quote});
|
||||
value = std::string{quote} + value + std::string{quote};
|
||||
}
|
||||
output.push_back(value);
|
||||
}
|
||||
} else {
|
||||
for (const auto& el : data) {
|
||||
output.push_back(el.value);
|
||||
}
|
||||
}
|
||||
|
||||
if (setup::trim_right::enabled) {
|
||||
for (auto& el : output) {
|
||||
size_t n = rng.rand();
|
||||
for (size_t i = 0; i < n % 3; ++i) {
|
||||
el = el + " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setup::trim_left::enabled) {
|
||||
for (auto& el : output) {
|
||||
size_t n = rng.rand();
|
||||
for (size_t i = 0; i < n % 3; ++i) {
|
||||
el = " " + el;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
[[maybe_unused]] void write_to_file(const std::vector<std::string>& data,
|
||||
const std::string& delim, const std::string& file_name) {
|
||||
std::ofstream out{file_name, std::ios_base::app};
|
||||
std::string line;
|
||||
for (size_t i = 0; i < data.size(); ++i) {
|
||||
line += data[i];
|
||||
if (i != data.size() - 1) {
|
||||
line += delim;
|
||||
}
|
||||
}
|
||||
|
||||
out << line << std::endl;
|
||||
}
|
||||
|
||||
#define CHECK_EQ_CRLF(V1, V2) \
|
||||
if (V1 != V2) { \
|
||||
auto tmp1 = V1; \
|
||||
auto tmp2 = V2; \
|
||||
replace_all2(tmp1, "\r\n", "\n"); \
|
||||
replace_all2(tmp2, "\r\n", "\n"); \
|
||||
\
|
||||
CHECK(tmp1 == tmp2); \
|
||||
\
|
||||
if (tmp1 != tmp2) { \
|
||||
replace_all2(tmp1, "\r", "(r)"); \
|
||||
replace_all2(tmp2, "\r", "(r)"); \
|
||||
\
|
||||
replace_all2(tmp1, "\n", "(n)"); \
|
||||
replace_all2(tmp2, "\n", "(n)"); \
|
||||
\
|
||||
replace_all2(tmp1, " ", "_"); \
|
||||
replace_all2(tmp2, " ", "_"); \
|
||||
\
|
||||
std::cout << "<" << tmp1 << ">" << std::endl; \
|
||||
std::cout << "<" << tmp2 << ">" << std::endl; \
|
||||
std::cout << "file: " << f.name << std::endl; \
|
||||
std::cout << "----------------" << std::endl; \
|
||||
} \
|
||||
\
|
||||
} else { \
|
||||
CHECK(V1 == V2); \
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_data_combinations(const std::vector<column>& input_data,
|
||||
const std::string& delim, bool include_header) {
|
||||
using setup = ss::setup<Ts...>;
|
||||
|
||||
if (setup::ignore_header && !include_header) {
|
||||
return;
|
||||
}
|
||||
|
||||
unique_file_name f{"test_parser2" + std::string{SEGMENT_NAME}};
|
||||
std::vector<std::vector<field>> expected_data;
|
||||
std::vector<std::string> header;
|
||||
std::vector<field> field_header;
|
||||
|
||||
auto add_blank_if_ignore_empty = [&] {
|
||||
if constexpr (setup::ignore_empty) {
|
||||
size_t n = rng.rand() % 3;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
write_to_file({}, delim, f.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& el : input_data) {
|
||||
header.push_back(el.header);
|
||||
field_header.push_back(field{el.header});
|
||||
}
|
||||
|
||||
if (include_header) {
|
||||
auto header_data = generate_csv_data<Ts...>(field_header, delim);
|
||||
write_to_file(header_data, delim, f.name);
|
||||
}
|
||||
|
||||
std::vector<int> layout;
|
||||
size_t n = 1 + rng.rand() % 5;
|
||||
|
||||
for (size_t i = 0; i < input_data.size(); ++i) {
|
||||
layout.push_back(i);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
std::vector<field> raw_data;
|
||||
for (const auto& el : input_data) {
|
||||
const auto& fields = el.fields;
|
||||
if (fields.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
raw_data.push_back(fields[rng.rand_index(fields)]);
|
||||
}
|
||||
|
||||
add_blank_if_ignore_empty();
|
||||
|
||||
expected_data.push_back(raw_data);
|
||||
auto data = generate_csv_data<Ts...>(raw_data, delim);
|
||||
write_to_file(data, delim, f.name);
|
||||
|
||||
/*
|
||||
std::cout << "[.";
|
||||
for (const auto& el : data) {
|
||||
std::cout << el << '.';
|
||||
}
|
||||
std::cout << "]" << std::endl;
|
||||
*/
|
||||
}
|
||||
|
||||
auto layout_combinations = include_header && !setup::ignore_header
|
||||
? vector_combinations(layout, layout.size())
|
||||
: std::vector<std::vector<int>>{layout};
|
||||
|
||||
auto remove_duplicates = [](const auto& vec) {
|
||||
std::vector<int> unique_vec;
|
||||
std::unordered_set<int> vec_set;
|
||||
for (const auto& el : vec) {
|
||||
if (vec_set.find(el) == vec_set.end()) {
|
||||
vec_set.insert(el);
|
||||
unique_vec.push_back(el);
|
||||
}
|
||||
}
|
||||
|
||||
return unique_vec;
|
||||
};
|
||||
|
||||
std::vector<std::vector<int>> unique_layout_combinations;
|
||||
for (const auto& layout : layout_combinations) {
|
||||
unique_layout_combinations.push_back(remove_duplicates(layout));
|
||||
}
|
||||
|
||||
for (const auto& layout : unique_layout_combinations) {
|
||||
ss::parser<setup> p{f.name, delim};
|
||||
|
||||
if (include_header && !setup::ignore_header) {
|
||||
std::vector<std::string> fields;
|
||||
for (const auto& index : layout) {
|
||||
fields.push_back(header[index]);
|
||||
}
|
||||
|
||||
p.use_fields(fields);
|
||||
|
||||
if (!p.valid()) {
|
||||
if constexpr (setup::string_error) {
|
||||
std::cout << p.error_msg() << std::endl;
|
||||
} else {
|
||||
std::cout << "use_fields failed" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
REQUIRE(p.valid());
|
||||
}
|
||||
|
||||
auto check_error = [&p] {
|
||||
CHECK(p.valid());
|
||||
if (!p.valid()) {
|
||||
if constexpr (setup::string_error) {
|
||||
std::cout << p.error_msg() << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int num_columns = layout.size();
|
||||
for (size_t i = 0; i < n + 1; ++i) {
|
||||
try {
|
||||
switch (num_columns) {
|
||||
case 1: {
|
||||
auto s0 = p.template get_next<std::string>();
|
||||
if (i < n) {
|
||||
check_error();
|
||||
// std::cout << s0 << std::endl;
|
||||
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||
} else {
|
||||
CHECK(p.eof());
|
||||
CHECK(!p.valid());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
auto [s0, s1] =
|
||||
p.template get_next<std::string, std::string>();
|
||||
if (i < n) {
|
||||
check_error();
|
||||
// std::cout << s0 << ' ' << s1 << std::endl;
|
||||
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||
} else {
|
||||
CHECK(p.eof());
|
||||
CHECK(!p.valid());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto [s0, s1, s2] =
|
||||
p.template get_next<std::string, std::string,
|
||||
std::string>();
|
||||
if (i < n) {
|
||||
check_error();
|
||||
// std::cout << s0 << ' ' << s1 << ' ' << s2 <<
|
||||
// std::endl;
|
||||
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||
} else {
|
||||
CHECK(p.eof());
|
||||
CHECK(!p.valid());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
auto [s0, s1, s2, s3] =
|
||||
p.template get_next<std::string, std::string,
|
||||
std::string, std::string>();
|
||||
if (i < n) {
|
||||
check_error();
|
||||
/*
|
||||
std::cout << s0 << ' ' << s1 << ' ' << s2 << ' ' << s3
|
||||
<< std::endl;
|
||||
*/
|
||||
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||
CHECK_EQ_CRLF(s3, expected_data[i][layout[3]].value);
|
||||
} else {
|
||||
CHECK(p.eof());
|
||||
CHECK(!p.valid());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
auto [s0, s1, s2, s3, s4] =
|
||||
p.template get_next<std::string, std::string,
|
||||
std::string, std::string,
|
||||
std::string>();
|
||||
if (i < n) {
|
||||
check_error();
|
||||
// std::cout << s0 << ' ' << s1 << ' ' << s2 << ' ' <<
|
||||
// s3
|
||||
// << ' ' << s4 << std::endl;
|
||||
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
|
||||
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
|
||||
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
|
||||
CHECK_EQ_CRLF(s3, expected_data[i][layout[3]].value);
|
||||
CHECK_EQ_CRLF(s4, expected_data[i][layout[4]].value);
|
||||
} else {
|
||||
CHECK(p.eof());
|
||||
CHECK(!p.valid());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
FAIL(("Invalid number of columns: " +
|
||||
std::to_string(num_columns)));
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
if (i < n) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_option_combinations() {
|
||||
column ints0 =
|
||||
make_column<Ts...>("ints0", {field{123}, field{45}, field{6}});
|
||||
column ints1 =
|
||||
make_column<Ts...>("ints1", {field{123}, field{45}, field{6}});
|
||||
column ints2 =
|
||||
make_column<Ts...>("ints2", {field{123}, field{45}, field{6}});
|
||||
|
||||
column floats0 =
|
||||
make_column<Ts...>("floats0", {field{1.23}, field{456.7}, field{0.8},
|
||||
field{910}, field{123456789.987654321}});
|
||||
column floats1 =
|
||||
make_column<Ts...>("floats1", {field{1.23}, field{456.7}, field{0.8},
|
||||
field{910}, field{123456789.987654321}});
|
||||
column floats2 =
|
||||
make_column<Ts...>("floats2", {field{1.23}, field{456.7}, field{0.8},
|
||||
field{910}, field{123456789.987654321}});
|
||||
|
||||
column strings0 =
|
||||
make_column<Ts...>("strings0", {field{"just"}, field{"some"},
|
||||
field{"random"}, field{"string"}});
|
||||
|
||||
column strings1 =
|
||||
make_column<Ts...>("strings1", {field{"st\"rings"}, field{"w\"\"ith"},
|
||||
field{"qu\"otes\\"}, field{"\\a\\n\\d"},
|
||||
field{"escapes\""}});
|
||||
|
||||
column strings2 =
|
||||
make_column<Ts...>("strings2",
|
||||
{field{" with "}, field{" spaces"},
|
||||
field{"and "}, field{"\nnew"}, field{" \nlines"},
|
||||
field{" a\n\nn\n\nd "}, field{" \nso\n "},
|
||||
field{"on"}});
|
||||
|
||||
auto columns0 = std::vector{ints0, strings0, floats0, strings1, strings2};
|
||||
auto columns1 = std::vector{strings2, strings1, floats0, strings0, ints0};
|
||||
auto columns2 = std::vector{floats0, strings1, ints0, strings2, strings0};
|
||||
auto columns3 = std::vector{ints0, ints1, ints2};
|
||||
auto columns4 = std::vector{floats0, floats1, floats2};
|
||||
auto columns5 = std::vector{strings1, strings2};
|
||||
auto columns6 = std::vector{strings1};
|
||||
auto columns7 = std::vector{strings2};
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
for (const auto& delimiter : {",", "-", "--"}) {
|
||||
for (const auto& columns :
|
||||
{columns0, columns1, columns2, columns3, columns4, columns5,
|
||||
columns6, columns7}) {
|
||||
try {
|
||||
test_data_combinations<Ts...>(columns, delimiter, false);
|
||||
test_data_combinations<Ts...>(columns, delimiter, true);
|
||||
} catch (std::exception& e) {
|
||||
std::cout << typeid(ss::parser<Ts...>).name() << std::endl;
|
||||
FAIL_CHECK(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_option_combinations0() {
|
||||
test_option_combinations<Ts...>();
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
test_option_combinations<Ts..., ss::ignore_empty>();
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_option_combinations1() {
|
||||
test_option_combinations0<Ts...>();
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
test_option_combinations0<Ts..., ss::ignore_header>();
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_option_combinations2() {
|
||||
test_option_combinations1<Ts...>();
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
test_option_combinations1<Ts..., ss::string_error>();
|
||||
test_option_combinations1<Ts..., ss::throw_on_error>();
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void test_option_combinations3() {
|
||||
using trim = ss::trim<' '>;
|
||||
|
||||
test_option_combinations2<Ts...>();
|
||||
test_option_combinations2<Ts..., trim>();
|
||||
}
|
||||
|
||||
} /* namespace */
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("parser test various cases version 2 segment 1") {
|
||||
using quote = ss::quote<'"'>;
|
||||
using escape = ss::escape<'\\'>;
|
||||
using multiline = ss::multiline;
|
||||
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
using multiline_r = ss::multiline_restricted<10>;
|
||||
using trimr = ss::trim_right<' '>;
|
||||
using triml = ss::trim_left<' '>;
|
||||
|
||||
// segment 1
|
||||
test_option_combinations3<>();
|
||||
test_option_combinations3<escape>();
|
||||
test_option_combinations3<quote>();
|
||||
|
||||
// segment 2
|
||||
test_option_combinations3<escape, quote>();
|
||||
test_option_combinations3<escape, multiline>();
|
||||
test_option_combinations3<quote, multiline>();
|
||||
|
||||
// segment 3
|
||||
test_option_combinations3<escape, quote, multiline>();
|
||||
test_option_combinations3<escape, quote, multiline_r>();
|
||||
|
||||
// segment 4
|
||||
test_option_combinations<escape, quote, multiline, triml>();
|
||||
test_option_combinations<escape, quote, multiline, trimr>();
|
||||
#else
|
||||
test_option_combinations3<escape, quote, multiline>();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
14
test/test_parser2_1.cpp
Normal file
14
test/test_parser2_1.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
#define SEGMENT_NAME "segment1"
|
||||
#include "test_parser2.hpp"
|
||||
|
||||
TEST_CASE("parser test various cases version 2 segment 1") {
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
using quote = ss::quote<'"'>;
|
||||
using escape = ss::escape<'\\'>;
|
||||
|
||||
test_option_combinations3<>();
|
||||
test_option_combinations3<escape>();
|
||||
test_option_combinations3<quote>();
|
||||
#endif
|
||||
}
|
||||
|
15
test/test_parser2_2.cpp
Normal file
15
test/test_parser2_2.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#define SEGMENT_NAME "segment2"
|
||||
#include "test_parser2.hpp"
|
||||
|
||||
TEST_CASE("parser test various cases version 2 segment 2") {
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
using quote = ss::quote<'"'>;
|
||||
using escape = ss::escape<'\\'>;
|
||||
using multiline = ss::multiline;
|
||||
|
||||
test_option_combinations3<escape, quote>();
|
||||
test_option_combinations3<escape, multiline>();
|
||||
test_option_combinations3<quote, multiline>();
|
||||
#endif
|
||||
}
|
||||
|
15
test/test_parser2_3.cpp
Normal file
15
test/test_parser2_3.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#define SEGMENT_NAME "segment3"
|
||||
#include "test_parser2.hpp"
|
||||
|
||||
TEST_CASE("parser test various cases version 2 segment 3") {
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
using quote = ss::quote<'"'>;
|
||||
using escape = ss::escape<'\\'>;
|
||||
using multiline = ss::multiline;
|
||||
using multiline_r = ss::multiline_restricted<10>;
|
||||
|
||||
test_option_combinations3<escape, quote, multiline>();
|
||||
test_option_combinations3<escape, quote, multiline_r>();
|
||||
#endif
|
||||
}
|
||||
|
19
test/test_parser2_4.cpp
Normal file
19
test/test_parser2_4.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#define SEGMENT_NAME "segment4"
|
||||
#include "test_parser2.hpp"
|
||||
|
||||
TEST_CASE("parser test various cases version 2 segment 4") {
|
||||
using quote = ss::quote<'"'>;
|
||||
using escape = ss::escape<'\\'>;
|
||||
using multiline = ss::multiline;
|
||||
|
||||
#ifdef CMAKE_GITHUB_CI
|
||||
using trimr = ss::trim_right<' '>;
|
||||
using triml = ss::trim_left<' '>;
|
||||
|
||||
test_option_combinations<escape, quote, multiline, triml>();
|
||||
test_option_combinations<escape, quote, multiline, trimr>();
|
||||
#else
|
||||
test_option_combinations3<escape, quote, multiline>();
|
||||
#endif
|
||||
}
|
||||
|
@ -5,15 +5,15 @@
|
||||
#include <ss/splitter.hpp>
|
||||
|
||||
namespace {
|
||||
constexpr static auto combinations_size_default = 4;
|
||||
size_t combinations_size = combinations_size_default;
|
||||
constexpr static auto num_combinations_default = 4;
|
||||
size_t num_combinations = num_combinations_default;
|
||||
|
||||
struct set_combinations_size {
|
||||
set_combinations_size(size_t size) {
|
||||
combinations_size = size;
|
||||
struct set_num_combinations {
|
||||
set_num_combinations(size_t size) {
|
||||
num_combinations = size;
|
||||
}
|
||||
~set_combinations_size() {
|
||||
combinations_size = combinations_size_default;
|
||||
~set_num_combinations() {
|
||||
num_combinations = num_combinations_default;
|
||||
}
|
||||
};
|
||||
|
||||
@ -127,33 +127,13 @@ std::vector<std::string> combinations(const std::vector<std::string>& v,
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> vector_combinations(
|
||||
const std::vector<std::string>& v, size_t n) {
|
||||
std::vector<std::vector<std::string>> ret;
|
||||
if (n <= 1) {
|
||||
for (const auto& i : v) {
|
||||
ret.push_back({i});
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto inner_combinations = vector_combinations(v, n - 1);
|
||||
for (const auto& i : v) {
|
||||
for (auto j : inner_combinations) {
|
||||
j.insert(j.begin(), i);
|
||||
ret.push_back(move(j));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::string>, std::vector<std::vector<std::string>>>
|
||||
make_combinations(const std::vector<std::string>& input,
|
||||
const std::vector<std::string>& output,
|
||||
const std::string& delim) {
|
||||
std::vector<std::string> lines;
|
||||
std::vector<std::vector<std::string>> expectations;
|
||||
for (size_t i = 0; i < combinations_size; ++i) {
|
||||
for (size_t i = 0; i < num_combinations; ++i) {
|
||||
auto l = combinations(input, delim, i);
|
||||
lines.reserve(lines.size() + l.size());
|
||||
lines.insert(lines.end(), l.begin(), l.end());
|
||||
@ -173,9 +153,12 @@ make_combinations(const std::vector<std::string>& input,
|
||||
using matches_type = std::vector<std::pair<case_type, std::string>>;
|
||||
|
||||
template <typename... Matchers>
|
||||
void test_combinations(matches_type& matches, std::vector<std::string> delims) {
|
||||
static inline void test_combinations(matches_type& matches,
|
||||
std::vector<std::string> delims) {
|
||||
|
||||
ss::splitter<Matchers...> s;
|
||||
ss::splitter<Matchers..., ss::throw_on_error> st;
|
||||
|
||||
std::vector<std::string> inputs;
|
||||
std::vector<std::string> outputs;
|
||||
for (const auto& [cases, e] : matches) {
|
||||
@ -194,6 +177,13 @@ void test_combinations(matches_type& matches, std::vector<std::string> delims) {
|
||||
auto vec = s.split(buff(lines[i].c_str()), delim);
|
||||
CHECK(s.valid());
|
||||
CHECK_EQ(words(vec), expectations[i]);
|
||||
|
||||
try {
|
||||
auto vec = st.split(buff(lines[i].c_str()), delim);
|
||||
CHECK_EQ(words(vec), expectations[i]);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,7 +243,7 @@ TEST_CASE("splitter test with quote") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with trim") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced({R"(x)"}, " ");
|
||||
case_type case2 = spaced({R"(yy)"}, " ");
|
||||
case_type case3 = spaced({R"(y y)"}, " ");
|
||||
@ -320,7 +310,7 @@ TEST_CASE("splitter test with escape") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with quote and trim") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced({R"("""")"}, " ");
|
||||
case_type case2 = spaced({R"("x""x")", R"(x"x)"}, " ");
|
||||
case_type case3 = spaced({R"("")", R"()"}, " ");
|
||||
@ -438,7 +428,7 @@ TEST_CASE("splitter test with escape and trim") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with quote and escape and trim") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced({R"("\"")", R"(\")", R"("""")"}, " ");
|
||||
case_type case2 =
|
||||
spaced({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, " ");
|
||||
@ -509,6 +499,15 @@ TEST_CASE("splitter test error mode") {
|
||||
CHECK_FALSE(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
CHECK_FALSE(s.error_msg().empty());
|
||||
|
||||
try {
|
||||
ss::splitter<ss::throw_on_error> s;
|
||||
s.split(buff("just,some,strings"), "");
|
||||
FAIL("expected exception");
|
||||
} catch (ss::exception& e) {
|
||||
CHECK_FALSE(std::string{e.what()}.empty());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
@ -522,11 +521,17 @@ TEST_CASE("splitter test error mode") {
|
||||
}
|
||||
|
||||
template <typename Splitter>
|
||||
auto expect_unterminated_quote(Splitter& s, const std::string& line) {
|
||||
auto vec = s.split(buff(line.c_str()));
|
||||
CHECK_FALSE(s.valid());
|
||||
CHECK(s.unterminated_quote());
|
||||
return vec;
|
||||
static inline auto expect_unterminated_quote(Splitter& s,
|
||||
const std::string& line) {
|
||||
try {
|
||||
auto vec = s.split(buff(line.c_str()));
|
||||
CHECK(s.valid());
|
||||
CHECK(s.unterminated_quote());
|
||||
return vec;
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
return decltype(s.split(buff(line.c_str()))){};
|
||||
}
|
||||
}
|
||||
|
||||
namespace ss {
|
||||
@ -550,7 +555,9 @@ TEST_CASE("splitter test resplit unterminated quote") {
|
||||
{
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::escape<'\\'>> c;
|
||||
auto& s = c.splitter;
|
||||
|
||||
auto vec = expect_unterminated_quote(s, R"("x)");
|
||||
|
||||
CHECK_EQ(vec.size(), 1);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
|
||||
@ -631,7 +638,7 @@ TEST_CASE("splitter test resplit unterminated quote") {
|
||||
{
|
||||
auto new_line = buff.append(R"(,dom)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK_FALSE(s.valid());
|
||||
CHECK(s.valid());
|
||||
CHECK(s.unterminated_quote());
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
@ -770,6 +777,269 @@ TEST_CASE("splitter test resplit unterminated quote") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test resplit unterminated quote with exceptions") {
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::escape<'\\'>,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
|
||||
auto vec = expect_unterminated_quote(s, R"("x)");
|
||||
|
||||
CHECK_EQ(vec.size(), 1);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
|
||||
{
|
||||
auto new_linet =
|
||||
buff.append_overwrite_last(R"(a\x)", c.size_shifted());
|
||||
|
||||
vec = c.resplit(new_linet, strlen(new_linet));
|
||||
|
||||
CHECK(s.unterminated_quote());
|
||||
CHECK_EQ(vec.size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto new_linet =
|
||||
buff.append_overwrite_last(R"(")", c.size_shifted());
|
||||
|
||||
vec = c.resplit(new_linet, strlen(new_linet));
|
||||
REQUIRE(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
REQUIRE_EQ(vec.size(), 1);
|
||||
CHECK_EQ(words(vec)[0], "xax");
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, "\"just");
|
||||
CHECK_EQ(vec.size(), 1);
|
||||
|
||||
auto new_line = buff.append(R"(",strings)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
std::vector<std::string> expected{"just", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, "just,some,\"random");
|
||||
std::vector<std::string> expected{"just", "some", "just,some,\""};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
|
||||
auto new_line = buff.append(R"(",strings)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just", "some", "random", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"("just","some","ran"")");
|
||||
std::vector<std::string> expected{"just", "some", R"("just","some",")"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
|
||||
auto new_line =
|
||||
buff.append_overwrite_last(R"(,dom","strings")", c.size_shifted());
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just", "some", "ran\",dom", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::multiline, ss::throw_on_error> c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"("just","some","ran)");
|
||||
std::vector<std::string> expected{"just", "some", R"("just","some",")"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
|
||||
{
|
||||
auto new_line = buff.append(R"(,dom)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK(s.unterminated_quote());
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
|
||||
{
|
||||
auto new_line = buff.append(R"(",strings)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just", "some", "ran,dom", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"("just\"some","ra)");
|
||||
std::vector<std::string> expected{"just\"some"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line = buff.append(R"(n,dom",str\"ings)");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just\"some", "ran,dom", "str\"ings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec =
|
||||
expect_unterminated_quote(s, "3,4,"
|
||||
"\"just0\\\n1\\\n22\\\n33333x\\\n4");
|
||||
|
||||
std::vector<std::string> expected{"3", "4"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line =
|
||||
buff.append_overwrite_last("\nx5strings\"", c.size_shifted());
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"3", "4", "just0\n1\n22\n33333x\n4\nx5strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"("just\"some","ra"")");
|
||||
std::vector<std::string> expected{"just\"some"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line = buff.append_overwrite_last(R"(n,dom",str\"ings)",
|
||||
c.size_shifted());
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just\"some", "ra\"n,dom", "str\"ings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::escape<'\\'>, ss::multiline,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"("just\"some","r\a\a\\\a\")");
|
||||
std::vector<std::string> expected{"just\"some"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line = buff.append_overwrite_last(R"(n,dom",str\"ings)",
|
||||
c.size_shifted());
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just\"some", "raa\\a\"n,dom", "str\"ings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::trim<' '>, ss::multiline,
|
||||
ss::throw_on_error>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"( "just" ,some, "ra )");
|
||||
std::vector<std::string> expected{"just", "some"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line = buff.append(R"( n,dom" , strings )");
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"just", "some", "ra n,dom", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
|
||||
try {
|
||||
ss::converter<ss::quote<'"'>, ss::trim<' '>, ss::escape<'\\'>,
|
||||
ss::multiline>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
auto vec = expect_unterminated_quote(s, R"( "ju\"st" ,some, "ra \")");
|
||||
std::vector<std::string> expected{"ju\"st", "some"};
|
||||
auto w = words(vec);
|
||||
w.pop_back();
|
||||
CHECK_EQ(w, expected);
|
||||
REQUIRE(s.unterminated_quote());
|
||||
{
|
||||
auto new_line =
|
||||
buff.append_overwrite_last(R"( n,dom" , strings )",
|
||||
c.size_shifted());
|
||||
vec = c.resplit(new_line, strlen(new_line));
|
||||
CHECK(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
expected = {"ju\"st", "some", "ra \" n,dom", "strings"};
|
||||
CHECK_EQ(words(vec), expected);
|
||||
}
|
||||
} catch (ss::exception& e) {
|
||||
FAIL(std::string{e.what()});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test invalid splits") {
|
||||
ss::converter<ss::string_error, ss::quote<'"'>, ss::trim<' '>,
|
||||
ss::escape<'\\'>>
|
||||
@ -808,14 +1078,44 @@ TEST_CASE("splitter test invalid splits") {
|
||||
|
||||
// invalid resplit
|
||||
char new_line[] = "some";
|
||||
auto a = c.resplit(new_line, strlen(new_line));
|
||||
c.resplit(new_line, strlen(new_line));
|
||||
CHECK_FALSE(s.valid());
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
CHECK_FALSE(s.error_msg().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test invalid splits with exceptions") {
|
||||
ss::converter<ss::throw_on_error, ss::quote<'"'>, ss::trim<' '>,
|
||||
ss::escape<'\\'>>
|
||||
c;
|
||||
auto& s = c.splitter;
|
||||
|
||||
// empty delimiter
|
||||
REQUIRE_EXCEPTION(s.split(buff("some,random,strings"), ""));
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
|
||||
// mismatched delimiter
|
||||
REQUIRE_EXCEPTION(s.split(buff(R"(some,"random,"strings")")));
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
|
||||
// unterminated escape
|
||||
REQUIRE_EXCEPTION(s.split(buff(R"(some,random,strings\)")));
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
|
||||
// unterminated escape
|
||||
REQUIRE_EXCEPTION(s.split(buff(R"(some,random,"strings\)")));
|
||||
CHECK_FALSE(s.unterminated_quote());
|
||||
|
||||
// unterminated quote
|
||||
REQUIRE_EXCEPTION(s.split(buff("some,random,\"strings")));
|
||||
CHECK(s.unterminated_quote());
|
||||
|
||||
// invalid resplit
|
||||
char new_line[] = "some";
|
||||
REQUIRE_EXCEPTION(c.resplit(new_line, strlen(new_line)));
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with trim_left") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced_left({R"(x )"}, " ");
|
||||
case_type case2 = spaced_left({R"(yy )"}, " ");
|
||||
case_type case3 = spaced_left({R"(y y )"}, " ");
|
||||
@ -845,7 +1145,7 @@ TEST_CASE("splitter test with trim_left") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with trim_right") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced_right({R"( x)"}, " ");
|
||||
case_type case2 = spaced_right({R"( yy)"}, " ");
|
||||
case_type case3 = spaced_right({R"( y y)"}, " ");
|
||||
@ -876,7 +1176,7 @@ TEST_CASE("splitter test with trim_right") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with trim_right and trim_left") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced_right({R"(-x)"}, "-");
|
||||
case_type case2 = spaced_left({R"(yy_)"}, "_");
|
||||
case_type case3 = spaced_right({R"(-y y)"}, "-");
|
||||
@ -894,7 +1194,7 @@ TEST_CASE("splitter test with trim_right and trim_left") {
|
||||
}
|
||||
|
||||
TEST_CASE("splitter test with quote and escape, trim_left and trim_right") {
|
||||
auto guard = set_combinations_size(3);
|
||||
auto guard = set_num_combinations(3);
|
||||
case_type case1 = spaced_left({R"("\"")", R"(\")", R"("""")"}, "_");
|
||||
case_type case2 =
|
||||
spaced_left({R"("x\"x")", R"(x\"x)", R"(x"x)", R"("x""x")"}, "_");
|
||||
|
Loading…
Reference in New Issue
Block a user