Note: Goby version 1 (shown here) is now considered obsolete. Please use version 2 for new projects, and consider upgrading old projects.

Goby Underwater Autonomy Project  Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
core/libcore/configuration_reader.cpp
00001 // copyright 2010 t. schneider tes@mit.edu
00002 // 
00003 // This program is free software: you can redistribute it and/or modify
00004 // it under the terms of the GNU General Public License as published by
00005 // the Free Software Foundation, either version 3 of the License, or
00006 // (at your option) any later version.
00007 //
00008 // This software is distributed in the hope that it will be useful,
00009 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 // GNU General Public License for more details.
00012 //
00013 // You should have received a copy of the GNU General Public License
00014 // along with this software.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 #include "goby/protobuf/option_extensions.pb.h"
00017 #include "goby/protobuf/app_base_config.pb.h"
00018 
00019 #include "configuration_reader.h"
00020 
00021 #include "goby/util/liblogger/term_color.h"
00022 #include "exception.h"
00023 #include <google/protobuf/dynamic_message.h>
00024 #include <algorithm>
00025 #include <boost/algorithm/string.hpp>
00026 
00027 // brings std::ostream& red, etc. into scope
00028 using namespace goby::util::tcolor;
00029 
00030 void goby::core::ConfigReader::read_cfg(int argc,
00031                                         char* argv[],
00032                                         google::protobuf::Message* message,
00033                                         std::string* application_name,
00034                                         boost::program_options::options_description* od_all,
00035                                         boost::program_options::variables_map* var_map)
00036 {
00037     if(!argv) return;
00038 
00039     boost::filesystem::path launch_path(argv[0]);
00040 
00041 #if BOOST_FILESYSTEM_VERSION == 3    
00042     *application_name = launch_path.filename().string();
00043 #else
00044     *application_name = launch_path.filename();
00045 #endif
00046     
00047     std::string cfg_path;    
00048 
00049     boost::program_options::options_description od_cli_only("Given on command line only");
00050 
00051     std::string cfg_path_desc = "path to " + *application_name + " configuration file (typically " + *application_name + ".cfg)";
00052     
00053     std::string app_name_desc = "name to use when connecting to gobyd (default: " + std::string(argv[0]) + ")";
00054     od_cli_only.add_options()
00055         ("cfg_path,c", boost::program_options::value<std::string>(&cfg_path), cfg_path_desc.c_str())
00056         ("help,h", "writes this help message")
00057         ("platform_name,p", boost::program_options::value<std::string>(), "name of this platform (same as gobyd configuration value `self.name`)")
00058         ("app_name,a", boost::program_options::value<std::string>(), app_name_desc.c_str())
00059         ("verbose,v", boost::program_options::value<std::string>()->implicit_value("")->multitoken(), "output useful information to std::cout. -v is verbosity: verbose, -vv is verbosity: debug, -vvv is verbosity: gui");
00060     
00061     std::string od_both_desc = "Typically given in " + *application_name + " configuration file, but may be specified on the command line";
00062     boost::program_options::options_description od_both(od_both_desc.c_str());
00063 
00064     if(message)
00065     {
00066         get_protobuf_program_options(od_both, message->GetDescriptor());
00067         od_all->add(od_both);
00068     }
00069     od_all->add(od_cli_only);
00070     
00071     boost::program_options::positional_options_description p;
00072     p.add("cfg_path", 1);
00073     p.add("platform_name", 2);
00074     p.add("app_name", 3);
00075     
00076         
00077     boost::program_options::store(boost::program_options::command_line_parser(argc, argv).
00078                                   options(*od_all).positional(p).run(), *var_map);
00079     
00080     if (var_map->count("help"))
00081     {
00082         ConfigException e("");
00083         e.set_error(false);
00084         throw(e);
00085     }
00086     
00087     boost::program_options::notify(*var_map);
00088         
00089     if(message)
00090     {
00091         if(!cfg_path.empty())
00092         {
00093                 
00094             // try to read file
00095             std::ifstream fin;
00096             fin.open(cfg_path.c_str(), std::ifstream::in);
00097             if(!fin.is_open())
00098                 throw(ConfigException(std::string("could not open '" + cfg_path + "' for reading. check value of --cfg_path")));   
00099                 
00100             google::protobuf::io::IstreamInputStream is(&fin);
00101                 
00102             google::protobuf::TextFormat::Parser parser;
00103             // maybe the command line will fill in the missing pieces
00104             parser.AllowPartialMessage(true);
00105             parser.Parse(&is, message);
00106         }
00107          
00108         // add / overwrite any options that are specified in the cfg file with those given on the command line
00109         typedef std::pair<std::string, boost::program_options::variable_value> P;
00110         BOOST_FOREACH(const P&p, *var_map)
00111         {
00112             // let protobuf deal with the defaults
00113             if(!p.second.defaulted())
00114                 set_protobuf_program_option(*var_map, *message, p.first, p.second);
00115         }
00116         
00117         // now the proto message must have all required fields
00118         if(!message->IsInitialized())
00119         {
00120             std::vector< std::string > errors;
00121             message->FindInitializationErrors(&errors);
00122                 
00123             std::stringstream err_msg;
00124             err_msg << "Configuration is missing required parameters: \n";
00125             BOOST_FOREACH(const std::string& s, errors)
00126                 err_msg << util::esc_red << s << "\n" << util::esc_nocolor;
00127                 
00128             err_msg << "Make sure you specified a proper `cfg_path` to the configuration file.";
00129             throw(ConfigException(err_msg.str()));
00130         }
00131     }        
00132 }
00133 
00134 
00135 void goby::core::ConfigReader::set_protobuf_program_option(const boost::program_options::variables_map& var_map,
00136                                                            google::protobuf::Message& message,
00137                                                            const std::string& full_name,
00138                                                            const boost::program_options::variable_value& value)
00139 {
00140     const google::protobuf::Descriptor* desc = message.GetDescriptor();
00141     const google::protobuf::Reflection* refl = message.GetReflection();
00142     
00143     const google::protobuf::FieldDescriptor* field_desc = desc->FindFieldByName(full_name);
00144     if(!field_desc) return;
00145     
00146     if(field_desc->is_repeated())
00147     {                    
00148         switch(field_desc->cpp_type())
00149         {
00150             case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
00151                 BOOST_FOREACH(std::string v,value.as<std::vector<std::string> >())
00152                 {
00153                     google::protobuf::TextFormat::Parser parser;
00154                     parser.AllowPartialMessage(true);   
00155                     parser.MergeFromString(v, refl->AddMessage(&message, field_desc));
00156                 }
00157                 
00158                 break;    
00159                     
00160             case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
00161                 BOOST_FOREACH(google::protobuf::int32 v,
00162                               value.as<std::vector<google::protobuf::int32> >())
00163                     refl->AddInt32(&message, field_desc, v);
00164                 break;
00165                     
00166             case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
00167                 BOOST_FOREACH(google::protobuf::int64 v,
00168                               value.as<std::vector<google::protobuf::int64> >())
00169                     refl->AddInt64(&message, field_desc, v);
00170                 break;
00171 
00172             case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
00173                 BOOST_FOREACH(google::protobuf::uint32 v,
00174                               value.as<std::vector<google::protobuf::uint32> >())
00175                     refl->AddUInt32(&message, field_desc, v);
00176                 break;
00177 
00178             case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
00179                 BOOST_FOREACH(google::protobuf::uint64 v,
00180                               value.as<std::vector<google::protobuf::uint64> >())
00181                     refl->AddUInt64(&message, field_desc, v);
00182                 break;
00183                         
00184             case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
00185                 BOOST_FOREACH(bool v, value.as<std::vector<bool> >())
00186                     refl->AddBool(&message, field_desc, v);
00187                 break;
00188                     
00189             case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
00190                 BOOST_FOREACH(const std::string& v,
00191                               value.as<std::vector<std::string> >())
00192                     refl->AddString(&message, field_desc, v);
00193                 break;                    
00194                 
00195             case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
00196                 BOOST_FOREACH(float v, value.as<std::vector<float> >())
00197                     refl->AddFloat(&message, field_desc, v);
00198                 break;
00199                 
00200             case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
00201                 BOOST_FOREACH(double v, value.as<std::vector<double> >())
00202                     refl->AddDouble(&message, field_desc, v);
00203                 break;
00204                 
00205             case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
00206                 BOOST_FOREACH(std::string v,
00207                               value.as<std::vector<std::string> >())
00208                 {
00209                     const google::protobuf::EnumValueDescriptor* enum_desc =
00210                         refl->GetEnum(message, field_desc)->type()->FindValueByName(v);
00211                     if(!enum_desc)
00212                         throw(ConfigException(std::string("invalid enumeration " + v + " for field " + full_name)));
00213                     
00214                     refl->AddEnum(&message, field_desc, enum_desc);
00215                 }
00216                 
00217                 break;                    
00218                 
00219         }
00220     }
00221     else
00222     {   
00223         switch(field_desc->cpp_type())
00224         {
00225             case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
00226             {
00227                 google::protobuf::TextFormat::Parser parser;
00228                 parser.AllowPartialMessage(true);
00229                 parser.MergeFromString(value.as<std::string>(),refl->MutableMessage(&message, field_desc));
00230                 break;    
00231             }
00232                     
00233             case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
00234                 refl->SetInt32(&message, field_desc, value.as<boost::int_least32_t>());
00235                 break;
00236                     
00237             case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
00238                 refl->SetInt64(&message, field_desc, value.as<boost::int_least64_t>());
00239                 break;
00240 
00241             case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
00242                 refl->SetUInt32(&message, field_desc, value.as<boost::uint_least32_t>());
00243                 break;
00244 
00245             case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
00246                 refl->SetUInt64(&message, field_desc, value.as<boost::uint_least64_t>()); 
00247                 break;
00248                         
00249             case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
00250                 refl->SetBool(&message, field_desc, value.as<bool>());
00251                 break;
00252                     
00253             case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
00254                 refl->SetString(&message, field_desc, value.as<std::string>());
00255                 break;                    
00256                 
00257             case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
00258                 refl->SetFloat(&message, field_desc, value.as<float>());
00259                 break;
00260                 
00261             case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
00262                 refl->SetDouble(&message, field_desc, value.as<double>());
00263                 break;
00264                 
00265             case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
00266                 const google::protobuf::EnumValueDescriptor* enum_desc =
00267                     refl->GetEnum(message, field_desc)->type()->FindValueByName(value.as<std::string>());
00268                 if(!enum_desc)
00269                     throw(ConfigException(std::string("invalid enumeration " + value.as<std::string>() + " for field "
00270                                                          + full_name)));
00271                 
00272                 refl->SetEnum(&message, field_desc, enum_desc);
00273                 break;                    
00274                 
00275         }
00276     }
00277 }
00278 
00279 void goby::core::ConfigReader::get_example_cfg_file(google::protobuf::Message* message, std::ostream* stream, const std::string& indent /*= ""*/)
00280 {
00281     build_description(message->GetDescriptor(), *stream, indent, false);
00282     *stream << std::endl;
00283 }
00284 
00285 
00286 void  goby::core::ConfigReader::get_protobuf_program_options(boost::program_options::options_description& po_desc,
00287                                                              const google::protobuf::Descriptor* desc)
00288 {
00289     for(int i = 0, n = desc->field_count(); i < n; ++i)
00290     {
00291         const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
00292         const std::string& field_name = field_desc->name();
00293         
00294         std::string cli_name = field_name;
00295         std::stringstream human_desc_ss;
00296         human_desc_ss << util::esc_lt_blue << field_desc->options().GetExtension(::description);
00297         human_desc_ss << label(field_desc);
00298         human_desc_ss << util::esc_nocolor;
00299         
00300         switch(field_desc->cpp_type())
00301         {
00302             case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
00303             {
00304                 build_description(field_desc->message_type(), human_desc_ss, "  ");
00305                 
00306                 set_single_option(po_desc,
00307                                   field_desc,
00308                                   std::string(),
00309                                   cli_name,
00310                                   human_desc_ss.str());
00311             }                
00312             break;
00313             
00314             case google::protobuf::FieldDescriptor::CPPTYPE_INT32:                    
00315             {
00316                 set_single_option(po_desc,
00317                                   field_desc,
00318                                   field_desc->default_value_int32(),
00319                                   cli_name,
00320                                   human_desc_ss.str());
00321             }
00322             break;
00323                     
00324             case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
00325             {
00326                 set_single_option(po_desc,
00327                                   field_desc,
00328                                   field_desc->default_value_int64(),
00329                                   cli_name,
00330                                   human_desc_ss.str());
00331             }
00332             break;
00333 
00334             case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
00335             {
00336                 set_single_option(po_desc,
00337                                   field_desc,
00338                                   field_desc->default_value_uint32(),
00339                                   cli_name,
00340                                   human_desc_ss.str());
00341             }
00342             break;
00343                                 
00344             case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
00345             {
00346                 set_single_option(po_desc,
00347                                   field_desc,
00348                                   field_desc->default_value_uint64(),
00349                                   cli_name,
00350                                   human_desc_ss.str());
00351             }
00352             break;
00353                         
00354             case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
00355             {
00356                 set_single_option(po_desc,
00357                                   field_desc,
00358                                   field_desc->default_value_bool(),
00359                                   cli_name,
00360                                   human_desc_ss.str());
00361             }
00362             break;
00363                     
00364             case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
00365             {
00366                 set_single_option(po_desc,
00367                                   field_desc,
00368                                   field_desc->default_value_string(),
00369                                   cli_name,
00370                                   human_desc_ss.str());
00371             }
00372             break;                    
00373                 
00374             case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
00375             {
00376                 set_single_option(po_desc,
00377                                   field_desc,
00378                                   field_desc->default_value_float(),
00379                                   cli_name,
00380                                   human_desc_ss.str());
00381             }
00382             break;
00383                         
00384             case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
00385             {
00386                 set_single_option(po_desc,
00387                                   field_desc,
00388                                   field_desc->default_value_double(),
00389                                   cli_name,
00390                                   human_desc_ss.str());
00391             }
00392             break;
00393                 
00394             case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
00395             {
00396                 set_single_option(po_desc,
00397                                   field_desc,
00398                                   field_desc->default_value_enum()->name(),
00399                                   cli_name,
00400                                   human_desc_ss.str());
00401             }
00402             break;
00403         }
00404     }
00405 }
00406 
00407 
00408 void goby::core::ConfigReader::build_description(const google::protobuf::Descriptor* desc,
00409                                                  std::ostream& stream,
00410                                                  const std::string& indent /*= ""*/,
00411                                                  bool use_color /* = true */ )
00412 {
00413     for(int i = 0, n = desc->field_count(); i < n; ++i)
00414     {
00415         const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
00416         build_description_field(field_desc, stream, indent, use_color);
00417     }
00418 }
00419 
00420 void goby::core::ConfigReader::build_description_field(
00421     const google::protobuf::FieldDescriptor* field_desc,
00422     std::ostream& stream,
00423     const std::string& indent,
00424     bool use_color)   
00425 {
00426     google::protobuf::DynamicMessageFactory factory;
00427     const google::protobuf::Message* default_msg = factory.GetPrototype(field_desc->containing_type());
00428     
00429     if(field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
00430     {
00431         std::string before_description = indent + field_desc->name() + " {  ";
00432         stream << "\n" << before_description;
00433 
00434         std::string description;
00435         if(use_color)
00436             description += util::esc_green;
00437         else
00438             description += "# ";
00439 
00440         description += field_desc->options().GetExtension(::description)
00441             + label(field_desc);
00442             
00443         if(use_color)
00444             description += util::esc_nocolor;
00445 
00446         if(!use_color)
00447             wrap_description(&description, before_description.size());
00448 
00449         stream << description;            
00450             
00451         build_description(field_desc->message_type(), stream, indent + "  ", use_color);
00452         stream << "\n" << indent << "}";
00453     }
00454     else
00455     {
00456         stream << "\n";
00457 
00458         std::string before_description = indent;
00459             
00460         std::string example;
00461         if(field_desc->has_default_value())
00462             google::protobuf::TextFormat::PrintFieldValueToString(*default_msg, field_desc, -1, &example);
00463         else
00464         {
00465             example = field_desc->options().GetExtension(::example);
00466             if(field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_STRING)
00467                 example = "\"" + example + "\""; 
00468         }
00469             
00470         before_description += field_desc->name() + ": " + example;
00471 
00472         before_description += "  ";
00473 
00474             
00475         stream << before_description;            
00476 
00477         std::string description;
00478 
00479         if(use_color)
00480             description +=  util::esc_green;
00481         else
00482             description += "# ";
00483             
00484         description += field_desc->options().GetExtension(::description);
00485         if(field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_ENUM)
00486         {
00487             description += " (";
00488             for(int i = 0, n = field_desc->enum_type()->value_count(); i < n; ++i)
00489             {
00490                 if(i) description +=  ", ";
00491                 description += field_desc->enum_type()->value(i)->name();
00492             }
00493                 
00494             description += ")";
00495         }
00496             
00497         description += label(field_desc);
00498             
00499 
00500         if(field_desc->has_default_value())
00501             description += " (default=" + example + ")";
00502 
00503         if(field_desc->options().HasExtension(::moos_global))
00504             description += " (can also set MOOS global \"" + field_desc->options().GetExtension(::moos_global) + "=\")";
00505             
00506         if(!use_color)
00507             wrap_description(&description, before_description.size());
00508             
00509         stream << description;            
00510             
00511         if(use_color)
00512             stream << util::esc_nocolor;
00513         
00514     }
00515 }
00516 
00517 
00518 
00519 std::string goby::core::ConfigReader::label(const google::protobuf::FieldDescriptor* field_desc)
00520 {
00521     switch(field_desc->label())
00522     {
00523         case google::protobuf::FieldDescriptor::LABEL_REQUIRED:
00524             return " (req)";
00525             
00526         case google::protobuf::FieldDescriptor::LABEL_OPTIONAL:
00527             return " (opt)";
00528             
00529         case google::protobuf::FieldDescriptor::LABEL_REPEATED:
00530             return " (repeat)";
00531     }
00532 
00533     return "";
00534 }
00535 
00536 void goby::core::ConfigReader::merge_app_base_cfg(AppBaseConfig* base_cfg,
00537                         const boost::program_options::variables_map& var_map)
00538 {
00539     if (var_map.count("verbose"))
00540     {
00541         switch(var_map["verbose"].as<std::string>().size())
00542         {
00543             case 0:
00544                 base_cfg->set_verbosity(AppBaseConfig::VERBOSE);
00545                 break;
00546             case 1:
00547                 base_cfg->set_verbosity(AppBaseConfig::DEBUG);
00548                 break;
00549             default:
00550             case 2:
00551                 base_cfg->set_verbosity(AppBaseConfig::GUI);
00552                 break;
00553         }
00554     }
00555 }
00556 
00557 
00558 
00559 std::string goby::core::ConfigReader::word_wrap(std::string s, unsigned width,
00560                                                 const std::string & delim)
00561 {
00562     std::string out;
00563             
00564     while(s.length() > width)
00565     {            
00566         std::string::size_type pos_newline = s.find("\n");
00567         std::string::size_type pos_delim = s.substr(0, width).find_last_of(delim);
00568         if(pos_newline < width)
00569         {
00570             out += s.substr(0, pos_newline);
00571             s = s.substr(pos_newline+1);
00572         }
00573         else if (pos_delim != std::string::npos)
00574         {
00575             out += s.substr(0, pos_delim+1);
00576             s = s.substr(pos_delim+1);
00577         }
00578         else
00579         {
00580             out += s.substr(0, width);
00581             s = s.substr(width);
00582         }
00583         out += "\n";
00584 
00585         // std::cout << "width: " << width << " " << out << std::endl;
00586     }
00587     out += s;
00588             
00589     return out;
00590 }
00591             
00592 void goby::core::ConfigReader::wrap_description(std::string* description, int num_blanks)
00593 {
00594     *description = word_wrap(*description,
00595                             std::max(MAX_CHAR_PER_LINE -
00596                                      num_blanks, (int)MIN_CHAR), " ");
00597 
00598     if(MIN_CHAR > MAX_CHAR_PER_LINE - num_blanks)
00599     {
00600         *description = "\n" + description->substr(2);
00601         num_blanks =  MAX_CHAR_PER_LINE - MIN_CHAR;
00602     }
00603     
00604     std::string spaces(num_blanks, ' ');
00605     spaces += "# ";
00606     boost::replace_all(*description, "\n", "\n" + spaces);
00607 }
00608 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends