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:
127
src/fileio/tagfile.cpp
Normal file
127
src/fileio/tagfile.cpp
Normal 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
276
src/fileio/tagfile.hpp
Normal 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
|
Reference in New Issue
Block a user