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