Введение
Как-то находясь в поиске как мне прикрутить конфигурационные ini файлы или json к моему сервачку перебирал варианты, но почему-то они были неудобны или слишком простые, или велосипеды. И хоть я люблю xml конфигурирование, но порою это чрезмерно огромные файлы и неудобно для небольшого количества настроек писать много текста. Раз задал другу вопрос по этой теме, он то мне и подкинул библиотеку. Напоминает она json в смеси с yaml.
![](https://habrastorage.org/storage2/aad/c7f/bb3/aadc7fbb3175b80d7b26887915c03fa4.png)
Библиотека имеет два интерфейса: функциональный и объектный. Они очень похожи, так как объектный использует внутри функциональную реализацию, но имеют некоторые различия, рассмотренные в данном посте.
Настройка и подключение
Конфигурационный фаил
Использование в С
Использование в С++
Настройка и подключение
Библиотека есть во многих репозиториях, поэтому установка простая:
$ sudo aptitude install libconfig8 libconfig8-dev libconfig++ libconfig++-dev
В исходниках С++ подключается одним лишь инклудом:
#include <libconfig.h++>
или
#include <libconfig.hh>
или для С
#include <libconfig.h>
Конфигурационный файл
Файл конфига представляет собой следующего вида структуру:
# Example application configuration file
version = "1.0";
application:
{
window:
{
title = "My Application";
size = { w = 640; h = 480; };
pos = { x = 350; y = 250; };
};
list = ( ( "abc", 123, true ), 1.234, ( /* an empty list */) );
books = ( { title = "Treasure Island";
author = "Robert Louis Stevenson";
price = 29.95;
qty = 5; },
{ title = "Snow Crash";
author = "Neal Stephenson";
price = 9.99;
qty = 8; } );
misc:
{
pi = 3.141592654;
bigint = 9223372036854775807L;
columns = [ "Last Name", "First Name", "MI" ];
bitmask = 0x1FC3;
};
};
Основными видами записей в конфиге являются такие типы:
Элемент (Setting)Это минимальная значимая часть конфигурационной структуры и имеют вид ключ-значение:
name = value;
или
name : value
Группы (Groups)
Группы могут содержать любое число элементов, но каждый элемент должен содержать уникальный ключ в пределах группы. Записывается в фигурных скобках:
{ settings... }
Массивы (Arrays)
Содержат любое количество элементов, даже ноль, но все элементы состоят лишь из значений и должны иметь один и тот же скалярный тип в пределах массива. Записывает в квадратных скобках:
[ value, value ... ]
Списки (Lists)
Списки содержат ноль или более элементов скалярного типа, массивов, групп или списков. Записывается в круглых скобках:
( value, value ... )
Целочисленые значения (Integers)
Записываются обычным нам десятичным способом (±0-9) или шестнадцатиричном виде (0xA-f). Но целочисленные значения ограничены диапазоном -2147483648..2147483647 (32bit), но если нужны большие диапазоны, то в конце добавляется ’L’.
3578934
897893450934L
Дробные числа с плавающей запятой (floats)
Записывается тоже привычным нам способом
3.1415
Запись с экспонентой стандартная с 'e'.
Булевые значения (Boolean)
Значения записываются как ’true’ или ’false’ и регистронезависимо (без кавычек, конечно).
Строки (Strings)
Записываются в двойных кавычках как "Обычная длинная строка записанная для примера"
.
Следующие варианты в итоге дадут то же значение строки:
"Обычная длинная строка"
"записанная для примера"
"Обычная длинная строка" /* комментарий */ " записанная " // комментарий
"для примера"
.
Комментарии
В конфиге возможны три привычных в С++ вида:
- # однострочный до конца строки
- // тоже однострочный до конца строки
- /*… */ многострочный комментарий включая переносы строк
Внешние подключения (Includes)
Это, в общем, самая вкусная вкусняшка.
# file: quote.cfg
quote = "Criticism may not be agreeable, but it is necessary."
" It fulfils the same function as pain in the human"
" body. It calls attention to an unhealthy state of"
" things.\n"
"\t--Winston Churchill";
# file: test.cfg
info: {
name = "Winston Churchill";
@include "quote.cfg"
country = "UK";
};
С API
В данной части я не стану расписывать все функции, только лишь основные, так как они в целом похожие, и основные нюансы.
Описание использованых функций ниже
#include <stdio.h>
#include <stdlib.h>
#include <libconfig.h>
/* Данный пример читает конфигурационный файл 'example.cfg' и выводит его составляющие
*/
int main(int argc, char **argv)
{
/* используются свои типы. */
config_t cfg;
config_setting_t *setting;
const char *str;
config_init(&cfg); /* обязательная инициализация */
/* Читаем файл. Если ошибка, то завершаем работу */
if(! config_read_file(&cfg, "example.cfg"))
{
fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return(EXIT_FAILURE);
}
/* Поиск некого значения "name". */
if(config_lookup_string(&cfg, "name", &str))
printf("Store name: %s\n\n", str);
else
fprintf(stderr, "No 'name' setting in configuration file.\n");
/* Вывод списка книжек с полок. */
setting = config_lookup(&cfg, "inventory.books");
if(setting != NULL)
{
int count = config_setting_length(setting);
int i;
printf("%-30s %-30s %-6s %s\n", "TITLE", "AUTHOR", "PRICE", "QTY");
for(i = 0; i < count; ++i)
{
config_setting_t *book = config_setting_get_elem(setting, i);
/* Выводим только те записи, если они имеют все нужные поля. */
const char *title, *author;
double price;
int qty;
if(!(config_setting_lookup_string(book, "title", &title)
&& config_setting_lookup_string(book, "author", &author)
&& config_setting_lookup_float(book, "price", &price)
&& config_setting_lookup_int(book, "qty", &qty)))
continue;
printf("%-30s %-30s $%6.2f %3d\n", title, author, price, qty);
}
putchar('\n');
}
/* Вывод всех фильмов с полки. */
setting = config_lookup(&cfg, "inventory.movies");
if(setting != NULL)
{
unsigned int count = config_setting_length(setting);
unsigned int i;
printf("%-30s %-10s %-6s %s\n", "TITLE", "MEDIA", "PRICE", "QTY");
for(i = 0; i < count; ++i)
{
config_setting_t *movie = config_setting_get_elem(setting, i);
/* Вывод только тех медиа, у которых заполнены все поля. */
const char *title, *media;
double price;
int qty;
if(!(config_setting_lookup_string(movie, "title", &title)
&& config_setting_lookup_string(movie, "media", &media)
&& config_setting_lookup_float(movie, "price", &price)
&& config_setting_lookup_int(movie, "qty", &qty)))
continue;
printf("%-30s %-10s $%6.2f %3d\n", title, media, price, qty);
}
putchar('\n');
}
config_destroy(&cfg); /* Освободить память обязательно, если это не конец программы */
return(EXIT_SUCCESS);
}
Небольшое описание функционала
Полное описание в документации.
config_t — тип файла конфигурации (это ещё не запись). Грубо говоря, основной контейнер.
config_setting_t — объект элемента конфигурации. В примере используется указатель, возвращаемый контейнером на искомый элемент.
int config_read_file(config_t * config, const char * filename) — функция читает конфигурационный файл filename в память и заполняет объект типа config_t. Можно не читать из файла, а сразу «скормить» строку в config_read_string() или отдать дескриптор файла в config_read()
int config_lookup_string (const config_t * config, const char * path, const char ** value) — ищет и возвращает значение в виде указателя на строку value, по заданному пути path внутри конфига config.
config_setting_t * config_lookup (const config_t * config, const char * path) — ищет запись внутри конфига по заданному внутреннему пути и возвращает её.
config_setting_t * config_setting_get_elem (const config_setting_t * setting, unsigned int index) — используется для массивов, списков чтобы возвращать из него элементы с таким-то номером по порядку
int config_setting_lookup_string (const config_setting_t * setting, const char * name, const char ** value) —
возвращает значение value дочернего элемента name относительно заданной записи setting
Когда же надо получить значение в конкретно заданной записи, то используются функции типа
int config_setting_get_int (const config_setting_t * setting)
C++ API
Тот же пример, но на С++. Полная документация на сайте
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <libconfig.h++>
using namespace std;
using namespace libconfig;
// Пример, читающий конфигурационный файл 'example.cfg' и выводит его записи
int main(int argc, char **argv)
{
Config cfg;
// Прочитать файл. Или выйти с ошибкой
// Класс в С++ не возвращает ошибку, а кидает исключение
try
{
cfg.readFile("example.cfg");
}
catch(const FileIOException &fioex)
{
std::cerr << "I/O error while reading file." << std::endl;
return(EXIT_FAILURE);
}
catch(const ParseException &pex)
{
std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine()
<< " - " << pex.getError() << std::endl;
return(EXIT_FAILURE);
}
// Получить некое название.
try
{
string name = cfg.lookup("name");
cout << "Store name: " << name << endl << endl;
}
catch(const SettingNotFoundException &nfex)
{
cerr << "No 'name' setting in configuration file." << endl;
}
const Setting& root = cfg.getRoot();
// Найти все книжки на полке.
try
{
const Setting &books = root["inventory"]["books"];
int count = books.getLength();
cout << setw(30) << left << "TITLE" << " "
<< setw(30) << left << "AUTHOR" << " "
<< setw(6) << left << "PRICE" << " "
<< "QTY"
<< endl;
for(int i = 0; i < count; ++i)
{
const Setting &book = books[i];
// Находим только те записи, что имеют все заполненные поля.
string title, author;
double price;
int qty;
if(!(book.lookupValue("title", title)
&& book.lookupValue("author", author)
&& book.lookupValue("price", price)
&& book.lookupValue("qty", qty)))
continue;
cout << setw(30) << left << title << " "
<< setw(30) << left << author << " "
<< '$' << setw(6) << right << price << " "
<< qty
<< endl;
}
cout << endl;
}
catch(const SettingNotFoundException &nfex)
{
// Ignore.
}
// Вывод всех фильмов с полки.
try
{
const Setting &movies = root["inventory"]["movies"];
int count = movies.getLength();
cout << setw(30) << left << "TITLE" << " "
<< setw(10) << left << "MEDIA" << " "
<< setw(6) << left << "PRICE" << " "
<< "QTY"
<< endl;
for(int i = 0; i < count; ++i)
{
const Setting &movie = movies[i];
// Вывод только тех, что содержат все поля.
string title, media;
double price;
int qty;
if(!(movie.lookupValue("title", title)
&& movie.lookupValue("media", media)
&& movie.lookupValue("price", price)
&& movie.lookupValue("qty", qty)))
continue;
cout << setw(30) << left << title << " "
<< setw(10) << left << media << " "
<< '$' << setw(6) << right << price << " "
<< qty
<< endl;
}
cout << endl;
}
catch(const SettingNotFoundException &nfex)
{
// Ignore.
}
return(EXIT_SUCCESS);
}
Тут тот же принцип, что и в функциональном стиле, только перед получением данных из конфига необходимо получать корневой элемент cfg.getRoot(); и уже потом от него обращаться к остальным элементам. Так же надо быть внимательным к тому, что практически на все ошибки кидаются исключения
Заключение
Кроме считывания удобных конфигов, в API предоставлен так же функционал создания элементов конфига и его записи на носитель.
Всё в документации [en] на сайте библиотеки.
Примеры идут вместе с исходниками. Их можно скачать следующей командой в консоли:
$ apt-get source libconfig++
или же из исходников по прямой ссылке.
Документация представлена в форматах HTML и PDF.
Лицензия GNU LGPL.
name = value;
или
name : value
Группы (Groups)
Группы могут содержать любое число элементов, но каждый элемент должен содержать уникальный ключ в пределах группы. Записывается в фигурных скобках:
{ settings... }
Массивы (Arrays)
Содержат любое количество элементов, даже ноль, но все элементы состоят лишь из значений и должны иметь один и тот же скалярный тип в пределах массива. Записывает в квадратных скобках:
[ value, value ... ]
Списки (Lists)
Списки содержат ноль или более элементов скалярного типа, массивов, групп или списков. Записывается в круглых скобках:
( value, value ... )
Целочисленые значения (Integers)
Записываются обычным нам десятичным способом (±0-9) или шестнадцатиричном виде (0xA-f). Но целочисленные значения ограничены диапазоном -2147483648..2147483647 (32bit), но если нужны большие диапазоны, то в конце добавляется ’L’.
3578934
897893450934L
Дробные числа с плавающей запятой (floats)
Записывается тоже привычным нам способом
3.1415
Запись с экспонентой стандартная с 'e'.
Булевые значения (Boolean)
Значения записываются как ’true’ или ’false’ и регистронезависимо (без кавычек, конечно).
Строки (Strings)
Записываются в двойных кавычках как
"Обычная длинная строка записанная для примера"
. Следующие варианты в итоге дадут то же значение строки:
"Обычная длинная строка"
"записанная для примера"
"Обычная длинная строка" /* комментарий */ " записанная " // комментарий
"для примера"
.Комментарии
В конфиге возможны три привычных в С++ вида:
- # однострочный до конца строки
- // тоже однострочный до конца строки
- /*… */ многострочный комментарий включая переносы строк
Внешние подключения (Includes)
Это, в общем, самая вкусная вкусняшка.
# file: quote.cfg
quote = "Criticism may not be agreeable, but it is necessary."
" It fulfils the same function as pain in the human"
" body. It calls attention to an unhealthy state of"
" things.\n"
"\t--Winston Churchill";
# file: test.cfg
info: {
name = "Winston Churchill";
@include "quote.cfg"
country = "UK";
};
С API
В данной части я не стану расписывать все функции, только лишь основные, так как они в целом похожие, и основные нюансы.
Описание использованых функций ниже
#include <stdio.h>
#include <stdlib.h>
#include <libconfig.h>
/* Данный пример читает конфигурационный файл 'example.cfg' и выводит его составляющие
*/
int main(int argc, char **argv)
{
/* используются свои типы. */
config_t cfg;
config_setting_t *setting;
const char *str;
config_init(&cfg); /* обязательная инициализация */
/* Читаем файл. Если ошибка, то завершаем работу */
if(! config_read_file(&cfg, "example.cfg"))
{
fprintf(stderr, "%s:%d - %s\n", config_error_file(&cfg),
config_error_line(&cfg), config_error_text(&cfg));
config_destroy(&cfg);
return(EXIT_FAILURE);
}
/* Поиск некого значения "name". */
if(config_lookup_string(&cfg, "name", &str))
printf("Store name: %s\n\n", str);
else
fprintf(stderr, "No 'name' setting in configuration file.\n");
/* Вывод списка книжек с полок. */
setting = config_lookup(&cfg, "inventory.books");
if(setting != NULL)
{
int count = config_setting_length(setting);
int i;
printf("%-30s %-30s %-6s %s\n", "TITLE", "AUTHOR", "PRICE", "QTY");
for(i = 0; i < count; ++i)
{
config_setting_t *book = config_setting_get_elem(setting, i);
/* Выводим только те записи, если они имеют все нужные поля. */
const char *title, *author;
double price;
int qty;
if(!(config_setting_lookup_string(book, "title", &title)
&& config_setting_lookup_string(book, "author", &author)
&& config_setting_lookup_float(book, "price", &price)
&& config_setting_lookup_int(book, "qty", &qty)))
continue;
printf("%-30s %-30s $%6.2f %3d\n", title, author, price, qty);
}
putchar('\n');
}
/* Вывод всех фильмов с полки. */
setting = config_lookup(&cfg, "inventory.movies");
if(setting != NULL)
{
unsigned int count = config_setting_length(setting);
unsigned int i;
printf("%-30s %-10s %-6s %s\n", "TITLE", "MEDIA", "PRICE", "QTY");
for(i = 0; i < count; ++i)
{
config_setting_t *movie = config_setting_get_elem(setting, i);
/* Вывод только тех медиа, у которых заполнены все поля. */
const char *title, *media;
double price;
int qty;
if(!(config_setting_lookup_string(movie, "title", &title)
&& config_setting_lookup_string(movie, "media", &media)
&& config_setting_lookup_float(movie, "price", &price)
&& config_setting_lookup_int(movie, "qty", &qty)))
continue;
printf("%-30s %-10s $%6.2f %3d\n", title, media, price, qty);
}
putchar('\n');
}
config_destroy(&cfg); /* Освободить память обязательно, если это не конец программы */
return(EXIT_SUCCESS);
}
Небольшое описание функционала
Полное описание в документации.
config_t — тип файла конфигурации (это ещё не запись). Грубо говоря, основной контейнер.
config_setting_t — объект элемента конфигурации. В примере используется указатель, возвращаемый контейнером на искомый элемент.
int config_read_file(config_t * config, const char * filename) — функция читает конфигурационный файл filename в память и заполняет объект типа config_t. Можно не читать из файла, а сразу «скормить» строку в config_read_string() или отдать дескриптор файла в config_read()
int config_lookup_string (const config_t * config, const char * path, const char ** value) — ищет и возвращает значение в виде указателя на строку value, по заданному пути path внутри конфига config.
config_setting_t * config_lookup (const config_t * config, const char * path) — ищет запись внутри конфига по заданному внутреннему пути и возвращает её.
config_setting_t * config_setting_get_elem (const config_setting_t * setting, unsigned int index) — используется для массивов, списков чтобы возвращать из него элементы с таким-то номером по порядку
int config_setting_lookup_string (const config_setting_t * setting, const char * name, const char ** value) —
возвращает значение value дочернего элемента name относительно заданной записи setting
Когда же надо получить значение в конкретно заданной записи, то используются функции типа
int config_setting_get_int (const config_setting_t * setting)
C++ API
Тот же пример, но на С++. Полная документация на сайте
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <libconfig.h++>
using namespace std;
using namespace libconfig;
// Пример, читающий конфигурационный файл 'example.cfg' и выводит его записи
int main(int argc, char **argv)
{
Config cfg;
// Прочитать файл. Или выйти с ошибкой
// Класс в С++ не возвращает ошибку, а кидает исключение
try
{
cfg.readFile("example.cfg");
}
catch(const FileIOException &fioex)
{
std::cerr << "I/O error while reading file." << std::endl;
return(EXIT_FAILURE);
}
catch(const ParseException &pex)
{
std::cerr << "Parse error at " << pex.getFile() << ":" << pex.getLine()
<< " - " << pex.getError() << std::endl;
return(EXIT_FAILURE);
}
// Получить некое название.
try
{
string name = cfg.lookup("name");
cout << "Store name: " << name << endl << endl;
}
catch(const SettingNotFoundException &nfex)
{
cerr << "No 'name' setting in configuration file." << endl;
}
const Setting& root = cfg.getRoot();
// Найти все книжки на полке.
try
{
const Setting &books = root["inventory"]["books"];
int count = books.getLength();
cout << setw(30) << left << "TITLE" << " "
<< setw(30) << left << "AUTHOR" << " "
<< setw(6) << left << "PRICE" << " "
<< "QTY"
<< endl;
for(int i = 0; i < count; ++i)
{
const Setting &book = books[i];
// Находим только те записи, что имеют все заполненные поля.
string title, author;
double price;
int qty;
if(!(book.lookupValue("title", title)
&& book.lookupValue("author", author)
&& book.lookupValue("price", price)
&& book.lookupValue("qty", qty)))
continue;
cout << setw(30) << left << title << " "
<< setw(30) << left << author << " "
<< '$' << setw(6) << right << price << " "
<< qty
<< endl;
}
cout << endl;
}
catch(const SettingNotFoundException &nfex)
{
// Ignore.
}
// Вывод всех фильмов с полки.
try
{
const Setting &movies = root["inventory"]["movies"];
int count = movies.getLength();
cout << setw(30) << left << "TITLE" << " "
<< setw(10) << left << "MEDIA" << " "
<< setw(6) << left << "PRICE" << " "
<< "QTY"
<< endl;
for(int i = 0; i < count; ++i)
{
const Setting &movie = movies[i];
// Вывод только тех, что содержат все поля.
string title, media;
double price;
int qty;
if(!(movie.lookupValue("title", title)
&& movie.lookupValue("media", media)
&& movie.lookupValue("price", price)
&& movie.lookupValue("qty", qty)))
continue;
cout << setw(30) << left << title << " "
<< setw(10) << left << media << " "
<< '$' << setw(6) << right << price << " "
<< qty
<< endl;
}
cout << endl;
}
catch(const SettingNotFoundException &nfex)
{
// Ignore.
}
return(EXIT_SUCCESS);
}
Тут тот же принцип, что и в функциональном стиле, только перед получением данных из конфига необходимо получать корневой элемент cfg.getRoot(); и уже потом от него обращаться к остальным элементам. Так же надо быть внимательным к тому, что практически на все ошибки кидаются исключения
Заключение
Кроме считывания удобных конфигов, в API предоставлен так же функционал создания элементов конфига и его записи на носитель.
Всё в документации [en] на сайте библиотеки.
Примеры идут вместе с исходниками. Их можно скачать следующей командой в консоли:
$ apt-get source libconfig++
или же из исходников по прямой ссылке.
Документация представлена в форматах HTML и PDF.
Лицензия GNU LGPL.