Несколько повторяющихся разделов в файле конфигурации

У меня есть формат файла конфигурации, который я надеялся реализовать с помощью параметров программы Boost (поскольку я использовал эту библиотеку раньше), но мне каким-то образом нужно реализовать такие блоки:

label = whatever
depth = 3

start
source = /etc
dest = /tmp/etc/
end

start
source = /usr/local/include
dest = /tmp/include
depth = 1
end

Я прочитал в документации, что я может иметь [sections], поэтому я сначала задумался об этом:

label = whatever
depth = 3

[dir]
source = /etc
dest = /tmp/etc/

[dir]
source = /usr/local/include
dest = /tmp/include
depth = 1

Но если я правильно понял, dir становится частью имени переменной, поэтому дубликаты невозможны, и это не сработает. Тогда я задумался о переносе source на имя раздела:

label = whatever
depth = 3

[/etc]
dest = /tmp/etc/

[/usr/local/include]
dest = /tmp/include
depth = 1

Это кажется разумным подходом? Мне интересно, как я перебираю список разделов, когда я заранее не знаю названия разделов?

Или есть лучший способ использовать библиотеку параметров программы для достижения этой цели?


person Darren Cook    schedule 20.04.2014    source источник


Ответы (2)


Возможно, вам следует использовать Boost property_tree вместо program_options, поскольку формат вашего файла очень похож на формат файла INI Windows. Boost property_tree имеет парсер для INI. файлы (и сериализатор, если он вам тоже нужен).

Затем обработка ваших параметров будет осуществляться путем обхода дерева. Параметры, отсутствующие в разделе, будут находиться в корне дерева, а параметры раздела будут находиться в узле для этого раздела.

Вы можете использовать program_options, если хотите. Суть в том, чтобы передать true в качестве последнего аргумента parse_config_file, то есть allow_unregistered_options:

#include <iostream>
#include <sstream>
#include <boost/program_options.hpp>

static const std::string fileData = // sample input
   "foo=1\n"
   "[bar]\n"
   "foo=a distinct foo\n"
   "[/etc]\n"
   "baz=all\n"
   "baz=multiple\n"
   "baz=values\n"
   "a.baz=appear\n";

int main(int argc, char *argv[]) {
   namespace po = boost::program_options;

   std::istringstream is(fileData);
   po::parsed_options parsedOptions = po::parse_config_file(
      is,
      po::options_description(),
      true);                     // <== allow unregistered options

   // Print out results.
   for (const auto& option : parsedOptions.options) {
      std::cout << option.string_key << ':';

      // Option value is a vector of strings.
      for (const auto& value : option.value)
         std::cout << ' ' << value;
      std::cout << '\n';
   }

   return 0;
}

Это выводит:

$ ./po
foo: 1
bar.foo: a distinct foo
/etc.baz: all
/etc.baz: multiple
/etc.baz: values
/etc.baz: appear

Однако обратите внимание, что при таком подходе вы получаете вектор вариантов, а не карту, которую создает типичное использование program_options. Таким образом, вы можете в конечном итоге обработать контейнер parsed_options во что-то, что вам будет проще запрашивать, и это что-то может выглядеть как property_tree.

Вот аналогичная программа, использующая property_tree. Ввод немного отличается, потому что property_tree не допускает дублирования ключей.

#include <iostream>
#include <sstream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>

static const std::string fileData = // sample input                             
   "foo=1\n"
   "[bar]\n"
   "foo=a distinct foo\n"
   "[/etc]\n"
   "foo=and another\n"
   "baz=all\n";

static void print_recursive(
   const std::string& prefix,
   const boost::property_tree::ptree& ptree) {
   for (const auto& entry : ptree) {
      const std::string& key = entry.first;
      const boost::property_tree::ptree& value = entry.second;
      if (!value.data().empty())
         std::cout << prefix + key << ": " << value.data() << '\n';
      else
         print_recursive(prefix + key + '.', value);
   }
}

int main() {
   namespace pt = boost::property_tree;

   std::istringstream is(fileData);
   pt::ptree root;
   pt::read_ini(is, root);

   print_recursive("", root);
   return 0;
}
person rhashimoto    schedule 23.04.2014
comment
Спасибо, похоже, это сработает. Я просто опасаюсь использовать другую библиотеку, когда знаком с параметрами программы. То, что я пытаюсь сделать, невозможно с библиотекой параметров программы? (формат стиля INI, который я предложил в своем вопросе, был в основном потому, что параметры программы предлагают это ... хотя, конечно, он также удобен и читабелен для большинства пользователей) - person Darren Cook; 24.04.2014
comment
@DarrenCook, см. редактирование для анализа с помощью program_options. Однако это не то, как обычно используется program_options, поэтому он может отличаться от вашего предыдущего опыта работы с ним. - person rhashimoto; 25.04.2014
comment
Спасибо. Я подумал, что могу попробовать property_tree на вашем примере данных: namespace pt = boost::property_tree;pt::ptree settings;pt::read_ini(fileData,settings); Я получаю ошибку во время выполнения: терминация вызывается после создания экземпляра 'boost::exception_detail::clone_impl‹boost::exception_detail::error_info_injector‹boost::property_tree: :ini_parser::ini_parser_error› ›’ - person Darren Cook; 27.04.2014
comment
Недружественная ошибка (с тем, что для меня выглядит как допустимый ввод) - это позор, поскольку я согласен с вами, что код PropertyTree кажется более чистым для того, что я пытаюсь сделать. - person Darren Cook; 27.04.2014
comment
@DarrenCook Я думаю, что вы получили исключение только потому, что property_tree не допускает дублирования ключей, как это делает program_options, поэтому он информировал вас о синтаксической ошибке. Я добавил в свой ответ пример программы property_tree, которая делает примерно то же самое. На практике вам может не понадобиться просматривать дерево, а можно просто выполнить запрос с get() или get_optional() в корне. Кроме того, рекурсивный обход является излишним для файлов .ini, поскольку они допускают только два уровня (в отличие от JSON и XML, которые также поддерживает property_tree). - person rhashimoto; 27.04.2014
comment
Еще раз спасибо за отличную помощь! Оказывается, повторяющиеся ключи дают другое сообщение об ошибке: повторяющееся имя ключа. Ошибка, которую я получил, была связана с попыткой использовать string вместо istringstream: я неправильно прочитал документы, а версия строкового параметра read_ini() предназначена для имени файла! - person Darren Cook; 27.04.2014

Не знаю о библиотеке Boost Programs Options. Но формат файла INI имеет встроенную поддержку в Win32 API.

Чтобы прочитать все разделы в INI-файле, не зная их имен, вы можете использовать MSDN Win32 GetPrivateProfileSectionNames

Чтобы прочитать все ключи в разделе, вы можете использовать MSDN Win32 GetPrivateProfileSection

Говорят, что этот API предназначен только для совместимости, и в фоновом режиме он может сопоставлять ключи файла INI с реестром.

На практике это должно работать нормально (так было всегда). Вы даже можете иметь INI-файлы UNICODE, если они имеют метку BOM, и вы вызываете версии ..W API.

Пригодность формата файла конфигурации и его расположения (например, возможность хранения в реестре) зависит от ваших сценариев развертывания.

person xmojmr    schedule 24.04.2014