Goby Underwater Autonomy Project
Series: 1.1, revision: 163, released on 2013-02-06 14:23:27 -0500
|
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