Config nodes: added the ability to clear out node values or create empty leaf nodes

This commit is contained in:
Bart Trzynadlowski 2023-03-04 11:58:52 -08:00 committed by trzy
parent 043f901c80
commit 83144f80b7
2 changed files with 78 additions and 36 deletions

View file

@ -7,7 +7,7 @@
*
* 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 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).
*
* 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
* 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.
*
*
* 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.
* 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
* 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
@ -39,7 +39,7 @@
*
* 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
* 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
@ -80,7 +80,7 @@
*
* Conversions as supported but are implemented via serialization and de-
* 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:
*
* node.ValueAs<T>()
@ -103,7 +103,7 @@
* - 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
* - 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
@ -133,7 +133,7 @@
* [ Section4, , Section5 ]
* 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"
* section because of the unnamed element.
*
@ -147,6 +147,7 @@
*/
#include "Util/NewConfig.h"
#include <iostream>
namespace Util
{
@ -251,8 +252,36 @@ namespace Util
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
// children) as a child
// children) as a child
void Node::AddChild(Node &parent, ptr_t &node)
{
if (!parent.m_last_child)
@ -264,7 +293,7 @@ namespace Util
{
parent.m_last_child->m_next_sibling = node;
parent.m_last_child = node;
}
}
parent.m_children[node->m_key] = node;
}
@ -290,7 +319,7 @@ namespace Util
m_last_child.swap(rhs.m_last_child);
m_children.swap(rhs.m_children);
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)
@ -340,5 +369,20 @@ namespace Util
{
//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
} // Util

View file

@ -36,6 +36,7 @@ namespace Util
void CheckEmptyOrMissing() const;
const Node &MissingNode(const std::string &key) const;
Node &AddEmpty(const std::string &path);
void AddChild(Node &parent, ptr_t &node);
void DeepCopy(const Node &that);
void Swap(Node &rhs);
@ -168,6 +169,11 @@ namespace Util
return m_value;
}
inline void Clear()
{
m_value = nullptr;
}
inline void SetValue(const std::shared_ptr<GenericValue> &value)
{
m_value = value;
@ -222,32 +228,9 @@ namespace Util
template <typename T>
Node &Add(const std::string &path, const T &value)
{
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
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 &new_leaf_node = AddEmpty(path);
new_leaf_node.SetValue(value);
return new_leaf_node;
}
Node &Add(const std::string &path)
@ -266,6 +249,19 @@ namespace Util
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)
inline bool Empty() const
{
@ -312,6 +308,8 @@ namespace Util
Node(Node&& that) noexcept;
~Node();
};
void PrintConfigTree(const Node &config, int indent_level = 0, int tab_stops = 2);
} // Config
} // Util