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