mirror of
https://github.com/red0124/ssp.git
synced 2025-01-23 13:05:20 +01:00
add possibility to iterate with the parser, update unit tests, update README
This commit is contained in:
parent
e62afbb8a5
commit
1ddc61c62e
26
README.md
26
README.md
@ -35,13 +35,8 @@ Bill (Heath) Gates,65,3.3
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
ss::parser p{"students.csv"};
|
ss::parser p{"students.csv"};
|
||||||
if (!p.valid()) {
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!p.eof()) {
|
|
||||||
auto [name, age, grade] = p.get_next<std::string, int, double>();
|
|
||||||
|
|
||||||
|
for(auto& [name, age, grade] : p.iterate<std::string, int, double>()) {
|
||||||
if (p.valid()) {
|
if (p.valid()) {
|
||||||
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
std::cout << name << ' ' << age << ' ' << grade << std::endl;
|
||||||
}
|
}
|
||||||
@ -88,7 +83,17 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
|||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## Conversions
|
## Conversions
|
||||||
The above example will be used to show some of the features of the library. As seen above, the **get_next** method returns a tuple of objects specified inside the template type list.
|
An alternate loop to the example above would look like:
|
||||||
|
```cpp
|
||||||
|
while(!p.eof()) {
|
||||||
|
auto [name, age, grade] = p.get_next<std::string, int, double>();
|
||||||
|
if (p.valid()) {
|
||||||
|
std::cout << name << ' ' << 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.
|
||||||
|
|
||||||
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 double the conversion would fail.
|
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 double the conversion would fail.
|
||||||
|
|
||||||
@ -119,6 +124,12 @@ This works with any object if the constructor could be invoked using the templat
|
|||||||
auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
|
auto vec = p.get_object<std::vector<std::string>, std::string, std::string,
|
||||||
std::string>();
|
std::string>();
|
||||||
```
|
```
|
||||||
|
An iteration loop as in the first example which returns objects would look like:
|
||||||
|
```cpp
|
||||||
|
for(auto& student : p.iterate_object<student, std::string, int, double>()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
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.
|
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
|
```cpp
|
||||||
struct student {
|
struct student {
|
||||||
@ -134,6 +145,7 @@ The method can be used to compare the object, serialize it, deserialize it, etc.
|
|||||||
// returns student
|
// returns student
|
||||||
auto s = p.get_next<student>();
|
auto s = p.get_next<student>();
|
||||||
```
|
```
|
||||||
|
This works with the iteration loop too.
|
||||||
*Note, the order in which the members of the tied method are returned must match the order of the elements in the csv*.
|
*Note, the order in which the members of the tied method are returned must match the order of the elements in the csv*.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ss {
|
namespace ss {
|
||||||
@ -21,7 +24,6 @@ inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
|||||||
return getline(lineptr, n, stream);
|
return getline(lineptr, n, stream);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
#include <cstdint>
|
|
||||||
using ssize_t = int64_t;
|
using ssize_t = int64_t;
|
||||||
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) {
|
||||||
size_t pos;
|
size_t pos;
|
||||||
|
@ -85,6 +85,73 @@ public:
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////
|
||||||
|
// iterator
|
||||||
|
////////////////
|
||||||
|
|
||||||
|
template <bool get_object, typename T, typename... Ts>
|
||||||
|
struct iterable {
|
||||||
|
struct iterator {
|
||||||
|
using value =
|
||||||
|
ss::ternary_t<get_object, T, no_void_validator_tup_t<T, Ts...>>;
|
||||||
|
|
||||||
|
iterator() : parser_{nullptr} {}
|
||||||
|
iterator(parser<Matchers...>* parser) : parser_{parser} {}
|
||||||
|
|
||||||
|
value& operator*() { return value_; }
|
||||||
|
value* operator->() { return &value_; }
|
||||||
|
|
||||||
|
iterator& operator++() {
|
||||||
|
if (parser_->eof()) {
|
||||||
|
parser_ = nullptr;
|
||||||
|
} else {
|
||||||
|
if constexpr (get_object) {
|
||||||
|
value_ =
|
||||||
|
std::move(parser_->template get_object<T, Ts...>());
|
||||||
|
} else {
|
||||||
|
value_ =
|
||||||
|
std::move(parser_->template get_next<T, Ts...>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator& operator++(int) { return ++*this; }
|
||||||
|
|
||||||
|
friend bool operator==(const iterator& lhs, const iterator& rhs) {
|
||||||
|
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
|
||||||
|
(lhs.parser_ == rhs.parser_ &&
|
||||||
|
&lhs.value_ == &rhs.value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
|
||||||
|
return !(lhs == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
value value_;
|
||||||
|
parser<Matchers...>* parser_;
|
||||||
|
};
|
||||||
|
|
||||||
|
iterable(parser<Matchers...>* parser) : parser_{parser} {}
|
||||||
|
|
||||||
|
iterator begin() { return ++iterator{parser_}; }
|
||||||
|
iterator end() { return iterator{}; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
parser<Matchers...>* parser_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
auto iterate() {
|
||||||
|
return iterable<false, Ts...>{this};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
auto iterate_object() {
|
||||||
|
return iterable<true, Ts...>{this};
|
||||||
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
// composite conversion
|
// composite conversion
|
||||||
////////////////
|
////////////////
|
||||||
|
@ -388,7 +388,7 @@ T to_object_impl(std::index_sequence<Is...>, U&& data) {
|
|||||||
|
|
||||||
template <class T, class U>
|
template <class T, class U>
|
||||||
T to_object(U&& data) {
|
T to_object(U&& data) {
|
||||||
using NoRefU = std::remove_reference_t<U>;
|
using NoRefU = std::decay_t<U>;
|
||||||
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
|
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
|
||||||
return to_object_impl<
|
return to_object_impl<
|
||||||
T>(std::make_index_sequence<std::tuple_size<NoRefU>{}>{},
|
T>(std::make_index_sequence<std::tuple_size<NoRefU>{}>{},
|
||||||
|
@ -21,12 +21,9 @@ struct unique_file_name {
|
|||||||
|
|
||||||
unique_file_name()
|
unique_file_name()
|
||||||
: name{"random_" + std::to_string(i++) + time_now_rand() +
|
: name{"random_" + std::to_string(i++) + time_now_rand() +
|
||||||
"_file.csv"} {
|
"_file.csv"} {}
|
||||||
}
|
|
||||||
|
|
||||||
~unique_file_name() {
|
~unique_file_name() { std::filesystem::remove(name); }
|
||||||
std::filesystem::remove(name);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void replace_all(std::string& s, const std::string& from,
|
void replace_all(std::string& s, const std::string& from,
|
||||||
@ -60,9 +57,7 @@ struct X {
|
|||||||
.append(delim)
|
.append(delim)
|
||||||
.append(s);
|
.append(s);
|
||||||
}
|
}
|
||||||
auto tied() const {
|
auto tied() const { return std::tie(i, d, s); }
|
||||||
return std::tie(i, d, s);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -98,40 +93,85 @@ TEST_CASE("parser test various cases") {
|
|||||||
ss::parser<ss::string_error> p{f.name, ","};
|
ss::parser<ss::string_error> p{f.name, ","};
|
||||||
ss::parser p0{std::move(p)};
|
ss::parser p0{std::move(p)};
|
||||||
p = std::move(p0);
|
p = std::move(p0);
|
||||||
|
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
|
ss::parser<ss::string_error> p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
auto a = p.get_next<int, double, std::string>();
|
auto a = p.get_next<int, double, std::string>();
|
||||||
i.emplace_back(ss::to_object<X>(a));
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& a : p2.iterate<int, double, std::string>()) {
|
||||||
|
i2.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_EQ(i, data);
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ss::parser p{f.name, ","};
|
ss::parser p{f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
|
ss::parser p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
|
ss::parser p3{f.name, ","};
|
||||||
|
std::vector<X> i3;
|
||||||
|
|
||||||
std::vector<X> expected = {std::begin(data) + 1, std::end(data)};
|
std::vector<X> expected = {std::begin(data) + 1, std::end(data)};
|
||||||
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
|
||||||
p.ignore_next();
|
p.ignore_next();
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
using tup = std::tuple<int, double, std::string>;
|
|
||||||
auto a = p.get_next<tup>();
|
auto a = p.get_next<tup>();
|
||||||
i.emplace_back(ss::to_object<X>(a));
|
i.emplace_back(ss::to_object<X>(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p2.ignore_next();
|
||||||
|
for (const auto& a : p2.iterate<tup>()) {
|
||||||
|
i2.emplace_back(ss::to_object<X>(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
p3.ignore_next();
|
||||||
|
for (auto it = p3.iterate<tup>().begin(); it != p3.iterate<tup>().end();
|
||||||
|
++it) {
|
||||||
|
i3.emplace_back(ss::to_object<X>(*it));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_EQ(i, expected);
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
|
CHECK_EQ(i3, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ss::parser p{f.name, ","};
|
ss::parser p{f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
ss::parser p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
i.push_back(p.get_object<X, int, double, std::string>());
|
i.push_back(p.get_object<X, int, double, std::string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto&& a : p2.iterate_object<X, int, double, std::string>()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (auto&& a : p.iterate_object<X, int, double, std::string>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_EQ(i, data);
|
CHECK_EQ(i, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,11 +179,32 @@ TEST_CASE("parser test various cases") {
|
|||||||
ss::parser p{f.name, ","};
|
ss::parser p{f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
while (!p.eof()) {
|
ss::parser p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
using tup = std::tuple<int, double, std::string>;
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
while (!p.eof()) {
|
||||||
i.push_back(p.get_object<X, tup>());
|
i.push_back(p.get_object<X, tup>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = p2.iterate_object<X, tup>().begin();
|
||||||
|
it != p2.iterate_object<X, tup>().end(); it++) {
|
||||||
|
i2.push_back({it->i, it->d, it->s});
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
CHECK_EQ(i2, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
using tup = std::tuple<int, double, std::string>;
|
||||||
|
for (auto&& a : p.iterate_object<X, tup>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_EQ(i, data);
|
CHECK_EQ(i, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,11 +219,25 @@ TEST_CASE("parser test various cases") {
|
|||||||
CHECK_EQ(i, data);
|
CHECK_EQ(i, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ss::parser p{f.name, ","};
|
||||||
|
std::vector<X> i;
|
||||||
|
|
||||||
|
for (auto&& a : p.iterate<X>()) {
|
||||||
|
i.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_EQ(i, data);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
constexpr int excluded = 3;
|
constexpr int excluded = 3;
|
||||||
ss::parser p{f.name, ","};
|
ss::parser p{f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
|
ss::parser p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
auto a =
|
auto a =
|
||||||
p.get_object<X, ss::ax<int, excluded>, double, std::string>();
|
p.get_object<X, ss::ax<int, excluded>, double, std::string>();
|
||||||
@ -171,6 +246,13 @@ TEST_CASE("parser test various cases") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto&& a : p2.iterate_object<X, ss::ax<int, excluded>, double,
|
||||||
|
std::string>()) {
|
||||||
|
if (p2.valid()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<X> expected;
|
std::vector<X> expected;
|
||||||
for (auto& x : data) {
|
for (auto& x : data) {
|
||||||
if (x.i != excluded) {
|
if (x.i != excluded) {
|
||||||
@ -181,34 +263,57 @@ TEST_CASE("parser test various cases") {
|
|||||||
std::copy_if(data.begin(), data.end(), expected.begin(),
|
std::copy_if(data.begin(), data.end(), expected.begin(),
|
||||||
[](const X& x) { return x.i != excluded; });
|
[](const X& x) { return x.i != excluded; });
|
||||||
CHECK_EQ(i, expected);
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
ss::parser p{f.name, ","};
|
ss::parser p{f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
|
ss::parser p2{f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
auto a = p.get_object<X, ss::nx<int, 3>, double, std::string>();
|
auto a = p.get_object<X, ss::nx<int, 3>, double, std::string>();
|
||||||
if (p.valid()) {
|
if (p.valid()) {
|
||||||
i.push_back(a);
|
i.push_back(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto&& a :
|
||||||
|
p2.iterate_object<X, ss::nx<int, 3>, double, std::string>()) {
|
||||||
|
if (p2.valid()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<X> expected = {{3, 4, "y"}};
|
std::vector<X> expected = {{3, 4, "y"}};
|
||||||
CHECK_EQ(i, expected);
|
CHECK_EQ(i, expected);
|
||||||
|
CHECK_EQ(i2, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
unique_file_name empty_f;
|
unique_file_name empty_f;
|
||||||
std::vector<X> empty_data = {};
|
std::vector<X> empty_data = {};
|
||||||
|
|
||||||
make_and_write(empty_f.name, empty_data);
|
make_and_write(empty_f.name, empty_data);
|
||||||
|
|
||||||
ss::parser p{empty_f.name, ","};
|
ss::parser p{empty_f.name, ","};
|
||||||
std::vector<X> i;
|
std::vector<X> i;
|
||||||
|
|
||||||
|
ss::parser p2{empty_f.name, ","};
|
||||||
|
std::vector<X> i2;
|
||||||
|
|
||||||
while (!p.eof()) {
|
while (!p.eof()) {
|
||||||
i.push_back(p.get_next<X>());
|
i.push_back(p.get_next<X>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto&& a : p2.iterate<X>()) {
|
||||||
|
i2.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
CHECK(i.empty());
|
CHECK(i.empty());
|
||||||
|
CHECK(i2.empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,13 +322,10 @@ struct test_struct {
|
|||||||
int i;
|
int i;
|
||||||
double d;
|
double d;
|
||||||
char c;
|
char c;
|
||||||
auto tied() {
|
auto tied() { return std::tie(i, d, c); }
|
||||||
return std::tie(i, d, c);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void expect_test_struct(const test_struct&) {
|
void expect_test_struct(const test_struct&) {}
|
||||||
}
|
|
||||||
|
|
||||||
// various scenarios
|
// various scenarios
|
||||||
TEST_CASE("parser test composite conversion") {
|
TEST_CASE("parser test composite conversion") {
|
||||||
@ -444,9 +546,7 @@ struct my_string {
|
|||||||
|
|
||||||
my_string() = default;
|
my_string() = default;
|
||||||
|
|
||||||
~my_string() {
|
~my_string() { delete[] data; }
|
||||||
delete[] data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure no object is copied
|
// make sure no object is copied
|
||||||
my_string(const my_string&) = delete;
|
my_string(const my_string&) = delete;
|
||||||
@ -477,9 +577,7 @@ struct xyz {
|
|||||||
my_string x;
|
my_string x;
|
||||||
my_string y;
|
my_string y;
|
||||||
my_string z;
|
my_string z;
|
||||||
auto tied() {
|
auto tied() { return std::tie(x, y, z); }
|
||||||
return std::tie(x, y, z);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_CASE("parser test the moving of parsed values") {
|
TEST_CASE("parser test the moving of parsed values") {
|
||||||
|
Loading…
Reference in New Issue
Block a user