File konfigurasi. Perpustakaan Libconfig dan definisi pengaturan yang tidak digunakan

Pendahuluan


Aplikasi besar menggunakan konfigurasi untuk mentransfer pengaturan. Dan sering terjadi bahwa pengeditan dan penghapusan fitur mengarah ke sinkronisasi antara kode aplikasi dan apa yang disimpan dalam pengaturan yang sama. Sederhananya, dalam data terakhir yang tidak akan pernah Anda gunakan lagi puas. Pengaturan seperti itu, idealnya, saya ingin melacak dan menandai sebagai usang, atau sepenuhnya dihapus.


Masalah


Kebetulan sebagian besar konfigurasi di dalam proyek kami ditulis dalam campuran json dan yaml dan diuraikan menggunakan perpustakaan libconfig . Tidak ada keinginan untuk menulis ulang kode yang sesuai dan konten konfigurasi, misalnya, pada yaml, terutama ketika ada banyak tugas menarik dan lebih kompleks lainnya. Dan bagian dari pustaka yang ditulis dalam C itu bagus: itu stabil dan kaya fungsionalitas (dalam pembungkus C ++ semuanya tidak begitu sederhana).


Suatu hari, kami berhati-hati untuk mencari tahu berapa banyak sampah yang telah kami kumpulkan dalam file konfigurasi. Sayangnya, libconfig tidak memiliki opsi seperti itu. Pertama, kami mencoba fork proyek pada github dan membuat perubahan pada bagian yang ditulis dalam C ++ (semua jenis metode pencarian dan operator), mengotomatiskan proses pengaturan flag yang dikunjungi untuk node. Tapi ini akan mengarah ke tambalan yang sangat besar, adopsi yang mungkin akan tertunda. Dan kemudian pilihan jatuh ke arah penulisan pembungkus Anda sendiri di C ++, tanpa mempengaruhi inti libconfig.


Dari sudut pandang menggunakan output, kami memiliki yang berikut:


#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 

Dalam kode contoh, kami beralih ke pengaturan module.name. Module.submodules.0.name pengaturan dan module.submodules.1.name tidak diakses. Inilah yang dilaporkan kepada kami di log.


Bungkus


Bagaimana menerapkan ini jika flag yang dikunjungi atau sesuatu seperti itu tidak ada di dalam libconfig? Pengembang perpustakaan berpikir sebelumnya dan menambahkan kemampuan untuk menghubungkan hook ke simpul config_setting_t, yang diatur menggunakan fungsi config_setting_set_hook dan membaca menggunakan config_setting_get_hook.


Definisikan kait ini sebagai:


 struct config_setting_hook { bool visited{false}; }; 

Ada dua struktur utama di dalam libconfig: config_t dan config_setting_t. Yang pertama memberikan akses ke seluruh konfigurasi sebagai keseluruhan dan mengembalikan pointer ke simpul root config_setting_t, yang kedua - akses ke simpul induk dan anak, serta nilai di dalam simpul saat ini.


Kami membungkus kedua struktur di kelas yang sesuai - pegangan.


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

Perhatikan bahwa suatu fungsi diteruskan ke konstruktor konfigurasi, yang akan dipanggil dalam destruktor pada saat traversal semua node ekstrim. Cara penggunaannya bisa dilihat pada contoh di atas.


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

Sihir utama terletak pada metode pencarian. Diasumsikan bahwa flag node yang dikunjungi diatur melalui argumen terakhir yang disebut kunjungan, yang secara default salah. Anda memiliki hak untuk menunjukkan nilai ini sendiri. Tetapi karena akses yang paling sering ke node masih melalui operator [], di dalamnya metode pencarian dipanggil dengan kunjungan sama dengan true. Dengan demikian, node yang Anda panggil operator [] akan secara otomatis ditandai sebagai dikunjungi. Selain itu, seperti yang dikunjungi, seluruh rantai node dari arus ke root akan ditandai.


Mari beralih ke implementasi. Kami menunjukkannya sepenuhnya untuk kelas konfigurasi:


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

Dan sebagian untuk 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; } 

Kami akan secara terpisah mempertimbangkan pembantu untuk bekerja dengan hook:


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

Dan penolong untuk memotong simpul ekstrem:


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

Kesimpulan


Ternyata kode itu indah dan lebih fleksibel. Tapi kami tidak meninggalkan ide untuk membuat perubahan yang mirip dengan libconfig library asli, atau lebih tepatnya, antarmuka yang ditulis dalam C ++. Permintaan penarikan sedang dipersiapkan sekarang, tetapi kami sudah bekerja dan kami sedang membersihkan konfigurasi kami dari pengaturan yang tidak digunakan.


Aplikasi


Lihat kode sumber di sini !

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


All Articles