mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2025-04-10 19:15:14 +00:00
Config nodes: added the ability to clear out node values or create empty leaf nodes
This commit is contained in:
parent
043f901c80
commit
83144f80b7
Src/Util
|
@ -7,7 +7,7 @@
|
||||||
*
|
*
|
||||||
* A hierarchical data structure supporting arbitrary nesting. Each node
|
* A hierarchical data structure supporting arbitrary nesting. Each node
|
||||||
* (Config::Node) has a key and either a value or children (in fact, it may
|
* (Config::Node) has a key and either a value or children (in fact, it may
|
||||||
* have both, but this rarely makes semantic sense and so the config tree
|
* have both, but this rarely makes semantic sense and so the config tree
|
||||||
* builders take care not to allow it).
|
* builders take care not to allow it).
|
||||||
*
|
*
|
||||||
* Nesting is denoted with the '/' separator. For example:
|
* Nesting is denoted with the '/' separator. For example:
|
||||||
|
@ -20,10 +20,10 @@
|
||||||
* [1] accesses the value of the node (more on this below). [2] accesses the
|
* [1] accesses the value of the node (more on this below). [2] accesses the
|
||||||
* a child node with key "foo". [3] accesses the value of the child node "foo".
|
* a child node with key "foo". [3] accesses the value of the child node "foo".
|
||||||
* [4] accesses child "bar" of child "foo", and so forth.
|
* [4] accesses child "bar" of child "foo", and so forth.
|
||||||
*
|
*
|
||||||
* Similar to map semantics, the operator [] never fails. If the node does not
|
* Similar to map semantics, the operator [] never fails. If the node does not
|
||||||
* exist, it creates a dummy "missing" node that is retained as a hidden child.
|
* exist, it creates a dummy "missing" node that is retained as a hidden child.
|
||||||
* This node will have an empty value, which cannot be accessed, except using
|
* This node will have an empty value, which cannot be accessed, except using
|
||||||
* the ValueAsDefault<> method. This scheme exists to simplify lookup code for
|
* the ValueAsDefault<> method. This scheme exists to simplify lookup code for
|
||||||
* keys known at compile time, the logic being that any "missing" key should
|
* keys known at compile time, the logic being that any "missing" key should
|
||||||
* could just as well have been there in the first place, thus making the added
|
* could just as well have been there in the first place, thus making the added
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
*
|
*
|
||||||
* Nodes at the same nesting level (siblings) are strung together in a
|
* 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
|
* 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
|
* children (for order-preserving iteration) as well as a map for direct
|
||||||
* lookup by key.
|
* lookup by key.
|
||||||
*
|
*
|
||||||
* Keys may be reused at a given level. Key-value pairs will have their order
|
* Keys may be reused at a given level. Key-value pairs will have their order
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
*
|
*
|
||||||
* Conversions as supported but are implemented via serialization and de-
|
* Conversions as supported but are implemented via serialization and de-
|
||||||
* serialization using sstream. Most "sane" conversions will work as expected.
|
* serialization using sstream. Most "sane" conversions will work as expected.
|
||||||
* When a conversion to T is desired, or if the stored value type is not
|
* When a conversion to T is desired, or if the stored value type is not
|
||||||
* precisely known, use:
|
* precisely known, use:
|
||||||
*
|
*
|
||||||
* node.ValueAs<T>()
|
* node.ValueAs<T>()
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
* - Section nodes have their key set to the section name and value empty.
|
* - 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,
|
* They are the only nodes that can have children (i.e., IsLeaf() == false,
|
||||||
* HasChildren() == true).
|
* HasChildren() == true).
|
||||||
* - Top-level node in the tree is the global section, and its key is
|
* - Top-level node in the tree is the global section, and its key is
|
||||||
* "Global".
|
* "Global".
|
||||||
* - Only the global section (top-level section) may have child nodes that are
|
* - 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
|
* sections. The config tree can therefore only be up to 2 levels deep: the
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
* [ Section4, , Section5 ]
|
* [ Section4, , Section5 ]
|
||||||
* SettingX = bar
|
* SettingX = bar
|
||||||
*
|
*
|
||||||
* In this example, SettingX will be set to "foo" in Section1, Section2, and
|
* In this example, SettingX will be set to "foo" in Section1, Section2, and
|
||||||
* Section3. It will be set to "bar" in Section4, Section5, and the "Global"
|
* Section3. It will be set to "bar" in Section4, Section5, and the "Global"
|
||||||
* section because of the unnamed element.
|
* section because of the unnamed element.
|
||||||
*
|
*
|
||||||
|
@ -147,6 +147,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Util/NewConfig.h"
|
#include "Util/NewConfig.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace Util
|
namespace Util
|
||||||
{
|
{
|
||||||
|
@ -251,8 +252,36 @@ namespace Util
|
||||||
return os.str();
|
return os.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds an empty node (no value and where Empty() will return true)
|
||||||
|
Node &Node::AddEmpty(const std::string &path)
|
||||||
|
{
|
||||||
|
std::vector<std::string> keys = Util::Format(path).Split('/');
|
||||||
|
Node *parent = this;
|
||||||
|
ptr_t node;
|
||||||
|
for (size_t i = 0; i < keys.size(); i++)
|
||||||
|
{
|
||||||
|
bool leaf = i == keys.size() - 1;
|
||||||
|
auto it = parent->m_children.find(keys[i]);
|
||||||
|
if (leaf || it == parent->m_children.end())
|
||||||
|
{
|
||||||
|
// Create node at this level and leave it empty
|
||||||
|
node = std::make_shared<Node>(keys[i]);
|
||||||
|
// Attach node to parent and move down to next nesting level: last
|
||||||
|
// created node is new parent
|
||||||
|
AddChild(*parent, node);
|
||||||
|
parent = node.get();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Descend deeper...
|
||||||
|
parent = it->second.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *node;
|
||||||
|
}
|
||||||
|
|
||||||
// Adds a newly-created node (which, among other things, implies no
|
// Adds a newly-created node (which, among other things, implies no
|
||||||
// children) as a child
|
// children) as a child
|
||||||
void Node::AddChild(Node &parent, ptr_t &node)
|
void Node::AddChild(Node &parent, ptr_t &node)
|
||||||
{
|
{
|
||||||
if (!parent.m_last_child)
|
if (!parent.m_last_child)
|
||||||
|
@ -264,7 +293,7 @@ namespace Util
|
||||||
{
|
{
|
||||||
parent.m_last_child->m_next_sibling = node;
|
parent.m_last_child->m_next_sibling = node;
|
||||||
parent.m_last_child = node;
|
parent.m_last_child = node;
|
||||||
}
|
}
|
||||||
parent.m_children[node->m_key] = node;
|
parent.m_children[node->m_key] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +319,7 @@ namespace Util
|
||||||
m_last_child.swap(rhs.m_last_child);
|
m_last_child.swap(rhs.m_last_child);
|
||||||
m_children.swap(rhs.m_children);
|
m_children.swap(rhs.m_children);
|
||||||
const_cast<std::string *>(&m_key)->swap(*const_cast<std::string *>(&rhs.m_key));
|
const_cast<std::string *>(&m_key)->swap(*const_cast<std::string *>(&rhs.m_key));
|
||||||
m_value.swap(rhs.m_value);
|
m_value.swap(rhs.m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Node &Node::operator=(const Node &rhs)
|
Node &Node::operator=(const Node &rhs)
|
||||||
|
@ -340,5 +369,20 @@ namespace Util
|
||||||
{
|
{
|
||||||
//std::cout << ">>> Destroyed " << m_key << " (" << this << ")" << std::endl;
|
//std::cout << ">>> Destroyed " << m_key << " (" << this << ")" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintConfigTree(const Node &config, int indent_level, int tab_stops)
|
||||||
|
{
|
||||||
|
std::fill_n(std::ostream_iterator<char>(std::cout), tab_stops * indent_level, ' ');
|
||||||
|
std::cout << config.Key();
|
||||||
|
if (config.Exists())
|
||||||
|
{
|
||||||
|
std::cout << " = " << config.ValueAs<std::string>();
|
||||||
|
}
|
||||||
|
std::cout << std::endl;
|
||||||
|
for (const Node &child: config)
|
||||||
|
{
|
||||||
|
PrintConfigTree(child, indent_level + 1, tab_stops);
|
||||||
|
}
|
||||||
|
}
|
||||||
} // Config
|
} // Config
|
||||||
} // Util
|
} // Util
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace Util
|
||||||
|
|
||||||
void CheckEmptyOrMissing() const;
|
void CheckEmptyOrMissing() const;
|
||||||
const Node &MissingNode(const std::string &key) const;
|
const Node &MissingNode(const std::string &key) const;
|
||||||
|
Node &AddEmpty(const std::string &path);
|
||||||
void AddChild(Node &parent, ptr_t &node);
|
void AddChild(Node &parent, ptr_t &node);
|
||||||
void DeepCopy(const Node &that);
|
void DeepCopy(const Node &that);
|
||||||
void Swap(Node &rhs);
|
void Swap(Node &rhs);
|
||||||
|
@ -168,6 +169,11 @@ namespace Util
|
||||||
return m_value;
|
return m_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Clear()
|
||||||
|
{
|
||||||
|
m_value = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
inline void SetValue(const std::shared_ptr<GenericValue> &value)
|
inline void SetValue(const std::shared_ptr<GenericValue> &value)
|
||||||
{
|
{
|
||||||
m_value = value;
|
m_value = value;
|
||||||
|
@ -222,32 +228,9 @@ namespace Util
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Node &Add(const std::string &path, const T &value)
|
Node &Add(const std::string &path, const T &value)
|
||||||
{
|
{
|
||||||
std::vector<std::string> keys = Util::Format(path).Split('/');
|
Node &new_leaf_node = AddEmpty(path);
|
||||||
Node *parent = this;
|
new_leaf_node.SetValue(value);
|
||||||
ptr_t node;
|
return new_leaf_node;
|
||||||
for (size_t i = 0; i < keys.size(); i++)
|
|
||||||
{
|
|
||||||
bool leaf = i == keys.size() - 1;
|
|
||||||
auto it = parent->m_children.find(keys[i]);
|
|
||||||
if (leaf || it == parent->m_children.end())
|
|
||||||
{
|
|
||||||
// Create node at this level
|
|
||||||
node = std::make_shared<Node>(keys[i]);
|
|
||||||
// The leaf node gets the value
|
|
||||||
if (leaf)
|
|
||||||
node->SetValue(value);
|
|
||||||
// Attach node to parent and move down to next nesting level: last
|
|
||||||
// created node is new parent
|
|
||||||
AddChild(*parent, node);
|
|
||||||
parent = node.get();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Descend deeper...
|
|
||||||
parent = it->second.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Node &Add(const std::string &path)
|
Node &Add(const std::string &path)
|
||||||
|
@ -266,6 +249,19 @@ namespace Util
|
||||||
Add(key, value);
|
Add(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetEmpty(const std::string &key)
|
||||||
|
{
|
||||||
|
Node *node = TryGet(key);
|
||||||
|
if (node)
|
||||||
|
{
|
||||||
|
node->Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddEmpty(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// True if value is empty (does not exist)
|
// True if value is empty (does not exist)
|
||||||
inline bool Empty() const
|
inline bool Empty() const
|
||||||
{
|
{
|
||||||
|
@ -312,6 +308,8 @@ namespace Util
|
||||||
Node(Node&& that) noexcept;
|
Node(Node&& that) noexcept;
|
||||||
~Node();
|
~Node();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void PrintConfigTree(const Node &config, int indent_level = 0, int tab_stops = 2);
|
||||||
} // Config
|
} // Config
|
||||||
} // Util
|
} // Util
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue