ctrl-utils
argparse.h
1 
10 #ifndef CTRL_UTILS_ARGPARSE_H_
11 #define CTRL_UTILS_ARGPARSE_H_
12 
13 #include <ctrl_utils/string.h>
14 
15 #include <algorithm> // std::find
16 #include <exception> // std::invalid_argument
17 #include <iomanip> // std::setw, std::left
18 #include <iostream> // std::cout
19 #include <list> // std::list
20 #include <memory> // std::shared_ptr
21 #include <optional> // std::optional
22 #include <sstream> // std::stringstream
23 #include <string> // std::string
24 #include <string_view> // std::string_view
25 #include <unordered_map> // std::unordered_map
26 #include <utility> // std::move, std::pair
27 #include <vector> // std::vector
28 
29 namespace ctrl_utils {
30 
74 template <typename Derived>
75 std::optional<Derived> ParseArgs(int argc, char* argv[]);
76 
83 class Args {
84  public:
88  const std::string& help_string() const { return help_string_; }
89 
93  friend std::ostream& operator<<(std::ostream& os, const Args& args);
94 
95  protected:
105  template <typename T>
106  T Arg(std::string_view name, std::string_view description) {
107  return InitializeOrParseArg<PositionalParam, T>(true, name, {},
108  description);
109  }
110 
122  template <typename T>
123  T Kwarg(std::string_view keys, T&& default_value,
124  std::string_view description) {
125  return InitializeOrParseArg<KeywordParam>(
126  false, keys, std::forward<T>(default_value), description);
127  }
128 
140  bool Flag(std::string_view name, bool default_value,
141  std::string_view description) {
142  return InitializeOrParseArg<FlagParam>(false, name, default_value,
143  description);
144  }
145 
149  virtual std::string_view description() const { return ""; };
150 
151  private:
152  static constexpr std::string_view kTab = " ";
153 
154  class Param {
155  public:
156  Param(std::string_view name, std::string_view description)
157  : name(name), description(description) {}
158 
159  std::string_view name;
160  std::string_view description;
161 
162  protected:
163  static constexpr size_t kLenKey = 3 * Args::kTab.size();
164 
165  virtual void Print(std::ostream& os) const = 0;
166 
167  void PrintDescription(std::ostream& os, size_t len_keys) const;
168 
169  friend std::ostream& operator<<(std::ostream& os, const Param& param) {
170  param.Print(os);
171  return os;
172  }
173  };
174 
175  class PositionalParam : public Param {
176  public:
177  template <typename T>
178  PositionalParam(std::string_view name, const T& default_value,
179  std::string_view description)
180  : Param(name, description) {}
181 
182  template <typename T>
183  void Parse(std::list<std::string_view>& args, T& val) const;
184 
185  protected:
186  virtual void Print(std::ostream& os) const override;
187  };
188 
189  class KeywordParam : public Param {
190  public:
191  template <typename T>
192  KeywordParam(std::string_view keys, const T& default_value,
193  std::string_view description)
194  : Param({}, description),
195  keywords(ParseKeysAndName(keys, name)),
196  default_value(Args::ParseValue(default_value)) {}
197 
198  std::vector<std::string> keywords;
199  std::string default_value;
200 
201  template <typename T>
202  void Parse(std::list<std::string_view>& args, T& val) const;
203 
204  protected:
205  static std::vector<std::string> ParseKeysAndName(std::string_view keys,
206  std::string_view& name);
207 
208  virtual void Print(std::ostream& os) const override;
209  };
210 
211  class FlagParam : public Param {
212  public:
213  FlagParam(std::string_view name, bool default_value,
214  std::string_view description)
215  : Param(name, description),
216  positive_keyword("--" + std::string{name}),
217  negative_keyword("--no-" + std::string{name}),
218  default_keyword(default_value ? positive_keyword : negative_keyword) {
219  }
220 
221  std::string positive_keyword;
222  std::string negative_keyword;
223  std::string_view default_keyword;
224 
225  template <typename T>
226  void Parse(std::list<std::string_view>& args, T& val) const {}
227 
228  protected:
229  virtual void Print(std::ostream& os) const override;
230  };
231 
232  class Parser {
233  public:
234  Parser(int argc, char* argv[])
235  : args_(TokenizeArgs(argc, argv)), name_app_(argv[0]) {}
236 
237  template <typename ParamT, typename T>
238  T InitializeArg(bool is_required, std::string_view key, T&& default_value,
239  std::string_view description);
240 
241  template <typename ParamT, typename T>
242  T ParseArg(
243  std::string_view key, T&& val,
244  std::vector<std::pair<std::string_view, std::string>>& parsed_args);
245 
246  static std::list<std::string_view> TokenizeArgs(int argc, char* argv[]);
247 
248  std::string GenerateHelpString(std::string_view app_description) const;
249 
250  void CheckRemainingArguments() const;
251 
252  private:
253  std::string_view name_app_;
254 
255  // List of tokens that gets cleared during parsing.
256  std::list<std::string_view> args_;
257 
258  // Map from name to param for use during parsing.
259  std::unordered_map<std::string, std::shared_ptr<Param>> params_;
260 
261  // List of positional params for generating the help string.
262  std::vector<std::shared_ptr<Param>> required_params_;
263 
264  // List of optional params for generating the help string.
265  std::vector<std::shared_ptr<Param>> optional_params_;
266  };
267 
268  Args(int argc, char* argv[])
269  : parser_(std::make_unique<Parser>(argc, argv)) {}
270 
271  template <typename ParamT, typename T>
272  T InitializeOrParseArg(bool is_required, std::string_view key,
273  T&& default_value, std::string_view description);
274 
275  void SwitchToParsingStage();
276 
277  void Cleanup();
278 
279  template <typename T>
280  static std::string ParseValue(const T& default_value);
281 
282  bool initializing_ = true;
283 
284  std::string help_string_;
285 
286  // List of keys and parsed values for printing the result.
287  std::vector<std::pair<std::string_view, std::string>> parsed_args_;
288 
289  std::unique_ptr<Parser> parser_;
290 
291  template <typename Derived>
292  friend std::optional<Derived> ParseArgs(int, char*[]);
293 };
294 
296 // Procedural methods //
298 
299 template <typename Derived>
300 std::optional<Derived> ParseArgs(int argc, char* argv[]) {
301  // Initialize empty arg parser.
302  Derived derived(Args(argc, argv));
303 
304  // Switch to parsing stage.
305  derived.SwitchToParsingStage();
306 
307  // Parse args.
308  Args& base_args = dynamic_cast<Args&>(derived);
309 
310  // Hold onto help string for exception catching.
311  std::string_view help = base_args.help_string();
312  try {
313  Derived args(std::move(base_args));
314  args.Cleanup();
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;
319  }
320  return {};
321 }
322 
323 inline void Args::SwitchToParsingStage() {
324  initializing_ = false;
325  help_string_ = parser_->GenerateHelpString(description());
326 }
327 
328 inline void Args::Cleanup() {
329  parser_->CheckRemainingArguments();
330  parser_.reset();
331 }
332 
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) {
336  if (initializing_) {
337  return parser_->InitializeArg<ParamT>(
338  is_required, key, std::forward<T>(default_value), description);
339  }
340  return parser_->ParseArg<ParamT>(key, std::forward<T>(default_value),
341  parsed_args_);
342 }
343 
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);
349 
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);
354 }
355 
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) {
360  const auto* param =
361  dynamic_cast<const ParamT*>(params_.at(std::string{key}).get());
362  param->Parse(args_, val);
363 
364  parsed_args.emplace_back(param->name, Args::ParseValue(val));
365  return std::forward<T>(val);
366 }
367 
369 // Parsing methods //
371 
372 template <typename T>
373 void Args::PositionalParam::Parse(std::list<std::string_view>& args,
374  T& val) const {
375  // Verify next arg matches this parameter.
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());
380  }
381 
382  // Remove positional arg from args.
383  try {
384  val = ctrl_utils::FromString<T>(args.front());
385  } catch (...) {
386  std::stringstream ss;
387  ss << "Unable to parse positional argument: " << bold << args.front()
388  << normal;
389  throw std::invalid_argument(ss.str());
390  }
391  args.pop_front();
392 }
393 
394 template <typename T>
395 void Args::KeywordParam::Parse(std::list<std::string_view>& args,
396  T& val) const {
397  for (auto it = args.begin(); it != args.end(); ++it) {
398  const std::string_view& arg = *it;
399 
400  // Skip if arg not in keywords.
401  if (std::find(keywords.begin(), keywords.end(), arg) == keywords.end())
402  continue;
403 
404  // Parse value.
405  auto it_val = it;
406  ++it_val;
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());
411  }
412  try {
413  val = ctrl_utils::FromString<T>(*it_val);
414  } catch (...) {
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());
419  }
420 
421  // Remove keyword and value from args.
422  ++it_val;
423  args.erase(it, it_val);
424  break;
425  }
426 }
427 
428 template <>
429 inline void Args::FlagParam::Parse(std::list<std::string_view>& args,
430  bool& val) const {
431  for (auto it = args.begin(); it != args.end(); ++it) {
432  const std::string_view& arg = *it;
433 
434  // Check if arg is positive, negative, or neither.
435  if (arg == positive_keyword) {
436  val = true;
437  } else if (arg == negative_keyword) {
438  val = false;
439  } else {
440  continue;
441  }
442 
443  // Remove flag from args.
444  args.erase(it);
445  break;
446  }
447 }
448 
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) {
454  ss << " " << *it;
455  }
456  ss << normal;
457  throw std::invalid_argument(ss.str());
458  }
459 }
460 
462 // Printing methods //
464 
465 inline void Args::PositionalParam::Print(std::ostream& os) const {
466  // Print name.
467  os << Args::kTab << bold << name << normal;
468 
469  // Print description.
470  Param::PrintDescription(os, name.size());
471 }
472 
473 inline void Args::KeywordParam::Print(std::ostream& os) const {
474  size_t len_keys = 0;
475 
476  // Print keywords.
477  os << Args::kTab;
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 = ", ";
483  }
484 
485  // Print name.
486  os << " " << underline << name << normal;
487  len_keys += 1 + name.size();
488 
489  // Print description.
490  Param::PrintDescription(os, len_keys);
491  os << " [default " << bold << default_value << normal << "]";
492 }
493 
494 inline void Args::FlagParam::Print(std::ostream& os) const {
495  // Print keywords.
496  os << Args::kTab << bold << positive_keyword << normal << "/" << bold
497  << negative_keyword << normal;
498 
499  // Print description.
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 << "]";
503 }
504 
505 inline void Args::Param::PrintDescription(std::ostream& os,
506  size_t len_keys) const {
507  // Print spacing.
508  if (len_keys < kLenKey) {
509  os << std::setw(kLenKey - len_keys) << " ";
510  } else {
511  os << std::endl << Args::kTab << std::setw(kLenKey) << " ";
512  }
513 
514  // Print description.
515  os << description;
516 }
517 
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;
523  }
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;
527  }
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;
533  }
534  }
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;
539  }
540  }
541  return ss.str();
542 }
543 
544 inline std::ostream& operator<<(std::ostream& os, const Args& args) {
545  // Get max key length;
546  size_t max_len_key = 0;
547  for (const std::pair<std::string_view, std::string>& key_val :
548  args.parsed_args_) {
549  if (key_val.first.size() > max_len_key) {
550  max_len_key = key_val.first.size();
551  }
552  }
553 
554  os << "Parsed args:" << std::endl;
555  for (const std::pair<std::string_view, std::string>& key_val :
556  args.parsed_args_) {
557  os << Args::kTab << bold << std::setw(max_len_key + 2) << std::left
558  << key_val.first << normal << key_val.second << std::endl;
559  }
560  return os;
561 }
562 
564 // Utility methods //
566 
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 = "\'\'";
571  return str_default;
572 }
573 
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;
578 
579  // Tokenize keys by ','.
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;
583 
584  // Get key.
585  const std::string_view key = keys.substr(idx_start, idx_end - idx_start);
586 
587  // Check if key is longest one.
588  if (key.size() > longest_key.size()) {
589  longest_key = key;
590  }
591 
592  // Prepend dashes to key.
593  const char* dashes = key.size() == 1 ? "-" : "--";
594  parsed_keys.push_back(dashes + std::string{key});
595 
596  // Advance idx_start to comma.
597  idx_start = idx_end;
598  break;
599  }
600  }
601 
602  // Set name to longest key.
603  name = longest_key;
604 
605  return parsed_keys;
606 }
607 
608 inline std::list<std::string_view> Args::Parser::TokenizeArgs(int argc,
609  char* argv[]) {
610  std::list<std::string_view> args;
611  for (int i = 1; i < argc; i++) {
612  args.emplace_back(argv[i]);
613  }
614  return args;
615 }
616 
617 } // namespace ctrl_utils
618 
619 #endif // CTRL_UTILS_ARGPARSE_H_
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
Definition: chrono.h:15