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