10 #ifndef CTRL_UTILS_ARGPARSE_H_
11 #define CTRL_UTILS_ARGPARSE_H_
13 #include <ctrl_utils/string.h>
24 #include <string_view>
25 #include <unordered_map>
74 template <
typename Derived>
88 const std::string&
help_string()
const {
return help_string_; }
93 friend std::ostream&
operator<<(std::ostream& os,
const Args& args);
105 template <
typename T>
107 return InitializeOrParseArg<PositionalParam, T>(
true, name, {},
122 template <
typename T>
123 T
Kwarg(std::string_view keys, T&& default_value,
125 return InitializeOrParseArg<KeywordParam>(
126 false, keys, std::forward<T>(default_value),
description);
140 bool Flag(std::string_view name,
bool default_value,
142 return InitializeOrParseArg<FlagParam>(
false, name, default_value,
152 static constexpr std::string_view kTab =
" ";
156 Param(std::string_view name, std::string_view
description)
159 std::string_view name;
163 static constexpr
size_t kLenKey = 3 * Args::kTab.size();
165 virtual void Print(std::ostream& os)
const = 0;
167 void PrintDescription(std::ostream& os,
size_t len_keys)
const;
169 friend std::ostream&
operator<<(std::ostream& os,
const Param& param) {
175 class PositionalParam :
public Param {
177 template <
typename T>
178 PositionalParam(std::string_view name,
const T& default_value,
182 template <
typename T>
183 void Parse(std::list<std::string_view>& args, T& val)
const;
186 virtual void Print(std::ostream& os)
const override;
189 class KeywordParam :
public Param {
191 template <
typename T>
192 KeywordParam(std::string_view keys,
const T& default_value,
195 keywords(ParseKeysAndName(keys, name)),
196 default_value(Args::ParseValue(default_value)) {}
198 std::vector<std::string> keywords;
199 std::string default_value;
201 template <
typename T>
202 void Parse(std::list<std::string_view>& args, T& val)
const;
205 static std::vector<std::string> ParseKeysAndName(std::string_view keys,
206 std::string_view& name);
208 virtual void Print(std::ostream& os)
const override;
211 class FlagParam :
public Param {
213 FlagParam(std::string_view name,
bool default_value,
216 positive_keyword(
"--" +
std::string{name}),
217 negative_keyword(
"--no-" +
std::string{name}),
218 default_keyword(default_value ? positive_keyword : negative_keyword) {
221 std::string positive_keyword;
222 std::string negative_keyword;
223 std::string_view default_keyword;
225 template <
typename T>
226 void Parse(std::list<std::string_view>& args, T& val)
const {}
229 virtual void Print(std::ostream& os)
const override;
234 Parser(
int argc,
char* argv[])
235 : args_(TokenizeArgs(argc, argv)), name_app_(argv[0]) {}
237 template <
typename ParamT,
typename T>
238 T InitializeArg(
bool is_required, std::string_view key, T&& default_value,
241 template <
typename ParamT,
typename T>
243 std::string_view key, T&& val,
244 std::vector<std::pair<std::string_view, std::string>>& parsed_args);
246 static std::list<std::string_view> TokenizeArgs(
int argc,
char* argv[]);
248 std::string GenerateHelpString(std::string_view app_description)
const;
250 void CheckRemainingArguments()
const;
253 std::string_view name_app_;
256 std::list<std::string_view> args_;
259 std::unordered_map<std::string, std::shared_ptr<Param>> params_;
262 std::vector<std::shared_ptr<Param>> required_params_;
265 std::vector<std::shared_ptr<Param>> optional_params_;
268 Args(
int argc,
char* argv[])
269 : parser_(
std::make_unique<Parser>(argc, argv)) {}
271 template <
typename ParamT,
typename T>
272 T InitializeOrParseArg(
bool is_required, std::string_view key,
275 void SwitchToParsingStage();
279 template <
typename T>
280 static std::string ParseValue(
const T& default_value);
282 bool initializing_ =
true;
284 std::string help_string_;
287 std::vector<std::pair<std::string_view, std::string>> parsed_args_;
289 std::unique_ptr<Parser> parser_;
291 template <
typename Derived>
299 template <
typename Derived>
302 Derived derived(
Args(argc, argv));
305 derived.SwitchToParsingStage();
308 Args& base_args =
dynamic_cast<Args&
>(derived);
313 Derived args(std::move(base_args));
315 return std::move(args);
316 }
catch (std::invalid_argument& e) {
317 std::cout << help << std::endl
318 << bold <<
"Error: " << normal << e.what() << std::endl;
323 inline void Args::SwitchToParsingStage() {
324 initializing_ =
false;
325 help_string_ = parser_->GenerateHelpString(
description());
328 inline void Args::Cleanup() {
329 parser_->CheckRemainingArguments();
333 template <
typename ParamT,
typename T>
334 T Args::InitializeOrParseArg(
bool is_required, std::string_view key,
335 T&& default_value, std::string_view description) {
337 return parser_->InitializeArg<ParamT>(
338 is_required, key, std::forward<T>(default_value),
description);
340 return parser_->ParseArg<ParamT>(key, std::forward<T>(default_value),
344 template <
typename ParamT,
typename T>
345 T Args::Parser::InitializeArg(
bool is_required, std::string_view key,
346 T&& default_value, std::string_view description) {
347 auto param = std::make_shared<ParamT>(key, default_value, description);
348 params_.emplace(std::string{key}, param);
350 std::vector<std::shared_ptr<Param>>& ordered_params =
351 is_required ? required_params_ : optional_params_;
352 ordered_params.push_back(std::move(param));
353 return std::forward<T>(default_value);
356 template <
typename ParamT,
typename T>
357 T Args::Parser::ParseArg(
358 std::string_view key, T&& val,
359 std::vector<std::pair<std::string_view, std::string>>& parsed_args) {
361 dynamic_cast<const ParamT*
>(params_.at(std::string{key}).get());
362 param->Parse(args_, val);
364 parsed_args.emplace_back(param->name, Args::ParseValue(val));
365 return std::forward<T>(val);
372 template <
typename T>
373 void Args::PositionalParam::Parse(std::list<std::string_view>& args,
376 if (args.empty() || args.front()[0] ==
'-') {
377 std::stringstream ss;
378 ss <<
"Missing positional argument: " << bold << name << normal;
379 throw std::invalid_argument(ss.str());
384 val = ctrl_utils::FromString<T>(args.front());
386 std::stringstream ss;
387 ss <<
"Unable to parse positional argument: " << bold << args.front()
389 throw std::invalid_argument(ss.str());
394 template <
typename T>
395 void Args::KeywordParam::Parse(std::list<std::string_view>& args,
397 for (
auto it = args.begin(); it != args.end(); ++it) {
398 const std::string_view& arg = *it;
401 if (std::find(keywords.begin(), keywords.end(), arg) == keywords.end())
407 if (it_val == args.end()) {
408 std::stringstream ss;
409 ss <<
"Missing value for keyword: " << bold << arg << normal;
410 throw std::invalid_argument(ss.str());
413 val = ctrl_utils::FromString<T>(*it_val);
415 std::stringstream ss;
416 ss <<
"Unable to parse value for keyword argument: " << bold << arg <<
" "
417 << *it_val << normal;
418 throw std::invalid_argument(ss.str());
423 args.erase(it, it_val);
429 inline void Args::FlagParam::Parse(std::list<std::string_view>& args,
431 for (
auto it = args.begin(); it != args.end(); ++it) {
432 const std::string_view& arg = *it;
435 if (arg == positive_keyword) {
437 }
else if (arg == negative_keyword) {
449 inline void Args::Parser::CheckRemainingArguments()
const {
450 if (!args_.empty()) {
451 std::stringstream ss;
452 ss <<
"Unrecognized arguments:" << bold;
453 for (
auto it = args_.begin(); it != args_.end(); ++it) {
457 throw std::invalid_argument(ss.str());
465 inline void Args::PositionalParam::Print(std::ostream& os)
const {
467 os << Args::kTab << bold << name << normal;
470 Param::PrintDescription(os, name.size());
473 inline void Args::KeywordParam::Print(std::ostream& os)
const {
478 std::string_view delimiter;
479 for (
const std::string& keyword : keywords) {
480 os << delimiter << bold << keyword << normal;
481 len_keys += delimiter.size() + keyword.size();
482 if (delimiter.empty()) delimiter =
", ";
486 os <<
" " << underline << name << normal;
487 len_keys += 1 + name.size();
490 Param::PrintDescription(os, len_keys);
491 os <<
" [default " << bold << default_value << normal <<
"]";
494 inline void Args::FlagParam::Print(std::ostream& os)
const {
496 os << Args::kTab << bold << positive_keyword << normal <<
"/" << bold
497 << negative_keyword << normal;
500 const size_t len_keys = positive_keyword.size() + 1 + negative_keyword.size();
501 Param::PrintDescription(os, len_keys);
502 os <<
" [default " << bold << default_keyword << normal <<
"]";
505 inline void Args::Param::PrintDescription(std::ostream& os,
506 size_t len_keys)
const {
508 if (len_keys < kLenKey) {
509 os << std::setw(kLenKey - len_keys) <<
" ";
511 os << std::endl << Args::kTab << std::setw(kLenKey) <<
" ";
518 inline std::string Args::Parser::GenerateHelpString(
519 std::string_view app_description)
const {
520 std::stringstream ss;
521 if (!app_description.empty()) {
522 ss << app_description << std::endl << std::endl;
524 ss <<
"Usage:" << std::endl << Args::kTab << bold << name_app_ << normal;
525 for (
const std::shared_ptr<Param>& param : required_params_) {
526 ss <<
" " << param->name;
528 ss <<
" [options...]" << std::endl;
529 if (!required_params_.empty()) {
530 ss << std::endl <<
"Required arguments:" << std::endl;
531 for (
const std::shared_ptr<Param>& param : required_params_) {
532 ss << *param << std::endl;
535 if (!optional_params_.empty()) {
536 ss << std::endl <<
"Optional arguments:" << std::endl;
537 for (
const std::shared_ptr<Param>& param : optional_params_) {
538 ss << *param << std::endl;
546 size_t max_len_key = 0;
547 for (
const std::pair<std::string_view, std::string>& key_val :
549 if (key_val.first.size() > max_len_key) {
550 max_len_key = key_val.first.size();
554 os <<
"Parsed args:" << std::endl;
555 for (
const std::pair<std::string_view, std::string>& key_val :
557 os << Args::kTab << bold << std::setw(max_len_key + 2) << std::left
558 << key_val.first << normal << key_val.second << std::endl;
567 template <
typename T>
568 std::string Args::ParseValue(
const T& default_value) {
569 std::string str_default = ToString(default_value);
570 if (str_default.empty()) str_default =
"\'\'";
574 inline std::vector<std::string> Args::KeywordParam::ParseKeysAndName(
575 std::string_view keys, std::string_view& name) {
576 std::vector<std::string> parsed_keys;
577 std::string_view longest_key;
580 for (
size_t idx_start = 0; idx_start < keys.size(); idx_start++) {
581 for (
size_t idx_end = idx_start; idx_end <= keys.size(); idx_end++) {
582 if (idx_end != keys.size() && keys[idx_end] !=
',')
continue;
585 const std::string_view key = keys.substr(idx_start, idx_end - idx_start);
588 if (key.size() > longest_key.size()) {
593 const char* dashes = key.size() == 1 ?
"-" :
"--";
594 parsed_keys.push_back(dashes + std::string{key});
608 inline std::list<std::string_view> Args::Parser::TokenizeArgs(
int argc,
610 std::list<std::string_view> args;
611 for (
int i = 1; i < argc; i++) {
612 args.emplace_back(argv[i]);
Definition: argparse.h:83
const std::string & help_string() const
Definition: argparse.h:88
bool Flag(std::string_view name, bool default_value, std::string_view description)
Definition: argparse.h:140
T Kwarg(std::string_view keys, T &&default_value, std::string_view description)
Definition: argparse.h:123
friend std::ostream & operator<<(std::ostream &os, const Args &args)
Definition: argparse.h:544
T Arg(std::string_view name, std::string_view description)
Definition: argparse.h:106
virtual std::string_view description() const
Definition: argparse.h:149
friend std::optional< Derived > ParseArgs(int, char *[])
Definition: argparse.h:300
Definition: optional.h:30
Definition: ctrl_utils.cc:18
std::optional< Derived > ParseArgs(int argc, char *argv[])
Definition: argparse.h:300
std::ostream & operator<<(std::ostream &os, const Args &args)
Definition: argparse.h:544