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