Leonetienne/Hazelnupp
Simple, easy to use, command line parameter interface
CmdArgsInterface.cpp
Go to the documentation of this file.
10#include <iostream>
11#include <cstdlib>
12
13using namespace Hazelnp;
14
15CmdArgsInterface::CmdArgsInterface()
16{
17 return;
18}
19
20CmdArgsInterface::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
36void 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
135std::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
168void 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
179void 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
199bool CmdArgsInterface::HasParam(const std::string& key) const
200{
201 return parameters.find(key) != parameters.end();
202}
203
204Value* 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
369{
370 this->catchHelp = catchHelp;
371 return;
372}
373
375{
376 return catchHelp;
377}
378
379void CmdArgsInterface::SetBriefDescription(const std::string& description)
380{
381 briefDescription = description;
382 return;
383}
384
386{
387 return briefDescription;
388}
389
390void Hazelnp::CmdArgsInterface::RegisterDescription(const std::string& parameter, const std::string& description)
391{
392 parameterDescriptions[parameter] = description;
393 return;
394}
395
396const 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
407bool CmdArgsInterface::HasDescription(const std::string& parameter) const
408{
409 return parameterDescriptions.find(parameter) != parameterDescriptions.end();
410}
411
412void 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
558void 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
607ParamConstraint CmdArgsInterface::GetConstraint(const std::string& parameter) const
608{
609 return parameterConstraints.find(parameter)->second;
610}
611
612void CmdArgsInterface::ClearConstraint(const std::string& parameter)
613{
614 parameterConstraints.erase(parameter);
615 return;
616}
617
618const std::string& CmdArgsInterface::GetExecutableName() const
619{
620 return executableName;
621}
622
623const 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
632void 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
638const std::string& CmdArgsInterface::GetAbbreviation(const std::string& abbrev) const
639{
640 if (!HasAbbreviation(abbrev))
642
643 return parameterAbreviations.find(abbrev)->second;
644}
645
646bool CmdArgsInterface::HasAbbreviation(const std::string& abbrev) const
647{
648 return parameterAbreviations.find(abbrev) != parameterAbreviations.end();
649}
650
651void CmdArgsInterface::ClearAbbreviation(const std::string& abbrevation)
652{
653 parameterAbreviations.erase(abbrevation);
654 return;
655}
656
658{
659 parameterAbreviations.clear();
660 return;
661}
662
663void 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
677{
678 this->crashOnFail = crashOnFail;
679 return;
680}
681
682const 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}
void ClearAbbreviations()
Will delete all abbreviations.
bool GetCatchHelp() const
Retruns whether the CmdArgsInterface should automatically catch the –help parameter,...
void ClearConstraint(const std::string &parameter)
Will the constraint of a specific parameter.
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 SetCrashOnFail(bool crashOnFail)
Sets whether to crash the application, and print to stderr, when an exception is raised whilst parsin...
const std::string & GetBriefDescription()
Returns the brief description of the application to be automatically added to the documentation.
void Parse(const int argc, const char *const *argv)
Will parse command line arguments.
void ClearAbbreviation(const std::string &abbrevation)
Will delete the abbreviation for a given parameter.
void ClearDescriptions()
Will delete all parameter descriptions.
bool HasParam(const std::string &key) const
Will check wether a parameter exists given a key, or not.
bool GetCrashOnFail() const
Gets whether the application crashes on an exception whilst parsing, and prints to stderr.
bool HasDescription(const std::string &parameter) const
Returns whether or not a given parameter has a registered description.
bool HasAbbreviation(const std::string &abbrev) const
Will check wether or not an abbreviation is registered.
void ClearDescription(const std::string &parameter)
Will delete the description of a parameter if it exists.
void RegisterDescription(const std::string &parameter, const std::string &description)
Willl register a short description for a parameter.
void SetBriefDescription(const std::string &description)
Sets a brief description of the application to be automatically added to the documentation.
const std::string & GetDescription(const std::string &parameter) const
Will return a short description for a parameter, if it exists.
std::string GenerateDocumentation() const
Will generate a text-based documentation suited to show the user, for example on –help.
void RegisterConstraint(const std::string &key, const ParamConstraint &constraint)
Will register a constraint for a parameter.
void RegisterAbbreviation(const std::string &abbrev, const std::string &target)
Will register an abbreviation (like -f for –force)
void SetCatchHelp(bool catchHelp)
Sets whether the CmdArgsInterface should automatically catch the –help parameter, print the parameter...
void ClearConstraints()
Will delete all constraints.
ParamConstraint GetConstraint(const std::string &parameter) const
Will return the constraint information for a specific parameter.
const Value & operator[](const std::string &key) const
Will return the value given a key.
const std::string & GetExecutableName() const
Will return argv[0], the name of the executable.
Specializations for floating point values (uses long double)
Definition: FloatValue.h:10
Gets thrown something bad happens because of parameter constraints.
Gets thrown when a parameter constrained to be incompatible with other parameters gets supplied along...
Gets thrown when a parameter constrained to be required is not provided, and has no default value set...
Gets thrown when a parameter is of a type that does not match the required type, and is not convertib...
Generic hazelnupp exception.
const std::string & What() const
Will return an error message.
Gets thrown when an non-existent key gets dereferenced.
Specializations for integer values (uses long long int)
Definition: IntValue.h:9
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 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
Specializations for list values (uses std::vector<Value*>)
Definition: ListValue.h:10
void AddValue(const Value *value)
Will add this value to the list.
Definition: ListValue.cpp:33
const std::string & Key() const
Will return the key of this parameter.
Definition: Parameter.cpp:21
Specializations for string values (uses std::string)
Definition: StringValue.h:10
Abstract class for values.
Definition: Value.h:11
Specializations for void values.
Definition: VoidValue.h:9
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
DATA_TYPE
The different data types a paramater can be.
Definition: DataType.h:9
static std::string DataTypeToString(DATA_TYPE type)
Definition: DataType.h:17
DATA_TYPE requiredType
Constrain the parameter to this value. Requires constrainType to be set to true.
bool constrainType
Should this parameter be forced to be of a certain type? Remember to set constrainTo to the wanted ...