Я пытаюсь получить boost :: program_options для чтения INI-файла с несколькими разделами:
[slave]
address=localhost
port=1111
[slave]
address=192.168.0.1
port=2222
Есть ли решение?
Заранее спасибо!
Есть несколько решений этой проблемы. Хотя на первый взгляд может показаться, что это должно быть легкой задачей, это часто довольно сложно. Это потому, что разделы примерно эквивалентны пространствам имен; разделы не эквивалентны объектам.
[Ведомый] Адрес = локальный Порт = 1111 [Ведомый] адрес = 192.168.0.1 Порт = 2222
Вышеуказанная конфигурация имеет один slave
пространство имен, которое содержит два address
значения и два port
ценности. Нет двух slave
объекты, каждый из которых имеет address
а также port
, Из-за этого различия в коде приложения должны выполняться ассоциированные значения или спаривание. Это представляет следующие варианты:
При таком подходе файл конфигурации может оставаться как есть. Простота этого подхода зависит от:
[Ведомый] address = localhost # slave.address [0] port = 1111 # slave.port [0] [Ведомый] address = 192.168.0.1 # slave.address [1] port = 2222 # slave.port [1]
Без изменения конфигурации, следующий код:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/program_options.hpp>
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const std::string& address,
unsigned short port )
{
return slave( address, port );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > addresses;
std::vector< unsigned short > ports;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slave.address", make_value( &addresses ),
"slave's hostname or ip address" )
( "slave.port" , make_value( &ports ),
"plugin id" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Transform each address and port pair into a slave via make_slave,
// inserting each object into the slaves vector.
std::vector< slave > slaves;
std::transform( addresses.begin(), addresses.end(),
ports.begin(),
std::back_inserter( slaves ),
make_slave );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
Производит этот вывод:
Адрес ведомого: localhost, порт: 1111 Адрес ведомого: 192.168.0.1, порт: 2222
Несколько значений могут быть иногда представлены в одном поле значимым образом. Одно общее представление обоих address
а также port
является address:port
, При таком сопряжении результирующий файл конфигурации будет выглядеть так:
[рабы] раб = локальный: 1111 раб = 192.168.0.1: 2222
Эта простота этого подхода зависит от:
Обновленный код:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const std::string& address_and_port )
{
// Tokenize the string on the ":" delimiter.
std::vector< std::string > tokens;
boost::split( tokens, address_and_port, boost::is_any_of( ":" ) );
// If the split did not result in exactly 2 tokens, then the value
// is formatted wrong.
if ( 2 != tokens.size() )
{
using boost::program_options::validation_error;
throw validation_error( validation_error::invalid_option_value,
"slaves.slave",
address_and_port );
}
// Create a slave from the token values.
return slave( tokens[0],
boost::lexical_cast< unsigned short >( tokens[1] ) );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > slave_configs;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slaves.slave", make_value( &slave_configs ),
"slave's address@port" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Transform each config into a slave via make_slave, inserting each
// object into the slaves vector.
std::vector< slave > slaves;
std::transform( slave_configs.begin(), slave_configs.end(),
std::back_inserter( slaves ),
make_slave );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
Выдает тот же результат:
Адрес ведомого: localhost, порт: 1111 Адрес ведомого: 192.168.0.1, порт: 2222
И заметные модификации кода следующие:
options_description
«s options
нужно читать slaves.slave
как std::vector< std::string >
,make_slave
займет один std::string
аргумент, из которого он будет извлекать address
а также port
,std::transform
вызовите только итерации по одному диапазону.Часто несколько полей не могут быть осмысленно представлены как одно значение без ключа, или объект имеет необязательные поля. В этих случаях необходим дополнительный уровень синтаксиса и синтаксического анализа. Хотя приложения могут вводить свой собственный синтаксис и синтаксические анализаторы, я предлагаю использовать синтаксис командной строки Boost.ProgramOption (--key value
а также --key=value
) и парсеры. Полученный файл конфигурации может выглядеть так:
[рабы] slave = --адрес localhost --port 1111 slave = --адрес = 192.168.0.1 --port = 2222
Обновленный код:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/bind.hpp>
#include <boost/program_options.hpp>
#include <boost/tokenizer.hpp>
// copy_if was accidently left out of the C++03 standard, so mimic the
// C++11 behavior to support all predicate types. The alternative is to
// use remove_copy_if, but it only works for adaptable functors.
template < typename InputIterator,
typename OutputIterator,
typename Predicate >
OutputIterator
copy_if( InputIterator first,
InputIterator last,
OutputIterator result,
Predicate pred )
{
while( first != last )
{
if( pred( *first ) )
*result++ = *first;
++first;
}
return result;
}
/// @brief Tokenize a string. The tokens will be separated by each non-quoted
/// character in @c separator_characters. Empty tokens are removed.
///
/// @param input The string to tokenize.
/// @param separator_characters The characters on which to delimit.
///
/// @return Vector of tokens.
std::vector< std::string > tokenize( const std::string& input,
const std::string& separator_characters )
{
typedef boost::escaped_list_separator< char > separator_type;
separator_type separator( "\\", // The escape characters.
separator_characters,
"\"\'" ); // The quote characters.
// Tokenize the intput.
boost::tokenizer< separator_type > tokens( input, separator );
// Copy non-empty tokens from the tokenizer into the result.
std::vector< std::string > result;
copy_if( tokens.begin(), tokens.end(), std::back_inserter( result ),
!boost::bind( &std::string::empty, _1 ) );
return result;
}
/// @brief option_builder provides a unary operator that can be used within
/// stl::algorithms.
template < typename ResultType,
typename Builder >
class option_builder
{
public:
typedef ResultType result_type;
public:
/// @brief Constructor
option_builder( const boost::program_options::options_description& options,
Builder builder )
: options_( options ),
builder_( builder )
{}
/// @brief Unary operator that will parse @c value, then delegate the
/// construction of @c result_type to the builder.
template < typename T >
result_type operator()( const T& value )
{
// Tokenize the value so that the command line parser can be used.
std::vector< std::string > tokens = tokenize( value, "= " );
// Parse the tokens.
namespace po = boost::program_options;
po::variables_map vm;
po::store( po::command_line_parser( tokens ).options( options_ ).run(),
vm );
po::notify( vm );
// Delegate object construction to the builder.
return builder_( vm );
}
private:
const boost::program_options::options_description& options_;
Builder builder_;
};
/// @brief Convenience function used to create option_builder types.
template < typename T,
typename Builder >
option_builder< T, Builder > make_option_builder(
const boost::program_options::options_description& options,
Builder builder )
{
return option_builder< T, Builder >( options, builder );
}
/// @brief Convenience function for when a 'store_to' value is being provided
/// to typed_value.
///
/// @param store_to The variable that will hold the parsed value upon notify.
///
/// @return Pointer to a type_value.
template < typename T >
boost::program_options::typed_value< T >* make_value( T* store_to )
{
return boost::program_options::value< T >( store_to );
}
/// @brief Slave type that contains an address and port.
struct slave
{
std::string address;
unsigned short port;
/// @brief Constructor.
slave( std::string address,
unsigned short port )
: address( address ),
port( port )
{}
};
/// @brief Stream insertion operator for slave.
///
/// @param stream The stream into which slave is being inserted.
/// @param s The slave object.
///
/// @return Reference to the ostream.
std::ostream& operator<<( std::ostream& stream,
const slave& slave )
{
return stream << "Slave address: " << slave.address
<< ", port: " << slave.port;
}
/// @brief Makes a slave given an address and port.
slave make_slave( const boost::program_options::variables_map& vm )
{
// Create a slave from the variable map.
return slave( vm["address"].as< std::string >(),
vm["port"].as< unsigned short >() );
}
int main()
{
// Variables that will store parsed values.
std::vector< std::string > slave_configs;
// Setup options.
namespace po = boost::program_options;
po::options_description desc( "Options" );
desc.add_options()
( "slaves.slave", make_value( &slave_configs ),
"slave's --address ip/hostname --port num" );
// Load setting file.
po::variables_map vm;
std::ifstream settings_file( "config.ini", std::ifstream::in );
po::store( po::parse_config_file( settings_file , desc ), vm );
settings_file.close();
po::notify( vm );
// Create options for slaves.slave.
po::options_description slave_desc( "Slave Options" );
slave_desc.add_options()
( "address", po::value< std::string >(),
"slave's hostname or ip address" )
( "port" , po::value< unsigned short >(),
"slave's port" );
// Transform each config into a slave via creating an option_builder that
// will use the slave_desc and make_slave to create slave objects. These
// objects will be inserted into the slaves vector.
std::vector< slave > slaves;
std::transform( slave_configs.begin(), slave_configs.end(),
std::back_inserter( slaves ),
make_option_builder< slave >( slave_desc, make_slave ) );
// Print slaves.
std::copy( slaves.begin(), slaves.end(),
std::ostream_iterator< slave >( std::cout, "\n" ) );
}
Создает тот же результат, что и предыдущие подходы:
Адрес ведомого: localhost, порт: 1111 Адрес ведомого: 192.168.0.1, порт: 2222
И заметные модификации кода следующие:
copy_if
так как это был забытый алгоритм в C ++ 03.option_builder
унарный функтор, чтобы помочь обеспечить идиоматическое повторное использование для применения преобразований.make_slave
сейчас занимает boost::program_options::variables_map
из которого он будет строить slave
объект.Этот подход также может быть легко расширен для поддержки следующих вариантов:
Поддержка нескольких командных строк для одного значения. Например, конфигурация может поддерживать двух подчиненных, причем один из подчиненных имеет вторичную конфигурацию в случае сбоя первого. Это требует выполнения начальной токенизации на ,
разделитель.
[рабы] slave = --адрес localhost --port 1111, --адрес 127.0.0.1 --port 1112 slave = - адрес 192.168.0.1 - порт 2222
Объявление вариантов для slave_desc
как typed_value
с переменными, предоставленными store_to
аргумент. Эти же переменные могут быть связаны с boost::ref
с помощью boost::bind
к make_slave
заводская функция. В то время как это разъединяет make_slave
из типов Boost.ProgramOptions может быть сложно поддерживать типы с множеством полей.
Альтернативные подходы все еще нуждаются в явном сопряжении путем помещения нескольких значений в одно значение. Однако преобразования могут происходить на этапе синтаксического анализа, наследуя от boost::program_options::typed_value
или же boost::program_options::untyped_value
,
typed_value
переопределить parse
функция. Одним из последствий использования typed_value
является то, что параметр шаблона должен соответствовать всем требованиям для typed_value
, Например, если typed_value< slave >
был использован, то это потребовало бы slave
конструируемый по умолчанию и определяющий оба istream
извлечение (>>
) а также ostream
вставка (<<
) операторы для slave
,untyped_value
Отменить оба parse
а также notify
функции. Этот подход не навязывает требования типа, такие как typed_value
, но он требует, чтобы производный класс сохранял свой собственный store_to
переменная.Других решений пока нет …