]> git.tdb.fi Git - libs/core.git/blobdiff - source/core/getopt.h
Add move semantics to Variant
[libs/core.git] / source / core / getopt.h
index dbc0f9cef0284e94d4f5ec66b890e94700515e4d..59c810c5e4a5ff4182cd42d9736f8af9d708eae9 100644 (file)
@@ -6,19 +6,20 @@
 #include <string>
 #include <vector>
 #include <msp/strings/lexicalcast.h>
+#include "mspcore_api.h"
+#include "noncopyable.h"
 
 namespace Msp {
 
-class usage_error: public std::runtime_error
+class MSPCORE_API usage_error: public std::runtime_error
 {
 private:
-       std::string help_;
+       std::string m_help;
 
 public:
-       usage_error(const std::string &w, const std::string &h = std::string()): std::runtime_error(w), help_(h) { }
-       ~usage_error() throw() { }
+       usage_error(const std::string &w, const std::string &h = std::string()): std::runtime_error(w), m_help(h) { }
 
-       const char *help() const throw() { return help_.c_str(); }
+       const char *help() const noexcept { return m_help.c_str(); }
 };
 
 
@@ -33,22 +34,32 @@ the string "-abc" could be interpreted as having the options 'a', 'b' and 'c'.
 If the option takes an argument and there are unused characters in the argv
 element, then those characters are interpreted as the argument.  Otherwise the
 next element is taken as the argument.  An optional argument must be given in
-the same element.
+the same element if it is given.
 
 Long options begin with a double dash and are identified by an arbitrary
 string.  An argument can be specified either in the same argv element,
 separated by an equals sign, or in the next element.  As with short options,
-an optional argument must be in the same element.
+an optional argument, if given, must be in the same element.
 
 A single option may have both alternative forms, but must always have at least
 a long form.  This is to encourage self-documenting options; it's much easier
 to remember words than letters.
 
-A built-in --help option is provided and will output a list of options and
-their associated help texts.  An application may override this by providing
-its own option with the same name.
+Positional arguments are also supported.  They are identified by an arbitrary
+string, but the identifier is only used in help text and error messages.  Any
+number of the final arguments may be optional.
+
+To support applications that take an arbitrary amount of arguments, a single
+positional argument list can be specified.  Fixed positional arguments are
+allowed together with a list, but they can't be optional.  An application that
+wants to do complex processing on the argument list can declare a list of
+string arguments.
+
+A built-in --help option is provided and will output a list of options,
+arguments and their associated help texts.  An application may override this by
+providing its own option with the same name.
 */
-class GetOpt
+class MSPCORE_API GetOpt: private NonCopyable
 {
 public:
        enum ArgType
@@ -58,12 +69,12 @@ public:
                REQUIRED_ARG
        };
 
-       class Option
+       class MSPCORE_API Option
        {
        protected:
-               Option() { }
+               Option() = default;
        public:
-               virtual ~Option() { }
+               virtual ~Option() = default;
 
                /// Sets help text for the option.
                virtual Option &set_help(const std::string &) = 0;
@@ -72,126 +83,189 @@ public:
                metavariable is used to denote the argument in the option list. */
                virtual Option &set_help(const std::string &, const std::string &) = 0;
 
+               virtual Option &bind_seen_count(unsigned &) = 0;
+
                /// Returns the number of times this option was seen on the command line.
                virtual unsigned get_seen_count() const = 0;
        };
 
+       class MSPCORE_API Argument
+       {
+       protected:
+               Argument() = default;
+       public:
+               virtual ~Argument() = default;
+
+               virtual Argument &set_help(const std::string &) = 0;
+       };
+
 private:
-       class OptBase: public Option
+       class Store
        {
        protected:
-               char shrt;
+               Store() = default;
+       public:
+               virtual ~Store() = default;
+
+               virtual Store *clone() const = 0;
+
+               virtual bool is_list() const = 0;
+               virtual void store() = 0;
+               virtual void store(const std::string &) = 0;
+       };
+
+       class OptionImpl: public Option
+       {
+       protected:
+               char shrt = 0;
                std::string lng;
-               ArgType arg_type;
-               unsigned seen_count;
+               ArgType arg_type = NO_ARG;
+               unsigned seen_count = 0;
+               unsigned *ext_seen_count = nullptr;
                std::string help;
-               std::string metavar;
+               std::string metavar = "ARG";
+               Store *store = nullptr;
 
-               OptBase(char, const std::string &, ArgType);
        public:
-               virtual ~OptBase() { }
+               OptionImpl(char, const std::string &, const Store &, ArgType);
+               ~OptionImpl() override;
 
-               virtual OptBase &set_help(const std::string &);
-               virtual OptBase &set_help(const std::string &, const std::string &);
+               OptionImpl &set_help(const std::string &) override;
+               OptionImpl &set_help(const std::string &, const std::string &) override;
+               OptionImpl &bind_seen_count(unsigned &) override;
                char get_short() const { return shrt; }
                const std::string &get_long() const { return lng; }
                ArgType get_arg_type() const { return arg_type; }
                const std::string &get_help() const { return help; }
                const std::string &get_metavar() const { return metavar; }
-               virtual unsigned get_seen_count() const { return seen_count; }
+               unsigned get_seen_count() const override { return seen_count; }
                void process();
                void process(const std::string &);
-       protected:
-               virtual void store() = 0;
-               virtual void store(const std::string &) = 0;
+       };
+
+       class ArgumentImpl: public Argument
+       {
+       private:
+               std::string name;
+               ArgType type = REQUIRED_ARG;
+               std::string help;
+               Store *store = nullptr;
+
+       public:
+               ArgumentImpl(const std::string &, const Store &, ArgType);
+               ~ArgumentImpl() override;
+
+               ArgumentImpl &set_help(const std::string &) override;
+               const std::string &get_name() const { return name; }
+               ArgType get_type() const { return type; }
+               const std::string &get_help() const { return help; }
+               bool is_list_store() const { return store->is_list(); }
+               void process(const std::string &);
        };
 
        template<typename T>
-       class SimpleOption: public OptBase
+       class SimpleStore: public Store
        {
        private:
                T &data;
 
        public:
-               SimpleOption(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d) { }
-
-               virtual void store() { }
-
-               virtual void store(const std::string &a)
-               {
-                       try
-                       {
-                               data = lexical_cast<T>(a);
-                       }
-                       catch(const lexical_error &e)
-                       {
-                               throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
-                       }
-               }
+               SimpleStore(T &d): data(d) { }
+
+               SimpleStore *clone() const override
+               { return new SimpleStore(data); }
+
+               bool is_list() const override { return false; }
+
+               void store() override { }
+
+               void store(const std::string &a) override
+               { data = lexical_cast<T>(a); }
        };
 
        template<typename T>
-       class ListOption: public OptBase
+       class ListStore: public Store
        {
        private:
                T &data;
 
        public:
-               ListOption(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d)
-               { if(arg_type!=REQUIRED_ARG) throw std::invalid_argument("ListOption arg_type!=REQUIRED"); }
-
-               virtual void store() { }
-
-               virtual void store(const std::string &a)
-               {
-                       try
-                       {
-                               data.push_back(lexical_cast<typename T::value_type>(a));
-                       }
-                       catch(const lexical_error &e)
-                       {
-                               throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
-                       }
-               }
+               ListStore(T &d): data(d) { }
+
+               ListStore *clone() const override
+               { return new ListStore(data); }
+
+               bool is_list() const override { return true; }
+
+               void store() override { }
+
+               void store(const std::string &a) override
+               { data.push_back(lexical_cast<typename T::value_type>(a)); }
        };
 
-       bool help;
-       std::list<OptBase *> opts;
-       std::vector<std::string> args;
+       bool help = false;
+       std::vector<OptionImpl *> opts;
+       std::vector<ArgumentImpl *> args;
+       std::vector<std::string> args_raw;
 
 public:
        GetOpt();
        ~GetOpt();
 
-       /// Returns any non-option arguments encountered during processing.
-       const std::vector<std::string> &get_args() const { return args; }
-
        /** Adds an option with both short and long forms.  Processing depends on
        the type of the destination variable and whether an argument is taken or
-       not.  With an argument, the value is lexical_cast to appropriate type and
-       stored in the destination.  Without an argument, a bool will be set to true
-       and an unsigned will be incremented; any other type will be ignored. */
+       not.  With an argument, the value is lexical_cast to the appropriate type
+       and stored in the destination.  Without an argument, a bool will be set to
+       true and an unsigned will be incremented; any other type will be ignored. */
        template<typename T>
        Option &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG)
-       { return add_option(new SimpleOption<T>(s, l, d, a)); }
+       { return add_option(s, l, SimpleStore<T>(d), a); }
+
+       /** Adds an option with both short and long forms.  The option may be
+       specified multiple times, and the argument from each occurrence is stored in
+       the list.  The argument type must be REQUIRED_ARG. */
+       template<typename T>
+       Option &add_option(char s, const std::string &l, std::vector<T> &d, ArgType a = REQUIRED_ARG)
+       { return add_option(s, l, ListStore<std::vector<T> >(d), a); }
 
        /** Adds an option with both short and long forms.  The option may be
        specified multiple times, and the argument from each occurrence is stored in
        the list.  The argument type must be REQUIRED_ARG. */
        template<typename T>
        Option &add_option(char s, const std::string &l, std::list<T> &d, ArgType a = REQUIRED_ARG)
-       { return add_option(new ListOption<std::list<T> >(s, l, d, a)); }
+       { return add_option(s, l, ListStore<std::list<T> >(d), a); }
 
        /** Adds an option with only a long form. */
        template<typename T>
        Option &add_option(const std::string &l, T &d, ArgType a)
        { return add_option(0, l, d, a); }
 
+       /** Adds a positional argument.  The value will be lexical_cast to the
+       appropriate type and stored in the destination. */
+       template<typename T>
+       Argument &add_argument(const std::string &n, T &d, ArgType a = REQUIRED_ARG)
+       { return add_argument(n, SimpleStore<T>(d), a); }
+
+       /** Adds a positional argument list.  If the list is declared as required,
+       at least one element must be given; an optional list may be empty.  Only one
+       list may be added, and optional fixed arguments can't be used with it. */
+       template<typename T>
+       Argument &add_argument(const std::string &n, std::vector<T> &d, ArgType a = REQUIRED_ARG)
+       { return add_argument(n, ListStore<std::vector<T> >(d), a); }
+
+       /** Adds a positional argument list.  If the list is declared as required,
+       at least one element must be given; an optional list may be empty.  Only one
+       list may be added, and optional fixed arguments can't be used with it. */
+       template<typename T>
+       Argument &add_argument(const std::string &n, std::list<T> &d, ArgType a = REQUIRED_ARG)
+       { return add_argument(n, ListStore<std::list<T> >(d), a); }
+
 private:
-       OptBase &add_option(OptBase *);
+       OptionImpl &add_option(char, const std::string &, const Store &, ArgType);
+       ArgumentImpl &add_argument(const std::string &, const Store &, ArgType);
 
-       OptBase &get_option(char);
-       OptBase &get_option(const std::string &);
+       OptionImpl &get_option(char);
+       OptionImpl &get_option(const std::string &);
 
 public:
        /** Processes argc/argv style command line arguments.  The contents of argv
@@ -206,24 +280,29 @@ private:
        unsigned process_short(const char *const *);
 
 public:
-       /** Generates a single line that describes known options. */
-       std::string generate_usage(const std::string &) const;
+       /** Generates a single line that describes known options and arguments.  If
+       compact is true, the options list is replaced with a placeholder.  This
+       provides cleaner output if full help text is printed. */
+       std::string generate_usage(const std::string &, bool compact = false) const;
 
-       /** Generates help for known options in tabular format, one option per
-       line.  The returned string will have a linefeed at the end. */
+       /** Generates help for known options and arguments in tabular format, one
+       item per line.  The returned string will have a linefeed at the end. */
        std::string generate_help() const;
 };
 
-template<> inline void GetOpt::SimpleOption<bool>::store()
+template<> inline void GetOpt::SimpleStore<bool>::store()
 { data = true; }
 
-template<> inline void GetOpt::SimpleOption<unsigned>::store()
+template<> inline void GetOpt::SimpleStore<unsigned>::store()
 { ++data; }
 
-template<> inline void GetOpt::SimpleOption<std::string>::store(const std::string &a)
+template<> inline void GetOpt::SimpleStore<std::string>::store(const std::string &a)
 { data = a; }
 
-template<> inline void GetOpt::ListOption<std::list<std::string> >::store(const std::string &a)
+template<> inline void GetOpt::ListStore<std::vector<std::string> >::store(const std::string &a)
+{ data.push_back(a); }
+
+template<> inline void GetOpt::ListStore<std::list<std::string> >::store(const std::string &a)
 { data.push_back(a); }
 
 } // namespace Msp