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.
This commit is contained in:
2023-01-15 20:30:23 -05:00
parent 1ee72df836
commit 657df9ea8d
3 changed files with 447 additions and 428 deletions

View File

@@ -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;
}

View File

@@ -9,7 +9,7 @@
#define BoE_tagfile_hpp
#include <string>
#include <vector>
#include <deque>
#include <map>
#include <functional> // for reference_wrapper
#include <istream>
@@ -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<Class>; 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<std::string> 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<typename T>
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 <sstream> here
using stream = typename std::conditional<std::is_same<T, T>::value, std::istringstream, void>::type;
stream file(values[i]);
file >> to;
return 1;
}
template<typename T>
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 <sstream> here
using stream = typename std::conditional<std::is_same<T, T>::value, std::ostringstream, void>::type;
stream file;
file << from;
values[i] = file.str();
return 1;
}
template<typename A, typename B>
void readValueFrom(std::istream& file, std::pair<A, B>& to) {
readValueFrom(file, to.first);
readValueFrom(file, to.second);
size_t extract(size_t i, std::pair<A, B>& to) const {
return extract(i, to.first) + extract(i + 1, to.second);
}
template<typename A, typename B>
void writeValueTo(std::ostream& file, const std::pair<A, B>& from) {
writeValueTo(file, from.first); file << ' ';
writeValueTo(file, from.second);
size_t encode(size_t i, const std::pair<A, B>& from) {
return encode(i, from.first) + encode(i + 1, from.second);
}
template<typename A, typename B, typename C>
void readValueFrom(std::istream& file, std::tuple<A, B, C>& to) {
readValueFrom(file, std::get<0>(to));
readValueFrom(file, std::get<1>(to));
readValueFrom(file, std::get<2>(to));
template<size_t j = 0, typename... T>
size_t extract(size_t i, std::tuple<T...>& to) const {
return extract(i, std::get<j>(to)) + extract<j + 1>(i + 1, to);
}
template<typename A, typename B, typename C>
void writeValueTo(std::ostream& file, const std::tuple<A, B, C>& from) {
writeValueTo(file, std::get<0>(from)); file << ' ';
writeValueTo(file, std::get<1>(from)); file << ' ';
writeValueTo(file, std::get<2>(from));
template<size_t j, typename... T>
size_t encode(size_t i, const std::tuple<T...>& from) {
return encode(i, std::get<j>(from)) + encode<j + 1>(i + 1, from);
}
template<typename A, typename B, typename C, typename D>
void readValueFrom(std::istream& file, std::tuple<A, B, C, D>& 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<typename T, size_t n>
size_t extract(size_t i, std::array<T, n>& to) const {
for(size_t j = 0; j < n; j++) {
extract(i + j, to[j]);
}
return n;
}
template<typename A, typename B, typename C, typename D>
void writeValueTo(std::ostream& file, const std::tuple<A, B, C, D>& 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<typename T, size_t n>
size_t encode(size_t i, const std::array<T, n>& from) {
for(size_t j = 0; j < n; j++) {
encode(i + j, from[j]);
}
return n;
}
template<typename T>
size_t extract(size_t i, boost::optional<T>& to) const {
if(i < values.size()) {
return extract(i, *to);
} else {
to = boost::none;
return 0;
}
}
template<typename T>
size_t encode(size_t i, const boost::optional<T>& 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<typename T>
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<typename T>
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<typename T>
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<cTagFile_Tag> 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<typename T>
cTagFile_TagEncodeProxy operator<<(const T& value) {
internal_add_tag();
return cTagFile_TagEncodeProxy(tags.back()) << value;
}
template<typename T>
cTagFile_TagExtractProxy operator>>(T& value) const {
if(i >= tags.size()) {
i = 0;
return cTagFile_TagExtractProxy() >> value;
} else {
return cTagFile_TagExtractProxy(tags[i++]) >> value;
}
}
template<typename Container>
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<typename Container>
void encode(const Container& values) {
for(const auto& value : values) {
*this << value;
}
}
};
class cTagFile_Page {
friend class cTagFile_Tag;
std::map<std::string, std::reference_wrapper<cTagFile_Tag>> tag_map;
std::vector<std::reference_wrapper<cTagFile_Tag>> 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<std::string, cTagFile_TagList> tag_map;
// However, we also record the real order the tags were encountered in.
std::deque<std::reference_wrapper<cTagFile_Tag>> 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<typename Self>
class pMultiPage : public cTagFile_Page {
std::shared_ptr<Self> next;
std::shared_ptr<Self> make_page() {
return std::shared_ptr<Self>(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<Self&>(*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<std::reference_wrapper<cTagFile_Page>> pages;
std::deque<cTagFile_Page> pages;
using iterator = typename std::deque<cTagFile_Page>::iterator;
using const_iterator = typename std::deque<cTagFile_Page>::const_iterator;
public:
void readFrom(std::istream& file);
void writeTo(std::ostream& file);
};
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(); }
template<typename T> class tArrayTag;
template<typename T>
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<typename T>
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<typename T>
class tOptionalTag : public cTagFile_Tag {
boost::optional<T> 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<bool> : 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<typename T>
class tArrayTag : public cTagFile_Tag {
std::vector<T> 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<T>::const_iterator begin() const {
return values.begin();
}
typename std::vector<T>::const_iterator end() const {
return values.end();
}
const T& operator[](size_t i) const {
return values.at(i);
}
};
#endif

View File

@@ -7,60 +7,10 @@
#include "fileio/tagfile.hpp"
#include <sstream>
#include <array>
#include "catch.hpp"
template<typename T>
std::ostream& operator<<(std::ostream& os, const tBasicTag<T>& tag) {
return os << T(tag);
}
class pSamplePage1 : public cTagFile_Page {
public:
DEFINE_TAGFILE_PAGE(pSamplePage1, cTagFile_Page);
tBasicTag<int> a{*this, "A"}, b{*this, "B"}, c{*this, "C"};
};
class pSamplePage2 : public cTagFile_Page {
public:
DEFINE_TAGFILE_PAGE(pSamplePage2, cTagFile_Page);
tBasicTag<std::string> 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<int> id{*this, "ID"};
tArrayTag<std::string> strings{*this, "STRING"};
tArrayTag<std::pair<int, int>> locations{*this, "LOC"};
tOptionalTag<int> filter{*this, "FILTER"};
tOptionalTag<int> count{*this, "COUNT"};
tOptionalTag<bool> yes{*this, "YES"};
tOptionalTag<bool> no{*this, "NO"};
tBasicTag<bool> enable{*this, "ENABLE"};
};
class pSampleMultiPage : public pMultiPage<pSampleMultiPage> {
public:
DEFINE_TAGFILE_MULTIPAGE(pSampleMultiPage, pMultiPage);
tBasicTag<char> id{*this, "ID"};
tBasicTag<int> value{*this, "VALUE"};
tBasicTag<std::string> 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<int,2>{{12,22}};
p2["LOC"] << std::pair<int,int>{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<std::string> strings;
page["STRING"].extract(strings);
REQUIRE(strings.size() == 2);
CHECK(strings[0] == "foo");
CHECK(strings[1] == "bar");
std::vector<std::pair<int,int>> 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);
}
}