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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
234
test/tagfile.cpp
234
test/tagfile.cpp
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user