You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1331 lines
36 KiB

3 months ago
  1. /***
  2. ____ __ ____ __
  3. ( _ \ / \( _ \( )
  4. ) __/( O )) __// (_/\
  5. (__) \__/(__) \____/
  6. version 1.3.0
  7. https://github.com/badaix/popl
  8. This file is part of popl (program options parser lib)
  9. Copyright (C) 2015-2021 Johannes Pohl
  10. This software may be modified and distributed under the terms
  11. of the MIT license. See the LICENSE file for details.
  12. ***/
  13. /// checked with clang-tidy:
  14. /// run-clang-tidy-3.8.py -header-filter='.*'
  15. /// -checks='*,-misc-definitions-in-headers,-google-readability-braces-around-statements,-readability-braces-around-statements,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-google-build-using-namespace,-google-build-using-namespace'
  16. #ifndef POPL_HPP
  17. #define POPL_HPP
  18. #ifndef NOMINMAX
  19. #define NOMINMAX
  20. #endif // NOMINMAX
  21. #include <algorithm>
  22. #include <cstdio>
  23. #include <cstring>
  24. #include <fstream>
  25. #include <iostream>
  26. #include <memory>
  27. #include <sstream>
  28. #include <stdexcept>
  29. #include <vector>
  30. #ifdef WINDOWS
  31. #include <cctype>
  32. #endif
  33. namespace popl
  34. {
  35. #define POPL_VERSION "1.3.0"
  36. /// Option's argument type
  37. /**
  38. * Switch has "no" argument
  39. * Value has "required" argument
  40. * Implicit has "optional" argument
  41. */
  42. enum class Argument
  43. {
  44. no = 0, // option never takes an argument
  45. required, // option always requires an argument
  46. optional // option may take an argument
  47. };
  48. /// Option's attribute
  49. /**
  50. * inactive: Option is not set and will not be parsed
  51. * hidden: Option is active, but will not show up in the help message
  52. * required: Option must be set on the command line. Otherwise an exception will be thrown
  53. * optional: Option must not be set. Default attribute.
  54. * advanced: Option is advanced and will only show up in the advanced help message
  55. * expoert: Option is expert and will only show up in the expert help message
  56. */
  57. enum class Attribute
  58. {
  59. inactive = 0,
  60. hidden = 1,
  61. required = 2,
  62. optional = 3,
  63. advanced = 4,
  64. expert = 5
  65. };
  66. /// Option name type. Used in invalid_option exception.
  67. /**
  68. * unspecified: not specified
  69. * short_name: The option's short name
  70. * long_name: The option's long name
  71. */
  72. enum class OptionName
  73. {
  74. unspecified,
  75. short_name,
  76. long_name
  77. };
  78. /// Abstract Base class for Options
  79. /**
  80. * Base class for Options
  81. * holds just configuration data, no runtime data.
  82. * Option is not bound to a special type "T"
  83. */
  84. class Option
  85. {
  86. friend class OptionParser;
  87. public:
  88. /// Construct an Option
  89. /// @param short_name the options's short name. Must be empty or one character.
  90. /// @param long_name the option's long name. Can be empty.
  91. /// @param description the Option's description that will be shown in the help message
  92. Option(const std::string& short_name, const std::string& long_name, std::string description);
  93. /// Destructor
  94. virtual ~Option() = default;
  95. /// default copy constructor
  96. Option(const Option&) = default;
  97. /// default move constructor
  98. Option(Option&&) = default;
  99. /// default assignement operator
  100. Option& operator=(const Option&) = default;
  101. /// default move assignement operator
  102. Option& operator=(Option&&) = default;
  103. /// Get the Option's short name
  104. /// @return character of the options's short name or 0 if no short name is defined
  105. char short_name() const;
  106. /// Get the Option's long name
  107. /// @return the long name of the Option. Empty string if no long name is defined
  108. std::string long_name() const;
  109. /// Get the Option's long or short name
  110. /// @param what_name the option's name to return
  111. /// @param what_hyphen preced the returned name with (double-)hypen
  112. /// @return the requested name of the Option. Empty string if not defined.
  113. std::string name(OptionName what_name, bool with_hypen = false) const;
  114. /// Get the Option's description
  115. /// @return the description
  116. std::string description() const;
  117. /// Get the Option's default value
  118. /// @param out stream to write the default value to
  119. /// @return true if a default value is available, false if not
  120. virtual bool get_default(std::ostream& out) const = 0;
  121. /// Set the Option's attribute
  122. /// @param attribute
  123. void set_attribute(const Attribute& attribute);
  124. /// Get the Option's attribute
  125. /// @return the Options's attribute
  126. Attribute attribute() const;
  127. /// Get the Option's argument type
  128. /// @return argument type (no, required, optional)
  129. virtual Argument argument_type() const = 0;
  130. /// Check how often the Option is set on command line
  131. /// @return the Option's count on command line
  132. virtual size_t count() const = 0;
  133. /// Check if the Option is set
  134. /// @return true if set at least once
  135. virtual bool is_set() const = 0;
  136. protected:
  137. /// Parse the command line option and fill the internal data structure
  138. /// @param what_name short or long option name
  139. /// @param value the value as given on command line
  140. virtual void parse(OptionName what_name, const char* value) = 0;
  141. /// Clear the internal data structure
  142. virtual void clear() = 0;
  143. std::string short_name_;
  144. std::string long_name_;
  145. std::string description_;
  146. Attribute attribute_;
  147. };
  148. /// Value option with optional default value
  149. /**
  150. * Value option with optional default value
  151. * If set, it requires an argument
  152. */
  153. template <class T>
  154. class Value : public Option
  155. {
  156. public:
  157. /// Construct an Value Option
  158. /// @param short_name the option's short name. Must be empty or one character.
  159. /// @param long_name the option's long name. Can be empty.
  160. /// @param description the Option's description that will be shown in the help message
  161. Value(const std::string& short_name, const std::string& long_name, const std::string& description);
  162. /// Construct an Value Option
  163. /// @param short_name the option's short name. Must be empty or one character.
  164. /// @param long_name the option's long name. Can be empty.
  165. /// @param description the Option's description that will be shown in the help message
  166. /// @param default_val the Option's default value
  167. /// @param assign_to pointer to a variable to assign the parsed command line value to
  168. Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to = nullptr);
  169. size_t count() const override;
  170. bool is_set() const override;
  171. /// Assign the last parsed command line value to "var"
  172. /// @param var pointer to the variable where is value is written to
  173. void assign_to(T* var);
  174. /// Manually set the Option's value. Deletes current value(s)
  175. /// @param value the new value of the option
  176. void set_value(const T& value);
  177. /// Get the Option's value. Will throw if option at index idx is not available
  178. /// @param idx the zero based index of the value (if set multiple times)
  179. /// @return the Option's value at index "idx"
  180. T value(size_t idx = 0) const;
  181. /// Get the Option's value, return default_value if not set.
  182. /// @param default_value return value if value is not set
  183. /// @param idx the zero based index of the value (if set multiple times)
  184. /// @return the Option's value at index "idx" or the default value or default_value
  185. T value_or(const T& default_value, size_t idx = 0) const;
  186. /// Set the Option's default value
  187. /// @param value the default value if not specified on command line
  188. void set_default(const T& value);
  189. /// Check if the Option has a default value
  190. /// @return true if the Option has a default value
  191. bool has_default() const;
  192. /// Get the Option's default value. Will throw if no default is set.
  193. /// @return the Option's default value
  194. T get_default() const;
  195. bool get_default(std::ostream& out) const override;
  196. Argument argument_type() const override;
  197. protected:
  198. void parse(OptionName what_name, const char* value) override;
  199. std::unique_ptr<T> default_;
  200. virtual void update_reference();
  201. virtual void add_value(const T& value);
  202. void clear() override;
  203. T* assign_to_;
  204. std::vector<T> values_;
  205. };
  206. /// Value option with implicit default value
  207. /**
  208. * Value option with implicit default value
  209. * If set, an argument is optional
  210. * -without argument it carries the implicit default value
  211. * -with argument it carries the explicit value
  212. */
  213. template <class T>
  214. class Implicit : public Value<T>
  215. {
  216. public:
  217. Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to = nullptr);
  218. Argument argument_type() const override;
  219. protected:
  220. void parse(OptionName what_name, const char* value) override;
  221. };
  222. /// Value option without value
  223. /**
  224. * Value option without value
  225. * Does not require an argument
  226. * Can be either set or not set
  227. */
  228. class Switch : public Value<bool>
  229. {
  230. public:
  231. Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to = nullptr);
  232. void set_default(const bool& value) = delete;
  233. Argument argument_type() const override;
  234. protected:
  235. void parse(OptionName what_name, const char* value) override;
  236. };
  237. using Option_ptr = std::shared_ptr<Option>;
  238. /// OptionParser manages all Options
  239. /**
  240. * OptionParser manages all Options
  241. * Add Options (Option_Type = Value<T>, Implicit<T> or Switch) with "add<Option_Type>(option params)"
  242. * Call "parse(argc, argv)" to trigger parsing of the options and to
  243. * fill "non_option_args" and "unknown_options"
  244. */
  245. class OptionParser
  246. {
  247. public:
  248. /// Construct the OptionParser
  249. /// @param description used for the help message
  250. explicit OptionParser(std::string description = "");
  251. /// Destructor
  252. virtual ~OptionParser() = default;
  253. /// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")'
  254. /// @param T the option type (Value, Switch, Implicit)
  255. /// @param attribute the Option's attribute (inactive, hidden, required, optional, ...)
  256. /// @param Ts the Option's parameter
  257. template <typename T, Attribute attribute, typename... Ts>
  258. std::shared_ptr<T> add(Ts&&... params);
  259. /// Add an Option e.g. 'add<Value<int>>("i", "int", "description for the -i option")'
  260. /// @param T the option type (Value, Switch, Implicit)
  261. /// @param Ts the Option's parameter
  262. template <typename T, typename... Ts>
  263. std::shared_ptr<T> add(Ts&&... params);
  264. /// Parse an ini file into the added Options
  265. /// @param ini_filename full path of the ini file
  266. void parse(const std::string& ini_filename);
  267. /// Parse the command line into the added Options
  268. /// @param argc command line argument count
  269. /// @param argv command line arguments
  270. void parse(int argc, const char* const argv[]);
  271. /// Delete all parsed options
  272. void reset();
  273. /// Produce a help message
  274. /// @param max_attribute show options up to this level (optional, advanced, expert)
  275. /// @return the help message
  276. std::string help(const Attribute& max_attribute = Attribute::optional) const;
  277. /// Get the OptionParser's description
  278. /// @return the description as given during construction
  279. std::string description() const;
  280. /// Get all options that where added with "add"
  281. /// @return a vector of the contained Options
  282. const std::vector<Option_ptr>& options() const;
  283. /// Get command line arguments without option
  284. /// e.g. "-i 5 hello" => hello
  285. /// e.g. "-i 5 -- from here non option args" => "from", "here", "non", "option", "args"
  286. /// @return vector to "stand-alone" command line arguments
  287. const std::vector<std::string>& non_option_args() const;
  288. /// Get unknown command options
  289. /// e.g. '--some_unknown_option="hello"'
  290. /// @return vector to "stand-alone" command line arguments
  291. const std::vector<std::string>& unknown_options() const;
  292. /// Get an Option by it's long name
  293. /// @param the Option's long name
  294. /// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr
  295. template <typename T>
  296. std::shared_ptr<T> get_option(const std::string& long_name) const;
  297. /// Get an Option by it's short name
  298. /// @param the Option's short name
  299. /// @return a pointer of type "Value, Switch, Implicit" to the Option or nullptr
  300. template <typename T>
  301. std::shared_ptr<T> get_option(char short_name) const;
  302. protected:
  303. std::vector<Option_ptr> options_;
  304. std::string description_;
  305. std::vector<std::string> non_option_args_;
  306. std::vector<std::string> unknown_options_;
  307. Option_ptr find_option(const std::string& long_name) const;
  308. Option_ptr find_option(char short_name) const;
  309. };
  310. class invalid_option : public std::invalid_argument
  311. {
  312. public:
  313. enum class Error
  314. {
  315. missing_argument,
  316. invalid_argument,
  317. too_many_arguments,
  318. missing_option
  319. };
  320. invalid_option(const Option* option, invalid_option::Error error, OptionName what_name, std::string value, const std::string& text)
  321. : std::invalid_argument(text.c_str()), option_(option), error_(error), what_name_(what_name), value_(std::move(value))
  322. {
  323. }
  324. invalid_option(const Option* option, invalid_option::Error error, const std::string& text)
  325. : invalid_option(option, error, OptionName::unspecified, "", text)
  326. {
  327. }
  328. const Option* option() const
  329. {
  330. return option_;
  331. }
  332. Error error() const
  333. {
  334. return error_;
  335. }
  336. OptionName what_name() const
  337. {
  338. return what_name_;
  339. }
  340. std::string value() const
  341. {
  342. return value_;
  343. }
  344. private:
  345. const Option* option_;
  346. Error error_;
  347. OptionName what_name_;
  348. std::string value_;
  349. };
  350. /// Base class for an OptionPrinter
  351. /**
  352. * OptionPrinter creates a help message for a given OptionParser
  353. */
  354. class OptionPrinter
  355. {
  356. public:
  357. /// Constructor
  358. /// @param option_parser the OptionParser to create the help message from
  359. explicit OptionPrinter(const OptionParser* option_parser) : option_parser_(option_parser)
  360. {
  361. }
  362. /// Destructor
  363. virtual ~OptionPrinter() = default;
  364. /// Create a help message
  365. /// @param max_attribute show options up to this level (optional, advanced, expert)
  366. /// @return the help message
  367. virtual std::string print(const Attribute& max_attribute = Attribute::optional) const = 0;
  368. protected:
  369. const OptionParser* option_parser_;
  370. };
  371. /// Option printer for the console
  372. /**
  373. * Standard console option printer
  374. * Creates a human readable help message
  375. */
  376. class ConsoleOptionPrinter : public OptionPrinter
  377. {
  378. public:
  379. explicit ConsoleOptionPrinter(const OptionParser* option_parser);
  380. ~ConsoleOptionPrinter() override = default;
  381. std::string print(const Attribute& max_attribute = Attribute::optional) const override;
  382. private:
  383. std::string to_string(Option_ptr option) const;
  384. };
  385. /// Option printer for man pages
  386. /**
  387. * Creates help messages in groff format that can be used in man pages
  388. */
  389. class GroffOptionPrinter : public OptionPrinter
  390. {
  391. public:
  392. explicit GroffOptionPrinter(const OptionParser* option_parser);
  393. ~GroffOptionPrinter() override = default;
  394. std::string print(const Attribute& max_attribute = Attribute::optional) const override;
  395. private:
  396. std::string to_string(Option_ptr option) const;
  397. };
  398. /// Option printer for bash completion
  399. /**
  400. * Creates a script with all options (short and long) that can be used for bash completion
  401. */
  402. class BashCompletionOptionPrinter : public OptionPrinter
  403. {
  404. public:
  405. BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name);
  406. ~BashCompletionOptionPrinter() override = default;
  407. std::string print(const Attribute& max_attribute = Attribute::optional) const override;
  408. private:
  409. std::string program_name_;
  410. };
  411. /// Option implementation /////////////////////////////////
  412. inline Option::Option(const std::string& short_name, const std::string& long_name, std::string description)
  413. : short_name_(short_name), long_name_(long_name), description_(std::move(description)), attribute_(Attribute::optional)
  414. {
  415. if (short_name.size() > 1)
  416. throw std::invalid_argument("length of short name must be <= 1: '" + short_name + "'");
  417. if (short_name.empty() && long_name.empty())
  418. throw std::invalid_argument("short and long name are empty");
  419. }
  420. inline char Option::short_name() const
  421. {
  422. if (!short_name_.empty())
  423. return short_name_[0];
  424. return 0;
  425. }
  426. inline std::string Option::long_name() const
  427. {
  428. return long_name_;
  429. }
  430. inline std::string Option::name(OptionName what_name, bool with_hypen) const
  431. {
  432. if (what_name == OptionName::short_name)
  433. return short_name_.empty() ? "" : ((with_hypen ? "-" : "") + short_name_);
  434. if (what_name == OptionName::long_name)
  435. return long_name_.empty() ? "" : ((with_hypen ? "--" : "") + long_name_);
  436. return "";
  437. }
  438. inline std::string Option::description() const
  439. {
  440. return description_;
  441. }
  442. inline void Option::set_attribute(const Attribute& attribute)
  443. {
  444. attribute_ = attribute;
  445. }
  446. inline Attribute Option::attribute() const
  447. {
  448. return attribute_;
  449. }
  450. /// Value implementation /////////////////////////////////
  451. template <class T>
  452. inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description)
  453. : Option(short_name, long_name, description), assign_to_(nullptr)
  454. {
  455. }
  456. template <class T>
  457. inline Value<T>::Value(const std::string& short_name, const std::string& long_name, const std::string& description, const T& default_val, T* assign_to)
  458. : Value<T>(short_name, long_name, description)
  459. {
  460. assign_to_ = assign_to;
  461. set_default(default_val);
  462. }
  463. template <class T>
  464. inline size_t Value<T>::count() const
  465. {
  466. return values_.size();
  467. }
  468. template <class T>
  469. inline bool Value<T>::is_set() const
  470. {
  471. return !values_.empty();
  472. }
  473. template <class T>
  474. inline void Value<T>::assign_to(T* var)
  475. {
  476. assign_to_ = var;
  477. update_reference();
  478. }
  479. template <class T>
  480. inline void Value<T>::set_value(const T& value)
  481. {
  482. clear();
  483. add_value(value);
  484. }
  485. template <class T>
  486. inline T Value<T>::value_or(const T& default_value, size_t idx) const
  487. {
  488. if (idx < values_.size())
  489. return values_[idx];
  490. else if (default_)
  491. return *default_;
  492. else
  493. return default_value;
  494. }
  495. template <class T>
  496. inline T Value<T>::value(size_t idx) const
  497. {
  498. if (!this->is_set() && default_)
  499. return *default_;
  500. if (!is_set() || (idx >= count()))
  501. {
  502. std::stringstream optionStr;
  503. if (!is_set())
  504. optionStr << "option not set: \"";
  505. else
  506. optionStr << "index out of range (" << idx << ") for \"";
  507. if (short_name() != 0)
  508. optionStr << "-" << short_name();
  509. else
  510. optionStr << "--" << long_name();
  511. optionStr << "\"";
  512. throw std::out_of_range(optionStr.str());
  513. }
  514. return values_[idx];
  515. }
  516. template <class T>
  517. inline void Value<T>::set_default(const T& value)
  518. {
  519. this->default_.reset(new T);
  520. *this->default_ = value;
  521. update_reference();
  522. }
  523. template <class T>
  524. inline bool Value<T>::has_default() const
  525. {
  526. return (this->default_ != nullptr);
  527. }
  528. template <class T>
  529. inline T Value<T>::get_default() const
  530. {
  531. if (!has_default())
  532. throw std::runtime_error("no default value set");
  533. return *this->default_;
  534. }
  535. template <class T>
  536. inline bool Value<T>::get_default(std::ostream& out) const
  537. {
  538. if (!has_default())
  539. return false;
  540. out << *this->default_;
  541. return true;
  542. }
  543. template <class T>
  544. inline Argument Value<T>::argument_type() const
  545. {
  546. return Argument::required;
  547. }
  548. template <>
  549. inline void Value<std::string>::parse(OptionName what_name, const char* value)
  550. {
  551. if (strlen(value) == 0)
  552. throw invalid_option(this, invalid_option::Error::missing_argument, what_name, value, "missing argument for " + name(what_name, true));
  553. add_value(value);
  554. }
  555. template <>
  556. inline void Value<bool>::parse(OptionName /*what_name*/, const char* value)
  557. {
  558. bool val =
  559. ((value != nullptr) && ((strcmp(value, "1") == 0) || (strcmp(value, "true") == 0) || (strcmp(value, "True") == 0) || (strcmp(value, "TRUE") == 0)));
  560. add_value(val);
  561. }
  562. template <class T>
  563. inline void Value<T>::parse(OptionName what_name, const char* value)
  564. {
  565. T parsed_value;
  566. std::string strValue;
  567. if (value != nullptr)
  568. strValue = value;
  569. std::istringstream is(strValue);
  570. int valuesRead = 0;
  571. while (is.good())
  572. {
  573. if (is.peek() != EOF)
  574. is >> parsed_value;
  575. else
  576. break;
  577. valuesRead++;
  578. }
  579. if (is.fail())
  580. throw invalid_option(this, invalid_option::Error::invalid_argument, what_name, value,
  581. "invalid argument for " + name(what_name, true) + ": '" + strValue + "'");
  582. if (valuesRead > 1)
  583. throw invalid_option(this, invalid_option::Error::too_many_arguments, what_name, value,
  584. "too many arguments for " + name(what_name, true) + ": '" + strValue + "'");
  585. if (strValue.empty())
  586. throw invalid_option(this, invalid_option::Error::missing_argument, what_name, "", "missing argument for " + name(what_name, true));
  587. this->add_value(parsed_value);
  588. }
  589. template <class T>
  590. inline void Value<T>::update_reference()
  591. {
  592. if (this->assign_to_)
  593. {
  594. if (!this->is_set() && default_)
  595. *this->assign_to_ = *default_;
  596. else if (this->is_set())
  597. *this->assign_to_ = values_.back();
  598. }
  599. }
  600. template <class T>
  601. inline void Value<T>::add_value(const T& value)
  602. {
  603. values_.push_back(value);
  604. update_reference();
  605. }
  606. template <class T>
  607. inline void Value<T>::clear()
  608. {
  609. values_.clear();
  610. update_reference();
  611. }
  612. /// Implicit implementation /////////////////////////////////
  613. template <class T>
  614. inline Implicit<T>::Implicit(const std::string& short_name, const std::string& long_name, const std::string& description, const T& implicit_val, T* assign_to)
  615. : Value<T>(short_name, long_name, description, implicit_val, assign_to)
  616. {
  617. }
  618. template <class T>
  619. inline Argument Implicit<T>::argument_type() const
  620. {
  621. return Argument::optional;
  622. }
  623. template <class T>
  624. inline void Implicit<T>::parse(OptionName what_name, const char* value)
  625. {
  626. if ((value != nullptr) && (strlen(value) > 0))
  627. Value<T>::parse(what_name, value);
  628. else
  629. this->add_value(*this->default_);
  630. }
  631. /// Switch implementation /////////////////////////////////
  632. inline Switch::Switch(const std::string& short_name, const std::string& long_name, const std::string& description, bool* assign_to)
  633. : Value<bool>(short_name, long_name, description, false, assign_to)
  634. {
  635. }
  636. inline void Switch::parse(OptionName /*what_name*/, const char* /*value*/)
  637. {
  638. add_value(true);
  639. }
  640. inline Argument Switch::argument_type() const
  641. {
  642. return Argument::no;
  643. }
  644. /// OptionParser implementation /////////////////////////////////
  645. inline OptionParser::OptionParser(std::string description) : description_(std::move(description))
  646. {
  647. }
  648. template <typename T, typename... Ts>
  649. inline std::shared_ptr<T> OptionParser::add(Ts&&... params)
  650. {
  651. return add<T, Attribute::optional>(std::forward<Ts>(params)...);
  652. }
  653. template <typename T, Attribute attribute, typename... Ts>
  654. inline std::shared_ptr<T> OptionParser::add(Ts&&... params)
  655. {
  656. static_assert(std::is_base_of<Option, typename std::decay<T>::type>::value, "type T must be Switch, Value or Implicit");
  657. std::shared_ptr<T> option = std::make_shared<T>(std::forward<Ts>(params)...);
  658. for (const auto& o : options_)
  659. {
  660. if ((option->short_name() != 0) && (option->short_name() == o->short_name()))
  661. throw std::invalid_argument("duplicate short option name '-" + std::string(1, option->short_name()) + "'");
  662. if (!option->long_name().empty() && (option->long_name() == (o->long_name())))
  663. throw std::invalid_argument("duplicate long option name '--" + option->long_name() + "'");
  664. }
  665. option->set_attribute(attribute);
  666. options_.push_back(option);
  667. return option;
  668. }
  669. inline std::string OptionParser::description() const
  670. {
  671. return description_;
  672. }
  673. inline const std::vector<Option_ptr>& OptionParser::options() const
  674. {
  675. return options_;
  676. }
  677. inline const std::vector<std::string>& OptionParser::non_option_args() const
  678. {
  679. return non_option_args_;
  680. }
  681. inline const std::vector<std::string>& OptionParser::unknown_options() const
  682. {
  683. return unknown_options_;
  684. }
  685. inline Option_ptr OptionParser::find_option(const std::string& long_name) const
  686. {
  687. for (const auto& option : options_)
  688. if (option->long_name() == long_name)
  689. return option;
  690. return nullptr;
  691. }
  692. inline Option_ptr OptionParser::find_option(char short_name) const
  693. {
  694. for (const auto& option : options_)
  695. if (option->short_name() == short_name)
  696. return option;
  697. return nullptr;
  698. }
  699. template <typename T>
  700. inline std::shared_ptr<T> OptionParser::get_option(const std::string& long_name) const
  701. {
  702. Option_ptr option = find_option(long_name);
  703. if (!option)
  704. throw std::invalid_argument("option not found: " + long_name);
  705. auto result = std::dynamic_pointer_cast<T>(option);
  706. if (!result)
  707. throw std::invalid_argument("cannot cast option to T: " + long_name);
  708. return result;
  709. }
  710. template <typename T>
  711. inline std::shared_ptr<T> OptionParser::get_option(char short_name) const
  712. {
  713. Option_ptr option = find_option(short_name);
  714. if (!option)
  715. throw std::invalid_argument("option not found: " + std::string(1, short_name));
  716. auto result = std::dynamic_pointer_cast<T>(option);
  717. if (!result)
  718. throw std::invalid_argument("cannot cast option to T: " + std::string(1, short_name));
  719. return result;
  720. }
  721. inline void OptionParser::parse(const std::string& ini_filename)
  722. {
  723. std::ifstream file(ini_filename.c_str());
  724. std::string line;
  725. auto trim = [](std::string& s) {
  726. s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); }));
  727. s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end());
  728. return s;
  729. };
  730. auto trim_copy = [trim](const std::string& s) {
  731. std::string copy(s);
  732. return trim(copy);
  733. };
  734. auto split = [trim_copy](const std::string& s) -> std::pair<std::string, std::string> {
  735. size_t pos = s.find('=');
  736. if (pos == std::string::npos)
  737. return {"", ""};
  738. return {trim_copy(s.substr(0, pos)), trim_copy(s.substr(pos + 1, std::string::npos))};
  739. };
  740. std::string section;
  741. while (std::getline(file, line))
  742. {
  743. trim(line);
  744. if (line.empty())
  745. continue;
  746. if (line.front() == '#')
  747. continue;
  748. if ((line.front() == '[') && (line.back() == ']'))
  749. {
  750. section = trim_copy(line.substr(1, line.size() - 2));
  751. continue;
  752. }
  753. auto key_value = split(line);
  754. if (key_value.first.empty())
  755. continue;
  756. std::string key = section.empty() ? key_value.first : section + "." + key_value.first;
  757. Option_ptr option = find_option(key);
  758. if (option && (option->attribute() == Attribute::inactive))
  759. option = nullptr;
  760. if (option)
  761. option->parse(OptionName::long_name, key_value.second.c_str());
  762. else
  763. unknown_options_.push_back(key);
  764. }
  765. }
  766. inline void OptionParser::parse(int argc, const char* const argv[])
  767. {
  768. for (int n = 1; n < argc; ++n)
  769. {
  770. const std::string arg(argv[n]);
  771. if (arg == "--")
  772. {
  773. /// from here on only non opt args
  774. for (int m = n + 1; m < argc; ++m)
  775. non_option_args_.emplace_back(argv[m]);
  776. }
  777. else if (arg.find("--") == 0)
  778. {
  779. /// long option arg
  780. std::string opt = arg.substr(2);
  781. std::string optarg;
  782. size_t equalIdx = opt.find('=');
  783. if (equalIdx != std::string::npos)
  784. {
  785. optarg = opt.substr(equalIdx + 1);
  786. opt.resize(equalIdx);
  787. }
  788. Option_ptr option = find_option(opt);
  789. if (option && (option->attribute() == Attribute::inactive))
  790. option = nullptr;
  791. if (option)
  792. {
  793. if (option->argument_type() == Argument::no)
  794. {
  795. if (!optarg.empty())
  796. option = nullptr;
  797. }
  798. else if (option->argument_type() == Argument::required)
  799. {
  800. if (optarg.empty() && n < argc - 1)
  801. optarg = argv[++n];
  802. }
  803. }
  804. if (option)
  805. option->parse(OptionName::long_name, optarg.c_str());
  806. else
  807. unknown_options_.push_back(arg);
  808. }
  809. else if (arg.find('-') == 0)
  810. {
  811. /// short option arg
  812. std::string opt = arg.substr(1);
  813. bool unknown = false;
  814. for (size_t m = 0; m < opt.size(); ++m)
  815. {
  816. char c = opt[m];
  817. std::string optarg;
  818. Option_ptr option = find_option(c);
  819. if (option && (option->attribute() == Attribute::inactive))
  820. option = nullptr;
  821. if (option)
  822. {
  823. if (option->argument_type() == Argument::required)
  824. {
  825. /// use the rest of the current argument as optarg
  826. optarg = opt.substr(m + 1);
  827. /// or the next arg
  828. if (optarg.empty() && n < argc - 1)
  829. optarg = argv[++n];
  830. m = opt.size();
  831. }
  832. else if (option->argument_type() == Argument::optional)
  833. {
  834. /// use the rest of the current argument as optarg
  835. optarg = opt.substr(m + 1);
  836. m = opt.size();
  837. }
  838. }
  839. if (option)
  840. option->parse(OptionName::short_name, optarg.c_str());
  841. else
  842. unknown = true;
  843. }
  844. if (unknown)
  845. unknown_options_.push_back(arg);
  846. }
  847. else
  848. {
  849. non_option_args_.push_back(arg);
  850. }
  851. }
  852. for (auto& opt : options_)
  853. {
  854. if ((opt->attribute() == Attribute::required) && !opt->is_set())
  855. {
  856. std::string option = opt->long_name().empty() ? std::string(1, opt->short_name()) : opt->long_name();
  857. throw invalid_option(opt.get(), invalid_option::Error::missing_option, "option \"" + option + "\" is required");
  858. }
  859. }
  860. }
  861. inline void OptionParser::reset()
  862. {
  863. unknown_options_.clear();
  864. non_option_args_.clear();
  865. for (auto& opt : options_)
  866. opt->clear();
  867. }
  868. inline std::string OptionParser::help(const Attribute& max_attribute) const
  869. {
  870. ConsoleOptionPrinter option_printer(this);
  871. return option_printer.print(max_attribute);
  872. }
  873. /// ConsoleOptionPrinter implementation /////////////////////////////////
  874. inline ConsoleOptionPrinter::ConsoleOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser)
  875. {
  876. }
  877. inline std::string ConsoleOptionPrinter::to_string(Option_ptr option) const
  878. {
  879. std::stringstream line;
  880. if (option->short_name() != 0)
  881. {
  882. line << " -" << option->short_name();
  883. if (!option->long_name().empty())
  884. line << ", ";
  885. }
  886. else
  887. line << " ";
  888. if (!option->long_name().empty())
  889. line << "--" << option->long_name();
  890. if (option->argument_type() == Argument::required)
  891. {
  892. line << " arg";
  893. std::stringstream defaultStr;
  894. if (option->get_default(defaultStr))
  895. {
  896. if (!defaultStr.str().empty())
  897. line << " (=" << defaultStr.str() << ")";
  898. }
  899. }
  900. else if (option->argument_type() == Argument::optional)
  901. {
  902. std::stringstream defaultStr;
  903. if (option->get_default(defaultStr))
  904. line << " [=arg(=" << defaultStr.str() << ")]";
  905. }
  906. return line.str();
  907. }
  908. inline std::string ConsoleOptionPrinter::print(const Attribute& max_attribute) const
  909. {
  910. if (option_parser_ == nullptr)
  911. return "";
  912. if (max_attribute < Attribute::optional)
  913. throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'");
  914. std::stringstream s;
  915. if (!option_parser_->description().empty())
  916. s << option_parser_->description() << ":\n";
  917. size_t optionRightMargin(20);
  918. const size_t maxDescriptionLeftMargin(40);
  919. // const size_t descriptionRightMargin(80);
  920. for (const auto& option : option_parser_->options())
  921. optionRightMargin = std::max(optionRightMargin, to_string(option).size() + 2);
  922. optionRightMargin = std::min(maxDescriptionLeftMargin - 2, optionRightMargin);
  923. for (const auto& option : option_parser_->options())
  924. {
  925. if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute))
  926. continue;
  927. std::string optionStr = to_string(option);
  928. if (optionStr.size() < optionRightMargin)
  929. optionStr.resize(optionRightMargin, ' ');
  930. else
  931. optionStr += "\n" + std::string(optionRightMargin, ' ');
  932. s << optionStr;
  933. std::string line;
  934. std::vector<std::string> lines;
  935. std::stringstream description(option->description());
  936. while (std::getline(description, line, '\n'))
  937. lines.push_back(line);
  938. std::string empty(optionRightMargin, ' ');
  939. for (size_t n = 0; n < lines.size(); ++n)
  940. {
  941. if (n > 0)
  942. s << "\n" << empty;
  943. s << lines[n];
  944. }
  945. s << "\n";
  946. }
  947. return s.str();
  948. }
  949. /// GroffOptionPrinter implementation /////////////////////////////////
  950. inline GroffOptionPrinter::GroffOptionPrinter(const OptionParser* option_parser) : OptionPrinter(option_parser)
  951. {
  952. }
  953. inline std::string GroffOptionPrinter::to_string(Option_ptr option) const
  954. {
  955. std::stringstream line;
  956. if (option->short_name() != 0)
  957. {
  958. line << "-" << option->short_name();
  959. if (!option->long_name().empty())
  960. line << ", ";
  961. }
  962. if (!option->long_name().empty())
  963. line << "--" << option->long_name();
  964. if (option->argument_type() == Argument::required)
  965. {
  966. line << " arg";
  967. std::stringstream defaultStr;
  968. if (option->get_default(defaultStr))
  969. {
  970. if (!defaultStr.str().empty())
  971. line << " (=" << defaultStr.str() << ")";
  972. }
  973. }
  974. else if (option->argument_type() == Argument::optional)
  975. {
  976. std::stringstream defaultStr;
  977. if (option->get_default(defaultStr))
  978. line << " [=arg(=" << defaultStr.str() << ")]";
  979. }
  980. return line.str();
  981. }
  982. inline std::string GroffOptionPrinter::print(const Attribute& max_attribute) const
  983. {
  984. if (option_parser_ == nullptr)
  985. return "";
  986. if (max_attribute < Attribute::optional)
  987. throw std::invalid_argument("attribute must be 'optional', 'advanced', or 'default'");
  988. std::stringstream s;
  989. if (!option_parser_->description().empty())
  990. s << ".SS " << option_parser_->description() << ":\n";
  991. for (const auto& option : option_parser_->options())
  992. {
  993. if ((option->attribute() <= Attribute::hidden) || (option->attribute() > max_attribute))
  994. continue;
  995. s << ".TP\n\\fB" << to_string(option) << "\\fR\n";
  996. if (!option->description().empty())
  997. s << option->description() << "\n";
  998. }
  999. return s.str();
  1000. }
  1001. /// BashCompletionOptionPrinter implementation /////////////////////////////////
  1002. inline BashCompletionOptionPrinter::BashCompletionOptionPrinter(const OptionParser* option_parser, std::string program_name)
  1003. : OptionPrinter(option_parser), program_name_(std::move(program_name))
  1004. {
  1005. }
  1006. inline std::string BashCompletionOptionPrinter::print(const Attribute& /*max_attribute*/) const
  1007. {
  1008. if (option_parser_ == nullptr)
  1009. return "";
  1010. std::stringstream s;
  1011. s << "_" << program_name_ << "()\n";
  1012. s << R"({
  1013. local cur prev opts
  1014. COMPREPLY=()
  1015. cur="${COMP_WORDS[COMP_CWORD]}"
  1016. prev="${COMP_WORDS[COMP_CWORD-1]}"
  1017. opts=")";
  1018. for (const auto& option : option_parser_->options())
  1019. {
  1020. if (option->attribute() > Attribute::hidden)
  1021. {
  1022. if (option->short_name() != 0)
  1023. s << "-" << option->short_name() << " ";
  1024. if (!option->long_name().empty())
  1025. s << "--" << option->long_name() << " ";
  1026. }
  1027. }
  1028. s << R"("
  1029. if [[ ${cur} == -* ]] ; then
  1030. COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
  1031. return 0
  1032. fi
  1033. }
  1034. complete -F )";
  1035. s << "_" << program_name_ << " " << program_name_ << "\n";
  1036. return s.str();
  1037. }
  1038. static inline std::ostream& operator<<(std::ostream& out, const OptionParser& op)
  1039. {
  1040. return out << op.help();
  1041. }
  1042. } // namespace popl
  1043. #endif // POPL_HPP