Goby v2
configuration_reader.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include "goby/common/protobuf/option_extensions.pb.h"
24 
25 #include "configuration_reader.h"
26 
27 #include "exception.h"
28 #include "goby/common/logger/flex_ostream.h"
29 #include "goby/common/logger/term_color.h"
30 #include "goby/version.h"
31 #include <algorithm>
32 #include <boost/algorithm/string.hpp>
33 #include <boost/filesystem.hpp>
34 #include <google/protobuf/dynamic_message.h>
35 
36 // brings std::ostream& red, etc. into scope
37 using namespace goby::common::tcolor;
38 
39 void goby::common::ConfigReader::read_cfg(int argc, char* argv[],
41  std::string* application_name,
42  boost::program_options::options_description* od_all,
43  boost::program_options::variables_map* var_map)
44 {
45  if (!argv)
46  return;
47 
48  boost::filesystem::path launch_path(argv[0]);
49 
50 #if BOOST_FILESYSTEM_VERSION == 3
51  *application_name = launch_path.filename().string();
52 #else
53  *application_name = launch_path.filename();
54 #endif
55 
56  std::string cfg_path;
57 
58  boost::program_options::options_description od_cli_only("Given on command line only");
59 
60  std::string cfg_path_desc = "path to " + *application_name + " configuration file (typically " +
61  *application_name + ".cfg)";
62 
63  std::string app_name_desc =
64  "name to use while communicating in goby (default: " + std::string(argv[0]) + ")";
65  od_cli_only.add_options()("cfg_path,c", boost::program_options::value<std::string>(&cfg_path),
66  cfg_path_desc.c_str())("help,h", "writes this help message")(
67  "app_name,a", boost::program_options::value<std::string>(),
68  app_name_desc.c_str())("example_config,e", "writes an example .pb.cfg file")(
69  "verbose,v", boost::program_options::value<std::string>()->implicit_value("")->multitoken(),
70  "output useful information to std::cout. -v is verbosity: verbose, -vv is verbosity: "
71  "debug1, -vvv is verbosity: debug2, -vvvv is verbosity: debug3")(
72  "ncurses,n", "output useful information to an NCurses GUI instead of stdout. If set, this "
73  "parameter overrides --verbose settings.")
74  // ("no_db,d", "disables the check for goby_database before publishing. You must set this if not running the goby_database.")
75  ("version,V", "writes the current version");
76 
77  std::string od_both_desc = "Typically given in " + *application_name +
78  " configuration file, but may be specified on the command line";
79  boost::program_options::options_description od_both(od_both_desc.c_str());
80 
81  if (message)
82  {
83  get_protobuf_program_options(od_both, message->GetDescriptor());
84  od_all->add(od_both);
85  }
86  od_all->add(od_cli_only);
87 
88  boost::program_options::positional_options_description p;
89  p.add("cfg_path", 1);
90  p.add("app_name", 2);
91 
92  try
93  {
94  boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
95  .options(*od_all)
96  .positional(p)
97  .run(),
98  *var_map);
99  }
100  catch (std::exception& e)
101  {
102  throw(ConfigException(e.what()));
103  }
104 
105  if (var_map->count("help"))
106  {
107  ConfigException e("");
108  e.set_error(false);
109  std::cerr << *od_all << "\n";
110  throw(e);
111  }
112  else if (var_map->count("example_config"))
113  {
114  ConfigException e("");
115  e.set_error(false);
116  get_example_cfg_file(message, &std::cout);
117  throw(e);
118  }
119  else if (var_map->count("version"))
120  {
121  ConfigException e("");
122  e.set_error(false);
123  std::cout << goby::version_message() << std::endl;
124  throw(e);
125  }
126 
127  if (var_map->count("app_name"))
128  {
129  *application_name = (*var_map)["app_name"].as<std::string>();
130  }
131 
132  boost::program_options::notify(*var_map);
133 
134  if (message)
135  {
136  if (!cfg_path.empty())
137  {
138  // try to read file
139  std::ifstream fin;
140  fin.open(cfg_path.c_str(), std::ifstream::in);
141  if (!fin.is_open())
142  throw(ConfigException(std::string("could not open '" + cfg_path +
143  "' for reading. check value of --cfg_path")));
144 
145  // google::protobuf::io::IstreamInputStream is(&fin);
146  std::string protobuf_text((std::istreambuf_iterator<char>(fin)),
147  std::istreambuf_iterator<char>());
148 
149  google::protobuf::TextFormat::Parser parser;
150  // maybe the command line will fill in the missing pieces
151  glog.set_name(*application_name);
152  glog.add_stream(protobuf::GLogConfig::VERBOSE, &std::cout);
153  FlexOStreamErrorCollector error_collector(protobuf_text);
154  parser.RecordErrorsTo(&error_collector);
155  parser.AllowPartialMessage(true);
156  parser.ParseFromString(protobuf_text, message);
157 
158  //parser.Parse(&is, message);
159 
160  if (error_collector.has_errors())
161  {
162  glog.is(goby::common::logger::DIE) &&
163  glog << "fatal configuration errors (see above)" << std::endl;
164  }
165  }
166 
167  // add / overwrite any options that are specified in the cfg file with those given on the command line
168  typedef std::pair<std::string, boost::program_options::variable_value> P;
169  BOOST_FOREACH (const P& p, *var_map)
170  {
171  // let protobuf deal with the defaults
172  if (!p.second.defaulted())
173  set_protobuf_program_option(*var_map, *message, p.first, p.second);
174  }
175 
176  // now the proto message must have all required fields
177  if (!message->IsInitialized())
178  {
179  std::vector<std::string> errors;
180  message->FindInitializationErrors(&errors);
181 
182  std::stringstream err_msg;
183  err_msg << "Configuration is missing required parameters: \n";
184  BOOST_FOREACH (const std::string& s, errors)
185  err_msg << common::esc_red << s << "\n" << common::esc_nocolor;
186 
187  err_msg << "Make sure you specified a proper `cfg_path` to the configuration file.";
188  throw(ConfigException(err_msg.str()));
189  }
190  }
191 }
192 
193 void goby::common::ConfigReader::set_protobuf_program_option(
194  const boost::program_options::variables_map& var_map, google::protobuf::Message& message,
195  const std::string& full_name, const boost::program_options::variable_value& value)
196 {
197  const google::protobuf::Descriptor* desc = message.GetDescriptor();
198  const google::protobuf::Reflection* refl = message.GetReflection();
199 
200  const google::protobuf::FieldDescriptor* field_desc = desc->FindFieldByName(full_name);
201  if (!field_desc)
202  return;
203 
204  if (field_desc->is_repeated())
205  {
206  switch (field_desc->cpp_type())
207  {
208  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
209  BOOST_FOREACH (std::string v, value.as<std::vector<std::string> >())
210  {
211  google::protobuf::TextFormat::Parser parser;
212  parser.AllowPartialMessage(true);
213  parser.MergeFromString(v, refl->AddMessage(&message, field_desc));
214  }
215 
216  break;
217 
218  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
219  BOOST_FOREACH (google::protobuf::int32 v,
220  value.as<std::vector<google::protobuf::int32> >())
221  refl->AddInt32(&message, field_desc, v);
222  break;
223 
224  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
225  BOOST_FOREACH (google::protobuf::int64 v,
226  value.as<std::vector<google::protobuf::int64> >())
227  refl->AddInt64(&message, field_desc, v);
228  break;
229 
230  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
231  BOOST_FOREACH (google::protobuf::uint32 v,
232  value.as<std::vector<google::protobuf::uint32> >())
233  refl->AddUInt32(&message, field_desc, v);
234  break;
235 
236  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
237  BOOST_FOREACH (google::protobuf::uint64 v,
238  value.as<std::vector<google::protobuf::uint64> >())
239  refl->AddUInt64(&message, field_desc, v);
240  break;
241 
242  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
243  BOOST_FOREACH (bool v, value.as<std::vector<bool> >())
244  refl->AddBool(&message, field_desc, v);
245  break;
246 
247  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
248  BOOST_FOREACH (const std::string& v, value.as<std::vector<std::string> >())
249  refl->AddString(&message, field_desc, v);
250  break;
251 
252  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
253  BOOST_FOREACH (float v, value.as<std::vector<float> >())
254  refl->AddFloat(&message, field_desc, v);
255  break;
256 
257  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
258  BOOST_FOREACH (double v, value.as<std::vector<double> >())
259  refl->AddDouble(&message, field_desc, v);
260  break;
261 
262  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
263  BOOST_FOREACH (std::string v, value.as<std::vector<std::string> >())
264  {
265  const google::protobuf::EnumValueDescriptor* enum_desc =
266  field_desc->enum_type()->FindValueByName(v);
267  if (!enum_desc)
268  throw(ConfigException(
269  std::string("invalid enumeration " + v + " for field " + full_name)));
270 
271  refl->AddEnum(&message, field_desc, enum_desc);
272  }
273 
274  break;
275  }
276  }
277  else
278  {
279  switch (field_desc->cpp_type())
280  {
281  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
282  {
283  google::protobuf::TextFormat::Parser parser;
284  parser.AllowPartialMessage(true);
285  parser.MergeFromString(value.as<std::string>(),
286  refl->MutableMessage(&message, field_desc));
287  break;
288  }
289 
290  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
291  refl->SetInt32(&message, field_desc, value.as<boost::int_least32_t>());
292  break;
293 
294  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
295  refl->SetInt64(&message, field_desc, value.as<boost::int_least64_t>());
296  break;
297 
298  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
299  refl->SetUInt32(&message, field_desc, value.as<boost::uint_least32_t>());
300  break;
301 
302  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
303  refl->SetUInt64(&message, field_desc, value.as<boost::uint_least64_t>());
304  break;
305 
306  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
307  refl->SetBool(&message, field_desc, value.as<bool>());
308  break;
309 
310  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
311  refl->SetString(&message, field_desc, value.as<std::string>());
312  break;
313 
314  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
315  refl->SetFloat(&message, field_desc, value.as<float>());
316  break;
317 
318  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
319  refl->SetDouble(&message, field_desc, value.as<double>());
320  break;
321 
322  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
323  const google::protobuf::EnumValueDescriptor* enum_desc =
324  field_desc->enum_type()->FindValueByName(value.as<std::string>());
325  if (!enum_desc)
326  throw(ConfigException(std::string("invalid enumeration " +
327  value.as<std::string>() + " for field " +
328  full_name)));
329 
330  refl->SetEnum(&message, field_desc, enum_desc);
331  break;
332  }
333  }
334 }
335 
336 void goby::common::ConfigReader::get_example_cfg_file(google::protobuf::Message* message,
337  std::ostream* stream,
338  const std::string& indent /*= ""*/)
339 {
340  build_description(message->GetDescriptor(), *stream, indent, false);
341  *stream << std::endl;
342 }
343 
344 void goby::common::ConfigReader::get_protobuf_program_options(
345  boost::program_options::options_description& po_desc, const google::protobuf::Descriptor* desc)
346 {
347  for (int i = 0, n = desc->field_count(); i < n; ++i)
348  {
349  const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
350  const std::string& field_name = field_desc->name();
351 
352  std::string cli_name = field_name;
353  std::stringstream human_desc_ss;
354  human_desc_ss << common::esc_lt_blue
355  << field_desc->options().GetExtension(goby::field).description();
356  human_desc_ss << label(field_desc);
357  human_desc_ss << common::esc_nocolor;
358 
359  switch (field_desc->cpp_type())
360  {
361  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
362  {
363  build_description(field_desc->message_type(), human_desc_ss, " ");
364 
365  set_single_option(po_desc, field_desc, std::string(), cli_name,
366  human_desc_ss.str());
367  }
368  break;
369 
370  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
371  {
372  set_single_option(po_desc, field_desc, field_desc->default_value_int32(), cli_name,
373  human_desc_ss.str());
374  }
375  break;
376 
377  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
378  {
379  set_single_option(po_desc, field_desc, field_desc->default_value_int64(), cli_name,
380  human_desc_ss.str());
381  }
382  break;
383 
384  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
385  {
386  set_single_option(po_desc, field_desc, field_desc->default_value_uint32(), cli_name,
387  human_desc_ss.str());
388  }
389  break;
390 
391  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
392  {
393  set_single_option(po_desc, field_desc, field_desc->default_value_uint64(), cli_name,
394  human_desc_ss.str());
395  }
396  break;
397 
398  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
399  {
400  set_single_option(po_desc, field_desc, field_desc->default_value_bool(), cli_name,
401  human_desc_ss.str());
402  }
403  break;
404 
405  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
406  {
407  set_single_option(po_desc, field_desc, field_desc->default_value_string(), cli_name,
408  human_desc_ss.str());
409  }
410  break;
411 
412  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
413  {
414  set_single_option(po_desc, field_desc, field_desc->default_value_float(), cli_name,
415  human_desc_ss.str());
416  }
417  break;
418 
419  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
420  {
421  set_single_option(po_desc, field_desc, field_desc->default_value_double(), cli_name,
422  human_desc_ss.str());
423  }
424  break;
425 
426  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
427  {
428  set_single_option(po_desc, field_desc, field_desc->default_value_enum()->name(),
429  cli_name, human_desc_ss.str());
430  }
431  break;
432  }
433  }
434 }
435 
436 void goby::common::ConfigReader::build_description(const google::protobuf::Descriptor* desc,
437  std::ostream& stream,
438  const std::string& indent /*= ""*/,
439  bool use_color /* = true */)
440 {
441  for (int i = 0, n = desc->field_count(); i < n; ++i)
442  {
443  const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
444 
445  if (field_desc->options().GetExtension(goby::field).cfg().action() ==
446  goby::GobyFieldOptions::ConfigurationOptions::NEVER)
447  continue;
448 
449  build_description_field(field_desc, stream, indent, use_color);
450  }
451 
452  std::vector<const google::protobuf::FieldDescriptor*> extensions;
453  google::protobuf::DescriptorPool::generated_pool()->FindAllExtensions(desc, &extensions);
454  for (int i = 0, n = extensions.size(); i < n; ++i)
455  {
456  const google::protobuf::FieldDescriptor* field_desc = extensions[i];
457 
458  if (field_desc->options().GetExtension(goby::field).cfg().action() ==
459  goby::GobyFieldOptions::ConfigurationOptions::NEVER)
460  continue;
461 
462  build_description_field(field_desc, stream, indent, use_color);
463  }
464 }
465 
466 void goby::common::ConfigReader::build_description_field(
467  const google::protobuf::FieldDescriptor* field_desc, std::ostream& stream,
468  const std::string& indent, bool use_color)
469 {
470  google::protobuf::DynamicMessageFactory factory;
471  const google::protobuf::Message* default_msg =
472  factory.GetPrototype(field_desc->containing_type());
473 
474  if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
475  {
476  std::string before_description = indent;
477 
478  if (field_desc->is_extension())
479  {
480  if (field_desc->extension_scope())
481  before_description +=
482  "[" + field_desc->extension_scope()->full_name() + "." + field_desc->name();
483  else
484  before_description += "[" + field_desc->full_name();
485  }
486  else
487  {
488  before_description += field_desc->name();
489  }
490 
491  if (field_desc->is_extension())
492  before_description += "]";
493 
494  before_description += " { ";
495  stream << "\n" << before_description;
496 
497  std::string description;
498  if (use_color)
499  description += common::esc_green;
500  else
501  description += "# ";
502 
503  description +=
504  field_desc->options().GetExtension(goby::field).description() + label(field_desc);
505 
506  if (use_color)
507  description += common::esc_nocolor;
508 
509  if (!use_color)
510  wrap_description(&description, before_description.size());
511 
512  stream << description;
513 
514  build_description(field_desc->message_type(), stream, indent + " ", use_color);
515  stream << "\n" << indent << "}";
516  }
517  else
518  {
519  stream << "\n";
520 
521  std::string before_description = indent;
522 
523  std::string example;
524  if (field_desc->has_default_value())
525  google::protobuf::TextFormat::PrintFieldValueToString(*default_msg, field_desc, -1,
526  &example);
527  else
528  {
529  example = field_desc->options().GetExtension(goby::field).example();
530  if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_STRING)
531  example = "\"" + example + "\"";
532  }
533 
534  if (field_desc->is_extension())
535  {
536  if (field_desc->extension_scope())
537  before_description +=
538  "[" + field_desc->extension_scope()->full_name() + "." + field_desc->name();
539  else
540  before_description += "[" + field_desc->full_name();
541  }
542  else
543  {
544  before_description += field_desc->name();
545  }
546 
547  if (field_desc->is_extension())
548  before_description += "]";
549 
550  before_description += ": ";
551  before_description += example;
552  before_description += " ";
553 
554  stream << before_description;
555 
556  std::string description;
557 
558  if (use_color)
559  description += common::esc_green;
560  else
561  description += "# ";
562 
563  description += field_desc->options().GetExtension(goby::field).description();
564  if (field_desc->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_ENUM)
565  {
566  description += " (";
567  for (int i = 0, n = field_desc->enum_type()->value_count(); i < n; ++i)
568  {
569  if (i)
570  description += ", ";
571  description += field_desc->enum_type()->value(i)->name();
572  }
573 
574  description += ")";
575  }
576 
577  description += label(field_desc);
578 
579  if (field_desc->has_default_value())
580  description += " (default=" + example + ")";
581 
582  if (field_desc->options().GetExtension(goby::field).has_moos_global())
583  description += " (can also set MOOS global \"" +
584  field_desc->options().GetExtension(goby::field).moos_global() + "=\")";
585 
586  if (!use_color)
587  wrap_description(&description, before_description.size());
588 
589  stream << description;
590 
591  if (use_color)
592  stream << common::esc_nocolor;
593  }
594 }
595 
596 std::string goby::common::ConfigReader::label(const google::protobuf::FieldDescriptor* field_desc)
597 {
598  switch (field_desc->label())
599  {
600  case google::protobuf::FieldDescriptor::LABEL_REQUIRED: return " (required)";
601 
602  case google::protobuf::FieldDescriptor::LABEL_OPTIONAL: return " (optional)";
603 
604  case google::protobuf::FieldDescriptor::LABEL_REPEATED: return " (repeated)";
605  }
606 
607  return "";
608 }
609 
610 std::string goby::common::ConfigReader::word_wrap(std::string s, unsigned width,
611  const std::string& delim)
612 {
613  std::string out;
614 
615  while (s.length() > width)
616  {
617  std::string::size_type pos_newline = s.find("\n");
618  std::string::size_type pos_delim = s.substr(0, width).find_last_of(delim);
619  if (pos_newline < width)
620  {
621  out += s.substr(0, pos_newline);
622  s = s.substr(pos_newline + 1);
623  }
624  else if (pos_delim != std::string::npos)
625  {
626  out += s.substr(0, pos_delim + 1);
627  s = s.substr(pos_delim + 1);
628  }
629  else
630  {
631  out += s.substr(0, width);
632  s = s.substr(width);
633  }
634  out += "\n";
635 
636  // std::cout << "width: " << width << " " << out << std::endl;
637  }
638  out += s;
639 
640  return out;
641 }
642 
643 void goby::common::ConfigReader::wrap_description(std::string* description, int num_blanks)
644 {
645  *description =
646  word_wrap(*description, std::max(MAX_CHAR_PER_LINE - num_blanks, (int)MIN_CHAR), " ");
647 
648  if (MIN_CHAR > MAX_CHAR_PER_LINE - num_blanks)
649  {
650  *description = "\n" + description->substr(2);
651  num_blanks = MAX_CHAR_PER_LINE - MIN_CHAR;
652  }
653 
654  std::string spaces(num_blanks, ' ');
655  spaces += "# ";
656  boost::replace_all(*description, "\n", "\n" + spaces);
657 }
void set_name(const std::string &s)
Set the name of the application that the logger is serving.
Definition: flex_ostream.h:67
Contains functions for adding color to Terminal window streams.
Definition: term_color.h:54
Definition: time.h:241
google::protobuf::uint32 uint32
an unsigned 32 bit integer
STL namespace.
int run(int argc, char *argv[], Config *cfg)
Run a Goby application derived from MinimalApplicationBase. blocks caller until MinimalApplicationBas...
google::protobuf::int64 int64
a signed 64 bit integer
common::FlexOstream glog
Access the Goby logger through this object.
The global namespace for the Goby project.
google::protobuf::uint64 uint64
an unsigned 64 bit integer
void add_stream(logger::Verbosity verbosity=logger::VERBOSE, std::ostream *os=0)
Attach a stream object (e.g. std::cout, std::ofstream, ...) to the logger with desired verbosity...
Definition: flex_ostream.h:96