From 657df9ea8d3ea64f0f62e5e39d5808bf750ecf85 Mon Sep 17 00:00:00 2001 From: Celtic Minstrel Date: Sun, 15 Jan 2023 20:30:23 -0500 Subject: [PATCH] Rework the tagfile library After further experimentation, the previous template-heavy design turned out to cause issues with compilation. Thus, it has now been replaced with a simpler, dumber implementation that pushes more of the logic into the caller. --- src/fileio/tagfile.cpp | 202 +++++++++++++------ src/fileio/tagfile.hpp | 439 +++++++++++++++++------------------------ test/tagfile.cpp | 234 +++++++++++----------- 3 files changed, 447 insertions(+), 428 deletions(-) diff --git a/src/fileio/tagfile.cpp b/src/fileio/tagfile.cpp index bd4923d8f..a61effaad 100644 --- a/src/fileio/tagfile.cpp +++ b/src/fileio/tagfile.cpp @@ -10,118 +10,204 @@ #include "fileio.hpp" void cTagFile::readFrom(std::istream& file) { - for(auto& p : pages) p.get().reset(); std::istringstream page_in; std::string cur_page; - size_t i = 0; + pages.clear(); while(getline(file, cur_page, '\f')) { page_in.clear(); page_in.str(cur_page); - while(!pages[i].get().canReadFrom(page_in)) { - i++; - } - if(i >= pages.size()) break; - pages[i].get().readFrom(page_in); + add().readFrom(page_in); } } -void cTagFile::writeTo(std::ostream& file) { +void cTagFile::writeTo(std::ostream& file) const { bool first = true; for(const auto& page : pages) { if(!first) file << '\f'; first = false; - page.get().writeTo(file); + page.writeTo(file); } } -cTagFile_Page::cTagFile_Page(cTagFile& owner) { - owner.pages.push_back(*this); + +cTagFile_Page& cTagFile::add() { + pages.emplace_back(); + return pages.back(); +} + +cTagFile_Page& cTagFile::operator[](unsigned long i) { + return pages.at(i); +} + +const cTagFile_Page& cTagFile::operator[](unsigned long i) const { + return pages.at(i); +} + +void cTagFile_Page::internal_add_page(const std::string &key) { + tag_map.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(std::ref(*this), key)); } void cTagFile_Page::readFrom(std::istream& file) { - hasBeenRead = true; std::istringstream tag_in; for(std::string key; file >> key; ) { - if(tag_map.count(key) == 0) continue; std::string line; getline(file, line); tag_in.clear(); tag_in.str(line); - tag_map.at(key).get().readFrom(key, tag_in); - tag_in.clear(); + internal_add_page(key); + auto& tag = tag_map.at(key).add(); + tag.readFrom(tag_in); + tag_list.push_back(tag); } } -void cTagFile_Page::writeTo(std::ostream& file) { +void cTagFile_Page::writeTo(std::ostream& file) const { for(const auto& tag : tag_list) { - tag.get().writeTo(tag.get().key, file); + tag.get().writeTo(file); } } -bool cTagFile_Page::canReadFrom(std::istream&) const { - return !hasBeenRead; -} - -void cTagFile_Page::reset() { - hasBeenRead = false; -} - std::string cTagFile_Page::getFirstKey() const { if(tag_list.empty()) return std::string(); return tag_list.front().get().key; } -cTagFile_Tag::cTagFile_Tag(cTagFile_Page& owner, const std::string& key) : key(key) { - owner.tag_map.emplace(key, *this); - owner.tag_list.push_back(*this); + +cTagFile_Tag& cTagFile_Page::add(const std::string& key) { + internal_add_page(key); + auto& tag = tag_map.at(key).add(); + tag_list.emplace_back(tag); + return tag; } -void cTagFile_Tag::readValueFrom(std::istream& file, std::string& to) { - to = read_maybe_quoted_string(file); +cTagFile_TagList& cTagFile_Page::operator[](const std::string& key) { + internal_add_page(key); + return tag_map.at(key); } -void cTagFile_Tag::writeValueTo(std::ostream& file, const std::string& from) { - file << maybe_quote_string(from); +const cTagFile_TagList& cTagFile_Page::operator[](const std::string& key) const { + return tag_map.at(key); } -void cTagFile_Tag::readValueFrom(std::istream& file, bool& to) { - auto f = file.flags(); - file >> std::boolalpha >> to; - file.flags(f); +cTagFile_Tag& cTagFile_TagList::add() { + tags.emplace_back(key); + return tags.back(); } -void cTagFile_Tag::writeValueTo(std::ostream& file, const bool& from) { - auto f = file.flags(); - file << std::boolalpha << from; - file.flags(f); +cTagFile_Tag& cTagFile_TagList::operator[](unsigned long i) { + return tags.at(i); } -void cTagFile_Tag::readValueFrom(std::istream& file, char& to) { - int temp; - file >> temp; - to = char(temp); +const cTagFile_Tag& cTagFile_TagList::operator[](unsigned long i) const { + return tags.at(i); } -void cTagFile_Tag::writeValueTo(std::ostream& file, const char& from) { - file << int(from); +bool cTagFile_TagList::empty() const { + return tags.empty(); } -void cTagFile_Tag::readValueFrom(std::istream& file, signed char& to) { - int temp; - file >> temp; - to = char(temp); +void cTagFile_TagList::internal_add_tag() { + tags.emplace_back(key); + parent->tag_list.push_back(tags.back()); } -void cTagFile_Tag::writeValueTo(std::ostream& file, const signed char& from) { - file << int(from); +bool cTagFile_Page::contains(const std::string& key) const { + return tag_map.count(key) > 0 && !tag_map.at(key).empty(); } -void cTagFile_Tag::readValueFrom(std::istream& file, unsigned char& to) { - int temp; - file >> temp; - to = char(temp); +cTagFile_TagList::cTagFile_TagList(cTagFile_Page& parent, const std::string& key) + : parent(&parent) + , key(key) +{} + +cTagFile_Tag::cTagFile_Tag(const std::string& key) : key(key) {} + +void cTagFile_Tag::readFrom(std::istream& file) { + while(file) { + values.push_back(read_maybe_quoted_string(file)); + } } -void cTagFile_Tag::writeValueTo(std::ostream& file, const unsigned char& from) { - file << int(from); +void cTagFile_Tag::writeTo(std::ostream& file) { + file << key; + for(const std::string& val : values) { + file << ' '; + file << maybe_quote_string(val); + } + file << '\n'; +} + +size_t cTagFile_Tag::extract(size_t i, std::string& to) const { + if(i < values.size()) to = values[i]; + return 1; +} + +size_t cTagFile_Tag::encode(size_t i, const std::string& from) { + if(i >= values.size()) values.resize(i + 1); + values[i] = from; + return 1; +} + +size_t cTagFile_Tag::extract(size_t i, bool& to) const { + if(i < values.size()) { + to = false; + if(values[i] == "true" || values[i] == "yes" || values[i] == "on" || values[i] == "1") { + to = true; + } + } + return 1; +} + +size_t cTagFile_Tag::encode(size_t i, const bool& from) { + if(i >= values.size()) values.resize(i + 1); + if(from) values[i] = "true"; + else values[i] = "false"; + return 1; +} + +size_t cTagFile_Tag::extract(size_t i, char& to) const { + if(i < values.size()) { + to = std::stoi(values[i]); + } + return 1; +} + +size_t cTagFile_Tag::encode(size_t i, const char& from) { + if(i >= values.size()) values.resize(i + 1); + values[i] = std::to_string(from); + return 1; +} + +size_t cTagFile_Tag::extract(size_t i, signed char& to) const { + if(i < values.size()) { + to = std::stoi(values[i]); + } + return 1; +} + +size_t cTagFile_Tag::encode(size_t i, const signed char& from) { + if(i >= values.size()) values.resize(i + 1); + values[i] = std::to_string(from); + return 1; +} + +size_t cTagFile_Tag::extract(size_t i, unsigned char& to) const { + if(i < values.size()) { + to = std::stoi(values[i]); + } + return 1; +} + +size_t cTagFile_Tag::encode(size_t i, const unsigned char& from) { + if(i >= values.size()) values.resize(i + 1); + values[i] = std::to_string(from); + return 1; +} + +size_t cTagFile_Tag::extract(size_t, std::tuple<>&) const { + return 0; +} + +size_t cTagFile_Tag::encode(size_t, const std::tuple<>&) { + return 0; } diff --git a/src/fileio/tagfile.hpp b/src/fileio/tagfile.hpp index b37f88659..823e95bd5 100644 --- a/src/fileio/tagfile.hpp +++ b/src/fileio/tagfile.hpp @@ -9,7 +9,7 @@ #define BoE_tagfile_hpp #include -#include +#include #include #include // for reference_wrapper #include @@ -20,293 +20,226 @@ class cTagFile; class cTagFile_Page; -// Helper functions for inheriting constructors in tagfile classes -// This is needed because Xcode 4 does not support inheriting constructors. -// Note that both these macros set the access level to public! -#define DEFINE_TAGFILE_PAGE(Class, Base) protected: Class() = default; public: Class(cTagFile& owner) : Base(owner) {} -#define DEFINE_TAGFILE_MULTIPAGE(Class, Base) friend class pMultiPage; DEFINE_TAGFILE_PAGE(Class, Base) -#define DEFINE_TAGFILE_TAG(Class, Base) public: Class(cTagFile_Page& owner, const std::string& key) : Base(owner, key) {} - class cTagFile_Tag { -protected: - void readValueFrom(std::istream& file, std::string& to); - void writeValueTo(std::ostream& file, const std::string& from); - void readValueFrom(std::istream& file, bool& to); - void writeValueTo(std::ostream& file, const bool& from); - void readValueFrom(std::istream& file, char& to); - void writeValueTo(std::ostream& file, const char& from); - void readValueFrom(std::istream& file, signed char& to); - void writeValueTo(std::ostream& file, const signed char& from); - void readValueFrom(std::istream& file, unsigned char& to); - void writeValueTo(std::ostream& file, const unsigned char& from); + std::deque values; +public: + // All extract/encode functions take a starting index and + // return the number of values extracted/encoded (usually 1). + size_t extract(size_t i, cTagFile_Tag&) const = delete; + size_t encode(size_t i, const cTagFile_Tag&) = delete; + size_t extract(size_t i, std::string& to) const; + size_t encode(size_t i, const std::string& from); + size_t extract(size_t i, bool& to) const; + size_t encode(size_t i, const bool& from); + size_t extract(size_t i, char& to) const; + size_t encode(size_t i, const char& from); + size_t extract(size_t i, signed char& to) const; + size_t encode(size_t i, const signed char& from); + size_t extract(size_t i, unsigned char& to) const; + size_t encode(size_t i, const unsigned char& from); + size_t extract(size_t i, std::tuple<>& to) const; + size_t encode(size_t i, const std::tuple<>& from); template - static void readValueFrom(std::istream& file, T& to) { - file >> std::boolalpha >> to; + size_t extract(size_t i, T& to) const { + if(i >= values.size()) return 1; + // This bizarre construct simply allows us to not include here + using stream = typename std::conditional::value, std::istringstream, void>::type; + stream file(values[i]); + file >> to; + return 1; } template - static void writeValueTo(std::ostream& file, const T& from) { - file << std::boolalpha << from; + size_t encode(size_t i, const T& from) { + if(i >= values.size()) values.resize(i + 1); + // This bizarre construct simply allows us to not include here + using stream = typename std::conditional::value, std::ostringstream, void>::type; + stream file; + file << from; + values[i] = file.str(); + return 1; } template - void readValueFrom(std::istream& file, std::pair& to) { - readValueFrom(file, to.first); - readValueFrom(file, to.second); + size_t extract(size_t i, std::pair& to) const { + return extract(i, to.first) + extract(i + 1, to.second); } template - void writeValueTo(std::ostream& file, const std::pair& from) { - writeValueTo(file, from.first); file << ' '; - writeValueTo(file, from.second); + size_t encode(size_t i, const std::pair& from) { + return encode(i, from.first) + encode(i + 1, from.second); } - template - void readValueFrom(std::istream& file, std::tuple& to) { - readValueFrom(file, std::get<0>(to)); - readValueFrom(file, std::get<1>(to)); - readValueFrom(file, std::get<2>(to)); + template + size_t extract(size_t i, std::tuple& to) const { + return extract(i, std::get(to)) + extract(i + 1, to); } - template - void writeValueTo(std::ostream& file, const std::tuple& from) { - writeValueTo(file, std::get<0>(from)); file << ' '; - writeValueTo(file, std::get<1>(from)); file << ' '; - writeValueTo(file, std::get<2>(from)); + template + size_t encode(size_t i, const std::tuple& from) { + return encode(i, std::get(from)) + encode(i + 1, from); } - template - void readValueFrom(std::istream& file, std::tuple& to) { - readValueFrom(file, std::get<0>(to)); - readValueFrom(file, std::get<1>(to)); - readValueFrom(file, std::get<2>(to)); - readValueFrom(file, std::get<3>(to)); + template + size_t extract(size_t i, std::array& to) const { + for(size_t j = 0; j < n; j++) { + extract(i + j, to[j]); + } + return n; } - template - void writeValueTo(std::ostream& file, const std::tuple& from) { - writeValueTo(file, std::get<0>(from)); file << ' '; - writeValueTo(file, std::get<1>(from)); file << ' '; - writeValueTo(file, std::get<2>(from)); file << ' '; - writeValueTo(file, std::get<3>(from)); + template + size_t encode(size_t i, const std::array& from) { + for(size_t j = 0; j < n; j++) { + encode(i + j, from[j]); + } + return n; + } + + template + size_t extract(size_t i, boost::optional& to) const { + if(i < values.size()) { + return extract(i, *to); + } else { + to = boost::none; + return 0; + } + } + + template + size_t encode(size_t i, const boost::optional& from) { + return from ? encode(i, *from) : 0; } + const std::string key; + cTagFile_Tag(const std::string& key); + void readFrom(std::istream& file); + void writeTo(std::ostream& file); + template + bool operator==(const T& other) { + T self; + if(extract(0, self) == 0) return false; + return self == other; + } + +}; + +class cTagFile_TagExtractProxy { + const cTagFile_Tag* tag; + size_t i = 0; + cTagFile_TagExtractProxy(const cTagFile_Tag& tag, size_t i) : tag(&tag), i(i) {} +public: + cTagFile_TagExtractProxy() : tag(nullptr) {} + cTagFile_TagExtractProxy(const cTagFile_Tag& tag) : tag(&tag) {} + operator bool() const { + return tag; + } + template + cTagFile_TagExtractProxy operator>>(T& value) { + if(tag == nullptr) return *this; + size_t j = i; + j += tag->extract(i, value); + return cTagFile_TagExtractProxy(*tag, j); + } +}; + +class cTagFile_TagEncodeProxy { + cTagFile_Tag* tag; + size_t i = 0; + cTagFile_TagEncodeProxy(cTagFile_Tag& tag, size_t i) : tag(&tag), i(i) {} +public: + cTagFile_TagEncodeProxy(cTagFile_Tag& tag) : tag(&tag) {} + template + cTagFile_TagEncodeProxy operator<<(const T& value) { + if(tag == nullptr) return *this; + size_t j = i; + j += tag->encode(i, value); + return cTagFile_TagEncodeProxy(*tag, j); + } +}; + +class cTagFile_TagList { + cTagFile_Page* parent; + std::deque tags; + mutable size_t i = 0; + cTagFile_TagList(const cTagFile_TagList&) = delete; + void internal_add_tag(); public: const std::string key; - cTagFile_Tag(cTagFile_Page& owner, const std::string& key); - virtual void readFrom(const std::string& key, std::istream& file) = 0; - virtual void writeTo(const std::string& key, std::ostream& file) = 0; - virtual ~cTagFile_Tag() = default; + cTagFile_TagList(cTagFile_Page& parent, const std::string& key); + cTagFile_Tag& add(); + cTagFile_Tag& operator[](size_t n); + const cTagFile_Tag& operator[](size_t n) const; + bool empty() const; + template + cTagFile_TagEncodeProxy operator<<(const T& value) { + internal_add_tag(); + return cTagFile_TagEncodeProxy(tags.back()) << value; + } + template + cTagFile_TagExtractProxy operator>>(T& value) const { + if(i >= tags.size()) { + i = 0; + return cTagFile_TagExtractProxy() >> value; + } else { + return cTagFile_TagExtractProxy(tags[i++]) >> value; + } + } + template + void extract(Container& values) const { + using T = typename Container::value_type; + values.clear(); + for(size_t n = 0; n < tags.size(); n++) { + T value; + *this >> value; + values.push_back(value); + if(i == 0) break; + } + } + template + void encode(const Container& values) { + for(const auto& value : values) { + *this << value; + } + } }; class cTagFile_Page { - friend class cTagFile_Tag; - std::map> tag_map; - std::vector> tag_list; + friend class cTagFile_TagList; + // Tags are kept in a map by key, and there can be more than one of each tag. + std::map tag_map; + // However, we also record the real order the tags were encountered in. + std::deque> tag_list; + cTagFile_Page(const cTagFile_Page&) = delete; + void internal_add_page(const std::string& key); protected: - mutable bool hasBeenRead = false; std::string getFirstKey() const; +public: cTagFile_Page() = default; -public: - cTagFile_Page(cTagFile& owner); - virtual void readFrom(std::istream& file); - virtual void writeTo(std::ostream& file); - virtual bool canReadFrom(std::istream& file) const; - virtual void reset(); - virtual ~cTagFile_Page() = default; -}; - -template -class pMultiPage : public cTagFile_Page { - std::shared_ptr next; - std::shared_ptr make_page() { - return std::shared_ptr(new Self); - } -protected: - pMultiPage() : cTagFile_Page() {} -public: - pMultiPage(cTagFile& owner) : cTagFile_Page(owner) {}; - void readFrom(std::istream& file) override { - if(!hasBeenRead) { - cTagFile_Page::readFrom(file); - } else { - if(!next) next = make_page(); - next->readFrom(file); - } - } - void writeTo(std::ostream& file) override { - cTagFile_Page::writeTo(file); - if(next) { - file << '\f'; - next->writeTo(file); - } - } - bool canReadFrom(std::istream& file) const override { - auto at = file.tellg(); - std::string key; - file >> key; - file.seekg(at); - return key == getFirstKey(); - } - void reset() override { - cTagFile_Page::reset(); - next.reset(); - } - Self& operator[](size_t i) { - if(i == 0) return static_cast(*this); - if(!next) next = make_page(); - return next->operator[](i - 1); - } - Self& add() { - if(next) return next->add(); - next = make_page(); - return *next; - } - size_t size() { - return 1 + (next ? next->size() : 0); - } + void readFrom(std::istream& file); + void writeTo(std::ostream& file) const; + cTagFile_Tag& add(const std::string& key); + bool contains(const std::string& key) const; + cTagFile_TagList& operator[](const std::string& key); + const cTagFile_TagList& operator[](const std::string& key) const; }; class cTagFile { - friend class cTagFile_Page; - std::vector> pages; + std::deque pages; + using iterator = typename std::deque::iterator; + using const_iterator = typename std::deque::const_iterator; public: void readFrom(std::istream& file); - void writeTo(std::ostream& file); -}; - -template class tArrayTag; - -template -class tBasicTag : public cTagFile_Tag { - T value; -public: - DEFINE_TAGFILE_TAG(tBasicTag, cTagFile_Tag); - void readFrom(const std::string&, std::istream& file) override { - readValueFrom(file, value); - } - void writeTo(const std::string& key, std::ostream& file) override { - file << key << ' '; - writeValueTo(file, value); - file << '\n'; - } - operator T() const { return value; } - tBasicTag operator=(const T& new_value) { value = new_value; return *this; } - T& operator*() { return value; } - const T& operator*() const { return value; } - T* operator->() { return &value; } - const T* operator->() const { return &value; } - bool operator==(const T& other) { return value == other; } - bool operator!=(const T& other) { return value != other; } -}; - -template -class tHexTag : public cTagFile_Tag { - T value; -public: - DEFINE_TAGFILE_TAG(tHexTag, cTagFile_Tag); - void readFrom(const std::string&, std::istream& file) override { - readValueFrom(file, value); - } - void writeTo(const std::string& key, std::ostream& file) override { - file << key << ' ' << std::hex; - writeValueTo(file, value); - file << std::dec << '\n'; - } - operator T() const { return value; } - tHexTag operator=(const T& new_value) { value = new_value; return *this; } - T& operator*() { return value; } - const T& operator*() const { return value; } - T* operator->() { return &value; } - const T* operator->() const { return &value; } - bool operator==(const T& other) { return value == other; } - bool operator!=(const T& other) { return value != other; } -}; - -template -class tOptionalTag : public cTagFile_Tag { - boost::optional value; -public: - DEFINE_TAGFILE_TAG(tOptionalTag, cTagFile_Tag); - void readFrom(const std::string&, std::istream& file) override { - value.emplace(); - readValueFrom(file, *value); - } - void writeTo(const std::string& key, std::ostream& file) override { - if(value) { - file << key << ' '; - writeValueTo(file, *value); - file << '\n'; - } - } - const T& get() const { return *value; } - tOptionalTag operator=(const T& new_value) { value = new_value; return *this; } - tOptionalTag operator=(boost::none_t) { value.reset(); return *this; } - T& operator*() { return value; } - const T& operator*() const { return value; } - T* operator->() { return &value; } - const T* operator->() const { return &value; } - bool operator==(const T& other) { return value == other; } - bool operator!=(const T& other) { return value != other; } - bool operator==(boost::none_t) { return value == boost::none; } - bool operator!=(boost::none_t) { return value != boost::none; } -}; - -template<> -class tOptionalTag : public cTagFile_Tag { - bool value = false; -public: - DEFINE_TAGFILE_TAG(tOptionalTag, cTagFile_Tag); - void readFrom(const std::string&, std::istream&) override { - value = true; - } - void writeTo(const std::string& key, std::ostream& file) override { - if(value) { - file << key << '\n'; - } - } - const bool& get() const { return value; } - tOptionalTag operator=(const bool& new_value) { value = new_value; return *this; } - bool& operator*() { return value; } - const bool& operator*() const { return value; } - bool operator==(const bool& other) { return value == other; } - bool operator!=(const bool& other) { return value != other; } - bool operator==(boost::none_t) { return false; } - bool operator!=(boost::none_t) { return false; } -}; - -template -class tArrayTag : public cTagFile_Tag { - std::vector values; -public: - DEFINE_TAGFILE_TAG(tArrayTag, cTagFile_Tag) - void readFrom(const std::string&, std::istream& file) override { - values.emplace_back(); - readValueFrom(file, values.back()); - } - void writeTo(const std::string& key, std::ostream& file) override { - for(const auto& v : values) { - file << key << ' '; - writeValueTo(file, v); - file << '\n'; - } - } - size_t size() const { - return values.size(); - } - void add(const T& v) { - values.push_back(v); - } - typename std::vector::const_iterator begin() const { - return values.begin(); - } - typename std::vector::const_iterator end() const { - return values.end(); - } - const T& operator[](size_t i) const { - return values.at(i); - } + void writeTo(std::ostream& file) const; + cTagFile_Page& add(); + cTagFile_Page& operator[](size_t n); + const cTagFile_Page& operator[](size_t n) const; + iterator begin() { return pages.begin(); } + iterator end() { return pages.end(); } + const_iterator begin() const { return pages.begin(); } + const_iterator end() const { return pages.end(); } + }; #endif diff --git a/test/tagfile.cpp b/test/tagfile.cpp index 5a19f4144..530e77475 100644 --- a/test/tagfile.cpp +++ b/test/tagfile.cpp @@ -7,60 +7,10 @@ #include "fileio/tagfile.hpp" #include +#include #include "catch.hpp" -template -std::ostream& operator<<(std::ostream& os, const tBasicTag& tag) { - return os << T(tag); -} - -class pSamplePage1 : public cTagFile_Page { -public: - DEFINE_TAGFILE_PAGE(pSamplePage1, cTagFile_Page); - tBasicTag a{*this, "A"}, b{*this, "B"}, c{*this, "C"}; -}; - -class pSamplePage2 : public cTagFile_Page { -public: - DEFINE_TAGFILE_PAGE(pSamplePage2, cTagFile_Page); - tBasicTag x{*this, "X"}, y{*this, "Y"}, z{*this, "Z"}; -}; - -class fSampleTagFile : public cTagFile { -public: - pSamplePage1 p1{*this}; - pSamplePage2 p2{*this}; -}; - -class pComplexPage : public cTagFile_Page { -public: - DEFINE_TAGFILE_PAGE(pComplexPage, cTagFile_Page); - tBasicTag id{*this, "ID"}; - tArrayTag strings{*this, "STRING"}; - tArrayTag> locations{*this, "LOC"}; - tOptionalTag filter{*this, "FILTER"}; - tOptionalTag count{*this, "COUNT"}; - tOptionalTag yes{*this, "YES"}; - tOptionalTag no{*this, "NO"}; - tBasicTag enable{*this, "ENABLE"}; -}; - -class pSampleMultiPage : public pMultiPage { -public: - DEFINE_TAGFILE_MULTIPAGE(pSampleMultiPage, pMultiPage); - tBasicTag id{*this, "ID"}; - tBasicTag value{*this, "VALUE"}; - tBasicTag comment{*this, "COMMENT"}; -}; - -class fComplexTagFile : public cTagFile { -public: - pSamplePage1 p1{*this}; - pComplexPage p2{*this}; - pSampleMultiPage p3{*this}; -}; - TEST_CASE("Simple tag file") { static const std::string file_contents = "A 12\n" @@ -71,36 +21,40 @@ TEST_CASE("Simple tag file") { "Y foo\n" "Z Blah!\n" ; - fSampleTagFile content; + cTagFile content; SECTION("output") { std::ostringstream file; - content.p1.a = 12; - content.p1.b = 22; - content.p1.c = 45; - content.p2.x = "Hello World"; - content.p2.y = "foo"; - content.p2.z = "Blah!"; + auto& p1 = content.add(); + p1["A"] << 12; + p1["B"] << 22; + p1["C"] << 45; + auto& p2 = content.add(); + p2["X"] << "Hello World"; + p2["Y"] << "foo"; + p2["Z"] << "Blah!"; content.writeTo(file); CHECK(file.str() == file_contents); } SECTION("input") { std::istringstream file(file_contents); content.readFrom(file); - CHECK(content.p1.a == 12); - CHECK(content.p1.b == 22); - CHECK(content.p1.c == 45); - CHECK(content.p2.x == std::string("Hello World")); - CHECK(content.p2.y == std::string("foo")); - CHECK(content.p2.z == std::string("Blah!")); + CHECK(content[0]["A"][0] == 12); + CHECK(content[0]["B"][0] == 22); + CHECK(content[0]["C"][0] == 45); + CHECK(content[1]["X"][0] == std::string("Hello World")); + CHECK(content[1]["Y"][0] == std::string("foo")); + CHECK(content[1]["Z"][0] == std::string("Blah!")); } } TEST_CASE("Complex tag file") { static const std::string file_contents = + // Page 1, a simple page with 3 single tags "A 12\n" "B 22\n" "C 45\n" "\f" + // Page 2, a complex page with some array-like values "ID 123\n" "STRING foo\n" "STRING bar\n" @@ -110,6 +64,7 @@ TEST_CASE("Complex tag file") { "COUNT 12\n" "YES\n" "ENABLE false\n" + // Page 3 and onward, a series of identical pages "\f" "ID 12\n" "VALUE 400\n" @@ -123,66 +78,111 @@ TEST_CASE("Complex tag file") { "VALUE 90\n" "COMMENT \"It's great!\"\n" ; - fComplexTagFile content; + cTagFile content; SECTION("output") { std::ostringstream file; - content.p1.a = 12; - content.p1.b = 22; - content.p1.c = 45; - content.p2.id = 123; - content.p2.strings.add("foo"); - content.p2.strings.add("bar"); - content.p2.locations.add({1,5}); - content.p2.locations.add({12,22}); - content.p2.locations.add({143,9}); - content.p2.count = 12; - content.p2.yes = true; - content.p2.no = false; - content.p2.enable = false; - content.p3[0].id = 0x0c; - content.p3[0].value = 400; - content.p3[0].comment = "This is a comment!!!"; - content.p3[1].id = 0x0d; - content.p3[1].value = 128; - content.p3[1].comment = "Nope nope nope"; - auto& p3c = content.p3.add(); - p3c.id = 0x12; - p3c.value = 90; - p3c.comment = "It's great!"; + auto& p1 = content.add(); + p1["A"] << 12; + p1["B"] << 22; + p1["C"] << 45; + auto& p2 = content.add(); + p2["ID"] << 123; + p2["STRING"] << "foo"; + p2["STRING"] << "bar"; + p2["LOC"] << 1 << 5; + p2["LOC"] << std::array{{12,22}}; + p2["LOC"] << std::pair{143,9}; + p2["COUNT"] << 12; + p2.add("YES"); + p2["ENABLE"] << false; + auto& p4a = content.add(); + p4a["ID"] << '\x0c'; + p4a["VALUE"] << 400; + p4a["COMMENT"] << "This is a comment!!!"; + auto& p4b = content.add(); + p4b["ID"] << '\x0d'; + p4b["VALUE"] << 128; + p4b["COMMENT"] << "Nope nope nope"; + auto& p4c = content.add(); + p4c["ID"] << '\x12'; + p4c["VALUE"] << 90; + p4c["COMMENT"] << "It's great!"; content.writeTo(file); CHECK(file.str() == file_contents); } SECTION("input") { std::istringstream file(file_contents); content.readFrom(file); - CHECK(content.p1.a == 12); - CHECK(content.p1.b == 22); - CHECK(content.p1.c == 45); - CHECK(content.p2.id == 123); - REQUIRE(content.p2.strings.size() == 2); - CHECK(content.p2.strings[0] == "foo"); - CHECK(content.p2.strings[1] == "bar"); - REQUIRE(content.p2.locations.size() == 3); - CHECK(content.p2.locations[0].first == 1); - CHECK(content.p2.locations[0].second == 5); - CHECK(content.p2.locations[1].first == 12); - CHECK(content.p2.locations[1].second == 22); - CHECK(content.p2.locations[2].first == 143); - CHECK(content.p2.locations[2].second == 9); - CHECK(content.p2.filter == boost::none); - CHECK(content.p2.count == 12); - CHECK(content.p2.yes == true); - CHECK(content.p2.no == false); - CHECK(content.p2.enable == false); - REQUIRE(content.p3.size() == 3); - CHECK(content.p3[0].id == '\x0c'); - CHECK(content.p3[0].value == 400); - CHECK(content.p3[0].comment == "This is a comment!!!"); - CHECK(content.p3[1].id == '\x0d'); - CHECK(content.p3[1].value == 128); - CHECK(content.p3[1].comment == "Nope nope nope"); - CHECK(content.p3[2].id == '\x12'); - CHECK(content.p3[2].value == 90); - CHECK(content.p3[2].comment == "It's great!"); + bool p1 = false, p2 = false; + size_t p3 = 0; + for(const auto& page : content) { + if(!p1) { + p1 = true; + int a = 0, b = 0, c = 0; + page["A"] >> a; + page["B"] >> b; + page["C"] >> c; + CHECK(a == 12); + CHECK(b == 22); + CHECK(c == 45); + } else if(!p2) { + p2 = true; + int id = 0, count = 0; + page["ID"] >> id; + page["COUNT"] >> count; + CHECK(id == 123); + CHECK(count == 12); + std::vector strings; + page["STRING"].extract(strings); + REQUIRE(strings.size() == 2); + CHECK(strings[0] == "foo"); + CHECK(strings[1] == "bar"); + std::vector> locations; + page["LOC"].extract(locations); + REQUIRE(locations.size() == 3); + CHECK(locations[0].first == 1); + CHECK(locations[0].second == 5); + CHECK(locations[1].first == 12); + CHECK(locations[1].second == 22); + CHECK(locations[2].first == 143); + CHECK(locations[2].second == 9); + bool yes = page.contains("YES"), no = page.contains("NO"), enable = true; + page["ENABLE"] >> enable; + CHECK(yes == true); + CHECK(no == false); + CHECK(enable == false); + } else { + p3++; + char id; + int value; + std::string comment; + page["ID"] >> id; + page["VALUE"] >> value; + page["COMMENT"] >> comment; + switch(p3) { + case 1: + CHECK(id == '\x0c'); + CHECK(value == 400); + CHECK(comment == "This is a comment!!!"); + break; + case 2: + CHECK(id == '\x0d'); + CHECK(value == 128); + CHECK(comment == "Nope nope nope"); + break; + case 3: + CHECK(id == '\x12'); + CHECK(value == 90); + CHECK(comment == "It's great!"); + break; + default: + FAIL("Too many extra pages!!!"); + } + } + } + CHECK(p1); + CHECK(p2); + CHECK(p3); + CHECK(p3 == 3); } }