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 
13 {
14  return;
15 }
16 
17 Hazelnupp::Hazelnupp(const int argc, const char* const* argv)
18 {
19  Parse(argc, argv);
20  return;
21 }
22 
24 {
25  for (auto& it : parameters)
26  delete it.second;
27 
28  parameters.clear();
29 
30  return;
31 }
32 
33 void Hazelnupp::Parse(const int argc, const char* const* argv)
34 {
35  try
36  {
37  // Populate raw arguments
38  PopulateRawArgs(argc, argv);
39 
40  // Expand abbreviations
41  ExpandAbbreviations();
42 
43  executableName = std::string(rawArgs[0]);
44 
45  std::size_t i = 1;
46  while (i < rawArgs.size())
47  {
48  if ((rawArgs[i].length() > 2) && (rawArgs[i].substr(0, 2) == "--"))
49  {
50  Parameter* param = nullptr;
51  i = ParseNextParameter(i, param);
52 
53  parameters.insert(std::pair<std::string, Parameter*>(param->Key(), param));
54  }
55  else
56  i++;
57  }
58 
59  // Apply constraints such as default values, and required parameters.
60  // Types have already been enforced.
61  ApplyConstraints();
62  }
63  catch (const HazelnuppConstraintTypeMissmatch& hctm)
64  {
65  if (crashOnFail)
66  {
67  std::cerr << "Fatal error: Command-line parameter value-type mismatch at \"" << hctm.What() << "\"!";
68  quick_exit(-1009);
69  }
70  else
71  throw hctm; // yeet
72  }
73  catch (const HazelnuppConstraintMissingValue& hctm)
74  {
75  if (crashOnFail)
76  {
77  std::cerr << "Fatal error: Missing required command-line parameter \"" << hctm.What() << "\"!";
78  quick_exit(-1010);
79  }
80  else
81  throw hctm; // yeet
82  }
83 
84  return;
85 }
86 
87 std::size_t Hazelnupp::ParseNextParameter(const std::size_t parIndex, Parameter*& out_Par)
88 {
89  std::size_t i = parIndex;
90  const std::string key = rawArgs[parIndex];
91  std::vector<std::string> values;
92 
93  // Get values
94  for (i++; i < rawArgs.size(); i++)
95  // If not another parameter
96  if ((rawArgs[i].length() < 2) || (rawArgs[i].substr(0, 2) != "--"))
97  values.emplace_back(rawArgs[i]);
98  else
99  {
100  break;
101  }
102 
103  // Fetch constraint info
104  const ParamConstraint* pcn = GetConstraintForKey(key);
105 
106  Value* parsedVal = ParseValue(values, pcn);
107  if (parsedVal != nullptr)
108  {
109  out_Par = new Parameter(key, parsedVal);
110 
111  delete parsedVal;
112  parsedVal = nullptr;
113  }
114  else
115  throw std::runtime_error("Unable to parse parameter!");
116 
117  return i;
118 }
119 
120 void Hazelnupp::PopulateRawArgs(const int argc, const char* const* argv)
121 {
122  rawArgs.clear();
123  rawArgs.reserve(argc);
124 
125  for (int i = 0; i < argc; i++)
126  rawArgs.emplace_back(std::string(argv[i]));
127 
128  return;
129 }
130 
131 void Hazelnupp::ExpandAbbreviations()
132 {
133  // Abort if no abbreviations
134  if (abbreviations.size() == 0)
135  return;
136 
137  for (std::string& arg : rawArgs)
138  {
139  // Is arg registered as an abbreviation?
140  auto abbr = abbreviations.find(arg);
141  if (abbr != abbreviations.end())
142  {
143  // Yes: replace arg with the long form
144  arg = abbr->second;
145  }
146  }
147 
148  return;
149 }
150 
151 bool Hazelnupp::HasParam(const std::string& key) const
152 {
153  return parameters.find(key) != parameters.end();
154 }
155 
156 Value* Hazelnupp::ParseValue(const std::vector<std::string>& values, const ParamConstraint* constraint)
157 {
158  // Constraint values
159  const bool constrainType = (constraint != nullptr) && (constraint->constrainType);
160 
161  // Void-type
162  if (values.size() == 0)
163  {
164  // Is a list forced via a constraint? If yes, return an empty list
165  if ((constrainType) &&
166  (constraint->wantedType == DATA_TYPE::LIST))
167  return new ListValue();
168 
169  return new VoidValue;
170  }
171 
172  // Force void type by constraint
173  if ((constrainType) &&
174  (constraint->wantedType == DATA_TYPE::VOID))
175  {
176  return new VoidValue;
177  }
178 
179  // List-type
180  else if (values.size() > 1)
181  {
182  // Should the type be something other than list?
183  if ((constrainType) &&
184  (constraint->wantedType != DATA_TYPE::LIST))
185  {
186  throw HazelnuppConstraintTypeMissmatch(values[0] + " " + values[1]);
187  }
188 
189  ListValue* newList = new ListValue();
190  for (const std::string& val : values)
191  {
192  Value* tmp = ParseValue({ val });
193  newList->AddValue(tmp);
194  delete tmp;
195  }
196  return newList;
197  }
198 
199  // Now we're only dealing with a single value
200  const std::string& val = values[0];
201 
202  // String
203  if (!StringTools::IsNumeric(val, true))
204  {
205  // Is the type not supposed to be a string?
206  // void and list are already sorted out
207  if ((constrainType) &&
208  (constraint->wantedType != DATA_TYPE::STRING))
209  {
210  // We can only force a list-value from here
211  if (constraint->wantedType == DATA_TYPE::LIST)
212  {
213  ListValue* list = new ListValue();
214  Value* tmp = ParseValue({ val });
215  list->AddValue(tmp);
216  delete tmp;
217  tmp = nullptr;
218  return list;
219  }
220  // Else it not possible to convert to a numeric
221  else
223  }
224 
225  return new StringValue(val);
226  }
227 
228  // In this case we have a numeric value.
229  // We should still produce a string if requested
230  if ((constrainType) &&
231  (constraint->wantedType == DATA_TYPE::STRING))
232  return new StringValue(val);
233 
234  // Numeric
235  bool isInt;
236  long double num;
237 
238  if (StringTools::ParseNumber(val, isInt, num))
239  {
240  // Is the type constrained?
241  // (only int and float left)
242  if (constrainType)
243  {
244  // Must it be an integer?
245  if (constraint->wantedType == DATA_TYPE::INT)
246  return new IntValue((long long int)num);
247  // Must it be a floating point?
248  else if (constraint->wantedType == DATA_TYPE::FLOAT)
249  return new FloatValue(num);
250  // Else it must be a List
251  else
252  {
253  ListValue* list = new ListValue();
254  Value* tmp = ParseValue({ val });
255  list->AddValue(tmp);
256  delete tmp;
257  tmp = nullptr;
258  return list;
259  }
260  }
261  // Type is not constrained
262  else
263  {
264  // Integer
265  if (isInt)
266  return new IntValue((long long int)num);
267 
268  // Double
269  return new FloatValue(num);
270  }
271  }
272 
273  // Failed
274  return nullptr;
275 }
276 
278 {
279  return crashOnFail;
280 }
281 
282 void Hazelnupp::ApplyConstraints()
283 {
284  // Enforce required parameters / default values
285  for (const auto& pc : constraints)
286  // Parameter in question is not supplied
287  if (!HasParam(pc.second.key))
288  {
289  // Do we have a default value?
290  if (pc.second.defaultValue.size() > 0)
291  {
292  // Then create it now, by its default value
293 
294  Value* tmp = ParseValue(pc.second.defaultValue, &pc.second);
295  parameters.insert(std::pair<std::string, Parameter*>(
296  pc.second.key,
297  new Parameter(pc.second.key, tmp)
298  ));
299 
300  delete tmp;
301  tmp = nullptr;
302  }
303  // So we do not have a default value...
304  else
305  {
306  // Is it important to have the missing parameter?
307  if (pc.second.required)
308  // Throw an error message then
309  throw HazelnuppConstraintMissingValue(pc.second.key);
310  }
311  }
312 
313  return;
314 }
315 
316 const std::string& Hazelnupp::GetExecutableName() const
317 {
318  return executableName;
319 }
320 
321 const Value& Hazelnupp::operator[](const std::string& key) const
322 {
323  // Throw exception if param is unknown
324  if (!HasParam(key))
326 
327  return *parameters.find(key)->second->GetValue();
328 }
329 
330 void Hazelnupp::RegisterAbbreviation(const std::string& abbrev, const std::string& target)
331 {
332  abbreviations.insert(std::pair<std::string, std::string>(abbrev, target));
333  return;
334 }
335 
336 const std::string& Hazelnupp::GetAbbreviation(const std::string& abbrev) const
337 {
338  return abbreviations.find(abbrev)->second;
339 }
340 
341 bool Hazelnupp::HasAbbreviation(const std::string& abbrev) const
342 {
343  return abbreviations.find(abbrev) != abbreviations.end();
344 }
345 
347 {
348  abbreviations.clear();
349  return;
350 }
351 
352 void Hazelnupp::RegisterConstraints(const std::vector<ParamConstraint>& constraints)
353 {
354  for (const ParamConstraint& pc : constraints)
355  {
356  // Does this constraint already exist?
357  const auto constraint = this->constraints.find(pc.key);
358  // If yes, replace it.
359  if (constraint != this->constraints.end())
360  constraint->second = pc;
361 
362  // Else, create a new pair
363  else
364  this->constraints.insert(std::pair<std::string, ParamConstraint>(
365  pc.key,
366  pc
367  ));
368  }
369 
370  return;
371 }
372 
374 {
375  constraints.clear();
376  return;
377 }
378 
379 void Hazelnupp::SetCrashOnFail(bool crashOnFail)
380 {
381  this->crashOnFail = crashOnFail;
382  return;
383 }
384 
385 const ParamConstraint* Hazelnupp::GetConstraintForKey(const std::string& key) const
386 {
387  const auto constraint = constraints.find(key);
388 
389  if (constraint == constraints.end())
390  return nullptr;
391 
392  return &constraint->second;
393 }
HazelnuppException::What
const std::string & What() const
Will return an error message.
Definition: HazelnuppException.h:13
Hazelnupp::Hazelnupp
Hazelnupp()
Definition: Hazelnupp.cpp:12
HazelnuppException.h
ListValue::AddValue
void AddValue(const Value *value)
Will add this value to the list.
Definition: ListValue.cpp:31
DATA_TYPE::VOID
@ VOID
Hazelnupp::RegisterAbbreviation
void RegisterAbbreviation(const std::string &abbrev, const std::string &target)
Will register an abbreviation (like -f for –force)
Definition: Hazelnupp.cpp:330
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:54
Hazelnupp::ClearConstraints
void ClearConstraints()
Will delete all constraints.
Definition: Hazelnupp.cpp:373
IntValue
Specializations for integer values (uses long long int)
Definition: IntValue.h:6
ListValue
Specializations for list values (uses std::vector<Value*>)
Definition: ListValue.h:7
Hazelnupp::RegisterConstraints
void RegisterConstraints(const std::vector< ParamConstraint > &constraints)
Will register parameter constraints.
Definition: Hazelnupp.cpp:352
DATA_TYPE::LIST
@ LIST
StringValue.h
IntValue.h
HazelnuppConstraintTypeMissmatch
Gets thrown when a parameter is of a type that does not match the required type, and is not convertib...
Definition: HazelnuppException.h:51
DATA_TYPE::FLOAT
@ FLOAT
Value
Abstract class for values.
Definition: Value.h:8
HazelnuppConstraintMissingValue
Gets thrown when a parameter constrained to be required is not provided, and has no default value set...
Definition: HazelnuppException.h:60
Parameter
Definition: Parameter.h:6
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:336
Parameter::Key
const std::string & Key() const
Will return the key of this parameter.
Definition: Parameter.cpp:19
VoidValue
Specializations for void values.
Definition: VoidValue.h:6
Hazelnupp::HasAbbreviation
bool HasAbbreviation(const std::string &abbrev) const
Will check wether or not an abbreviation is registered.
Definition: Hazelnupp.cpp:341
DATA_TYPE::INT
@ INT
ListValue.h
Hazelnupp::GetExecutableName
const std::string & GetExecutableName() const
Will return argv[0], the name of the executable.
Definition: Hazelnupp.cpp:316
FloatValue
Specializations for floating point values (uses long double)
Definition: FloatValue.h:7
StringValue
Specializations for string values (uses std::string)
Definition: StringValue.h:7
Hazelnupp::~Hazelnupp
~Hazelnupp()
Definition: Hazelnupp.cpp:23
VoidValue.h
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:80
Hazelnupp.h
HazelnuppInvalidKeyException
Gets thrown when an non-existent key gets dereferenced.
Definition: HazelnuppException.h:24
Hazelnupp::GetCrashOnFail
bool GetCrashOnFail() const
Gets whether the application crashes on an exception whilst parsing, and prints to stderr.
Definition: Hazelnupp.cpp:277
FloatValue.h
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:379
Hazelnupp::HasParam
bool HasParam(const std::string &key) const
Will check wether a parameter exists given a key, or not.
Definition: Hazelnupp.cpp:151
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:52
DATA_TYPE::STRING
@ STRING
ParamConstraint::wantedType
DATA_TYPE wantedType
Constrain the parameter to this value. Requires constrainType to be set to true.
Definition: ParamConstraint.h:55
ParamConstraint
Definition: ParamConstraint.h:6
Hazelnupp::Parse
void Parse(const int argc, const char *const *argv)
Will parse command line arguments.
Definition: Hazelnupp.cpp:33
StringTools.h
Hazelnupp::operator[]
const Value & operator[](const std::string &key) const
Will return the value given a key.
Definition: Hazelnupp.cpp:321
Hazelnupp::ClearAbbreviations
void ClearAbbreviations()
Will delete all abbreviations.
Definition: Hazelnupp.cpp:346