Конфигурационные файлы. Библиотека libconfig

в 13:42, , рубрики: c++, c++ библиотеки, конфигурационные файлы, метки: , ,

Введение

Как-то находясь в поиске как мне прикрутить конфигурационные ini файлы или json к моему сервачку перебирал варианты, но почему-то они были неудобны или слишком простые, или велосипеды. И хоть я люблю xml конфигурирование, но порою это чрезмерно огромные файлы и неудобно для небольшого количества настроек писать много текста. Раз задал другу вопрос по этой теме, он то мне и подкинул библиотеку. Напоминает она json в смеси с yaml.

Конфигурационные файлы. Библиотека libconfig

Библиотека имеет два интерфейса: функциональный и объектный. Они очень похожи, так как объектный использует внутри функциональную реализацию, но имеют некоторые различия, рассмотренные в данном посте.


Настройка и подключение
Конфигурационный фаил
Использование в С
Использование в С++

Настройка и подключение

Библиотека есть во многих репозиториях, поэтому установка простая:

$ 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 - %sn", 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: %snn", 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  %sn", "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  %3dn", 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  %sn", "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  %3dn", 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.

Автор: romy4

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js