Fichiers de configuration. Bibliothèque Libconfig et définition des paramètres inutilisés

Présentation


Les grandes applications utilisent des configurations pour transférer les paramètres. Et il arrive souvent que la modification et la suppression de fonctionnalités entraînent une désynchronisation entre le code d'application et ce qui est stocké dans ces mêmes paramètres. Simplement, dans les dernières données que vous n'utiliserez plus jamais s'installer. De tels paramètres, idéalement, je voudrais suivre et marquer comme obsolète, ou supprimer complètement.


Le problème


Il se trouve que la plupart des configurations de notre projet ont été écrites dans un mélange de json et de yaml et analysées à l'aide de la bibliothèque libconfig . Il n'y avait aucun désir de réécrire le code correspondant et le contenu des configurations, par exemple, sur yaml, surtout quand il y a un tas d'autres tâches intéressantes et plus complexes. Et la partie de la bibliothèque qui est écrite en C est bonne en soi: elle est stable et riche en fonctionnalités (dans le wrapper en C ++ tout n'est pas si simple).


Un beau jour, nous avons pris soin de savoir combien de déchets nous avions accumulés dans les fichiers de configuration. Malheureusement, libconfig n'avait pas une telle option. Tout d'abord, nous avons essayé de bifurquer le projet sur github et d'apporter des modifications à la partie écrite en C ++ (toutes sortes de méthodes de recherche et d'opérateur []), automatisant le processus de définition de l'indicateur visité pour le nœud. Mais cela conduirait à un patch très important, dont l'adoption serait probablement retardée. Et puis le choix s'est porté sur l'écriture de votre propre wrapper en C ++, sans affecter le coeur de libconfig.


Du point de vue de l'utilisation de la sortie, nous avons les éléments suivants:


#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 

Dans l'exemple de code, nous nous sommes tournés vers la définition de module.name. Les paramètres module.submodules.0.name et module.submodules.1.name n'ont pas été accessibles. C'est ce qui nous est rapporté dans le journal.


Envelopper


Comment l'implémenter si le drapeau visité ou quelque chose comme ça n'est pas dans libconfig? Les développeurs de la bibliothèque ont pensé à l'avance et ont ajouté la possibilité de raccorder un crochet au nœud config_setting_t, qui est défini à l'aide de la fonction config_setting_set_hook et lu à l'aide de config_setting_get_hook.


Définissez ce crochet comme:


 struct config_setting_hook { bool visited{false}; }; 

Il existe deux structures principales à l'intérieur de libconfig: config_t et config_setting_t. Le premier donne accès à l'ensemble de la configuration dans son ensemble et renvoie un pointeur vers le nœud racine config_setting_t, le second - l'accès aux nœuds parent et enfant, ainsi que la valeur à l'intérieur du nœud actuel.


Nous enveloppons les deux structures dans les classes correspondantes - les poignées.


Traitez 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; }; 

Notez qu'une fonction est passée au constructeur de configuration qui sera appelé dans le destructeur au moment de la traversée de tous les nœuds extrêmes. La façon dont il peut être utilisé peut être vue dans l'exemple ci-dessus.


Traitez 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; }; 

La magie principale réside dans les méthodes de recherche. Il est supposé que l'indicateur des nœuds visités est défini via le dernier argument appelé visite, qui est faux par défaut. Vous avez le droit d'indiquer cette valeur vous-même. Mais comme l'accès le plus fréquent aux nœuds se fait toujours via l'opérateur [], à l'intérieur de celui-ci, la méthode de recherche est appelée avec une visite égale à true. Ainsi, les nœuds pour lesquels vous appelez l'opérateur [] seront automatiquement marqués comme visités. De plus, comme visité, la chaîne entière de nœuds du courant à la racine sera marquée.


Passons à la mise en œuvre. Nous le montrons complètement pour la classe 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)); } 

Et en partie pour 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; } 

Nous considérerons séparément les aides pour travailler avec un crochet:


 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; } 

Et un assistant pour contourner les nœuds extrêmes:


 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); } 

Conclusion


Il s'est avéré être un code beau et plus flexible, à notre avis. Mais nous n'avons pas abandonné l'idée d'apporter des modifications similaires à la bibliothèque libconfig d'origine, ou plutôt à son interface écrite en C ++. Une demande d'extraction est en cours de préparation, mais nous travaillons déjà et nous nettoyons nos configurations des paramètres inutilisés.


App


Découvrez le code source ici !

Source: https://habr.com/ru/post/fr477454/


All Articles