diff --git a/README.md b/README.md index 7a601bd..e6a3763 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,8 @@ Bill (Heath) Gates,65,3.3 int main() { ss::parser p{"students.csv"}; - if (!p.valid()) { - exit(EXIT_FAILURE); - } - - while (!p.eof()) { - auto [name, age, grade] = p.get_next(); + for(auto& [name, age, grade] : p.iterate()) { if (p.valid()) { std::cout << name << ' ' << age << ' ' << grade << std::endl; } @@ -88,7 +83,17 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems # Usage ## 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(); + 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. @@ -119,6 +124,12 @@ This works with any object if the constructor could be invoked using the templat auto vec = p.get_object, 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()) { +// ... +} +``` 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 { @@ -134,6 +145,7 @@ The method can be used to compare the object, serialize it, deserialize it, etc. // returns student auto s = p.get_next(); ``` +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*. ## Setup diff --git a/include/ss/common.hpp b/include/ss/common.hpp index 99ce493..f8563dc 100644 --- a/include/ss/common.hpp +++ b/include/ss/common.hpp @@ -1,4 +1,7 @@ #pragma once +#include +#include +#include #include namespace ss { @@ -21,7 +24,6 @@ inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) { return getline(lineptr, n, stream); } #else -#include using ssize_t = int64_t; inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) { size_t pos; diff --git a/include/ss/parser.hpp b/include/ss/parser.hpp index 62e0664..61636fd 100644 --- a/include/ss/parser.hpp +++ b/include/ss/parser.hpp @@ -85,6 +85,73 @@ public: return value; } + //////////////// + // iterator + //////////////// + + template + struct iterable { + struct iterator { + using value = + ss::ternary_t>; + + iterator() : parser_{nullptr} {} + iterator(parser* 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()); + } else { + value_ = + std::move(parser_->template get_next()); + } + } + 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* parser_; + }; + + iterable(parser* parser) : parser_{parser} {} + + iterator begin() { return ++iterator{parser_}; } + iterator end() { return iterator{}; } + + private: + parser* parser_; + }; + + template + auto iterate() { + return iterable{this}; + } + + template + auto iterate_object() { + return iterable{this}; + } + //////////////// // composite conversion //////////////// diff --git a/include/ss/type_traits.hpp b/include/ss/type_traits.hpp index 2fd75ad..5d4aa89 100644 --- a/include/ss/type_traits.hpp +++ b/include/ss/type_traits.hpp @@ -388,7 +388,7 @@ T to_object_impl(std::index_sequence, U&& data) { template T to_object(U&& data) { - using NoRefU = std::remove_reference_t; + using NoRefU = std::decay_t; if constexpr (is_instance_of_v) { return to_object_impl< T>(std::make_index_sequence{}>{}, diff --git a/test/test_parser.cpp b/test/test_parser.cpp index ab5d8c9..f51a2ac 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -21,12 +21,9 @@ struct unique_file_name { unique_file_name() : name{"random_" + std::to_string(i++) + time_now_rand() + - "_file.csv"} { - } + "_file.csv"} {} - ~unique_file_name() { - std::filesystem::remove(name); - } + ~unique_file_name() { std::filesystem::remove(name); } }; void replace_all(std::string& s, const std::string& from, @@ -60,9 +57,7 @@ struct X { .append(delim) .append(s); } - auto tied() const { - return std::tie(i, d, s); - } + auto tied() const { return std::tie(i, d, s); } }; template @@ -98,40 +93,85 @@ TEST_CASE("parser test various cases") { ss::parser p{f.name, ","}; ss::parser p0{std::move(p)}; p = std::move(p0); - std::vector i; + ss::parser p2{f.name, ","}; + std::vector i2; + while (!p.eof()) { auto a = p.get_next(); i.emplace_back(ss::to_object(a)); } + for (const auto& a : p2.iterate()) { + i2.emplace_back(ss::to_object(a)); + } + CHECK_EQ(i, data); + CHECK_EQ(i2, data); } { ss::parser p{f.name, ","}; std::vector i; + + ss::parser p2{f.name, ","}; + std::vector i2; + + ss::parser p3{f.name, ","}; + std::vector i3; + std::vector expected = {std::begin(data) + 1, std::end(data)}; + using tup = std::tuple; p.ignore_next(); while (!p.eof()) { - using tup = std::tuple; auto a = p.get_next(); i.emplace_back(ss::to_object(a)); } + p2.ignore_next(); + for (const auto& a : p2.iterate()) { + i2.emplace_back(ss::to_object(a)); + } + + p3.ignore_next(); + for (auto it = p3.iterate().begin(); it != p3.iterate().end(); + ++it) { + i3.emplace_back(ss::to_object(*it)); + } + CHECK_EQ(i, expected); + CHECK_EQ(i2, expected); + CHECK_EQ(i3, expected); } { ss::parser p{f.name, ","}; std::vector i; + ss::parser p2{f.name, ","}; + std::vector i2; while (!p.eof()) { i.push_back(p.get_object()); } + for (auto&& a : p2.iterate_object()) { + i2.push_back(std::move(a)); + } + + CHECK_EQ(i, data); + CHECK_EQ(i2, data); + } + + { + ss::parser p{f.name, ","}; + std::vector i; + + for (auto&& a : p.iterate_object()) { + i.push_back(std::move(a)); + } + CHECK_EQ(i, data); } @@ -139,11 +179,32 @@ TEST_CASE("parser test various cases") { ss::parser p{f.name, ","}; std::vector i; + ss::parser p2{f.name, ","}; + std::vector i2; + + using tup = std::tuple; while (!p.eof()) { - using tup = std::tuple; i.push_back(p.get_object()); } + for (auto it = p2.iterate_object().begin(); + it != p2.iterate_object().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 i; + + using tup = std::tuple; + for (auto&& a : p.iterate_object()) { + i.push_back(std::move(a)); + } + CHECK_EQ(i, data); } @@ -158,11 +219,25 @@ TEST_CASE("parser test various cases") { CHECK_EQ(i, data); } + { + ss::parser p{f.name, ","}; + std::vector i; + + for (auto&& a : p.iterate()) { + i.push_back(std::move(a)); + } + + CHECK_EQ(i, data); + } + { constexpr int excluded = 3; ss::parser p{f.name, ","}; std::vector i; + ss::parser p2{f.name, ","}; + std::vector i2; + while (!p.eof()) { auto a = p.get_object, double, std::string>(); @@ -171,6 +246,13 @@ TEST_CASE("parser test various cases") { } } + for (auto&& a : p2.iterate_object, double, + std::string>()) { + if (p2.valid()) { + i2.push_back(std::move(a)); + } + } + std::vector expected; for (auto& x : data) { if (x.i != excluded) { @@ -181,34 +263,57 @@ TEST_CASE("parser test various cases") { std::copy_if(data.begin(), data.end(), expected.begin(), [](const X& x) { return x.i != excluded; }); CHECK_EQ(i, expected); + CHECK_EQ(i2, expected); } { ss::parser p{f.name, ","}; std::vector i; + ss::parser p2{f.name, ","}; + std::vector i2; + while (!p.eof()) { auto a = p.get_object, double, std::string>(); if (p.valid()) { i.push_back(a); } } + + for (auto&& a : + p2.iterate_object, double, std::string>()) { + if (p2.valid()) { + i2.push_back(std::move(a)); + } + } + std::vector expected = {{3, 4, "y"}}; CHECK_EQ(i, expected); + CHECK_EQ(i2, expected); } { unique_file_name empty_f; std::vector empty_data = {}; + make_and_write(empty_f.name, empty_data); ss::parser p{empty_f.name, ","}; std::vector i; + ss::parser p2{empty_f.name, ","}; + std::vector i2; + while (!p.eof()) { i.push_back(p.get_next()); } + + for (auto&& a : p2.iterate()) { + i2.push_back(std::move(a)); + } + CHECK(i.empty()); + CHECK(i2.empty()); } } @@ -217,13 +322,10 @@ struct test_struct { int i; double d; char c; - auto tied() { - return std::tie(i, d, c); - } + auto tied() { return std::tie(i, d, c); } }; -void expect_test_struct(const test_struct&) { -} +void expect_test_struct(const test_struct&) {} // various scenarios TEST_CASE("parser test composite conversion") { @@ -444,9 +546,7 @@ struct my_string { my_string() = default; - ~my_string() { - delete[] data; - } + ~my_string() { delete[] data; } // make sure no object is copied my_string(const my_string&) = delete; @@ -477,9 +577,7 @@ struct xyz { my_string x; my_string y; my_string z; - auto tied() { - return std::tie(x, y, z); - } + auto tied() { return std::tie(x, y, z); } }; TEST_CASE("parser test the moving of parsed values") {