Leonetienne/Hazelnupp
Simple, easy to use, command line parameter interface
CmdArgsInterface.cpp
Go to the documentation of this file.
1 #include "CmdArgsInterface.h"
2 #include "VoidValue.h"
3 #include "IntValue.h"
4 #include "FloatValue.h"
5 #include "StringValue.h"
6 #include "ListValue.h"
7 #include "HazelnuppException.h"
8 #include "Placeholders.h"
9 #include "StringTools.h"
10 #include <iostream>
11 #include <cstdlib>
12 
13 using namespace Hazelnp;
14 
16 {
17  return;
18 }
19 
20 CmdArgsInterface::CmdArgsInterface(const int argc, const char* const* argv)
21 {
22  Parse(argc, argv);
23  return;
24 }
25 
27 {
28  for (auto& it : parameters)
29  delete it.second;
30 
31  parameters.clear();
32 
33  return;
34 }
35 
36 void CmdArgsInterface::Parse(const int argc, const char* const* argv)
37 {
38  try
39  {
40  // Populate raw arguments
41  PopulateRawArgs(argc, argv);
42 
43  // Expand abbreviations
44  ExpandAbbreviations();
45 
46  executableName = std::string(rawArgs[0]);
47 
48  // Read and parse all parameters
49  std::size_t i = 1;
50  while (i < rawArgs.size())
51  {
52  if ((rawArgs[i].length() > 2) && (rawArgs[i].substr(0, 2) == "--"))
53  {
54  Parameter* param = nullptr;
55  i = ParseNextParameter(i, param);
56 
57  parameters.insert(std::pair<std::string, Parameter*>(param->Key(), param));
58  }
59  else
60  i++;
61  }
62 
63  // Apply constraints such as default values, and required parameters.
64  // Types have already been enforced.
65  // Dont apply constraints when we are just printind the param docs
66  if ((!catchHelp) || (!HasParam("--help")))
67  ApplyConstraints();
68  }
70  {
71  if (crashOnFail)
72  {
73  std::cout << GenerateDocumentation() << std::endl << std::endl;
74  std::cerr << "Parameter error: " << exc.What() << std::endl;
75  exit(-1000);
76  }
77  else
78  throw exc; // yeet
79  }
80  catch (const HazelnuppConstraintMissingValue& exc)
81  {
82  if (crashOnFail)
83  {
84  std::cout << GenerateDocumentation() << std::endl << std::endl;
85  std::cerr << "Parameter error: " << exc.What() << std::endl;
86  exit(-1001);
87  }
88  else
89  throw exc; // yeet
90  }
91  catch (const HazelnuppConstraintTypeMissmatch& exc)
92  {
93  if (crashOnFail)
94  {
95  std::cout << GenerateDocumentation() << std::endl << std::endl;
96  std::cerr << "Parameter error: " << exc.What() << std::endl;
97  exit(-1002);
98  }
99  else
100  throw exc; // yeet
101  }
102  catch (const HazelnuppConstraintException& exc)
103  {
104  if (crashOnFail)
105  {
106  std::cout << GenerateDocumentation() << std::endl << std::endl;
107  std::cerr << "Parameter error: " << exc.What() << std::endl;
108  exit(-1003);
109  }
110  else
111  throw exc; // yeet
112  }
113  catch (const HazelnuppException& exc)
114  {
115  if (crashOnFail)
116  {
117  std::cout << GenerateDocumentation() << std::endl << std::endl;
118  std::cerr << "Parameter error: " << exc.What() << std::endl;
119  exit(-1004);
120  }
121  else
122  throw exc; // yeet
123  }
124 
125  // Catch --help parameter
126  if ((catchHelp) && (HasParam("--help")))
127  {
128  std::cout << GenerateDocumentation() << std::endl;
129  exit(0);
130  }
131 
132  return;
133 }
134 
135 std::size_t CmdArgsInterface::ParseNextParameter(const std::size_t parIndex, Parameter*& out_Par)
136 {
137  std::size_t i = parIndex;
138  const std::string key = rawArgs[parIndex];
139  std::vector<std::string> values;
140 
141  // Get values
142  for (i++; i < rawArgs.size(); i++)
143  // If not another parameter
144  if ((rawArgs[i].length() < 2) || (rawArgs[i].substr(0, 2) != "--"))
145  values.emplace_back(rawArgs[i]);
146  else
147  {
148  break;
149  }
150 
151  // Fetch constraint info
152  const ParamConstraint* pcn = GetConstraintForKey(key);
153 
154  Value* parsedVal = ParseValue(values, pcn);
155  if (parsedVal != nullptr)
156  {
157  out_Par = new Parameter(key, parsedVal);
158 
159  delete parsedVal;
160  parsedVal = nullptr;
161  }
162  else
163  throw std::runtime_error("Unable to parse parameter!");
164 
165  return i;
166 }
167 
168 void CmdArgsInterface::PopulateRawArgs(const int argc, const char* const* argv)
169 {
170  rawArgs.clear();
171  rawArgs.reserve(argc);
172 
173  for (int i = 0; i < argc; i++)
174  rawArgs.emplace_back(std::string(argv[i]));
175 
176  return;
177 }
178 
179 void CmdArgsInterface::ExpandAbbreviations()
180 {
181  // Abort if no abbreviations
182  if (parameterAbreviations.size() == 0)
183  return;
184 
185  for (std::string& arg : rawArgs)
186  {
187  // Is arg registered as an abbreviation?
188  auto abbr = parameterAbreviations.find(arg);
189  if (abbr != parameterAbreviations.end())
190  {
191  // Yes: replace arg with the long form
192  arg = abbr->second;
193  }
194  }
195 
196  return;
197 }
198 
199 bool CmdArgsInterface::HasParam(const std::string& key) const
200 {
201  return parameters.find(key) != parameters.end();
202 }
203 
204 Value* CmdArgsInterface::ParseValue(const std::vector<std::string>& values, const ParamConstraint* constraint)
205 {
206  // This is the raw (unconverted) data type the user provided
207  DATA_TYPE rawInputType;
208 
209  // Constraint values
210  const bool constrainType = (constraint != nullptr) && (constraint->constrainType);
211 
212  // Void-type
213  if (values.size() == 0)
214  {
215  rawInputType = DATA_TYPE::VOID;
216 
217  // Is a list forced via a constraint? If yes, return an empty list
218  if ((constrainType) &&
219  (constraint->requiredType == DATA_TYPE::LIST))
220  return new ListValue();
221 
222  // Is a string forced via a constraint? If yes, return an empty string
223  else if ((constrainType) &&
224  (constraint->requiredType == DATA_TYPE::STRING))
225  return new StringValue("");
226 
227  // Is an int or float forced via constraint? If yes, throw an exception
228  else if ((constrainType) &&
229  ((constraint->requiredType == DATA_TYPE::INT) ||
230  (constraint->requiredType == DATA_TYPE::FLOAT)))
232  constraint->key,
233  constraint->requiredType,
234  rawInputType,
235  GetDescription(constraint->key)
236  );
237 
238  // Else, just return the void type
239  return new VoidValue;
240  }
241 
242  // Force void type by constraint
243  else if ((constrainType) &&
244  (constraint->requiredType == DATA_TYPE::VOID))
245  {
246  return new VoidValue;
247  }
248 
249  // List-type
250  else if (values.size() > 1)
251  {
252  rawInputType = DATA_TYPE::LIST;
253 
254  // Should the type be something other than list?
255  if ((constrainType) &&
256  (constraint->requiredType != DATA_TYPE::LIST))
257  {
259  constraint->key,
260  constraint->requiredType,
261  rawInputType,
262  GetDescription(constraint->key)
263  );
264  }
265 
266  ListValue* newList = new ListValue();
267  for (const std::string& val : values)
268  {
269  Value* tmp = ParseValue({ val });
270  newList->AddValue(tmp);
271  delete tmp;
272  }
273  return newList;
274  }
275 
276  // Now we're only dealing with a single value
277  const std::string& val = values[0];
278 
279  // String
280  if (!Internal::StringTools::IsNumeric(val, true))
281  {
282  rawInputType = DATA_TYPE::STRING;
283 
284  // Is the type not supposed to be a string?
285  // void and list are already sorted out
286  if ((constrainType) &&
287  (constraint->requiredType != DATA_TYPE::STRING))
288  {
289  // We can only force a list-value from here
290  if (constraint->requiredType == DATA_TYPE::LIST)
291  {
292  ListValue* list = new ListValue();
293  Value* tmp = ParseValue({ val });
294  list->AddValue(tmp);
295  delete tmp;
296  tmp = nullptr;
297  return list;
298  }
299  // Else it is not possible to convert to a numeric
300  else
302  constraint->key,
303  constraint->requiredType,
304  rawInputType,
305  GetDescription(constraint->key)
306  );
307  }
308 
309  return new StringValue(val);
310  }
311 
312  // In this case we have a numeric value.
313  // We should still produce a string if requested
314  if ((constrainType) &&
315  (constraint->requiredType == DATA_TYPE::STRING))
316  return new StringValue(val);
317 
318  // Numeric
319  bool isInt;
320  long double num;
321 
322  if (Internal::StringTools::ParseNumber(val, isInt, num))
323  {
324  rawInputType = isInt ? DATA_TYPE::INT : DATA_TYPE::FLOAT;
325 
326  // Is the type constrained?
327  // (only int and float left)
328  if (constrainType)
329  {
330  // Must it be an integer?
331  if (constraint->requiredType == DATA_TYPE::INT)
332  return new IntValue((long long int)num);
333  // Must it be a floating point?
334  else if (constraint->requiredType == DATA_TYPE::FLOAT)
335  return new FloatValue(num);
336  // Else it must be a List
337  else
338  {
339  ListValue* list = new ListValue();
340  Value* tmp = ParseValue({ val });
341  list->AddValue(tmp);
342  delete tmp;
343  tmp = nullptr;
344  return list;
345  }
346  }
347  // Type is not constrained
348  else
349  {
350  // Integer
351  if (isInt)
352  return new IntValue((long long int)num);
353 
354  // Double
355  return new FloatValue(num);
356  }
357  }
358 
359  // Failed
360  return nullptr;
361 }
362 
364 {
365  return crashOnFail;
366 }
367 
368 void CmdArgsInterface::SetCatchHelp(bool catchHelp)
369 {
370  this->catchHelp = catchHelp;
371  return;
372 }
373 
375 {
376  return catchHelp;
377 }
378 
379 void CmdArgsInterface::SetBriefDescription(const std::string& description)
380 {
381  briefDescription = description;
382  return;
383 }
384 
386 {
387  return briefDescription;
388 }
389 
390 void Hazelnp::CmdArgsInterface::RegisterDescription(const std::string& parameter, const std::string& description)
391 {
392  parameterDescriptions[parameter] = description;
393  return;
394 }
395 
396 const std::string& Hazelnp::CmdArgsInterface::GetDescription(const std::string& parameter) const
397 {
398  // Do we already have a description for this parameter?
399  if (!HasDescription(parameter))
400  // No? Then return ""
402 
403  // We do? Then return it
404  return parameterDescriptions.find(parameter)->second;
405 }
406 
407 bool CmdArgsInterface::HasDescription(const std::string& parameter) const
408 {
409  return parameterDescriptions.find(parameter) != parameterDescriptions.end();
410 }
411 
412 void CmdArgsInterface::ClearDescription(const std::string& parameter)
413 {
414  // This will just do nothing if the entry does not exist
415  parameterDescriptions.erase(parameter);
416  return;
417 }
418 
420 {
421  parameterDescriptions.clear();
422  return;
423 }
424 
426 {
427  std::stringstream ss;
428 
429  // Add brief, if available
430  if (briefDescription.length() > 0)
431  ss << briefDescription << std::endl;
432 
433  // Collect parameter information
434  struct ParamDocEntry
435  {
436  std::string abbreviation;
437  std::string description;
438  std::string type;
439  bool required = false;
440  bool typeIsForced = false;
441  std::string defaultVal;
442  std::string incompatibilities;
443  };
444  std::unordered_map<std::string, ParamDocEntry> paramInfos;
445 
446  // Collect descriptions
447  for (const auto& it : parameterDescriptions)
448  {
449  // Do we already have that param in the paramInfo set?
450  if (paramInfos.find(it.first) == paramInfos.end())
451  // No? Create it.
452  paramInfos[it.first] = ParamDocEntry();
453 
454  paramInfos[it.first].description = it.second;
455  }
456 
457  // Collect abbreviations
458  // first value is abbreviation, second is long form
459  for (const auto& it : parameterAbreviations)
460  {
461  // Do we already have that param in the paramInfo set?
462  if (paramInfos.find(it.second) == paramInfos.end())
463  // No? Create it.
464  paramInfos[it.second] = ParamDocEntry();
465 
466  paramInfos[it.second].abbreviation = it.first;
467  }
468 
469  // Collect constraints
470  for (const auto& it : parameterConstraints)
471  {
472  // Do we already have that param in the paramInfo set?
473  if (paramInfos.find(it.first) == paramInfos.end())
474  // No? Create it.
475  paramInfos[it.first] = ParamDocEntry();
476 
477  ParamDocEntry& cached = paramInfos[it.first];
478  cached.required = it.second.required;
479  cached.typeIsForced = it.second.constrainType;
480  cached.type = DataTypeToString(it.second.requiredType);
481 
482  // Build default-value string
483  std::stringstream vec2str_ss;
484  for (const std::string& s : it.second.defaultValue)
485  {
486  vec2str_ss << '\'' << s << '\'';
487 
488  // Add a space if we are not at the last entry
489  if ((void*)&s != (void*)&it.second.defaultValue.back())
490  vec2str_ss << " ";
491  }
492  cached.defaultVal = vec2str_ss.str();
493 
494 
495  // Build incompatibilities string
496  vec2str_ss.str("");
497  for (const std::string& s : it.second.incompatibleParameters)
498  {
499  vec2str_ss << s;
500 
501  // Add a comma-space if we are not at the last entry
502  if ((void*)&s != (void*)&it.second.incompatibleParameters.back())
503  vec2str_ss << ", ";
504  }
505  cached.incompatibilities = vec2str_ss.str();
506  }
507 
508  // Now generate the documentation body
509  if (paramInfos.size() > 0)
510  {
511  ss << std::endl
512  << "==== AVAILABLE PARAMETERS ===="
513  << std::endl << std::endl;
514 
515  std::size_t counter = 0;
516  for (const auto& it : paramInfos)
517  {
518  const ParamDocEntry& pde = it.second;
519 
520  // Put name
521  ss << it.first << " ";
522 
523  // Put abbreviation
524  if (pde.abbreviation.length() > 0)
525  ss << pde.abbreviation << " ";
526 
527  // Put type
528  if (pde.typeIsForced)
529  ss << pde.type << " ";
530 
531  // Put default value
532  if (pde.defaultVal.length() > 0)
533  ss << "default=[" << pde.defaultVal << "] ";
534 
535  // Put incompatibilities
536  if (pde.incompatibilities.length() > 0)
537  ss << "incompatibilities=[" << pde.incompatibilities << "] ";
538 
539  // Put required tag, but only if no default value
540  if ((pde.required) && (pde.defaultVal.length() == 0))
541  ss << "[[REQUIRED]] ";
542 
543  // Put brief description
544  if (pde.description.length() > 0)
545  ss << pde.description;
546 
547  // Omit linebreaks when we're on the last element
548  if (counter < paramInfos.size()-1)
549  ss << std::endl << std::endl;
550 
551  counter++;
552  }
553  }
554 
555  return ss.str();
556 }
557 
558 void CmdArgsInterface::ApplyConstraints()
559 {
560  // Enforce required parameters / default values
561  for (const auto& pc : parameterConstraints)
562  // Parameter in question is not supplied
563  if (!HasParam(pc.second.key))
564  {
565  // Do we have a default value?
566  if (pc.second.defaultValue.size() > 0)
567  {
568  // Then create it now, by its default value
569  Value* tmp = ParseValue(pc.second.defaultValue, &pc.second);
570  parameters.insert(std::pair<std::string, Parameter*>(
571  pc.second.key,
572  new Parameter(pc.second.key, tmp)
573  ));
574 
575  delete tmp;
576  tmp = nullptr;
577  }
578  // So we do not have a default value...
579  else
580  {
581  // Is it important to have the missing parameter?
582  if (pc.second.required)
583  // Throw an error message then
585  pc.second.key,
586  GetDescription(pc.second.key)
587  );
588  }
589  }
590  // The parameter in question IS supplied
591  else
592  {
593  // Enforce parameter incompatibility
594 
595  // Is ANY parameter present listed as incompatible with our current one?
596  for (const std::string& incompatibility : pc.second.incompatibleParameters)
597  for (const auto& otherParam : parameters)
598  {
599  if (otherParam.first == incompatibility)
600  throw HazelnuppConstraintIncompatibleParameters(pc.second.key, incompatibility);
601  }
602  }
603 
604  return;
605 }
606 
607 ParamConstraint CmdArgsInterface::GetConstraint(const std::string& parameter) const
608 {
609  return parameterConstraints.find(parameter)->second;
610 }
611 
612 void CmdArgsInterface::ClearConstraint(const std::string& parameter)
613 {
614  parameterConstraints.erase(parameter);
615  return;
616 }
617 
618 const std::string& CmdArgsInterface::GetExecutableName() const
619 {
620  return executableName;
621 }
622 
623 const Value& CmdArgsInterface::operator[](const std::string& key) const
624 {
625  // Throw exception if param is unknown
626  if (!HasParam(key))
628 
629  return *parameters.find(key)->second->GetValue();
630 }
631 
632 void CmdArgsInterface::RegisterAbbreviation(const std::string& abbrev, const std::string& target)
633 {
634  parameterAbreviations.insert(std::pair<std::string, std::string>(abbrev, target));
635  return;
636 }
637 
638 const std::string& CmdArgsInterface::GetAbbreviation(const std::string& abbrev) const
639 {
640  if (!HasAbbreviation(abbrev))
642 
643  return parameterAbreviations.find(abbrev)->second;
644 }
645 
646 bool CmdArgsInterface::HasAbbreviation(const std::string& abbrev) const
647 {
648  return parameterAbreviations.find(abbrev) != parameterAbreviations.end();
649 }
650 
651 void CmdArgsInterface::ClearAbbreviation(const std::string& abbrevation)
652 {
653  parameterAbreviations.erase(abbrevation);
654  return;
655 }
656 
658 {
659  parameterAbreviations.clear();
660  return;
661 }
662 
663 void CmdArgsInterface::RegisterConstraint(const std::string& key, const ParamConstraint& constraint)
664 {
665  // Magic syntax, wooo
666  (parameterConstraints[key] = constraint).key = key;
667  return;
668 }
669 
671 {
672  parameterConstraints.clear();
673  return;
674 }
675 
676 void CmdArgsInterface::SetCrashOnFail(bool crashOnFail)
677 {
678  this->crashOnFail = crashOnFail;
679  return;
680 }
681 
682 const ParamConstraint* CmdArgsInterface::GetConstraintForKey(const std::string& key) const
683 {
684  const auto constraint = parameterConstraints.find(key);
685 
686  if (constraint == parameterConstraints.end())
687  return nullptr;
688 
689  return &constraint->second;
690 }
bool HasAbbreviation(const std::string &abbrev) const
Will check wether or not an abbreviation is registered.
Specializations for void values.
Definition: VoidValue.h:8
const Value & operator[](const std::string &key) const
Will return the value given a key.
Specializations for string values (uses std::string)
Definition: StringValue.h:9
bool GetCrashOnFail() const
Gets whether the application crashes on an exception whilst parsing, and prints to stderr...
static bool IsNumeric(const std::string &str, const bool allowDecimalPoint=false)
Will return true if the given string consists only of digits (including signage)
Definition: StringTools.cpp:56
static const std::string g_emptyString
The only purpose of this is to provide the ability to return an empty string as an error for std::str...
Definition: Placeholders.h:9
Specializations for integer values (uses long long int)
Definition: IntValue.h:8
const std::string & Key() const
Will return the key of this parameter.
Definition: Parameter.cpp:21
Gets thrown when a parameter is of a type that does not match the required type, and is not convertib...
void Parse(const int argc, const char *const *argv)
Will parse command line arguments.
Specializations for floating point values (uses long double)
Definition: FloatValue.h:9
bool GetCatchHelp() const
Retruns whether the CmdArgsInterface should automatically catch the –help parameter, print the parameter documentation to stdout, and exit or not.
Gets thrown something bad happens because of parameter constraints.
const std::string & What() const
Will return an error message.
void RegisterDescription(const std::string &parameter, const std::string &description)
Willl register a short description for a parameter.
Specializations for list values (uses std::vector<Value*>)
Definition: ListValue.h:9
DATA_TYPE requiredType
Constrain the parameter to this value. Requires constrainType to be set to true.
const std::string & GetBriefDescription()
Returns the brief description of the application to be automatically added to the documentation...
const std::string & GetAbbreviation(const std::string &abbrev) const
Will return the long form of an abbreviation (like –force for -f) Returns "" if no match is found...
void SetCatchHelp(bool catchHelp)
Sets whether the CmdArgsInterface should automatically catch the –help parameter, print the parameter documentation to stdout, and exit or not.
void RegisterConstraint(const std::string &key, const ParamConstraint &constraint)
Will register a constraint for a parameter.
const std::string & GetDescription(const std::string &parameter) const
Will return a short description for a parameter, if it exists.
Gets thrown when a parameter constrained to be required is not provided, and has no default value set...
static bool ParseNumber(const std::string &str, bool &out_isInt, long double &out_number)
Will convert the number in str to a number.
Definition: StringTools.cpp:82
ParamConstraint GetConstraint(const std::string &parameter) const
Will return the constraint information for a specific parameter.
void ClearDescription(const std::string &parameter)
Will delete the description of a parameter if it exists.
void ClearConstraint(const std::string &parameter)
Will the constraint of a specific parameter.
void SetBriefDescription(const std::string &description)
Sets a brief description of the application to be automatically added to the documentation.
void ClearConstraints()
Will delete all constraints.
std::string GenerateDocumentation() const
Will generate a text-based documentation suited to show the user, for example on –help.
void ClearAbbreviations()
Will delete all abbreviations.
void ClearAbbreviation(const std::string &abbrevation)
Will delete the abbreviation for a given parameter.
static std::string DataTypeToString(DATA_TYPE type)
Definition: DataType.h:17
void RegisterAbbreviation(const std::string &abbrev, const std::string &target)
Will register an abbreviation (like -f for –force)
Generic hazelnupp exception.
void ClearDescriptions()
Will delete all parameter descriptions.
Abstract class for values.
Definition: Value.h:10
bool HasParam(const std::string &key) const
Will check wether a parameter exists given a key, or not.
bool constrainType
Should this parameter be forced to be of a certain type? Remember to set constrainTo to the wanted ty...
bool HasDescription(const std::string &parameter) const
Returns whether or not a given parameter has a registered description.
DATA_TYPE
The different data types a paramater can be.
Definition: DataType.h:8
Gets thrown when an non-existent key gets dereferenced.
void SetCrashOnFail(bool crashOnFail)
Sets whether to crash the application, and print to stderr, when an exception is raised whilst parsin...
Gets thrown when a parameter constrained to be incompatible with other parameters gets supplied along...
void AddValue(const Value *value)
Will add this value to the list.
Definition: ListValue.cpp:33
const std::string & GetExecutableName() const
Will return argv[0], the name of the executable.