Add a new class to encapsulate the file format used by save files

The class is not yet used in the wild, but does have a unit test
This commit is contained in:
2023-01-04 19:57:00 -05:00
parent 43ad1be6ca
commit b469b3aeea
4 changed files with 605 additions and 0 deletions

127
src/fileio/tagfile.cpp Normal file
View File

@@ -0,0 +1,127 @@
//
// tagfile.cpp
// BoE
//
// Created by Celtic Minstrel on 2022-07-12.
//
#include "tagfile.hpp"
#include <sstream>
#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;
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);
}
}
void cTagFile::writeTo(std::ostream& file) {
bool first = true;
for(const auto& page : pages) {
if(!first) file << '\f';
first = false;
page.get().writeTo(file);
}
}
cTagFile_Page::cTagFile_Page(cTagFile& owner) {
owner.pages.push_back(*this);
}
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();
}
}
void cTagFile_Page::writeTo(std::ostream& file) {
for(const auto& tag : tag_list) {
tag.get().writeTo(tag.get().key, 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);
}
void cTagFile_Tag::readValueFrom(std::istream& file, std::string& to) {
to = read_maybe_quoted_string(file);
}
void cTagFile_Tag::writeValueTo(std::ostream& file, const std::string& from) {
file << maybe_quote_string(from);
}
void cTagFile_Tag::readValueFrom(std::istream& file, bool& to) {
auto f = file.flags();
file >> std::boolalpha >> to;
file.flags(f);
}
void cTagFile_Tag::writeValueTo(std::ostream& file, const bool& from) {
auto f = file.flags();
file << std::boolalpha << from;
file.flags(f);
}
void cTagFile_Tag::readValueFrom(std::istream& file, char& to) {
int temp;
file >> temp;
to = char(temp);
}
void cTagFile_Tag::writeValueTo(std::ostream& file, const char& from) {
file << int(from);
}
void cTagFile_Tag::readValueFrom(std::istream& file, signed char& to) {
int temp;
file >> temp;
to = char(temp);
}
void cTagFile_Tag::writeValueTo(std::ostream& file, const signed char& from) {
file << int(from);
}
void cTagFile_Tag::readValueFrom(std::istream& file, unsigned char& to) {
int temp;
file >> temp;
to = char(temp);
}
void cTagFile_Tag::writeValueTo(std::ostream& file, const unsigned char& from) {
file << int(from);
}

276
src/fileio/tagfile.hpp Normal file
View File

@@ -0,0 +1,276 @@
//
// tagfile.hpp
// BoE
//
// Created by Celtic Minstrel on 2022-07-12.
//
#ifndef BoE_tagfile_hpp
#define BoE_tagfile_hpp
#include <string>
#include <vector>
#include <map>
#include <functional> // for reference_wrapper
#include <istream>
#include <ostream>
#include <boost/optional.hpp>
class cTagFile;
class cTagFile_Page;
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);
template<typename T>
static void readValueFrom(std::istream& file, T& to) {
file >> to;
}
template<typename T>
static void writeValueTo(std::ostream& file, const T& from) {
file << from;
}
template<typename A, typename B>
void readValueFrom(std::istream& file, std::pair<A, B>& to) {
readValueFrom(file, to.first);
readValueFrom(file, 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);
}
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<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<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 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));
}
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;
};
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;
protected:
mutable bool hasBeenRead = false;
std::string getFirstKey() const;
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 cTagFile_MultiPage : public cTagFile_Page {
std::shared_ptr<Self> next;
public:
using cTagFile_Page::cTagFile_Page;
void readFrom(std::istream& file) override {
if(!hasBeenRead) {
cTagFile_Page::readFrom(file);
} else {
if(!next) next = std::make_shared<Self>();
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 = std::make_shared<Self>();
return next->operator[](i - 1);
}
Self& add() {
if(next) return next->add();
next = std::make_shared<Self>();
return *next;
}
size_t size() {
return 1 + (next ? next->size() : 0);
}
};
class cTagFile {
friend class cTagFile_Page;
std::vector<std::reference_wrapper<cTagFile_Page>> pages;
public:
void readFrom(std::istream& file);
void writeTo(std::ostream& file);
};
template<typename T> class cTagFile_ArrayTag;
template<typename T>
class cTagFile_BasicTag : public cTagFile_Tag {
T value;
public:
using cTagFile_Tag::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; }
cTagFile_BasicTag 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 cTagFile_OptionalTag : public cTagFile_Tag {
boost::optional<T> value;
public:
using cTagFile_Tag::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.has_value()) {
file << key << ' ';
writeValueTo(file, *value);
file << '\n';
}
}
const T& get() const { return *value; }
cTagFile_OptionalTag operator=(const T& new_value) { value = new_value; return *this; }
cTagFile_OptionalTag 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 cTagFile_OptionalTag<bool> : public cTagFile_Tag {
bool value = false;
public:
using cTagFile_Tag::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; }
cTagFile_OptionalTag 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 cTagFile_ArrayTag : public cTagFile_Tag {
std::vector<T> values;
public:
using cTagFile_Tag::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