2 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
42 template <typename T> T lexical_cast(const std::string &str)
44 std::istringstream ss;
51 template <> inline bool lexical_cast(const std::string &str)
54 if (str == "false" || str == "False" || str == "FALSE" || str == "0")
59 template <typename T> inline std::string to_string(const T value) { return std::to_string(value); }
61 template <> inline std::string to_string(const char *value) { return std::string(value); }
63 template <> inline std::string to_string(const bool value) { return value ? "true" : "false"; }
66 * @brief Returns the string with the leading dash removed.
68 * If there is no dash, it returns as it is.
70 inline std::string remove_dash(const std::string &str)
73 auto pos = ret.find_first_not_of('-');
74 if (pos == std::string::npos)
76 return ret.substr(pos);
80 * @brief Returns the string that created by concatenating the elements of a vector with commas.
82 inline std::string make_comma_concatenated(const std::vector<std::string> &vec)
84 std::ostringstream oss;
85 std::copy(vec.begin(), std::prev(vec.end()), std::ostream_iterator<std::string>(oss, ", "));
90 } // namespace internal
96 // TypeName declaration
97 template <typename T> struct TypeName
99 static const char *Get() { return typeid(T).name(); }
101 template <> struct TypeName<int>
103 static const char *Get() { return "int"; }
105 template <> struct TypeName<std::vector<int>>
107 static const char *Get() { return "vector<int>"; }
109 template <> struct TypeName<float>
111 static const char *Get() { return "float"; }
113 template <> struct TypeName<std::vector<float>>
115 static const char *Get() { return "vector<float>"; }
117 template <> struct TypeName<bool>
119 static const char *Get() { return "bool"; }
121 template <> struct TypeName<std::string>
123 static const char *Get() { return "string"; }
125 template <> struct TypeName<std::vector<std::string>>
127 static const char *Get() { return "vector<string>"; }
129 template <> struct TypeName<const char *>
131 static const char *Get() { return "string"; }
133 template <> struct TypeName<std::vector<const char *>>
135 static const char *Get() { return "vector<string>"; }
138 // supported DataType
154 * ├── positional argument
155 * └── optioanl argument [ dash at the beginning of the string ]
156 * ├── long option [ two or more dashes ]
157 * └── short option [ one dash ]
159 * Argument has two types - positional argument, optional argument.
161 * The way to distinguish the two types is whether there is a dash('-') at the beginning of the
164 * And, optional argument has two types as well - long option, short option, which is distinguished
165 * by the number of dash.
170 explicit Argument(const std::string &arg_name) : _long_name{arg_name}, _names{arg_name} {}
171 explicit Argument(const std::string &short_name, const std::string &long_name)
172 : _short_name{short_name}, _long_name{long_name}, _names{short_name, long_name}
175 explicit Argument(const std::string &short_name, const std::string &long_name,
176 const std::vector<std::string> &names)
177 : _short_name{short_name}, _long_name{long_name}, _names{names}
179 // 'names' must have 'short_name' and 'long_name'.
180 auto it = std::find(names.begin(), names.end(), short_name);
181 assert(it != names.end());
182 it = std::find(names.begin(), names.end(), long_name);
183 assert(it != names.end());
184 // for avoiding unused warning.
188 Argument &nargs(uint32_t num)
198 Argument &type(DataType type)
202 case DataType::INT32:
205 case DataType::INT32_VEC:
206 _type = "vector<int>";
208 case DataType::FLOAT:
211 case DataType::FLOAT_VEC:
212 _type = "vector<float>";
220 case DataType::STR_VEC:
221 _type = "vector<string>";
224 throw std::runtime_error("NYI DataType");
229 Argument &required(void)
235 Argument &required(bool value)
237 _is_required = value;
241 Argument &accumulated(void)
243 _is_accumulated = true;
247 Argument &accumulated(bool value)
249 _is_accumulated = value;
253 Argument &help(std::string help_message)
255 _help_message = help_message;
259 Argument &exit_with(const std::function<void(void)> &func)
265 template <typename T> Argument &default_value(const T value)
267 if ((_nargs <= 1 && TypeName<T>::Get() == _type) ||
268 (_nargs > 1 && TypeName<std::vector<T>>::Get() == _type))
269 _values.emplace_back(internal::to_string(value));
272 throw std::runtime_error("Type mismatch. "
273 "You called default_value() method with a type different "
274 "from the one you specified. "
275 "Please check the type of what you specified in "
276 "add_argument() method.");
281 template <typename T, typename... Ts> Argument &default_value(const T value, const Ts... values)
283 if ((_nargs <= 1 && TypeName<T>::Get() == _type) ||
284 (_nargs > 1 && TypeName<std::vector<T>>::Get() == _type))
286 _values.emplace_back(internal::to_string(value));
287 default_value(values...);
291 throw std::runtime_error("Type mismatch. "
292 "You called default_value() method with a type different "
293 "from the one you specified. "
294 "Please check the type of what you specified in "
295 "add_argument() method.");
301 // The '_names' vector contains all of the options specified by the user.
302 // And among them, '_long_name' and '_short_name' are selected.
303 std::string _long_name;
304 std::string _short_name;
305 std::vector<std::string> _names;
307 std::string _help_message;
308 std::function<void(void)> _func;
310 bool _is_required{false};
311 bool _is_accumulated{false};
312 std::vector<std::string> _values;
313 std::vector<std::vector<std::string>> _accum_values;
316 friend std::ostream &operator<<(std::ostream &, const Arser &);
322 explicit Arser(const std::string &program_description = {})
323 : _program_description{program_description}
325 add_argument("-h", "--help").help("Show help message and exit").nargs(0);
328 Argument &add_argument(const std::string &arg_name)
330 if (arg_name.at(0) != '-') /* positional */
332 _positional_arg_vec.emplace_back(arg_name);
333 _arg_map[arg_name] = &_positional_arg_vec.back();
337 // The length of optional argument name must be 2 or more.
338 // And it shouldn't be hard to recognize. e.g. '-', '--'
339 if (arg_name.size() < 2)
341 throw std::runtime_error("Too short name. The length of argument name must be 2 or more.");
343 if (arg_name == "--")
345 throw std::runtime_error(
346 "Too short name. Option name must contain at least one character other than dash.");
348 _optional_arg_vec.emplace_back(arg_name);
349 _optional_arg_vec.back()._short_name = arg_name;
350 _arg_map[arg_name] = &_optional_arg_vec.back();
352 return *_arg_map[arg_name];
355 Argument &add_argument(const std::vector<std::string> &arg_name_vec)
357 assert(arg_name_vec.size() >= 2);
358 std::string long_opt, short_opt;
359 // find long and short option
360 for (const auto &arg_name : arg_name_vec)
362 if (arg_name.at(0) != '-')
364 throw std::runtime_error("Invalid argument. "
365 "Positional argument cannot have short option.");
367 assert(arg_name.size() >= 2);
368 if (long_opt.empty() && arg_name.at(0) == '-' && arg_name.at(1) == '-')
372 if (short_opt.empty() && arg_name.at(0) == '-' && arg_name.at(1) != '-')
374 short_opt = arg_name;
377 // If one of the two is empty, fill it with the non-empty one for pretty printing.
378 if (long_opt.empty())
380 assert(not short_opt.empty());
381 long_opt = short_opt;
383 if (short_opt.empty())
385 assert(not long_opt.empty());
386 short_opt = long_opt;
389 _optional_arg_vec.emplace_back(short_opt, long_opt, arg_name_vec);
390 for (const auto &arg_name : arg_name_vec)
392 _arg_map[arg_name] = &_optional_arg_vec.back();
394 return _optional_arg_vec.back();
397 template <typename... Ts> Argument &add_argument(const std::string &arg_name, Ts... arg_names)
399 if (sizeof...(arg_names) == 0)
401 return add_argument(arg_name);
403 // sizeof...(arg_names) > 0
406 return add_argument(std::vector<std::string>{arg_name, arg_names...});
410 void validate_arguments(void)
412 // positional argument is always required.
413 for (const auto &arg : _positional_arg_vec)
415 if (arg._is_required)
417 throw std::runtime_error("Invalid arguments. Positional argument must always be required.");
420 // TODO accumulated arguments shouldn't be enabled to positional arguments.
421 // TODO accumulated arguments shouldn't be enabled to optional arguments whose `narg` == 0.
424 void parse(int argc, char **argv)
426 validate_arguments();
427 _program_name = argv[0];
428 _program_name.erase(0, _program_name.find_last_of("/\\") + 1);
431 if (!std::strcmp(argv[1], "--help") || !std::strcmp(argv[1], "-h"))
438 for (const auto &arg : _arg_map)
440 const auto &func = arg.second->_func;
441 if (func && !std::strcmp(argv[1], arg.first.c_str()))
450 ** ./program_name [optional argument] [positional argument]
452 // get the number of positioanl argument
453 size_t parg_num = _positional_arg_vec.size();
454 // get the number of "required" optional argument
455 size_t required_oarg_num = 0;
456 for (auto arg : _optional_arg_vec)
458 if (arg._is_required)
462 for (int c = 1; c < argc;)
464 std::string arg_name{argv[c++]};
465 auto arg = _arg_map.find(arg_name);
466 // check whether arg is positional or not
467 if (arg == _arg_map.end())
471 auto it = _positional_arg_vec.begin();
472 std::advance(it, _positional_arg_vec.size() - parg_num);
473 (*it)._values.clear();
474 (*it)._values.emplace_back(arg_name);
478 throw std::runtime_error("Invalid argument. "
479 "You've given more positional argument than necessary.");
481 else // optional argument
483 // check whether arg is required or not
484 if (arg->second->_is_required)
486 arg->second->_values.clear();
487 for (uint32_t n = 0; n < arg->second->_nargs; n++)
490 throw std::runtime_error("Invalid argument. "
491 "You must have missed some argument.");
492 arg->second->_values.emplace_back(argv[c++]);
495 if (arg->second->_is_accumulated)
497 arg->second->_accum_values.emplace_back(arg->second->_values);
499 if (arg->second->_nargs == 0)
501 // TODO std::boolalpha for true or false
502 arg->second->_values.emplace_back("1");
506 if (parg_num || required_oarg_num)
507 throw std::runtime_error("Invalid argument. "
508 "You must have missed some argument.");
511 bool operator[](const std::string &arg_name)
513 auto arg = _arg_map.find(arg_name);
514 if (arg == _arg_map.end())
517 if (arg->second->_is_accumulated)
518 return arg->second->_accum_values.size() > 0 ? true : false;
520 return arg->second->_values.size() > 0 ? true : false;
523 template <typename T> T get_impl(const std::string &arg_name, T *);
525 template <typename T> std::vector<T> get_impl(const std::string &arg_name, std::vector<T> *);
527 template <typename T>
528 std::vector<std::vector<T>> get_impl(const std::string &arg_name, std::vector<std::vector<T>> *);
530 template <typename T> T get(const std::string &arg_name);
532 friend std::ostream &operator<<(std::ostream &stream, const Arser &parser)
535 if (!parser._program_description.empty())
537 stream << "What " << parser._program_name << " does: " << parser._program_description
543 stream << "Usage: ./" << parser._program_name << " ";
544 // required optional argument
545 for (const auto &arg : parser._optional_arg_vec)
547 if (!arg._is_required)
549 stream << arg._short_name << " ";
550 std::string arg_name = arser::internal::remove_dash(arg._long_name);
551 std::for_each(arg_name.begin(), arg_name.end(),
552 [&stream](const char &c) { stream << static_cast<char>(::toupper(c)); });
555 // rest of the optional argument
556 for (const auto &arg : parser._optional_arg_vec)
558 if (arg._is_required)
560 stream << "[" << arg._short_name;
564 std::string arg_name = arser::internal::remove_dash(arg._long_name);
565 std::for_each(arg_name.begin(), arg_name.end(),
566 [&stream](const char &c) { stream << static_cast<char>(::toupper(c)); });
571 // positional arguement
572 for (const auto &arg : parser._positional_arg_vec)
574 stream << arg._long_name << " ";
578 ** print argument list and its help message
580 // get the length of the longest argument
581 size_t length_of_longest_arg = 0;
582 for (const auto &arg : parser._positional_arg_vec)
584 length_of_longest_arg = std::max(length_of_longest_arg,
585 arser::internal::make_comma_concatenated(arg._names).size());
587 for (const auto &arg : parser._optional_arg_vec)
589 length_of_longest_arg = std::max(length_of_longest_arg,
590 arser::internal::make_comma_concatenated(arg._names).size());
593 const size_t message_width = 60;
594 // positional argument
595 if (!parser._positional_arg_vec.empty())
597 stream << "[Positional argument]" << std::endl;
598 for (const auto &arg : parser._positional_arg_vec)
600 stream.width(length_of_longest_arg);
601 stream << std::left << arser::internal::make_comma_concatenated(arg._names) << "\t";
602 for (size_t i = 0; i < arg._help_message.length(); i += message_width)
605 stream << std::string(length_of_longest_arg, ' ') << "\t";
606 stream << arg._help_message.substr(i, message_width) << std::endl;
609 std::cout << std::endl;
612 if (!parser._optional_arg_vec.empty())
614 stream << "[Optional argument]" << std::endl;
615 for (const auto &arg : parser._optional_arg_vec)
617 stream.width(length_of_longest_arg);
618 stream << std::left << arser::internal::make_comma_concatenated(arg._names) << "\t";
619 for (size_t i = 0; i < arg._help_message.length(); i += message_width)
622 stream << std::string(length_of_longest_arg, ' ') << "\t";
623 stream << arg._help_message.substr(i, message_width) << std::endl;
632 std::string _program_name;
633 std::string _program_description;
634 std::list<Argument> _positional_arg_vec;
635 std::list<Argument> _optional_arg_vec;
636 std::map<std::string, Argument *> _arg_map;
639 template <typename T> T Arser::get_impl(const std::string &arg_name, T *)
641 auto arg = _arg_map.find(arg_name);
642 if (arg == _arg_map.end())
643 throw std::runtime_error("Invalid argument. "
644 "There is no argument you are looking for: " +
647 if (arg->second->_is_accumulated)
648 throw std::runtime_error(
650 "You called get using a type different from the one you specified."
651 "Accumulated argument is returned as std::vector of the specified type");
653 if (arg->second->_type != TypeName<T>::Get())
654 throw std::runtime_error("Type mismatch. "
655 "You called get() method with a type different "
656 "from the one you specified. "
657 "Please check the type of what you specified in "
658 "add_argument() method.");
660 if (arg->second->_values.size() == 0)
661 throw std::runtime_error("Wrong access. "
662 "You must make sure that the argument is given before accessing it. "
663 "You can do it by calling arser[\"argument\"].");
665 return internal::lexical_cast<T>(arg->second->_values[0]);
668 template <typename T> std::vector<T> Arser::get_impl(const std::string &arg_name, std::vector<T> *)
670 auto arg = _arg_map.find(arg_name);
671 if (arg == _arg_map.end())
672 throw std::runtime_error("Invalid argument. "
673 "There is no argument you are looking for: " +
676 // Accumulated arguments with scalar type (e.g., STR)
677 if (arg->second->_is_accumulated)
679 if (arg->second->_type != TypeName<T>::Get())
680 throw std::runtime_error("Type mismatch. "
681 "You called get using a type different from the one you specified.");
684 for (auto values : arg->second->_accum_values)
686 assert(values.size() == 1);
687 data.emplace_back(internal::lexical_cast<T>(values[0]));
692 if (arg->second->_type != TypeName<std::vector<T>>::Get())
693 throw std::runtime_error("Type mismatch. "
694 "You called get using a type different from the one you specified.");
697 std::transform(arg->second->_values.begin(), arg->second->_values.end(), std::back_inserter(data),
698 [](std::string str) -> T { return internal::lexical_cast<T>(str); });
702 // Accumulated arguments with vector type (e.g., STR_VEC)
703 template <typename T>
704 std::vector<std::vector<T>> Arser::get_impl(const std::string &arg_name,
705 std::vector<std::vector<T>> *)
707 auto arg = _arg_map.find(arg_name);
708 if (arg == _arg_map.end())
709 throw std::runtime_error("Invalid argument. "
710 "There is no argument you are looking for: " +
713 if (not arg->second->_is_accumulated)
714 throw std::runtime_error("Type mismatch. "
715 "You called get using a type different from the one you specified.");
717 if (arg->second->_type != TypeName<std::vector<T>>::Get())
718 throw std::runtime_error(
720 "You called get using a type different from the one you specified."
721 "Accumulated argument is returned as std::vector of the specified type");
723 std::vector<std::vector<T>> result;
724 for (auto values : arg->second->_accum_values)
727 std::transform(values.begin(), values.end(), std::back_inserter(data),
728 [](std::string str) -> T { return internal::lexical_cast<T>(str); });
729 result.emplace_back(data);
735 template <typename T> T Arser::get(const std::string &arg_name)
737 return get_impl(arg_name, static_cast<T *>(nullptr));
742 #endif // __ARSER_H__