mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-21 21:35:38 +00:00
Moved logging functions into OSD/Logger.cpp (eventually logging system will be reworked entirely) and added new config tree structure (not yet used).
This commit is contained in:
parent
0650ffc37c
commit
bcc663d4eb
|
@ -119,7 +119,8 @@ endif
|
|||
#
|
||||
# Objects and Dependencies
|
||||
#
|
||||
OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
|
||||
OBJ = $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o \
|
||||
$(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/INIFile.o $(OBJ_DIR)/BlockFile.o $(OBJ_DIR)/93C46.o \
|
||||
$(OBJ_DIR)/ROMLoad.o $(OBJ_DIR)/unzip.o $(OBJ_DIR)/ioapi.o $(OBJ_DIR)/Error.o $(OBJ_DIR)/glew.o $(OBJ_DIR)/Shader.o \
|
||||
$(OBJ_DIR)/Real3D.o $(OBJ_DIR)/Legacy3D.o $(OBJ_DIR)/Models.o $(OBJ_DIR)/TextureRefs.o \
|
||||
$(OBJ_DIR)/New3D.o $(OBJ_DIR)/Mat4.o $(OBJ_DIR)/Model.o $(OBJ_DIR)/PolyHeader.o $(OBJ_DIR)/Texture.o $(OBJ_DIR)/TextureSheet.o $(OBJ_DIR)/VBO.o $(OBJ_DIR)/Vec.o $(OBJ_DIR)/R3DShader.o $(OBJ_DIR)/R3DFloat.o \
|
||||
|
@ -135,7 +136,7 @@ OBJ = $(OBJ_DIR)/PPCDisasm.o $(OBJ_DIR)/Games.o $(OBJ_DIR)/Config.o $(OBJ_DIR)/I
|
|||
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
|
||||
$(OBJ_DIR)/amp_util.o \
|
||||
$(OBJ_DIR)/Crypto.o \
|
||||
$(OBJ_DIR)/Format.o
|
||||
$(OBJ_DIR)/Logger.o
|
||||
|
||||
|
||||
# If built-in debugger enabled, include all debugging classes
|
||||
|
@ -157,6 +158,10 @@ ppcd: $(BIN_DIR) $(OBJ_DIR)
|
|||
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
|
||||
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
|
||||
|
||||
tests: $(BIN_DIR) $(OBJ_DIR) $(OBJ) Src/Util/Test_Config.cpp
|
||||
$(CXX) Src/Util/Test_Config.cpp $(CPPFLAGS) -o $(OBJ_DIR)/Test_Config.o
|
||||
$(LD) -o $(BIN_DIR)/test_config.exe -mconsole $(OBJ_DIR)/Test_Config.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/Logger.o
|
||||
|
||||
$(BIN_DIR):
|
||||
mkdir $(BIN_DIR)
|
||||
|
||||
|
|
45
Src/OSD/Logger.cpp
Normal file
45
Src/OSD/Logger.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "OSD/Logger.h"
|
||||
|
||||
// Logger object is used to redirect log messages appropriately
|
||||
static CLogger *s_Logger = NULL;
|
||||
|
||||
CLogger *GetLogger()
|
||||
{
|
||||
return s_Logger;
|
||||
}
|
||||
|
||||
void SetLogger(CLogger *Logger)
|
||||
{
|
||||
s_Logger = Logger;
|
||||
}
|
||||
|
||||
void DebugLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->DebugLog(fmt, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
void InfoLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->InfoLog(fmt, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
bool ErrorLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return FAIL;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->ErrorLog(fmt, vl);
|
||||
va_end(vl);
|
||||
return FAIL;
|
||||
}
|
|
@ -239,8 +239,8 @@ private:
|
|||
/******************************************************************************
|
||||
Log Functions
|
||||
|
||||
Message logging interface. All messages are passed by the OSD layer to the
|
||||
currently active logger object.
|
||||
Message logging interface. All messages are passed to the currently active
|
||||
logger object.
|
||||
******************************************************************************/
|
||||
|
||||
/*
|
||||
|
|
|
@ -63,59 +63,10 @@
|
|||
#include "WinOutputs.h"
|
||||
#endif
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
Error and Debug Logging
|
||||
******************************************************************************/
|
||||
|
||||
// Log file names
|
||||
#define DEBUG_LOG_FILE "debug.log"
|
||||
#define ERROR_LOG_FILE "error.log"
|
||||
|
||||
// Logger object is used to redirect log messages appropriately
|
||||
static CLogger *s_Logger = NULL;
|
||||
|
||||
CLogger *GetLogger()
|
||||
{
|
||||
return s_Logger;
|
||||
}
|
||||
|
||||
void SetLogger(CLogger *Logger)
|
||||
{
|
||||
s_Logger = Logger;
|
||||
}
|
||||
|
||||
void DebugLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->DebugLog(fmt, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
void InfoLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->InfoLog(fmt, vl);
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
bool ErrorLog(const char *fmt, ...)
|
||||
{
|
||||
if (s_Logger == NULL)
|
||||
return FAIL;
|
||||
va_list vl;
|
||||
va_start(vl, fmt);
|
||||
s_Logger->ErrorLog(fmt, vl);
|
||||
va_end(vl);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
Display Management
|
||||
|
@ -1549,9 +1500,9 @@ static void PrintGameList(void)
|
|||
*
|
||||
* Program entry point.
|
||||
*/
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
|
||||
#ifdef SUPERMODEL_DEBUGGER
|
||||
bool cmdEnterDebugger = false;
|
||||
#endif // SUPERMODEL_DEBUGGER
|
||||
|
|
|
@ -1,10 +1,33 @@
|
|||
#include "Util/Format.h"
|
||||
#include <cctype>
|
||||
|
||||
namespace Util
|
||||
{
|
||||
std::string TrimWhiteSpace(const std::string &str)
|
||||
{
|
||||
if (str.empty())
|
||||
return std::string();
|
||||
size_t first = 0;
|
||||
for (; first < str.length(); ++first)
|
||||
{
|
||||
if (!isspace(str[first]))
|
||||
break;
|
||||
}
|
||||
if (first >= str.length())
|
||||
return std::string();
|
||||
size_t last = str.length() - 1;
|
||||
for (; last > first; --last)
|
||||
{
|
||||
if (!isspace(str[last]))
|
||||
break;
|
||||
}
|
||||
++last;
|
||||
return std::string(str.c_str() + first, last - first);
|
||||
}
|
||||
|
||||
static const char hex_digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
const std::string Hex(uint32_t n, size_t num_digits)
|
||||
std::string Hex(uint32_t n, size_t num_digits)
|
||||
{
|
||||
Util::Format f;
|
||||
f << "0x";
|
||||
|
@ -16,17 +39,17 @@ namespace Util
|
|||
return f;
|
||||
}
|
||||
|
||||
const std::string Hex(uint32_t n)
|
||||
std::string Hex(uint32_t n)
|
||||
{
|
||||
return Hex(n, 8);
|
||||
}
|
||||
|
||||
const std::string Hex(uint16_t n)
|
||||
std::string Hex(uint16_t n)
|
||||
{
|
||||
return Hex(n, 4);
|
||||
}
|
||||
|
||||
const std::string Hex(uint8_t n)
|
||||
std::string Hex(uint8_t n)
|
||||
{
|
||||
return Hex(n, 2);
|
||||
}
|
||||
|
|
|
@ -84,10 +84,11 @@ namespace Util
|
|||
}
|
||||
};
|
||||
|
||||
const std::string Hex(uint32_t n, size_t num_digits);
|
||||
const std::string Hex(uint32_t n);
|
||||
const std::string Hex(uint16_t n);
|
||||
const std::string Hex(uint8_t n);
|
||||
std::string TrimWhiteSpace(const std::string &str);
|
||||
std::string Hex(uint32_t n, size_t num_digits);
|
||||
std::string Hex(uint32_t n);
|
||||
std::string Hex(uint16_t n);
|
||||
std::string Hex(uint8_t n);
|
||||
} // Util
|
||||
|
||||
#endif // INCLUDED_FORMAT_H
|
||||
|
|
372
Src/Util/NewConfig.cpp
Normal file
372
Src/Util/NewConfig.cpp
Normal file
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
* Config Tree
|
||||
* -----------
|
||||
*
|
||||
* A hierarchical data structure supporting arbitrary nesting. Each node
|
||||
* (Config::Node) has a key and either a value or children (in fact, it may
|
||||
* have both, but this does not make semantic sense and so the config tree
|
||||
* builders take care not to allow it).
|
||||
*
|
||||
* All values are simply strings for now. They may have defaults, which are
|
||||
* actually supplied by the caller and returned when a queried node cannot be
|
||||
* found.
|
||||
*
|
||||
* Nodes at the same nesting level (siblings) are strung together in a
|
||||
* linked list. Parents also maintain pointers to the first and last of their
|
||||
* children (for order-preserving iteration) as well as a map for direct
|
||||
* lookup by key.
|
||||
*
|
||||
* Keys may be reused at a given level. Key-value pairs will have their order
|
||||
* preserved when iterated but only the most recent key is returned in direct
|
||||
* map lookups.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <game name="scud">
|
||||
* <roms>
|
||||
* <crom name="foo.bin" offset=0/>
|
||||
* <crom name="bar.bin" offset=2/>
|
||||
* </roms>
|
||||
*
|
||||
* config["game/roms"].Value() <-- should return nothing
|
||||
* config["game/roms/crom/offset"].Value() <-- "2", the second <crom> tag
|
||||
* config["game/roms"].begin() <-- iterate over two <crom> tags
|
||||
*
|
||||
* INI Semantics
|
||||
* -------------
|
||||
*
|
||||
* INI files are linear in nature and divided into sections. Config trees built
|
||||
* to represent INI files must adhere to the following rules:
|
||||
*
|
||||
* - Section nodes have their key set to the section name and value empty.
|
||||
* They are the only nodes that can have children (i.e., IsLeaf() == false,
|
||||
* HasChildren() == true).
|
||||
* - Top-level node in the tree is the global section, and its key is
|
||||
* "Global".
|
||||
* - Only the global section (top-level section) may have child nodes that are
|
||||
* sections. The config tree can therefore only be up to 2 levels deep: the
|
||||
* first, top-most level consists of global parameters, and section nodes
|
||||
* provide one more level of nesting.
|
||||
*
|
||||
* INI files contain zero or more sections followed by zero or more settings
|
||||
* each.
|
||||
*
|
||||
* Setting0 = 100
|
||||
* [ Global ]
|
||||
* Setting1 = 200
|
||||
* [ Section1 ]
|
||||
* SettingA = 1 ; this is a comment
|
||||
* SettingB = foo
|
||||
* [ Section2 ]
|
||||
* SettingX = bar
|
||||
* SettingZ = "baz" ; quotes are optional and are stripped off during parsing
|
||||
*
|
||||
* Any setting not explicitly part of a section is assigned to the "Global"
|
||||
* section. For example, Setting0 and Setting1 above are both part of "Global".
|
||||
*/
|
||||
|
||||
#include "Util/NewConfig.h"
|
||||
#include "OSD/Logger.h"
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace Util
|
||||
{
|
||||
namespace Config
|
||||
{
|
||||
Node Node::s_empty_node;
|
||||
|
||||
const Node &Node::operator[](const std::string &path) const
|
||||
{
|
||||
const Node *e = this;
|
||||
std::vector<std::string> keys = Util::Format(path).Split('/');
|
||||
for (auto &key: keys)
|
||||
{
|
||||
auto it = e->m_children.find(key);
|
||||
if (it == e->m_children.end())
|
||||
return s_empty_node;
|
||||
e = it->second.get();
|
||||
}
|
||||
return *e;
|
||||
}
|
||||
|
||||
Node &Node::Get(const std::string &path)
|
||||
{
|
||||
return const_cast<Node &>(operator[](path));
|
||||
}
|
||||
|
||||
const Node &Node::Get(const std::string &path) const
|
||||
{
|
||||
return operator[](path);
|
||||
}
|
||||
|
||||
void Node::Print(size_t indent_level) const
|
||||
{
|
||||
std::fill_n(std::ostream_iterator<char>(std::cout), 2 * indent_level, ' ');
|
||||
std::cout << m_key;
|
||||
if (m_value.length())
|
||||
std::cout << '=' << m_value;
|
||||
std::cout << " children={";
|
||||
for (auto v: m_children)
|
||||
std::cout << ' ' << v.first;
|
||||
std::cout << " }" << std::endl;
|
||||
for (Ptr_t child = m_first_child; child; child = child->m_next_sibling)
|
||||
child->Print(indent_level + 1);
|
||||
}
|
||||
|
||||
Node &Node::Create(const std::string &key)
|
||||
{
|
||||
Ptr_t node = std::make_shared<Node>(key);
|
||||
AddChild(node);
|
||||
return *node;
|
||||
}
|
||||
|
||||
Node &Node::Create(const std::string &key, const std::string &value)
|
||||
{
|
||||
Ptr_t node = std::make_shared<Node>(key, value);
|
||||
AddChild(node);
|
||||
return *node;
|
||||
}
|
||||
|
||||
// Adds a newly-created node (which, among other things, implies no
|
||||
// children) as a child
|
||||
void Node::AddChild(Ptr_t &node)
|
||||
{
|
||||
if (!m_last_child)
|
||||
{
|
||||
m_first_child = node;
|
||||
m_last_child = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_last_child->m_next_sibling = node;
|
||||
m_last_child = node;
|
||||
}
|
||||
m_children[node->m_key] = node;
|
||||
}
|
||||
|
||||
/*
|
||||
void Node::Clear()
|
||||
{
|
||||
m_next_sibling.reset();
|
||||
m_first_child.reset();
|
||||
m_last_child.reset();
|
||||
m_children.clear();
|
||||
m_key.clear();
|
||||
m_value.clear();
|
||||
}
|
||||
*/
|
||||
|
||||
Node::Node()
|
||||
{
|
||||
//std::cout << "Created " << "<null>" << " (" << this << ")" << std::endl;
|
||||
}
|
||||
|
||||
Node::Node(const std::string &key)
|
||||
: m_key(key)
|
||||
{
|
||||
//std::cout << "Created " << key << " (" << this << ")" << std::endl;
|
||||
}
|
||||
|
||||
Node::Node(const std::string &key, const std::string &value)
|
||||
: m_key(key),
|
||||
m_value(value)
|
||||
{
|
||||
//std::cout << "Created " << key << '=' << value << " (" << this << ")" << std::endl;
|
||||
}
|
||||
|
||||
// Deep copy
|
||||
Node::Node(const Node &that)
|
||||
: m_key(that.m_key),
|
||||
m_value(that.m_value)
|
||||
{
|
||||
for (Ptr_t child = that.m_first_child; child; child = child->m_next_sibling)
|
||||
{
|
||||
Ptr_t copied_child = std::make_shared<Node>(*child);
|
||||
AddChild(copied_child);
|
||||
}
|
||||
}
|
||||
|
||||
Node::~Node()
|
||||
{
|
||||
//std::cout << "Destroyed " << m_key << " (" << this << ")" << std::endl;
|
||||
}
|
||||
|
||||
Node::Ptr_t CreateEmpty()
|
||||
{
|
||||
return std::shared_ptr<Node>(new Node());
|
||||
}
|
||||
|
||||
static std::string StripComment(const std::string &line)
|
||||
{
|
||||
// Find first semicolon not enclosed in ""
|
||||
bool inside_quotes = false;
|
||||
for (auto it = line.begin(); it != line.end(); ++it)
|
||||
{
|
||||
inside_quotes ^= (*it == '\"');
|
||||
if (!inside_quotes && *it == ';')
|
||||
return std::string(line.begin(), it);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
static void ParseAssignment(Node ¤t_section, const std::string &filename, size_t line_num, const std::string &line)
|
||||
{
|
||||
size_t idx_equals = line.find_first_of('=');
|
||||
if (idx_equals == std::string::npos)
|
||||
{
|
||||
//std::cerr << filename << ':' << line_num << ": Syntax error. No '=' found." << std::endl;
|
||||
ErrorLog("%s:%d: Syntax error. No '=' found.", filename.c_str(), line_num);
|
||||
return;
|
||||
}
|
||||
std::string lvalue(TrimWhiteSpace(std::string(line.begin(), line.begin() + idx_equals)));
|
||||
if (lvalue.empty())
|
||||
{
|
||||
//std::cerr << filename << ':' << line_num << ": Syntax error. Setting name missing before '='." << std::endl;
|
||||
ErrorLog("%s:%d: Syntax error. Setting name missing before '='.", filename.c_str(), line_num);
|
||||
return;
|
||||
}
|
||||
// Get rvalue, which is allowed to be empty and strip off quotes if they
|
||||
// exist. Only a single pair of quotes encasing the rvalue are permitted.
|
||||
std::string rvalue(TrimWhiteSpace(std::string(line.begin() + idx_equals + 1, line.end())));
|
||||
size_t idx_first_quote = rvalue.find_first_of('\"');
|
||||
size_t idx_last_quote = rvalue.find_last_of('\"');
|
||||
if (idx_first_quote == 0 && idx_last_quote == (rvalue.length() - 1) && idx_first_quote != idx_last_quote)
|
||||
rvalue = std::string(rvalue.begin() + idx_first_quote + 1, rvalue.begin() + idx_last_quote);
|
||||
if (std::count(rvalue.begin(), rvalue.end(), '\"') != 0)
|
||||
{
|
||||
//std::cerr << filename << ':' << line_num << ": Warning: Extraneous quotes present on line." << std::endl;
|
||||
ErrorLog("%s:%d: Warning: Extraneous quotes present on line.", filename.c_str(), line_num);
|
||||
}
|
||||
// In INI files, we do not allow multiple settings with the same key. If
|
||||
// a setting is specified multiple times, previous ones are overwritten.
|
||||
if (current_section[lvalue].Empty())
|
||||
current_section.Create(lvalue, rvalue);
|
||||
else
|
||||
current_section.Get(lvalue).SetValue(rvalue);
|
||||
}
|
||||
|
||||
Node::Ptr_t FromINIFile(const std::string &filename)
|
||||
{
|
||||
std::ifstream file;
|
||||
file.open(filename);
|
||||
if (file.fail())
|
||||
{
|
||||
ErrorLog("Unable to open '%s'. Configuration will not be loaded.", filename.c_str());
|
||||
return CreateEmpty();
|
||||
}
|
||||
|
||||
Node::Ptr_t global_ptr = std::make_shared<Node>("Global"); // the root is also the "Global" section
|
||||
Node &global = *global_ptr;
|
||||
Node *current_section = &global;
|
||||
|
||||
size_t line_num = 1;
|
||||
while (!file.eof())
|
||||
{
|
||||
if (file.fail())
|
||||
{
|
||||
ErrorLog("%s:%d: File read error. Configuration will be incomplete.");
|
||||
return CreateEmpty();
|
||||
}
|
||||
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
line = StripComment(line);
|
||||
line = TrimWhiteSpace(line);
|
||||
|
||||
if (!line.empty())
|
||||
{
|
||||
if (*line.begin() == '[' && *line.rbegin() == ']')
|
||||
{
|
||||
// Check if section exists else create a new one
|
||||
std::string section(TrimWhiteSpace(std::string(line.begin() + 1, line.begin() + line.length() - 1)));
|
||||
if (section.empty() || section == "Global") // empty section names (e.g., "[]") assigned to "[ Global ]"
|
||||
current_section = &global;
|
||||
else if (global[section].Empty())
|
||||
{
|
||||
Node &new_section = global.Create(section);
|
||||
current_section = &new_section;
|
||||
}
|
||||
else
|
||||
current_section = &global.Get(section);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseAssignment(*current_section, filename, line_num, line);
|
||||
}
|
||||
}
|
||||
|
||||
line_num++;
|
||||
}
|
||||
|
||||
return global_ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Produces a new INI section by merging two existing sections.
|
||||
*
|
||||
* - Nodes from x and y with children are ignored as per INI semantics.
|
||||
* The presence of children indicates a section, not a setting.
|
||||
* - Settings from y override settings in x if already present.
|
||||
* - x's key is retained.
|
||||
*/
|
||||
Node::Ptr_t MergeINISections(const Node &x, const Node &y)
|
||||
{
|
||||
Node::Ptr_t merged_ptr = std::make_shared<Node>(x.Key()); // result has same key as x
|
||||
Node &merged = *merged_ptr;
|
||||
// First copy settings from section x
|
||||
for (auto it = x.begin(); it != x.end(); ++it)
|
||||
{
|
||||
if (it->IsLeaf())
|
||||
merged.Create(it->Key(), it->Value());
|
||||
}
|
||||
// Merge in settings from section y
|
||||
for (auto it = y.begin(); it != y.end(); ++it)
|
||||
{
|
||||
auto &key = it->Key();
|
||||
auto &value = it->Value();
|
||||
if (it->IsLeaf())
|
||||
{
|
||||
if (merged[key].Empty())
|
||||
merged.Create(key, value);
|
||||
else
|
||||
merged.Get(key).SetValue(value);
|
||||
}
|
||||
}
|
||||
return merged_ptr;
|
||||
}
|
||||
|
||||
static void WriteSection(std::ofstream &file, const Node §ion)
|
||||
{
|
||||
file << "[ " << section.Key() << " ]" << std::endl;
|
||||
file << std::endl;
|
||||
for (auto it = section.begin(); it != section.end(); ++it)
|
||||
{
|
||||
if (it->IsLeaf())
|
||||
file << it->Key() << " = \"" << it->Value() << "\"" << std::endl;
|
||||
}
|
||||
file << std::endl << std::endl;
|
||||
}
|
||||
|
||||
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment)
|
||||
{
|
||||
std::ofstream file;
|
||||
file.open(filename);
|
||||
if (file.fail())
|
||||
{
|
||||
ErrorLog("Unable to write to '%s'. Configuration will not be saved.");
|
||||
return;
|
||||
}
|
||||
if (!header_comment.empty())
|
||||
file << header_comment << std::endl << std::endl;
|
||||
WriteSection(file, config);
|
||||
for (auto it = config.begin(); it != config.end(); ++it)
|
||||
{
|
||||
if (it->HasChildren())
|
||||
WriteSection(file, *it);
|
||||
}
|
||||
}
|
||||
} // Config
|
||||
} // Util
|
||||
|
204
Src/Util/NewConfig.h
Normal file
204
Src/Util/NewConfig.h
Normal file
|
@ -0,0 +1,204 @@
|
|||
#ifndef INCLUDED_UTIL_CONFIG_H
|
||||
#define INCLUDED_UTIL_CONFIG_H
|
||||
|
||||
#include "Util/Format.h"
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
|
||||
namespace Util
|
||||
{
|
||||
namespace Config
|
||||
{
|
||||
class Node
|
||||
{
|
||||
public:
|
||||
typedef std::shared_ptr<Node> Ptr_t;
|
||||
typedef std::shared_ptr<const Node> ConstPtr_t;
|
||||
|
||||
private:
|
||||
Ptr_t m_next_sibling;
|
||||
Ptr_t m_first_child;
|
||||
Ptr_t m_last_child;
|
||||
std::map<std::string, Ptr_t> m_children;
|
||||
const std::string m_key;
|
||||
std::string m_value;
|
||||
static Node s_empty_node; // key, value, and children must always be empty
|
||||
|
||||
void AddChild(Ptr_t &node);
|
||||
Node(); // prohibit accidental/unintentional creation of blank nodes
|
||||
friend Ptr_t CreateEmpty();
|
||||
|
||||
public:
|
||||
class const_iterator;
|
||||
|
||||
class iterator: public std::iterator<std::forward_iterator_tag, Node>
|
||||
{
|
||||
private:
|
||||
Ptr_t m_node;
|
||||
friend class const_iterator;
|
||||
public:
|
||||
inline iterator(Ptr_t node = Ptr_t())
|
||||
: m_node(node)
|
||||
{}
|
||||
inline iterator(const iterator &it)
|
||||
: m_node(it.m_node)
|
||||
{}
|
||||
inline iterator operator++()
|
||||
{
|
||||
// Prefix increment
|
||||
m_node = m_node->m_next_sibling;
|
||||
return *this;
|
||||
}
|
||||
inline iterator operator++(int)
|
||||
{
|
||||
// Postfix increment
|
||||
iterator current(*this);
|
||||
m_node = m_node->m_next_sibling;
|
||||
return *this;
|
||||
}
|
||||
inline Node &operator*() const
|
||||
{
|
||||
return *m_node;
|
||||
}
|
||||
inline Ptr_t operator->() const
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
inline bool operator==(const iterator &rhs) const
|
||||
{
|
||||
return m_node == rhs.m_node;
|
||||
}
|
||||
inline bool operator!=(const iterator &rhs) const
|
||||
{
|
||||
return m_node != rhs.m_node;
|
||||
}
|
||||
};
|
||||
|
||||
class const_iterator: public std::iterator<std::forward_iterator_tag, const Node>
|
||||
{
|
||||
private:
|
||||
ConstPtr_t m_node;
|
||||
public:
|
||||
inline const_iterator(ConstPtr_t node = ConstPtr_t())
|
||||
: m_node(node)
|
||||
{}
|
||||
inline const_iterator(Ptr_t node = Ptr_t())
|
||||
: m_node(std::const_pointer_cast<const Node>(node))
|
||||
{}
|
||||
inline const_iterator(const const_iterator &it)
|
||||
: m_node(it.m_node)
|
||||
{}
|
||||
inline const_iterator(const Node::iterator &it)
|
||||
: m_node(it.m_node)
|
||||
{}
|
||||
inline const_iterator operator++()
|
||||
{
|
||||
// Prefix increment
|
||||
m_node = m_node->m_next_sibling;
|
||||
return *this;
|
||||
}
|
||||
inline const_iterator operator++(int)
|
||||
{
|
||||
// Postfix increment
|
||||
iterator current(*this);
|
||||
m_node = m_node->m_next_sibling;
|
||||
return *this;
|
||||
}
|
||||
inline const Node &operator*() const
|
||||
{
|
||||
return *m_node;
|
||||
}
|
||||
inline ConstPtr_t operator->() const
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
inline bool operator==(const const_iterator &rhs) const
|
||||
{
|
||||
return m_node == rhs.m_node;
|
||||
}
|
||||
inline bool operator!=(const const_iterator &rhs) const
|
||||
{
|
||||
return m_node != rhs.m_node;
|
||||
}
|
||||
};
|
||||
|
||||
inline iterator begin()
|
||||
{
|
||||
return iterator(m_first_child);
|
||||
}
|
||||
|
||||
inline iterator end()
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
|
||||
inline const_iterator begin() const
|
||||
{
|
||||
return iterator(m_first_child);
|
||||
}
|
||||
|
||||
inline const_iterator end() const
|
||||
{
|
||||
return iterator();
|
||||
}
|
||||
|
||||
const inline std::string &Key() const
|
||||
{
|
||||
return m_key;
|
||||
}
|
||||
|
||||
const inline std::string &ValueWithDefault(const std::string &default_value) const
|
||||
{
|
||||
if (this == &s_empty_node)
|
||||
return default_value;
|
||||
return m_value;
|
||||
}
|
||||
|
||||
const inline std::string &Value() const
|
||||
{
|
||||
//TODO: if empty node, throw exception?
|
||||
return m_value;
|
||||
}
|
||||
|
||||
inline void SetValue(const std::string &value)
|
||||
{
|
||||
if (this != &s_empty_node) // not allowed to modify empty node
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
inline bool Empty() const
|
||||
{
|
||||
return m_key.empty();
|
||||
}
|
||||
|
||||
inline bool IsLeaf() const
|
||||
{
|
||||
return m_children.empty();
|
||||
}
|
||||
|
||||
inline bool HasChildren() const
|
||||
{
|
||||
return !IsLeaf();
|
||||
}
|
||||
|
||||
Node &Get(const std::string &path);
|
||||
const Node &operator[](const std::string &path) const;
|
||||
const Node &Get(const std::string &path) const;
|
||||
void Print(size_t indent_level = 0) const;
|
||||
Node &Create(const std::string &key);
|
||||
Node &Create(const std::string &key, const std::string &value);
|
||||
Node(const std::string &key);
|
||||
Node(const std::string &key, const std::string &value);
|
||||
Node(const Node &that);
|
||||
~Node();
|
||||
};
|
||||
|
||||
Node::Ptr_t CreateEmpty();
|
||||
Node::Ptr_t FromINIFile(const std::string &filename);
|
||||
Node::Ptr_t MergeINISections(const Node &x, const Node &y);
|
||||
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment);
|
||||
} // Config
|
||||
} // Util
|
||||
|
||||
#endif // INCLUDED_UTIL_CONFIG_H
|
61
Src/Util/Test_Config.cpp
Normal file
61
Src/Util/Test_Config.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#include "Util/NewConfig.h"
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
/*
|
||||
* Creates a config tree corresponding to the following:
|
||||
*
|
||||
* <game>
|
||||
* <name>scud</name>
|
||||
* <roms>
|
||||
* <crom>
|
||||
* <name>foo.bin</name>
|
||||
* <offset>0</offset>
|
||||
* </crom>
|
||||
* <crom>
|
||||
* <name>bar.bin</name>
|
||||
* <offset>2</offset>
|
||||
* </crom>
|
||||
* </roms>
|
||||
* </game>
|
||||
*/
|
||||
Util::Config::Node::Ptr_t root = std::make_shared<Util::Config::Node>("global");
|
||||
auto &game = root->Create("game");
|
||||
game.Create("name", "scud");
|
||||
auto &roms = game.Create("roms");
|
||||
auto &crom1 = roms.Create("crom");
|
||||
crom1.Create("name", "foo.bin");
|
||||
crom1.Create("offset", "0");
|
||||
auto &crom2 = roms.Create("crom");
|
||||
crom2.Create("name", "bar.bin");
|
||||
crom2.Create("offset", "2");
|
||||
|
||||
const char *expected_output =
|
||||
"global children={ game } \n"
|
||||
" game children={ name roms } \n"
|
||||
" name=scud children={ } \n"
|
||||
" roms children={ crom } \n"
|
||||
" crom children={ name offset } \n"
|
||||
" name=foo.bin children={ } \n"
|
||||
" offset=0 children={ } \n"
|
||||
" crom children={ name offset } \n"
|
||||
" name=bar.bin children={ } \n"
|
||||
" offset=2 children={ } \n";
|
||||
std::cout << "Expected output:" << std::endl << std::endl << expected_output << std::endl;
|
||||
std::cout << "Actual config tree:" << std::endl << std::endl;
|
||||
root->Print();
|
||||
std::cout << std::endl;
|
||||
|
||||
// Expect second crom: bar.bin
|
||||
auto &global = *root;
|
||||
std::cout << "game/roms/crom/name=" << global["game/roms/crom/name"].Value() << " (expected: bar.bin)" << std::endl << std::endl;
|
||||
|
||||
// Make a copy
|
||||
std::cout << "Copy:" << std::endl << std::endl;
|
||||
Util::Config::Node::Ptr_t copy = std::make_shared<Util::Config::Node>(*root);
|
||||
copy->Print();
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in a new issue