Leonetienne/Hazelnupp
Simple, easy to use, command line parameter interface
Hazelnupp.cpp
Go to the documentation of this file.
1 #include "Hazelnupp.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 Hazelnupp::Hazelnupp(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 Hazelnupp::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  std::size_t i = 1;
49  while (i < rawArgs.size())
50  {
51  if ((rawArgs[i].length() > 2) && (rawArgs[i].substr(0, 2) == "--"))
52  {
53  Parameter* param = nullptr;
54  i = ParseNextParameter(i, param);
55 
56  parameters.insert(std::pair<std::string, Parameter*>(param->Key(), param));
57  }
58  else
59  i++;
60  }
61 
62  // Apply constraints such as default values, and required parameters.
63  // Types have already been enforced.
64  // Dont apply constraints when we are just printind the param docs
65  if ((!catchHelp) || (!HasParam("--help")))
66  ApplyConstraints();
67  }
68  catch (const HazelnuppConstraintTypeMissmatch& hctm)
69  {
70  if (crashOnFail)
71  {
72  std::cout << GenerateDocumentation() << std::endl;
73  std::cerr << "Fatal error: Command-line parameter value-type mismatch at \"" << hctm.What() << "\"!";
74  quick_exit(-1009);
75  }
76  else
77  throw hctm; // yeet
78  }
79  catch (const HazelnuppConstraintMissingValue& hctm)
80  {
81  if (crashOnFail)
82  {
83  std::cout << GenerateDocumentation() << std::endl;
84  std::cerr << "Fatal error: Missing required command-line parameter \"" << hctm.What() << "\"!";
85  quick_exit(-1010);
86  }
87  else
88  throw hctm; // yeet
89  }
90 
91  // Catch --help parameter
92  if ((catchHelp) && (HasParam("--help")))
93  {
94  std::cout << GenerateDocumentation() << std::endl;
95  quick_exit(0);
96  }
97 
98  return;
99 }
100 
101 std::size_t Hazelnupp::ParseNextParameter(const std::size_t parIndex, Parameter*& out_Par)
102 {
103  std::size_t i = parIndex;
104  const std::string key = rawArgs[parIndex];
105  std::vector<std::string> values;
106 
107  // Get values
108  for (i++; i < rawArgs.size(); i++)
109  // If not another parameter
110  if ((rawArgs[i].length() < 2) || (rawArgs[i].substr(0, 2) != "--"))
111  values.emplace_back(rawArgs[i]);
112  else
113  {
114  break;
115  }
116 
117  // Fetch constraint info
118  const ParamConstraint* pcn = GetConstraintForKey(key);
119 
120  Value* parsedVal = ParseValue(values, pcn);
121  if (parsedVal != nullptr)
122  {
123  out_Par = new Parameter(key, parsedVal);
124 
125  delete parsedVal;
126  parsedVal = nullptr;
127  }
128  else
129  throw std::runtime_error("Unable to parse parameter!");
130 
131  return i;
132 }
133 
134 void Hazelnupp::PopulateRawArgs(const int argc, const char* const* argv)
135 {
136  rawArgs.clear();
137  rawArgs.reserve(argc);
138 
139  for (int i = 0; i < argc; i++)
140  rawArgs.emplace_back(std::string(argv[i]));
141 
142  return;
143 }
144 
145 void Hazelnupp::ExpandAbbreviations()
146 {
147  // Abort if no abbreviations
148  if (parameterAbreviations.size() == 0)
149  return;
150 
151  for (std::string& arg : rawArgs)
152  {
153  // Is arg registered as an abbreviation?
154  auto abbr = parameterAbreviations.find(arg);
155  if (abbr != parameterAbreviations.end())
156  {
157  // Yes: replace arg with the long form
158  arg = abbr->second;
159  }
160  }
161 
162  return;
163 }
164 
165 bool Hazelnupp::HasParam(const std::string& key) const
166 {
167  return parameters.find(key) != parameters.end();
168 }
169 
170 Value* Hazelnupp::ParseValue(const std::vector<std::string>& values, const ParamConstraint* constraint)
171 {
172  // Constraint values
173  const bool constrainType = (constraint != nullptr) && (constraint->constrainType);
174 
175  // Void-type
176  if (values.size() == 0)
177  {
178  // Is a list forced via a constraint? If yes, return an empty list
179  if ((constrainType) &&
180  (constraint->wantedType == DATA_TYPE::LIST))
181  return new ListValue();
182 
183  // Is a string forced via a constraint? If yes, return an empty string
184  if ((constrainType) &&
185  (constraint->wantedType == DATA_TYPE::STRING))
186  return new StringValue("");
187 
188  // Else, just return the void type
189  return new VoidValue;
190  }
191 
192  // Force void type by constraint
193  if ((constrainType) &&
194  (constraint->wantedType == DATA_TYPE::VOID))
195  {
196  return new VoidValue;
197  }
198 
199  // List-type
200  else if (values.size() > 1)
201  {
202  // Should the type be something other than list?
203  if ((constrainType) &&
204  (constraint->wantedType != DATA_TYPE::LIST))
205  {
206  throw HazelnuppConstraintTypeMissmatch(values[0] + " " + values[1]);
207  }
208 
209  ListValue* newList = new ListValue();
210  for (const std::string& val : values)
211  {
212  Value* tmp = ParseValue({ val });
213  newList->AddValue(tmp);
214  delete tmp;
215  }
216  return newList;
217  }
218 
219  // Now we're only dealing with a single value
220  const std::string& val = values[0];
221 
222  // String
223  if (!StringTools::IsNumeric(val, true))
224  {
225  // Is the type not supposed to be a string?
226  // void and list are already sorted out
227  if ((constrainType) &&
228  (constraint->wantedType != DATA_TYPE::STRING))
229  {
230  // We can only force a list-value from here
231  if (constraint->wantedType == DATA_TYPE::LIST)
232  {
233  ListValue* list = new ListValue();
234  Value* tmp = ParseValue({ val });
235  list->AddValue(tmp);
236  delete tmp;
237  tmp = nullptr;
238  return list;
239  }
240  // Else it not possible to convert to a numeric
241  else
243  }
244 
245  return new StringValue(val);
246  }
247 
248  // In this case we have a numeric value.
249  // We should still produce a string if requested
250  if ((constrainType) &&
251  (constraint->wantedType == DATA_TYPE::STRING))
252  return new StringValue(val);
253 
254  // Numeric
255  bool isInt;
256  long double num;
257 
258  if (StringTools::ParseNumber(val, isInt, num))
259  {
260  // Is the type constrained?
261  // (only int and float left)
262  if (constrainType)
263  {
264  // Must it be an integer?
265  if (constraint->wantedType == DATA_TYPE::INT)
266  return new IntValue((long long int)num);
267  // Must it be a floating point?
268  else if (constraint->wantedType == DATA_TYPE::FLOAT)
269  return new FloatValue(num);
270  // Else it must be a List
271  else
272  {
273  ListValue* list = new ListValue();
274  Value* tmp = ParseValue({ val });
275  list->AddValue(tmp);
276  delete tmp;
277  tmp = nullptr;
278  return list;
279  }
280  }
281  // Type is not constrained
282  else
283  {
284  // Integer
285  if (isInt)
286  return new IntValue((long long int)num);
287 
288  // Double
289  return new FloatValue(num);
290  }
291  }
292 
293  // Failed
294  return nullptr;
295 }
296 
298 {
299  return crashOnFail;
300 }
301 
302 void Hazelnupp::SetCatchHelp(bool catchHelp)
303 {
304  this->catchHelp = catchHelp;
305  return;
306 }
307 
309 {
310  return catchHelp;
311 }
312 
313 void Hazelnupp::SetBriefDescription(const std::string& description)
314 {
315  briefDescription = description;
316  return;
317 }
318 
319 const std::string& Hazelnupp::GetBriefDescription()
320 {
321  return briefDescription;
322 }
323 
324 void Hazelnp::Hazelnupp::RegisterDescription(const std::string& parameter, const std::string& description)
325 {
326  parameterDescriptions[parameter] = description;
327  return;
328 }
329 
330 const std::string& Hazelnp::Hazelnupp::GetDescription(const std::string& parameter) const
331 {
332  // Do we already have a description for this parameter?
333  if (!HasDescription(parameter))
334  // No? Then return ""
336 
337  // We do? Then return it
338  return parameterDescriptions.find(parameter)->second;
339 }
340 
341 bool Hazelnupp::HasDescription(const std::string& parameter) const
342 {
343  return parameterDescriptions.find(parameter) != parameterDescriptions.end();
344 }
345 
346 void Hazelnupp::ClearDescription(const std::string& parameter)
347 {
348  // This will just do nothing if the entry does not exist
349  parameterDescriptions.erase(parameter);
350  return;
351 }
352 
354 {
355  parameterDescriptions.clear();
356  return;
357 }
358 
360 {
361  std::stringstream ss;
362 
363  // Add brief, if available
364  if (briefDescription.length() > 0)
365  ss << briefDescription << std::endl;
366 
367  // Collect parameter information
368  struct ParamDocEntry
369  {
370  std::string abbreviation;
371  std::string description;
372  std::string type;
373  bool required = false;
374  bool typeIsForced = false;
375  std::string defaultVal;
376  };
377  std::unordered_map<std::string, ParamDocEntry> paramInfos;
378 
379  // Collect descriptions
380  for (const auto& it : parameterDescriptions)
381  {
382  // Do we already have that param in the paramInfo set?
383  if (paramInfos.find(it.first) == paramInfos.end())
384  // No? Create it.
385  paramInfos[it.first] = ParamDocEntry();
386 
387  paramInfos[it.first].description = it.second;
388  }
389 
390  // Collect abbreviations
391  // first value is abbreviation, second is long form
392  for (const auto& it : parameterAbreviations)
393  {
394  // Do we already have that param in the paramInfo set?
395  if (paramInfos.find(it.second) == paramInfos.end())
396  // No? Create it.
397  paramInfos[it.second] = ParamDocEntry();
398 
399  paramInfos[it.second].abbreviation = it.first;
400  }
401 
402  // Collect constraints
403  for (const auto& it : parameterConstraints)
404  {
405  // Do we already have that param in the paramInfo set?
406  if (paramInfos.find(it.first) == paramInfos.end())
407  // No? Create it.
408  paramInfos[it.first] = ParamDocEntry();
409 
410  ParamDocEntry& cached = paramInfos[it.first];
411  cached.required = it.second.required;
412  cached.typeIsForced = it.second.constrainType;
413  cached.type = DataTypeToString(it.second.wantedType);
414 
415  std::stringstream defaultValueSs;
416  for (const std::string& s : it.second.defaultValue)
417  {
418  defaultValueSs << '\'' << s << '\'';
419 
420  // Add a space if we are not at the last entry
421  if ((void*)&s != (void*)&it.second.defaultValue.back())
422  defaultValueSs << " ";
423  }
424  cached.defaultVal = defaultValueSs.str();
425  }
426 
427  // Now generate the documentatino body
428  if (paramInfos.size() > 0)
429  {
430  ss << std::endl
431  << "==== AVAILABLE PARAMETERS ===="
432  << std::endl << std::endl;
433 
434  for (const auto& it : paramInfos)
435  {
436  const ParamDocEntry& pde = it.second;
437 
438  // Put name
439  ss << it.first << " ";
440 
441  // Put abbreviation
442  if (pde.abbreviation.length() > 0)
443  ss << pde.abbreviation << " ";
444 
445  // Put type
446  if (pde.typeIsForced)
447  ss << pde.type << " ";
448 
449  // Put default value
450  if (pde.defaultVal.length() > 0)
451  ss << "default=[" << pde.defaultVal << "] ";
452 
453  // Put required tag, but only if no default value
454  if ((pde.required) && (pde.defaultVal.length() == 0))
455  ss << "[[REQUIRED]] ";
456 
457  // Put brief description
458  if (pde.description.length() > 0)
459  ss << pde.description;
460 
461  ss << std::endl << std::endl;
462  }
463  }
464 
465  return ss.str();
466 }
467 
468 void Hazelnupp::ApplyConstraints()
469 {
470  // Enforce required parameters / default values
471  for (const auto& pc : parameterConstraints)
472  // Parameter in question is not supplied
473  if (!HasParam(pc.second.key))
474  {
475  // Do we have a default value?
476  if (pc.second.defaultValue.size() > 0)
477  {
478  // Then create it now, by its default value
479 
480  Value* tmp = ParseValue(pc.second.defaultValue, &pc.second);
481  parameters.insert(std::pair<std::string, Parameter*>(
482  pc.second.key,
483  new Parameter(pc.second.key, tmp)
484  ));
485 
486  delete tmp;
487  tmp = nullptr;
488  }
489  // So we do not have a default value...
490  else
491  {
492  // Is it important to have the missing parameter?
493  if (pc.second.required)
494  // Throw an error message then
495  throw HazelnuppConstraintMissingValue(pc.second.key);
496  }
497  }
498 
499  return;
500 }
501 
502 ParamConstraint Hazelnupp::GetConstraint(const std::string& parameter) const
503 {
504  return parameterConstraints.find(parameter)->second;
505 }
506 
507 void Hazelnupp::ClearConstraint(const std::string& parameter)
508 {
509  parameterConstraints.erase(parameter);
510  return;
511 }
512 
513 const std::string& Hazelnupp::GetExecutableName() const
514 {
515  return executableName;
516 }
517 
518 const Value& Hazelnupp::operator[](const std::string& key) const
519 {
520  // Throw exception if param is unknown
521  if (!HasParam(key))
523 
524  return *parameters.find(key)->second->GetValue();
525 }
526 
527 void Hazelnupp::RegisterAbbreviation(const std::string& abbrev, const std::string& target)
528 {
529  parameterAbreviations.insert(std::pair<std::string, std::string>(abbrev, target));
530  return;
531 }
532 
533 const std::string& Hazelnupp::GetAbbreviation(const std::string& abbrev) const
534 {
535  if (!HasAbbreviation(abbrev))
537 
538  return parameterAbreviations.find(abbrev)->second;
539 }
540 
541 bool Hazelnupp::HasAbbreviation(const std::string& abbrev) const
542 {
543  return parameterAbreviations.find(abbrev) != parameterAbreviations.end();
544 }
545 
546 void Hazelnupp::ClearAbbreviation(const std::string& abbrevation)
547 {
548  parameterAbreviations.erase(abbrevation);
549  return;
550 }
551 
553 {
554  parameterAbreviations.clear();
555  return;
556 }
557 
558 void Hazelnupp::RegisterConstraint(const std::string& key, const ParamConstraint& constraint)
559 {
560  // Magic syntax, wooo
561  (parameterConstraints[key] = constraint).key = key;
562  return;
563 }
564 
566 {
567  parameterConstraints.clear();
568  return;
569 }
570 
571 void Hazelnupp::SetCrashOnFail(bool crashOnFail)
572 {
573  this->crashOnFail = crashOnFail;
574  return;
575 }
576 
577 const ParamConstraint* Hazelnupp::GetConstraintForKey(const std::string& key) const
578 {
579  const auto constraint = parameterConstraints.find(key);
580 
581  if (constraint == parameterConstraints.end())
582  return nullptr;
583 
584  return &constraint->second;
585 }
HazelnuppException.h
Hazelnp
Definition: DataType.h:4
Hazelnp::IntValue
Specializations for integer values (uses long long int)
Definition: IntValue.h:8
Hazelnp::StringTools::IsNumeric
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
Hazelnp::StringTools::ParseNumber
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
Hazelnp::Hazelnupp::GetBriefDescription
const std::string & GetBriefDescription()
Returns the brief description of the application to be automatically added to the documentation.
Definition: Hazelnupp.cpp:319
Hazelnp::Hazelnupp::ClearConstraints
void ClearConstraints()
Will delete all constraints.
Definition: Hazelnupp.cpp:565
Hazelnp::HazelnuppInvalidKeyException
Gets thrown when an non-existent key gets dereferenced.
Definition: HazelnuppException.h:26
Hazelnp::Hazelnupp::GetExecutableName
const std::string & GetExecutableName() const
Will return argv[0], the name of the executable.
Definition: Hazelnupp.cpp:513
Hazelnp::Hazelnupp::GenerateDocumentation
std::string GenerateDocumentation() const
Will generate a text-based documentation suited to show the user, for example on –help.
Definition: Hazelnupp.cpp:359
Hazelnp::Hazelnupp::GetConstraint
ParamConstraint GetConstraint(const std::string &parameter) const
Will return the constraint information for a specific parameter.
Definition: Hazelnupp.cpp:502
Hazelnp::Hazelnupp::~Hazelnupp
~Hazelnupp()
Definition: Hazelnupp.cpp:26
Hazelnp::Hazelnupp::operator[]
const Value & operator[](const std::string &key) const
Will return the value given a key.
Definition: Hazelnupp.cpp:518
StringValue.h
Hazelnp::HazelnuppException::What
const std::string & What() const
Will return an error message.
Definition: HazelnuppException.h:15
IntValue.h
Hazelnp::Hazelnupp::Hazelnupp
Hazelnupp()
Definition: Hazelnupp.cpp:15
Placeholders.h
Hazelnp::Hazelnupp::ClearAbbreviations
void ClearAbbreviations()
Will delete all abbreviations.
Definition: Hazelnupp.cpp:552
Hazelnp::DATA_TYPE::VOID
@ VOID
Hazelnp::FloatValue
Specializations for floating point values (uses long double)
Definition: FloatValue.h:9
Hazelnp::Hazelnupp::SetBriefDescription
void SetBriefDescription(const std::string &description)
Sets a brief description of the application to be automatically added to the documentation.
Definition: Hazelnupp.cpp:313
Hazelnp::Value
Abstract class for values.
Definition: Value.h:10
Hazelnp::Hazelnupp::GetCrashOnFail
bool GetCrashOnFail() const
Gets whether the application crashes on an exception whilst parsing, and prints to stderr.
Definition: Hazelnupp.cpp:297
Hazelnp::Hazelnupp::HasDescription
bool HasDescription(const std::string &parameter) const
Returns whether or not a given parameter has a registered description.
Definition: Hazelnupp.cpp:341
Hazelnp::Hazelnupp::SetCatchHelp
void SetCatchHelp(bool catchHelp)
Sets whether the Hazelnupp should automatically catch the –help parameter, print the parameter docume...
Definition: Hazelnupp.cpp:302
Hazelnp::Hazelnupp::GetCatchHelp
bool GetCatchHelp() const
Retruns whether the Hazelnupp should automatically catch the –help parameter, print the parameter doc...
Definition: Hazelnupp.cpp:308
Hazelnp::DATA_TYPE::LIST
@ LIST
Hazelnp::Parameter
Definition: Parameter.h:8
Hazelnp::ParamConstraint::wantedType
DATA_TYPE wantedType
Constrain the parameter to this value. Requires constrainType to be set to true.
Definition: ParamConstraint.h:51
Hazelnp::ParamConstraint
Definition: ParamConstraint.h:8
Hazelnp::Placeholders::g_emptyString
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
Hazelnp::Hazelnupp::HasParam
bool HasParam(const std::string &key) const
Will check wether a parameter exists given a key, or not.
Definition: Hazelnupp.cpp:165
Hazelnp::Hazelnupp::Parse
void Parse(const int argc, const char *const *argv)
Will parse command line arguments.
Definition: Hazelnupp.cpp:36
ListValue.h
Hazelnp::ParamConstraint::constrainType
bool constrainType
Should this parameter be forced to be of a certain type? Remember to set constrainTo to the wanted ...
Definition: ParamConstraint.h:48
Hazelnp::Hazelnupp::ClearDescription
void ClearDescription(const std::string &parameter)
Will delete the description of a parameter if it exists.
Definition: Hazelnupp.cpp:346
Hazelnp::Parameter::Key
const std::string & Key() const
Will return the key of this parameter.
Definition: Parameter.cpp:21
Hazelnp::HazelnuppConstraintMissingValue
Gets thrown when a parameter constrained to be required is not provided, and has no default value set...
Definition: HazelnuppException.h:62
Hazelnp::Hazelnupp::RegisterAbbreviation
void RegisterAbbreviation(const std::string &abbrev, const std::string &target)
Will register an abbreviation (like -f for –force)
Definition: Hazelnupp.cpp:527
Hazelnp::HazelnuppConstraintTypeMissmatch
Gets thrown when a parameter is of a type that does not match the required type, and is not convertib...
Definition: HazelnuppException.h:53
VoidValue.h
Hazelnp::Hazelnupp::GetAbbreviation
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.
Definition: Hazelnupp.cpp:533
Hazelnp::Hazelnupp::ClearDescriptions
void ClearDescriptions()
Will delete all parameter descriptions.
Definition: Hazelnupp.cpp:353
Hazelnupp.h
Hazelnp::ListValue::AddValue
void AddValue(const Value *value)
Will add this value to the list.
Definition: ListValue.cpp:33
Hazelnp::Hazelnupp::RegisterConstraint
void RegisterConstraint(const std::string &key, const ParamConstraint &constraint)
Will register a constraint for a parameter.
Definition: Hazelnupp.cpp:558
Hazelnp::VoidValue
Specializations for void values.
Definition: VoidValue.h:8
Hazelnp::Hazelnupp::GetDescription
const std::string & GetDescription(const std::string &parameter) const
Will return a short description for a parameter, if it exists.
Definition: Hazelnupp.cpp:330
Hazelnp::DATA_TYPE::FLOAT
@ FLOAT
Hazelnp::DATA_TYPE::INT
@ INT
Hazelnp::Hazelnupp::ClearConstraint
void ClearConstraint(const std::string &parameter)
Will the constraint of a specific parameter.
Definition: Hazelnupp.cpp:507
FloatValue.h
Hazelnp::Hazelnupp::HasAbbreviation
bool HasAbbreviation(const std::string &abbrev) const
Will check wether or not an abbreviation is registered.
Definition: Hazelnupp.cpp:541
Hazelnp::DATA_TYPE::STRING
@ STRING
Hazelnp::Hazelnupp::SetCrashOnFail
void SetCrashOnFail(bool crashOnFail)
Sets whether to crash the application, and print to stderr, when an exception is raised whilst parsin...
Definition: Hazelnupp.cpp:571
Hazelnp::Hazelnupp::RegisterDescription
void RegisterDescription(const std::string &parameter, const std::string &description)
Willl register a short description for a parameter.
Definition: Hazelnupp.cpp:324
Hazelnp::DataTypeToString
static std::string DataTypeToString(DATA_TYPE type)
Definition: DataType.h:17
Hazelnp::Hazelnupp::ClearAbbreviation
void ClearAbbreviation(const std::string &abbrevation)
Will delete the abbreviation for a given parameter.
Definition: Hazelnupp.cpp:546
Hazelnp::ListValue
Specializations for list values (uses std::vector<Value*>)
Definition: ListValue.h:9
Hazelnp::StringValue
Specializations for string values (uses std::string)
Definition: StringValue.h:9
StringTools.h