引言
大型应用程序使用配置来传输设置。 通常,编辑和删除功能会导致应用程序代码与这些相同设置中存储的内容之间失去同步。 简单地说,在您将永远不会再次使用的最后一个数据中进行结算。 理想情况下,我想跟踪并标记此类设置为已弃用或完全删除。
问题
碰巧,我们项目中的大多数配置都是用json和yaml混合编写的,并使用libconfig库进行了解析。 不想重写相应的代码和配置内容,例如在yaml上,尤其是当还有许多其他有趣且更复杂的任务时。 用C编写的库的一部分本身就很好:它稳定且功能丰富(在C ++的包装器中,并不是那么简单)。
有一天,我们小心翼翼地找出了在配置文件中累积了多少垃圾。 不幸的是,libconfig没有这样的选项。 首先,我们尝试将项目分叉到github上,并更改用C ++编写的部分(各种lookup和operator []方法),从而自动为节点设置访问标记的过程。 但是,这将导致补丁很大,采用该补丁的时间可能会延迟。 然后选择落在使用C ++编写自己的包装器的方向上,而不会影响libconfig的核心。
从使用输出的角度来看,我们有以下几点:
#include <variti/util/config.hpp> #include <iostream> #include <cassert> int main(int argc, char* argv[]) { using namespace variti; using namespace variti::util; assert(argc = 2); config conf( [](const config_setting& st) { if (!st.visited()) std::cerr << "config not visited: " << st.path() << "\n"; }); conf.load(argv[1]); auto root = conf.root(); root["module"]["name"].to_string(); return 0; }
laptop :: work/configpp/example ‹master*› % cat config_example1.conf version = "1.0"; module: { name = "module1"; submodules = ( { name = "submodule1"; }, { name = "submodule2"; } ); }; laptop :: work/configpp/example ‹master*› % ./config-example1 config_example1.conf config not visited: root.module.submodules.0.name config not visited: root.module.submodules.1.name
在示例代码中,我们转向设置module.name。 未访问设置module.submodules.0.name和module.submodules.1.name。 这就是在日志中报告给我们的内容。
包装纸
如果访问标志或类似的东西不在libconfig中,如何实现呢? 库的开发人员预先考虑并添加了将钩子钩到config_setting_t节点的功能,该节点使用config_setting_set_hook函数设置,并使用config_setting_get_hook读取。
将此钩定义为:
struct config_setting_hook { bool visited{false}; };
libconfig内部有两个主要结构:config_t和config_setting_t。 第一个提供对整个配置的整体访问,并返回指向根节点config_setting_t的指针,第二个提供对父节点和子节点的访问以及当前节点内部的值。
我们将两个结构都包装在相应的类-句柄中。
处理config_t:
using config_notify = std::function<void(const config_setting&)>; struct config : boost::noncopyable { config(config_notify n = nullptr); ~config(); void load(const std::string& filename); config_setting root() const; config_notify n; config_t* h; };
请注意,在遍历所有极端节点时,会将函数传递给config构造函数,该函数将在析构函数中调用。 在上面的示例中可以看到如何使用它。
处理config_setting_t:
struct config_setting : boost::noncopyable { config_setting(config_setting_t* h, bool visit = false); ~config_setting(); bool to_bool() const; std::int32_t to_int32() const; std::int64_t to_int64() const; double to_double() const; std::string to_string() const; bool is_bool() const; bool is_int32() const; bool is_int64() const; bool is_double() const; bool is_string() const; bool is_group() const; bool is_array() const; bool is_list() const; bool is_scalar() const; bool is_root() const; std::string path() const; std::size_t size() const; bool exists(const std::string& name) const; config_setting parent() const; config_setting lookup(const std::string& name, bool visit = false) const; config_setting lookup(std::size_t indx, bool visit = false) const; config_setting operator[](const std::string& name) const; config_setting operator[](std::size_t indx) const; std::string filename() const; std::size_t fileline() const; bool visited() const; config_setting_t* h; };
主要魔术在于查找方法。 假定通过称为visit的最后一个参数设置了访问节点标志,默认情况下为false。 您有权自己指出此值。 但是,由于对节点的最频繁访问仍然是通过operator []进行的,因此在内部调用了lookup方法,其访问次数等于true。 因此,您为其调用operator []的节点将被自动标记为已访问。 此外,在访问时,将标记从当前节点到根节点的整个节点链。
让我们继续执行。 我们为config类完整显示它:
config::config(config_notify n) : n(n) { h = (config_t*)malloc(sizeof(config_t)); config_init(h); config_set_destructor(h, [](void* p) { delete reinterpret_cast<config_setting_hook*>(p); }); } config::~config() { if (n) for_each(root(), n); config_destroy(h); free(h); } void config::load(const std::string& filename) { if (!config_read_file(h, filename.c_str())) throw std::runtime_error(std::string("config read file error: ") + filename); } config_setting config::root() const { return config_setting(config_root_setting(h)); }
部分用于config_setting:
config_setting::config_setting(config_setting_t* h, bool visit) : h(h) { assert(h); if (!config_setting_get_hook(h)) hook(h, new config_setting_hook()) if (visit) visit_up(h); } config_setting::~config_setting() { h = nullptr; } std::size_t config_setting::size() const { return config_setting_length(h); } config_setting config_setting::parent() const { return config_setting(config_setting_parent(h)); } bool config_setting::exists(const std::string& name) const { if (!is_group()) return false; return config_setting_get_member(h, name.c_str()); } config_setting config_setting::lookup(const std::string& name, bool visit) const { assert(is_group()); auto p = config_setting_get_member(h, name.c_str()); if (!p) throw_not_found(*this); return config_setting(p, visit); } config_setting config_setting::lookup(std::size_t indx, bool visit) const { assert(is_group() || is_array() || is_list()); auto p = config_setting_get_elem(h, indx); if (!p) throw_not_found(*this); return config_setting(p, visit); } config_setting config_setting::operator[](const std::string& name) const { return lookup(name, true); } config_setting config_setting::operator[](std::size_t indx) const { return lookup(indx, true); } bool config_setting::visited() const { return boost::algorithm::starts_with(path(), "root") || boost::algorithm::starts_with(path(), "root.version") || hook(h)->visited; }
我们将单独考虑使用钩子的助手:
void hook(config_setting_t* h, config_setting_hook* k) { config_setting_set_hook(h, k); } config_setting_hook* hook(config_setting_t* h) { return reinterpret_cast<config_setting_hook*>(config_setting_get_hook(h)); } void visit_up(config_setting_t* h) { for (; !config_setting_is_root(h) && !hook(h)->visited; h = config_setting_parent(h)) hook(h)->visited = true; }
还有一个绕过极端节点的助手:
template <typename F> void for_each(const config_setting& st, F f) { if (st.size()) for (std::size_t i = 0; i < st.size(); ++i) for_each(st.lookup(i), f); else f(st); }
结论
在我们看来,结果证明这是一种美观且更灵活的代码。 但是,我们并没有放弃对原始libconfig库(或以C ++编写的接口)进行类似更改的想法。 现在正在准备请求请求,但是我们已经在工作,并且正在从未使用的设置中清除配置。
应用程式
在此处查看源代码!