Nymph  v1.5.2
Flow-Based Data Processing Framework
KTCommandLineHandler.cc
Go to the documentation of this file.
1 /*
2  * KTCommandLineHandler.cxx
3  *
4  * Created on: Nov 21, 2011
5  * Author: nsoblath
6  */
7 
9 
10 #include "KTCommandLineOption.hh"
11 
12 //#include "NymphConfig.hh"
13 
14 #include <sstream>
15 
16 #ifndef PACKAGE_STRING
17 #define PACKAGE_STRING Nymph (unknown version)
18 #endif
19 #define STRINGIFY_1(x) #x
20 #define STRINGIFY_2(x) STRINGIFY_1(x)
21 
22 using std::string;
23 using std::vector;
24 
25 namespace Nymph
26 {
27  KTLOGGER(utillog, "KTCommandLineHandler");
28 
30  : std::logic_error(why)
31  {}
32 
34  fExecutableName("NONE"),
35  fPackageString(STRINGIFY_2(PACKAGE_STRING)),
36  fNArgs(0),
37  fArgV(NULL),
38  fArgumentsTaken(false),
39  fCommandLineOptions(),
40  fPrintHelpOptions(),
41  fCommandLineParseLater(),
42  fParsedOptions(NULL),
43  fCommandLineVarMap(),
44  fConfigOverrideValues(),
45  fPrintHelpMessage(false),
46  fPrintVersionMessage(false),
47  fPrintHelpMessageAfterConfig(false),
48  fConfigFilename(),
49  fCommandLineJSON()
50  {
51  }
52 
54  {
55  while (! fProposedGroups.empty())
56  {
57  OptDescMapIt tIter = fProposedGroups.begin();
58  delete tIter->second;
59  fProposedGroups.erase(tIter);
60  }
61  }
62 
63  bool KTCommandLineHandler::TakeArguments(int argC, char** argV)
64  {
65  if (fArgumentsTaken) return false;
66 
67  fNArgs = argC;
68  fArgV = argV;
69  fArgumentsTaken = true;
70 
71  return true;
72  }
73 
74  bool KTCommandLineHandler::ProcessCommandLine(int argC, char** argV)
75  {
76  TakeArguments(argC, argV);
77 
79 
81  }
82 
83  //**************
84 
86  {
87  return fArgumentsTaken;
88  }
89 
91  {
92  return fNArgs;
93  }
94 
96  {
97  return fArgV;
98  }
99 
100  //**************
101 
103  {
104  po::options_description* tNewOpts = new po::options_description(aTitle);
105  std::pair< OptDescMapIt, bool > result = fProposedGroups.insert(OptDescMap::value_type(aTitle, tNewOpts));
106  if (! result.second)
107  {
108  KTWARN(utillog, "There is already an option group with title <" << aTitle << ">");
109  delete tNewOpts;
110  }
111 
112  return result.first;
113  }
114 
115  bool KTCommandLineHandler::AddOption(const string& aTitle, const string& aHelpMsg, const string& aLongOpt, char aShortOpt, bool aWarnOnDuplicate)
116  {
117  if (fAllOptionsLong.find(aLongOpt) != fAllOptionsLong.end())
118  {
119  if (aWarnOnDuplicate)
120  KTWARN(utillog, "There is already an option called <" << aLongOpt << ">");
121  return false;
122  }
123  if (aShortOpt != '#')
124  {
125  if (fAllOptionsShort.find(aShortOpt) != fAllOptionsShort.end())
126  {
127  if (aWarnOnDuplicate)
128  KTWARN(utillog, "There is already a short option called <" << aShortOpt << ">");
129  return false;
130  }
131  }
132 
133  // option is okay at this point
134 
135  OptDescMapIt tIter = fProposedGroups.find(aTitle);
136  if (tIter == fProposedGroups.end())
137  {
138  tIter = CreateNewOptionGroup(aTitle);
139  }
140 
141  string tOptionName = aLongOpt;
142  fAllOptionsLong.insert(aLongOpt);
143  if (aShortOpt != '#')
144  {
145  tOptionName += "," + string(&aShortOpt);
146  fAllOptionsShort.insert(aShortOpt);
147  }
148  tIter->second->add_options()(tOptionName.c_str(), aHelpMsg.c_str());
149 
150  return true;
151  }
152 
153  bool KTCommandLineHandler::AddOption(const string& aTitle, const string& aHelpMsg, const string& aLongOpt, bool aWarnOnDuplicate)
154  {
155  if (fAllOptionsLong.find(aLongOpt) != fAllOptionsLong.end())
156  {
157  if (aWarnOnDuplicate)
158  KTWARN(utillog, "There is already an option called <" << aLongOpt << ">");
159  return false;
160  }
161 
162  // option is okay at this point
163 
164  OptDescMapIt tIter = fProposedGroups.find(aTitle);
165  if (tIter == fProposedGroups.end())
166  {
167  tIter = CreateNewOptionGroup(aTitle);
168  }
169 
170  fAllOptionsLong.insert(aLongOpt);
171  tIter->second->add_options()(aLongOpt.c_str(), aHelpMsg.c_str());
172 
173  return true;
174  }
175 
176  po::options_description* KTCommandLineHandler::GetOptionsDescription(const string& aKey)
177  {
178  OptDescMapIt tIter = fProposedGroups.find(aKey);
179  if (tIter == fProposedGroups.end())
180  {
181  KTWARN(utillog, "There no proposed option group with key <" << aKey << ">");
182  return NULL;
183  }
184  return tIter->second;
185  }
186 
187  //**************
188 
190  {
191  for (OptDescMapIt tIter = fProposedGroups.begin(); tIter != fProposedGroups.end(); tIter++)
192  {
193  if (! AddCommandLineOptions(*(tIter->second)))
194  {
195  return false;
196  }
197  delete tIter->second;
198  }
199  fProposedGroups.clear();
200 
201  return true;
202  }
203 
204  bool KTCommandLineHandler::AddCommandLineOptions(const po::options_description& aSetOfOpts)
205  {
206  try
207  {
208  fCommandLineOptions.add(aSetOfOpts);
209  fPrintHelpOptions.add(aSetOfOpts);
210  }
211  catch (std::exception& e)
212  {
213  KTERROR(utillog, "Exception thrown while adding options: " << e.what());
214  return false;
215  }
216  catch (...)
217  {
218  KTERROR(utillog, "Exception was thrown, but caught in a generic way!");
219  return false;
220  }
221  return true;
222  }
223 
224  //**************
225 
226  bool KTCommandLineHandler::IsCommandLineOptSet(const string& aCLOption)
227  {
228  return fCommandLineVarMap.count(aCLOption) != 0;
229  }
230 
231  //**************
232 
234  {
235  if (! fProposedGroups.empty())
236  {
237  if (! this->FinalizeNewOptionGroups())
238  {
239  KTERROR(utillog, "An error occurred while adding the proposed option groups\n" <<
240  "Command-line options were not parsed");
241  return false;
242  }
243  }
244 
245  // Parse the command line options that remain after the initial parsing
246  try
247  {
248  fParsedOptions = po::command_line_parser(fCommandLineParseLater).options(fCommandLineOptions).allow_unregistered().run();
249  }
250  catch (std::exception& e)
251  {
252  KTERROR(utillog, "An error occurred while boost was parsing the command line options:\n" << e.what());
253  return false;
254  }
255 
256  // these will be the unregistered items, which are assumed to be intended to edit the config file
257  vector< string > tRemainingToParse = po::collect_unrecognized(fParsedOptions.options, po::include_positional);
258 
259  // Create the variable map from the parse options
261  po::notify(fCommandLineVarMap);
262 
263  // now parse the remaining items into the config override param node
264  for (vector< string >::const_iterator tokenIt = tRemainingToParse.begin(); tokenIt != tRemainingToParse.end(); ++tokenIt)
265  {
266  string argument(*tokenIt);
267  size_t t_name_pos = argument.find_first_not_of( fDash );
268  size_t t_val_pos = argument.find_first_of( fSeparator );
269  // the name should have 2 dashes before it, and there should be a separator
270  if( t_name_pos == 2 && t_val_pos != string::npos )
271  {
272  string t_full_name(argument.substr( t_name_pos, t_val_pos-2 ));
273 
274  size_t t_node_start_pos = 0;
275  size_t t_node_sep_pos = t_full_name.find_first_of( fNodeSeparator );
276  scarab::param_node& parentNode = fConfigOverrideValues;
277  while (t_node_sep_pos != string::npos)
278  {
279  string nodeName(t_full_name.substr(t_node_start_pos, t_node_sep_pos));
280  if (parentNode.has(nodeName))
281  {
282  parentNode = parentNode[nodeName].as_node();
283  }
284  else
285  {
286  parentNode.add(nodeName, scarab::param_ptr_t( new scarab::param_node()));
287  parentNode = parentNode[nodeName].as_node();
288  }
289  t_node_start_pos = t_node_sep_pos + 1;
290  t_node_sep_pos = t_full_name.find_first_of(fNodeSeparator, t_node_start_pos);
291  }
292 
293  string valueName(t_full_name.substr(t_node_start_pos, t_val_pos));
294 
295  scarab::param_value* new_value = new scarab::param_value();
296  new_value->set(argument.substr( t_val_pos + 1 ));
297 
298  //std::cout << "(parser) adding < " << t_name << "<" << t_type << "> > = <" << new_value.value() << ">" << std::endl;
299 
300  parentNode.replace( valueName, new_value );
301 
302  continue;
303  }
304 
305  KTERROR(utillog, "Argument <" << argument << "> does not match --<name>=<value> pattern");
306  return false;
307  }
308 
309  return true;
310  }
311 
313  {
314  if (fNArgs <= 0 || fArgV == NULL)
315  {
316  fNArgs = 0;
317  fArgV = NULL;
318  return;
319  }
320 
321  // Get the executable name
322  if (fNArgs >= 1) fExecutableName = string(fArgV[0]);
323 
324  // If no arguments were given, just return now
325  if (fNArgs == 1) return;
326 
327  // Define general options, and add them to the complete option list
328  po::options_description tGeneralOpts("General options");
329  tGeneralOpts.add_options()("help,h", "Print help message")("help-config", "Print help message after reading config file")("version,v", "Print version information");
330  /* WHEN NOT USING POSITIONAL CONFIG FILE ARGUMENT */
331  tGeneralOpts.add_options()("config,c", po::value< string >(), "Configuration file");
332  tGeneralOpts.add_options()("json,j", po::value< string >(), "Command-Line JSON");
333 
334  // We want to have the general options printed if --help is used
335  fPrintHelpOptions.add(tGeneralOpts);
336 
337  // Fill in the duplication-checking sets
338  fAllGroupKeys.insert("General");
339  fAllOptionsLong.insert("help");
340  fAllOptionsShort.insert('h');
341  fAllOptionsLong.insert("help-config");
342  fAllOptionsLong.insert("version");
343  fAllOptionsShort.insert('v');
344 
345  // Define the option for the user configuration file; this does not get printed in list of options when --help is used
346  po::options_description tHiddenOpts("Hidden options");
347  /* WHEN USING POSITIONAL CONFIG FILE ARGUMENT
348  tHiddenOpts.add_options()("config-file", po::value< string >(), "Configuration file");
349  */
350  // Add together any options that will be parsed here, in the initial command-line processing
351  po::options_description tInitialOptions("Initial options");
352  tInitialOptions.add(tGeneralOpts).add(tHiddenOpts);
353 
354  // Allow the UserConfiguration file to be specified with the only positional option
355  /* WHEN USING POSITIONAL CONFIG FILE ARGUMENT
356  po::positional_options_description tPositionOpt;
357  tPositionOpt.add("config-file", 1);
358  */
359 
360  // Add contributions of other options from elsewhere in Nymph
362 
363  // Command line style
364  po::command_line_style::style_t pstyle = po::command_line_style::unix_style;
365  /*
366  po::command_line_style::style_t pstyle = po::command_line_style::style_t(
367  po::command_line_style::allow_long |
368  po::command_line_style::allow_short |
369  po::command_line_style::allow_dash_for_short |
370  po::command_line_style::long_allow_next |
371  po::command_line_style::long_allow_adjacent |
372  po::command_line_style::short_allow_next |
373  po::command_line_style::short_allow_adjacent |
374  po::command_line_style::allow_guessing |
375  po::command_line_style::allow_sticky);
376  */
377 
378  po::parsed_options tParsedOpts(NULL);
379  // Parse the command line looking only for the general options
380  try
381  {
382  tParsedOpts = po::command_line_parser(fNArgs, fArgV).style(pstyle).options(tInitialOptions).allow_unregistered().run();
383  /* WHEN USING POSITIONAL CONFIG FILE ARGUMENT
384  tParsedOpts = po::command_line_parser(fNArgs, fArgV).style(pstyle).options(tInitialOptions).positional(tPositionOpt).allow_unregistered().run();
385  */
386  }
387  catch (std::exception& e)
388  {
389  KTERROR(utillog, "Exception caught while performing initial CL parsing:\n"
390  << '\t' << e.what());
391  throw std::logic_error(e.what());
392  }
393  // Save the remaining command-line options for later parsing (after the full option list has been populated)
394  fCommandLineParseLater = po::collect_unrecognized(tParsedOpts.options, po::include_positional);
395  /* some debugging couts
396  std::cout << "there are " << fCommandLineParseLater.size() << " tokens to parse later." << std::endl;
397  for (unsigned i = 0; i < fCommandLineParseLater.size(); i++)
398  {
399  std::cout << " " << fCommandLineParseLater[i] << std::endl;
400  }
401  */
402 
403  // Create the variable map from the general options
404  po::variables_map tGeneralOptsVarMap;
405  po::store(tParsedOpts, tGeneralOptsVarMap);
406  po::notify(tGeneralOptsVarMap);
407 
408  // Use the general options information
409  if (tGeneralOptsVarMap.count("help"))
410  {
411  fPrintHelpMessage = true;
412  }
413  if (tGeneralOptsVarMap.count("help-config"))
414  {
416  }
417  if (tGeneralOptsVarMap.count("version"))
418  {
419  fPrintVersionMessage = true;
420  }
421  if (tGeneralOptsVarMap.count("config"))
422  {
423  fConfigFilename = tGeneralOptsVarMap["config"].as< string >();
424  }
425  if (tGeneralOptsVarMap.count("json"))
426  {
427  fCommandLineJSON = tGeneralOptsVarMap["json"].as< string >();
428  }
429 
430  return;
431  }
432 
433  //**************
434 
436  {
437  KTPROG(utillog, "\nUsage: " << fExecutableName << " [options]\n\n" <<
438  " If using a config file, it should be specified as: -c config_file.json\n" <<
439  " Config file options can be modified using: --address.of.option=\"value\"\n" <<
441  return;
442  }
443 
445  {
446  KTPROG(utillog, fExecutableName << " -- Version Information\n" << "Built with: " << fPackageString);
447  return;
448  }
449 
450 
451 
452 } /* namespace Nymph */
#define PACKAGE_STRING
CommandLineHandlerException(std::string const &why)
std::vector< std::string > fCommandLineParseLater
void InitialCommandLineProcessing()
Parses the general options and stores the remaining options available for later parsing.
OptDescMap::iterator OptDescMapIt
bool IsCommandLineOptSet(const std::string &aCLOption)
Check if a command line option was set.
po::options_description fPrintHelpOptions
bool AddOption(const std::string &aTitle, const std::string &aHelpMsg, const std::string &aLongOpt, char aShortOpt, bool aWarnOnDuplicate=true)
Simple option adding function, with short option (flag only; no values allowed)
bool ProcessCommandLine(int argC, char **argV)
STL namespace.
bool TakeArguments(int argC, char **argV)
scarab::param_node fConfigOverrideValues
po::options_description fCommandLineOptions
std::set< std::string > fAllOptionsLong
po::options_description * GetOptionsDescription(const std::string &aKey)
Request access to the options description object for more freedom (and responsibility!) in adding opt...
OptDescMapIt CreateNewOptionGroup(const std::string &aTitle)
Makes a new option group available for command line options.
bool DelayedCommandLineProcessing()
Parses the remaining command line options (those that weren&#39;t parsed during the InitialCommandLinePro...
KTLOGGER(applog, "KTApplication")
Contains KTCommandLineHandler.
#define STRINGIFY_2(x)
#define KTPROG(...)
Definition: KTLogger.hh:345
bool FinalizeNewOptionGroups()
Adds the groups of options to the set of usable options groups (note: this must be called to make the...
bool AddCommandLineOptions(const po::options_description &aSetOfOpts)
Adds a set of command line options.
#define KTERROR(...)
Definition: KTLogger.hh:347
#define KTWARN(...)
Definition: KTLogger.hh:346
std::set< std::string > fAllGroupKeys