Archivos de configuración. Biblioteca Libconfig y definición de configuraciones no utilizadas

Introduccion


Las aplicaciones grandes usan configuraciones para transferir configuraciones. Y a menudo sucede que la edición y eliminación de características lleva a una desincronización entre el código de la aplicación y lo que está almacenado en esta misma configuración. Simplemente, en los últimos datos que nunca volverá a utilizar se liquidan. Dicha configuración, idealmente, me gustaría rastrear y marcar como obsoleta, o eliminar por completo.


El problema


Dio la casualidad de que la mayoría de las configuraciones dentro de nuestro proyecto se escribieron en una mezcla de json y yaml y se analizaron utilizando la biblioteca libconfig . No había ningún deseo de reescribir el código correspondiente y el contenido de las configuraciones, por ejemplo, en yaml, especialmente cuando hay un montón de otras tareas interesantes y más complejas. Y la parte de la biblioteca que está escrita en C es buena en sí misma: es estable y rica en funcionalidad (en el contenedor en C ++ no todo es tan simple).


Un buen día, nos ocupamos de averiguar cuánta basura habíamos acumulado en los archivos de configuración. Desafortunadamente, libconfig no tenía esa opción. Primero, intentamos bifurcar el proyecto en github y hacer cambios en la parte que estaba escrita en C ++ (todo tipo de métodos de búsqueda y operador []), automatizando el proceso de configuración del indicador visitado para el nodo. Pero esto llevaría a un parche muy grande, cuya adopción probablemente se retrasaría. Y luego la elección cayó en la dirección de escribir su propio contenedor en C ++, sin afectar el núcleo de libconfig.


Desde el punto de vista del uso de resultados, tenemos lo siguiente:


#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 

En el código de ejemplo, pasamos a configurar module.name. No se accedió a la configuración module.submodules.0.name y module.submodules.1.name. Esto es lo que se nos informa en el registro.


Envolver


¿Cómo implementar esto si la bandera visitada o algo así no está dentro de libconfig? Los desarrolladores de la biblioteca pensaron de antemano y agregaron la capacidad de enganchar un gancho al nodo config_setting_t, que se configura usando la función config_setting_set_hook y se lee usando config_setting_get_hook.


Defina este gancho como:


 struct config_setting_hook { bool visited{false}; }; 

Hay dos estructuras principales dentro de libconfig: config_t y config_setting_t. El primero proporciona acceso a toda la configuración como un todo y devuelve un puntero al nodo raíz config_setting_t, el segundo: acceso a los nodos padre e hijo, así como al valor dentro del nodo actual.


Envolvemos ambas estructuras en las clases correspondientes: manijas.


Manejar alrededor de 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; }; 

Tenga en cuenta que se pasa una función al constructor de configuración que se llamará en el destructor en el momento del recorrido de todos los nodos extremos. Cómo se puede usar se puede ver en el ejemplo anterior.


Manejar alrededor de 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 magia principal radica en los métodos de búsqueda. Se supone que el indicador de nodos visitados se establece a través del último argumento llamado visita, que es falso de forma predeterminada. Tiene derecho a indicar este valor usted mismo. Pero dado que el acceso más frecuente a los nodos todavía es a través del operador [], dentro de él, el método de búsqueda se llama con visita igual a verdadero. Por lo tanto, los nodos para los que llama al operador [] se marcarán automáticamente como visitados. Además, según lo visitado, se marcará toda la cadena de nodos desde la corriente hasta la raíz.


Pasemos a la implementación. Lo mostramos completamente para la clase 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)); } 

Y parcialmente para 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; } 

Consideraremos por separado ayudantes para trabajar con un gancho:


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

Y un ayudante para evitar nodos extremos:


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

Conclusión


Resultó ser un código hermoso y más flexible, en nuestra opinión,. Pero no abandonamos la idea de hacer cambios similares a la biblioteca libconfig original, o más bien, a su interfaz escrita en C ++. Ahora se está preparando una solicitud de extracción, pero ya estamos trabajando y estamos limpiando nuestras configuraciones de configuraciones no utilizadas.


App


¡Mira el código fuente aquí !

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


All Articles