//! //! \file //! \author Andre Prins //! \date 7 April 2010 //! #include "Settings.h" #include #include #include #include #include "Global_Build_Config.h" #include using namespace Utilities; using namespace boost::program_options; // Only for saving a boost::any... std::ostream& operator<<( std::ostream& out, const boost::any& a ); // The key used in config-files to specify the testdatapath... static const std::string key_testdatapath = "[testdatapath]"; Settings::Settings( const std::string& name, const std::string& defaults ) : m_name( name ) , m_defaultOptions( defaults ) { // Always 'add' the initial-path this->m_searchPaths.push_back( boost::filesystem::initial_path< boost::filesystem::path >( ).string( ) ); #if MBFLUO_DEBUG this->m_searchPaths.push_back( global_testdatapath ); #endif this->m_defaultFileName = "default_"+name+".ini"; // We fill the command-line options. this->m_commandLineOptions.add_options( ) ( "help,h", " Produce this help message" ) ( "config", value< std::string >( ), " The config-file" ) ( "output-config", value< std::string >( ), " Output the current config to the file" ) ; } Settings::~Settings( ) { } void Settings::add( const boost::program_options::options_description& d ) { this->m_commandLineOptions.add( d ); this->m_configFileOptions.add( d ); } void Settings::addpath( const std::string& p ) { // Insert at the front. this->m_searchPaths.insert( this->m_searchPaths.begin( ), p ); } void Settings::clearpaths( ) { this->m_searchPaths.clear( ); } bool Settings::load( ) { for( std::vector< std::string >::const_iterator i = this->m_searchPaths.begin( ); i != this->m_searchPaths.end( ); i++ ) { std::string fullfilename = *i + "/" + this->m_defaultFileName; if ( boost::filesystem::exists( fullfilename ) ) { std::cerr << "Loading: " << fullfilename << std::endl; std::ifstream defaultin( fullfilename.c_str( ) ); return this->loadOptions( defaultin ); } } return false; } bool Settings::load( int argc, char* argv[] ) { this->m_optionsMap = boost::program_options::variables_map( ); // Reconstruct a second application-path, based on argv[0] this->m_searchPaths.push_back( boost::filesystem::absolute( boost::filesystem::path( argv[0] ) ).parent_path( ).string( ) ); // First check: If no arguments presented: load 'default_NAME.ini' bool success = false; if ( argc == 1 && boost::filesystem::exists( this->m_defaultFileName ) ) { std::ifstream defaultin( this->m_defaultFileName.c_str( ) ); success = this->loadOptions( defaultin ); } else { success = this->loadOptions( argc, argv ); } // When loaded from the command-line, deal with "help" and "output". if ( this->contains( "help" ) ) { this->help( std::cout ); success = false; } else if ( this->contains( "output-config" ) ) { std::string configfile = this->get< std::string >( "output-config" ); if ( configfile == "stdout" ) { this->save( std::cout ); } else if ( configfile == "stderr" ) { this->save( std::cerr ); } else { std::ofstream configout( configfile.c_str( ) ); this->save( configout ); } success = false; } return success; } bool Settings::load( std::istream &in ) { this->m_optionsMap = boost::program_options::variables_map( ); return this->loadOptions( in ); } bool Settings::load( const std::string& filename ) { bool success = false; this->m_optionsMap = boost::program_options::variables_map( ); if( boost::filesystem::exists( filename ) ) { std::ifstream configin( filename.c_str( ) ); success = this->loadOptions( configin ); } return success; } void Settings::save( std::ostream& out ) { // Start with a generic comment-section out << "# Configuration options for " << this->m_name << std::endl; out << "# to generate this options-file, run " << this->m_name << " --output-config ." << std::endl; out << "# use 'stdout' or 'stderr' as the output for standard output and error." << std::endl; out << "# use " << this->m_name << " --config to run with these options." << std::endl; out << "# use [testdatapath] (with square brackes) in a path to specify the testdata-directory." << std::endl; out << "#" << std::endl; // Then try to iterate over the "variables_map" and output the values variables_map::const_iterator vbegin = this->m_optionsMap.begin( ); variables_map::const_iterator vend = this->m_optionsMap.end( ); // Keep track of the "current" section // Note: This assumes that the variables-map stores its values ordered by // section, which is in general the case, because the map is "default" // sorted by "section.keyname". std::string currentsection = ""; for ( variables_map::const_iterator vit = vbegin; vit != vend; vit++ ) { std::string keystring = vit->first; // We skip all "command-line" options (by explicitly checking if the name corresponds to "help", "config" or "output-config". bool iscommandline = (keystring == "help") || (keystring == "config") || (keystring == "output-config"); if ( !iscommandline ) { // Try to extract a "section-name" std::string::size_type dotindex = keystring.find( '.' ); if ( dotindex != std::string::npos ) { std::string sectionname = keystring.substr( 0, dotindex ); // Update keystring (stripped from its sectionname ) keystring = keystring.substr( dotindex+1 ); if ( sectionname != currentsection ) { // Print an extra line with the new out << "\n[" << sectionname << "]" << std::endl; currentsection = sectionname; } } out << keystring << " = " << vit->second.value( ) << std::endl; } } } void Settings::help( std::ostream& out ) { out << this->m_name << " usage:" << std::endl; out << this->m_commandLineOptions; out << "\ncurrent build:\n " << global_buildmessage << std::endl; } bool Settings::contains( const std::string& key ) const { return (this->m_optionsMap.count( key ) > 0); } // The actual methods for loading, by command-line or by filestream. bool Settings::loadOptions( int argc, char *argv[] ) { bool result = true; // Start parsing the command-line boost::program_options::store( parse_command_line( argc, argv, this->m_commandLineOptions ), this->m_optionsMap ); boost::program_options::notify( this->m_optionsMap ); if ( this->m_optionsMap.count( "config" ) > 0 ) { // Then fill the remaining values from the config-file. std::ifstream configin( this->m_optionsMap["config"].as< std::string >( ).c_str( ) ); this->loadOptions( configin ); } else { std::string initialconfig = this->m_defaultOptions; std::istringstream defaultsin( initialconfig ); this->loadOptions( defaultsin ); } return result; } bool Settings::loadOptions( std::istream& in ) { boost::program_options::store( parse_config_file( in, this->m_configFileOptions ), this->m_optionsMap ); boost::program_options::notify( this->m_optionsMap ); return true; } /////////////////////////////////////////////////////////////////////////////// // Implementation of templated methods. // /////////////////////////////////////////////////////////////////////////////// namespace Utilities { template<> const std::vector< int > Settings::get< std::vector< int > >( const std::string& key ) const { std::vector< int > result; std::string stringvalue; try { stringvalue = this->m_optionsMap[key].as< std::string >( ); } catch( std::exception& e ) { std::cerr << "Error catching: " << key << " - " << e.what( ) << std::endl; stringvalue = "(0, 0, 0)"; } std::istringstream in( stringvalue ); char dummy; in >> dummy; // Ifnore the first '('; // Assume there is "always" a single value available... int val; in >> val; result.push_back( val ); // Assume it is followed by ','. in >> dummy; in >> val; // As long as the extracted value was "good" continue pushing back while( dummy == ',' && in.good( ) ) { result.push_back( val ); in >> dummy; in >> val; } return result; } template<> const std::vector< double > Settings::get< std::vector< double > >( const std::string& key ) const { std::vector< double > result; std::string stringvalue; try { stringvalue = this->m_optionsMap[key].as< std::string >( ); } catch( std::exception& e ) { std::cerr << "Error catching: " << key << " - " << e.what( ) << std::endl; stringvalue = "(0.0, 0.0, 0.0)"; } std::istringstream in( stringvalue ); char dummy; in >> dummy; // Ignore the first '('; // Assume always a single char.. double val; in >> val; result.push_back( val ); // Assume only ',' in >> dummy ; in >> val; while( dummy == ',' && in.good( ) ) { result.push_back( val ); in >> dummy ; in >> val; } return result; } template<> const std::string Settings::get< std::string >( const std::string& key ) const { std::string stringvalue; try { stringvalue = this->m_optionsMap[key].as< std::string >( ); } catch( std::exception& e ) { std::cerr << "Error catching: " << key << " - " << e.what( ) << std::endl; stringvalue = ""; } // Try to replace each occurence of the default [testdatapath] with the actual // value. (using boost::algorithm::replace from string algorithms) boost::algorithm::replace_all( stringvalue, key_testdatapath, global_testdatapath ); return stringvalue; } template<> const std::vector< std::string > Settings::get< std::vector< std::string > >( const std::string& key ) const { std::string stringvalue; try { stringvalue = this->m_optionsMap[key].as< std::string >( ); } catch( std::exception& e ) { std::cerr << "Error catching: " << key << " - " << e.what( ) << std::endl; stringvalue = ""; } // First try to replace each occurence of the default [testdatapath], then // split it by ';' character. boost::algorithm::replace_all( stringvalue, key_testdatapath, global_testdatapath ); std::vector< std::string > stringlist; boost::algorithm::split( stringlist, stringvalue, boost::algorithm::is_any_of( ";" ) ); //! \todo Alternative approach would be to require a list like with the //! vector< double >, surrounded by round brackets and split by commas. return stringlist; } } // Utilities namespace // Simple overload of operator<< for boost::any, it uses a simple switch // (actually if-else) with limited cases: // bool, double, int and std::string #include std::ostream& operator<<( std::ostream& out, const boost::any& a ) { // For now, we simply check typeinfo.... if ( a.type( ) == typeid( std::string ) ) { std::string astring = boost::any_cast< std::string >( a ); out << astring ; } else if ( a.type( ) == typeid( bool ) ) { bool abool = boost::any_cast< bool >( a ); out << abool ; } else if ( a.type( ) == typeid( int ) ) { int aint = boost::any_cast< int >( a ); out << aint ; } else if ( a.type( ) == typeid( double ) ) { double adouble = boost::any_cast< double >( a ); out << adouble ; } else if ( a.type( ) == typeid( unsigned int ) ) { int auint = boost::any_cast< unsigned int >( a ); out << auint ; } else { throw std::runtime_error( "unknown type for conversion" ); } return out; }