ONE - On-device Neural Engine
Loading...
Searching...
No Matches
arser.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifndef __ARSER_H__
18#define __ARSER_H__
19
20#include <iostream>
21#include <sstream>
22
23#include <iterator>
24#include <typeinfo>
25
26#include <algorithm>
27#include <functional>
28#include <list>
29#include <map>
30#include <string>
31#include <vector>
32#include <cstdint>
33
34#include <cstring>
35
36#include <cassert>
37
38namespace arser
39{
40namespace internal
41{
42
43template <typename T> T lexical_cast(const std::string &str)
44{
45 std::istringstream ss;
46 ss.str(str);
47 T data;
48 ss >> data;
49 return data;
50}
51
52template <> inline bool lexical_cast(const std::string &str)
53{
54 bool data = true;
55 if (str == "false" || str == "False" || str == "FALSE" || str == "0")
56 data = false;
57 return data;
58}
59
60template <typename T> inline std::string to_string(const T value) { return std::to_string(value); }
61
62template <> inline std::string to_string(const char *value) { return std::string(value); }
63
64template <> inline std::string to_string(const bool value) { return value ? "true" : "false"; }
65
71inline std::string remove_dash(const std::string &str)
72{
73 std::string ret{str};
74 auto pos = ret.find_first_not_of('-');
75 if (pos == std::string::npos)
76 return ret;
77 return ret.substr(pos);
78}
79
83inline std::string make_comma_concatenated(const std::vector<std::string> &vec)
84{
85 std::ostringstream oss;
86 std::copy(vec.begin(), std::prev(vec.end()), std::ostream_iterator<std::string>(oss, ", "));
87 oss << vec.back();
88 return oss.str();
89}
90
91} // namespace internal
92} // namespace arser
93
94namespace arser
95{
96
97// TypeName declaration
98template <typename T> struct TypeName
99{
100 static const char *Get() { return typeid(T).name(); }
101};
102template <> struct TypeName<int>
103{
104 static const char *Get() { return "int"; }
105};
106template <> struct TypeName<std::vector<int>>
107{
108 static const char *Get() { return "vector<int>"; }
109};
110template <> struct TypeName<float>
111{
112 static const char *Get() { return "float"; }
113};
114template <> struct TypeName<std::vector<float>>
115{
116 static const char *Get() { return "vector<float>"; }
117};
118template <> struct TypeName<bool>
119{
120 static const char *Get() { return "bool"; }
121};
122template <> struct TypeName<std::string>
123{
124 static const char *Get() { return "string"; }
125};
126template <> struct TypeName<std::vector<std::string>>
127{
128 static const char *Get() { return "vector<string>"; }
129};
130template <> struct TypeName<const char *>
131{
132 static const char *Get() { return "string"; }
133};
134template <> struct TypeName<std::vector<const char *>>
135{
136 static const char *Get() { return "vector<string>"; }
137};
138
139// supported DataType
140enum class DataType
141{
142 INT32,
143 INT32_VEC,
144 FLOAT,
145 FLOAT_VEC,
146 BOOL,
147 STR,
148 STR_VEC,
149};
150
151class Arser;
152
169{
170public:
171 explicit Argument(const std::string &arg_name) : _long_name{arg_name}, _names{arg_name} {}
172 explicit Argument(const std::string &short_name, const std::string &long_name)
173 : _short_name{short_name}, _long_name{long_name}, _names{short_name, long_name}
174 {
175 }
176 explicit Argument(const std::string &short_name, const std::string &long_name,
177 const std::vector<std::string> &names)
178 : _short_name{short_name}, _long_name{long_name}, _names{names}
179 {
180 // 'names' must have 'short_name' and 'long_name'.
181 auto it = std::find(names.begin(), names.end(), short_name);
182 assert(it != names.end());
183 it = std::find(names.begin(), names.end(), long_name);
184 assert(it != names.end());
185 // for avoiding unused warning.
186 (void)it;
187 }
188
189 Argument &nargs(uint32_t num)
190 {
191 if (num == 0)
192 {
193 _type = "bool";
194 }
195 _nargs = num;
196 return *this;
197 }
198
200 {
201 switch (type)
202 {
203 case DataType::INT32:
204 _type = "int";
205 break;
206 case DataType::INT32_VEC:
207 _type = "vector<int>";
208 break;
209 case DataType::FLOAT:
210 _type = "float";
211 break;
212 case DataType::FLOAT_VEC:
213 _type = "vector<float>";
214 break;
215 case DataType::BOOL:
216 _type = "bool";
217 break;
218 case DataType::STR:
219 _type = "string";
220 break;
221 case DataType::STR_VEC:
222 _type = "vector<string>";
223 break;
224 default:
225 throw std::runtime_error("NYI DataType");
226 }
227 return *this;
228 }
229
231 {
232 _is_required = true;
233 return *this;
234 }
235
236 Argument &required(bool value)
237 {
238 _is_required = value;
239 return *this;
240 }
241
243 {
244 _is_accumulated = true;
245 return *this;
246 }
247
249 {
250 _is_accumulated = value;
251 return *this;
252 }
253
254 Argument &help(std::string help_message)
255 {
256 _help_message.emplace_back(help_message);
257 return *this;
258 }
259
260 Argument &help(std::initializer_list<std::string> help_messages)
261 {
262 if (help_messages.size() == 0)
263 throw std::runtime_error("Empty help message list");
264
265 _help_message = help_messages;
266 return *this;
267 }
268
269 Argument &exit_with(const std::function<void(void)> &func)
270 {
271 _func = func;
272 return *this;
273 }
274
275 template <typename T> Argument &default_value(const T value)
276 {
277 if ((_nargs <= 1 && TypeName<T>::Get() == _type) ||
278 (_nargs > 1 && TypeName<std::vector<T>>::Get() == _type))
279 _values.emplace_back(internal::to_string(value));
280 else
281 {
282 throw std::runtime_error("Type mismatch. "
283 "You called default_value() method with a type different "
284 "from the one you specified. "
285 "Please check the type of what you specified in "
286 "add_argument() method.");
287 }
288 return *this;
289 }
290
291 template <typename T, typename... Ts> Argument &default_value(const T value, const Ts... values)
292 {
293 if ((_nargs <= 1 && TypeName<T>::Get() == _type) ||
294 (_nargs > 1 && TypeName<std::vector<T>>::Get() == _type))
295 {
296 _values.emplace_back(internal::to_string(value));
297 default_value(values...);
298 }
299 else
300 {
301 throw std::runtime_error("Type mismatch. "
302 "You called default_value() method with a type different "
303 "from the one you specified. "
304 "Please check the type of what you specified in "
305 "add_argument() method.");
306 }
307 return *this;
308 }
309
310private:
311 // The '_names' vector contains all of the options specified by the user.
312 // And among them, '_short_name' and '_long_name' are selected.
313 std::string _short_name;
314 std::string _long_name;
315 std::vector<std::string> _names;
316 std::string _type = "string";
317 std::vector<std::string> _help_message;
318 std::function<void(void)> _func;
319 uint32_t _nargs{1};
320 bool _is_required{false};
321 bool _is_accumulated{false};
322 std::vector<std::string> _values;
323 std::vector<std::vector<std::string>> _accum_values;
324
325 friend class Arser;
326 friend std::ostream &operator<<(std::ostream &, const Arser &);
327};
328
329class Arser
330{
331public:
332 explicit Arser(const std::string &program_description = {})
333 : _program_description{program_description}
334 {
335 add_argument("-h", "--help").help("Show help message and exit").nargs(0);
336 }
337
338 Argument &add_argument(const std::string &arg_name)
339 {
340 if (arg_name.at(0) != '-') /* positional */
341 {
342 _positional_arg_vec.emplace_back(arg_name);
343 _arg_map[arg_name] = &_positional_arg_vec.back();
344 }
345 else /* optional */
346 {
347 // The length of optional argument name must be 2 or more.
348 // And it shouldn't be hard to recognize. e.g. '-', '--'
349 if (arg_name.size() < 2)
350 {
351 throw std::runtime_error("Too short name. The length of argument name must be 2 or more.");
352 }
353 if (arg_name == "--")
354 {
355 throw std::runtime_error(
356 "Too short name. Option name must contain at least one character other than dash.");
357 }
358 _optional_arg_vec.emplace_back(arg_name);
359 _optional_arg_vec.back()._short_name = arg_name;
360 _arg_map[arg_name] = &_optional_arg_vec.back();
361 }
362 return *_arg_map[arg_name];
363 }
364
365 Argument &add_argument(const std::vector<std::string> &arg_name_vec)
366 {
367 assert(arg_name_vec.size() >= 2);
368 std::string long_opt, short_opt;
369 // find long and short option
370 for (const auto &arg_name : arg_name_vec)
371 {
372 if (arg_name.at(0) != '-')
373 {
374 throw std::runtime_error("Invalid argument. "
375 "Positional argument cannot have short option.");
376 }
377 assert(arg_name.size() >= 2);
378 if (long_opt.empty() && arg_name.at(0) == '-' && arg_name.at(1) == '-')
379 {
380 long_opt = arg_name;
381 }
382 if (short_opt.empty() && arg_name.at(0) == '-' && arg_name.at(1) != '-')
383 {
384 short_opt = arg_name;
385 }
386 }
387 // If one of the two is empty, fill it with the non-empty one for pretty printing.
388 if (long_opt.empty())
389 {
390 assert(not short_opt.empty());
391 long_opt = short_opt;
392 }
393 if (short_opt.empty())
394 {
395 assert(not long_opt.empty());
396 short_opt = long_opt;
397 }
398
399 _optional_arg_vec.emplace_back(short_opt, long_opt, arg_name_vec);
400 for (const auto &arg_name : arg_name_vec)
401 {
402 _arg_map[arg_name] = &_optional_arg_vec.back();
403 }
404 return _optional_arg_vec.back();
405 }
406
407 template <typename... Ts> Argument &add_argument(const std::string &arg_name, Ts... arg_names)
408 {
409 if (sizeof...(arg_names) == 0)
410 {
411 return add_argument(arg_name);
412 }
413 // sizeof...(arg_names) > 0
414 else
415 {
416 return add_argument(std::vector<std::string>{arg_name, arg_names...});
417 }
418 }
419
421 {
422 // positional argument is always required.
423 for (const auto &arg : _positional_arg_vec)
424 {
425 if (arg._is_required)
426 {
427 throw std::runtime_error("Invalid arguments. Positional argument must always be required.");
428 }
429 }
430 // TODO accumulated arguments shouldn't be enabled to positional arguments.
431 // TODO accumulated arguments shouldn't be enabled to optional arguments whose `narg` == 0.
432 }
433
434 void parse(int argc, char **argv)
435 {
437 _program_name = argv[0];
438 _program_name.erase(0, _program_name.find_last_of("/\\") + 1);
439 if (argc >= 2)
440 {
441 if (!std::strcmp(argv[1], "--help") || !std::strcmp(argv[1], "-h"))
442 {
443 std::cout << *this;
444 std::exit(0);
445 }
446 else
447 {
448 for (const auto &arg : _arg_map)
449 {
450 const auto &func = arg.second->_func;
451 if (func && !std::strcmp(argv[1], arg.first.c_str()))
452 {
453 func();
454 std::exit(0);
455 }
456 }
457 }
458 }
459 /*
460 ** ./program_name [optional argument] [positional argument]
461 */
462 // get the number of positioanl argument
463 size_t parg_num = _positional_arg_vec.size();
464 // get the number of "required" optional argument
465 size_t required_oarg_num = 0;
466 for (auto arg : _optional_arg_vec)
467 {
468 if (arg._is_required)
469 required_oarg_num++;
470 }
471 // parse argument
472 for (int c = 1; c < argc;)
473 {
474 std::string arg_name{argv[c++]};
475 auto arg = _arg_map.find(arg_name);
476 // check whether arg is positional or not
477 if (arg == _arg_map.end())
478 {
479 if (parg_num)
480 {
481 auto it = _positional_arg_vec.begin();
482 std::advance(it, _positional_arg_vec.size() - parg_num);
483 (*it)._values.clear();
484 (*it)._values.emplace_back(arg_name);
485 parg_num--;
486 }
487 else
488 throw std::runtime_error("Invalid argument. "
489 "You've given more positional argument than necessary.");
490 }
491 else // optional argument
492 {
493 // check whether arg is required or not
494 if (arg->second->_is_required)
495 required_oarg_num--;
496 arg->second->_values.clear();
497 for (uint32_t n = 0; n < arg->second->_nargs; n++)
498 {
499 if (c >= argc)
500 throw std::runtime_error("Invalid argument. "
501 "You must have missed some argument.");
502 arg->second->_values.emplace_back(argv[c++]);
503 }
504 // accumulate values
505 if (arg->second->_is_accumulated)
506 {
507 arg->second->_accum_values.emplace_back(arg->second->_values);
508 }
509 if (arg->second->_nargs == 0)
510 {
511 // TODO std::boolalpha for true or false
512 arg->second->_values.emplace_back("1");
513 }
514 }
515 }
516 if (parg_num || required_oarg_num)
517 throw std::runtime_error("Invalid argument. "
518 "You must have missed some argument.");
519 }
520
521 bool operator[](const std::string &arg_name)
522 {
523 auto arg = _arg_map.find(arg_name);
524 if (arg == _arg_map.end())
525 return false;
526
527 if (arg->second->_is_accumulated)
528 return arg->second->_accum_values.size() > 0 ? true : false;
529
530 return arg->second->_values.size() > 0 ? true : false;
531 }
532
533 template <typename T> T get_impl(const std::string &arg_name, T *);
534
535 template <typename T> std::vector<T> get_impl(const std::string &arg_name, std::vector<T> *);
536
537 template <typename T>
538 std::vector<std::vector<T>> get_impl(const std::string &arg_name, std::vector<std::vector<T>> *);
539
540 template <typename T> T get(const std::string &arg_name);
541
542 friend std::ostream &operator<<(std::ostream &stream, const Arser &parser)
543 {
544 // print description
545 if (!parser._program_description.empty())
546 {
547 stream << "What " << parser._program_name << " does: " << parser._program_description
548 << "\n\n";
549 }
550 /*
551 ** print usage
552 */
553 auto print_usage_arg = [&](const arser::Argument &arg) {
554 stream << " ";
555 std::string arg_name = arser::internal::remove_dash(arg._long_name);
556 std::for_each(arg_name.begin(), arg_name.end(),
557 [&stream](const char &c) { stream << static_cast<char>(::toupper(c)); });
558 };
559 stream << "Usage: ./" << parser._program_name << " ";
560 // required optional argument
561 for (const auto &arg : parser._optional_arg_vec)
562 {
563 if (!arg._is_required)
564 continue;
565 stream << arg._short_name;
566 print_usage_arg(arg);
567 stream << " ";
568 }
569 // rest of the optional argument
570 for (const auto &arg : parser._optional_arg_vec)
571 {
572 if (arg._is_required)
573 continue;
574 stream << "[" << arg._short_name;
575 if (arg._nargs)
576 {
577 print_usage_arg(arg);
578 }
579 stream << "]"
580 << " ";
581 }
582 // positional arguement
583 for (const auto &arg : parser._positional_arg_vec)
584 {
585 stream << arg._long_name << " ";
586 }
587 stream << "\n\n";
588 /*
589 ** print argument list and its help message
590 */
591 // get the length of the longest argument
592 size_t length_of_longest_arg = 0;
593 for (const auto &arg : parser._positional_arg_vec)
594 {
595 length_of_longest_arg = std::max(length_of_longest_arg,
597 }
598 for (const auto &arg : parser._optional_arg_vec)
599 {
600 length_of_longest_arg = std::max(length_of_longest_arg,
602 }
603
604 const size_t message_width = 60;
605 auto print_help_args = [&](const std::list<Argument> &args, const std::string &title) {
606 if (!args.empty())
607 {
608 stream << title << std::endl;
609 for (const auto &arg : args)
610 {
611 stream.width(length_of_longest_arg);
612 stream << std::left << arser::internal::make_comma_concatenated(arg._names) << "\t";
613 for (size_t i = 0; i < arg._help_message.size(); i++)
614 {
615 for (size_t j = 0; j < arg._help_message[i].length(); j += message_width)
616 {
617 if (i || j)
618 stream << std::string(length_of_longest_arg, ' ') << "\t";
619 stream << arg._help_message[i].substr(j, message_width) << std::endl;
620 }
621 }
622 }
623 std::cout << std::endl;
624 }
625 };
626 // positional argument
627 print_help_args(parser._positional_arg_vec, "[Positional argument]");
628 // optional argument
629 print_help_args(parser._optional_arg_vec, "[Optional argument]");
630
631 return stream;
632 }
633
634private:
635 std::string _program_name;
636 std::string _program_description;
637 std::list<Argument> _positional_arg_vec;
638 std::list<Argument> _optional_arg_vec;
639 std::map<std::string, Argument *> _arg_map;
640};
641
642template <typename T> T Arser::get_impl(const std::string &arg_name, T *)
643{
644 auto arg = _arg_map.find(arg_name);
645 if (arg == _arg_map.end())
646 throw std::runtime_error("Invalid argument. "
647 "There is no argument you are looking for: " +
648 arg_name);
649
650 if (arg->second->_is_accumulated)
651 throw std::runtime_error(
652 "Type mismatch. "
653 "You called get using a type different from the one you specified."
654 "Accumulated argument is returned as std::vector of the specified type: " +
655 arg_name);
656
657 if (arg->second->_type != TypeName<T>::Get())
658 throw std::runtime_error("Type mismatch. "
659 "You called get() method with a type different "
660 "from the one you specified. "
661 "Please check the type of what you specified in "
662 "add_argument() method: " +
663 arg_name);
664
665 if (arg->second->_values.size() == 0)
666 throw std::runtime_error("Wrong access. "
667 "You must make sure that the argument is given before accessing it. "
668 "You can do it by calling arser[\"" +
669 arg_name + "\"].");
670
671 return internal::lexical_cast<T>(arg->second->_values[0]);
672}
673
674template <typename T> std::vector<T> Arser::get_impl(const std::string &arg_name, std::vector<T> *)
675{
676 auto arg = _arg_map.find(arg_name);
677 if (arg == _arg_map.end())
678 throw std::runtime_error("Invalid argument. "
679 "There is no argument you are looking for: " +
680 arg_name);
681
682 // Accumulated arguments with scalar type (e.g., STR)
683 if (arg->second->_is_accumulated)
684 {
685 if (arg->second->_type != TypeName<T>::Get())
686 throw std::runtime_error(
687 "Type mismatch. "
688 "You called get using a type different from the one you specified: " +
689 arg_name);
690
691 std::vector<T> data;
692 for (auto values : arg->second->_accum_values)
693 {
694 assert(values.size() == 1);
695 data.emplace_back(internal::lexical_cast<T>(values[0]));
696 }
697 return data;
698 }
699
700 if (arg->second->_type != TypeName<std::vector<T>>::Get())
701 throw std::runtime_error(
702 "Type mismatch. "
703 ". You called get using a type different from the one you specified: " +
704 arg_name);
705
706 std::vector<T> data;
707 std::transform(arg->second->_values.begin(), arg->second->_values.end(), std::back_inserter(data),
708 [](std::string str) -> T { return internal::lexical_cast<T>(str); });
709 return data;
710}
711
712// Accumulated arguments with vector type (e.g., STR_VEC)
713template <typename T>
714std::vector<std::vector<T>> Arser::get_impl(const std::string &arg_name,
715 std::vector<std::vector<T>> *)
716{
717 auto arg = _arg_map.find(arg_name);
718 if (arg == _arg_map.end())
719 throw std::runtime_error("Invalid argument. "
720 "There is no argument you are looking for: " +
721 arg_name);
722
723 if (not arg->second->_is_accumulated)
724 throw std::runtime_error("Type mismatch. "
725 "You called get using a type different from the one you specified: " +
726 arg_name);
727
728 if (arg->second->_type != TypeName<std::vector<T>>::Get())
729 throw std::runtime_error(
730 "Type mismatch. "
731 "You called get using a type different from the one you specified."
732 "Accumulated argument is returned as std::vector of the specified type: " +
733 arg_name);
734
735 std::vector<std::vector<T>> result;
736 for (auto values : arg->second->_accum_values)
737 {
738 std::vector<T> data;
739 std::transform(values.begin(), values.end(), std::back_inserter(data),
740 [](std::string str) -> T { return internal::lexical_cast<T>(str); });
741 result.emplace_back(data);
742 }
743
744 return result;
745}
746
747template <typename T> T Arser::get(const std::string &arg_name)
748{
749 return get_impl(arg_name, static_cast<T *>(nullptr));
750}
751
753{
754public:
755 static void add_version(Arser &arser, const std::function<void(void)> &func)
756 {
757 arser.add_argument("--version")
758 .nargs(0)
759 .required(false)
760 .default_value(false)
761 .help("Show version information and exit")
762 .exit_with(func);
763 }
764
765 static void add_verbose(Arser &arser)
766 {
767 arser.add_argument("-V", "--verbose")
768 .nargs(0)
769 .required(false)
770 .default_value(false)
771 .help("output additional information to stdout or stderr");
772 }
773};
774
775} // namespace arser
776
777#endif // __ARSER_H__
Argument & type(DataType type)
Definition arser.h:199
Argument(const std::string &arg_name)
Definition arser.h:171
Argument & help(std::initializer_list< std::string > help_messages)
Definition arser.h:260
Argument & default_value(const T value)
Definition arser.h:275
Argument & exit_with(const std::function< void(void)> &func)
Definition arser.h:269
Argument & nargs(uint32_t num)
Definition arser.h:189
Argument & help(std::string help_message)
Definition arser.h:254
Argument(const std::string &short_name, const std::string &long_name, const std::vector< std::string > &names)
Definition arser.h:176
Argument & accumulated(bool value)
Definition arser.h:248
Argument & default_value(const T value, const Ts... values)
Definition arser.h:291
Argument & accumulated(void)
Definition arser.h:242
friend std::ostream & operator<<(std::ostream &, const Arser &)
Argument & required(bool value)
Definition arser.h:236
Argument(const std::string &short_name, const std::string &long_name)
Definition arser.h:172
Argument & required(void)
Definition arser.h:230
void validate_arguments(void)
Definition arser.h:420
Arser(const std::string &program_description={})
Definition arser.h:332
bool operator[](const std::string &arg_name)
Definition arser.h:521
friend std::ostream & operator<<(std::ostream &stream, const Arser &parser)
Definition arser.h:542
T get_impl(const std::string &arg_name, T *)
Definition arser.h:642
Argument & add_argument(const std::string &arg_name, Ts... arg_names)
Definition arser.h:407
T get(const std::string &arg_name)
Definition arser.h:747
Argument & add_argument(const std::string &arg_name)
Definition arser.h:338
Argument & add_argument(const std::vector< std::string > &arg_name_vec)
Definition arser.h:365
void parse(int argc, char **argv)
Definition arser.h:434
static void add_version(Arser &arser, const std::function< void(void)> &func)
Definition arser.h:755
static void add_verbose(Arser &arser)
Definition arser.h:765
std::string to_string(const T value)
Definition arser.h:60
std::string make_comma_concatenated(const std::vector< std::string > &vec)
Returns the string that created by concatenating the elements of a vector with commas.
Definition arser.h:83
T lexical_cast(const std::string &str)
Definition arser.h:43
std::string remove_dash(const std::string &str)
Returns the string with the leading dash removed.
Definition arser.h:71
Definition arser.h:39
DataType
Definition arser.h:141
static const char * Get()
Definition arser.h:120
static const char * Get()
Definition arser.h:132
static const char * Get()
Definition arser.h:112
static const char * Get()
Definition arser.h:104
static const char * Get()
Definition arser.h:124
static const char * Get()
Definition arser.h:108
static const char * Get()
Definition arser.h:100