X-Git-Url: http://git.tdb.fi/?p=libs%2Fcore.git;a=blobdiff_plain;f=source%2Fcore%2Fgetopt.h;h=2aaceef836730aa24a966c3cfaa20bb9864e23a7;hp=2824a0b7c7f3b79378477853fa8874300a98eb75;hb=9f8d6a18f68fb54b62e9f4a5cf1a1650282b3667;hpb=521cf1db00f8ce2d9f9494dca503d6c17d89ac2f diff --git a/source/core/getopt.h b/source/core/getopt.h index 2824a0b..2aaceef 100644 --- a/source/core/getopt.h +++ b/source/core/getopt.h @@ -1,19 +1,53 @@ -/* $Id$ - -This file is part of libmspcore -Copyright © 2006-2007 Mikko Rasa, Mikkosoft Productions -Distributed under the LGPL -*/ #ifndef MSP_CORE_GETOPT_H_ #define MSP_CORE_GETOPT_H_ #include -#include +#include #include -#include "error.h" +#include +#include namespace Msp { +class usage_error: public std::runtime_error +{ +private: + std::string help_; + +public: + usage_error(const std::string &w, const std::string &h = std::string()): std::runtime_error(w), help_(h) { } + ~usage_error() throw() { } + + const char *help() const throw() { return help_.c_str(); } +}; + + +/** +Command line option processor. Both short and long options are supported, with +optional and required arguments. Automatic help text generation is also +available. + +Short options begin with a single dash and are identified by a single letter. +Multiple short options may be grouped if they take no arguments; for example, +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. + +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. + +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. +*/ class GetOpt { public: @@ -23,121 +57,178 @@ public: OPTIONAL_ARG, REQUIRED_ARG }; - - class OptBase + + class Option { - public: - OptBase &set_help(const std::string &h) { help=h; return *this; } - 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; } - unsigned get_seen_count() const { return seen_count; } - virtual void process()=0; - virtual void process(const std::string &)=0; - virtual ~OptBase() { } protected: - char shrt; - std::string lng; - ArgType arg_type; - unsigned seen_count; - std::string help; + Option() { } + public: + virtual ~Option() { } - OptBase(char s, const std::string &l, ArgType a): shrt(s), lng(l), arg_type(a), seen_count(0) { } - }; + /// Sets help text for the option. + virtual Option &set_help(const std::string &) = 0; - const std::list &get_args() const { return args; } + /** Sets help text for the option, with a placeholder metavariable. The + metavariable is used to denote the argument in the option list. */ + virtual Option &set_help(const std::string &, const std::string &) = 0; - template - OptBase &add_option(char s, const std::string &l, T &d, ArgType a=NO_ARG) - { opts.push_back(new Option(s, l, d, a)); return *opts.back(); } - - template - OptBase &add_option(char s, const std::string &l, std::list &d, ArgType a=REQUIRED_ARG) - { opts.push_back(new ListOption >(s, l, d, a)); return *opts.back(); } - - template - OptBase &add_option(const std::string &l, T &d, ArgType a) - { return add_option(0, l, d, a); } + virtual Option &bind_seen_count(unsigned &) = 0; - std::string generate_usage(const std::string &) const; - std::string generate_help() const; - void operator()(unsigned, const char *const *); + /// Returns the number of times this option was seen on the command line. + virtual unsigned get_seen_count() const = 0; + }; - ~GetOpt(); private: - template - class Option: public OptBase + class OptBase: public Option { - public: - Option(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d) { } - - virtual void process() - { - if(arg_type==REQUIRED_ARG) - throw UsageError("--"+lng+" requires an argument"); - process_(); - ++seen_count; - } + protected: + char shrt; + std::string lng; + ArgType arg_type; + unsigned seen_count; + unsigned *ext_seen_count; + std::string help; + std::string metavar; - virtual void process(const std::string &a) - { - if(arg_type==NO_ARG) - throw UsageError("--"+lng+" takes no argument"); + OptBase(char, const std::string &, ArgType); + public: + virtual ~OptBase() { } - T tmp; - std::istringstream ss(a); - ss>>tmp; - if(ss.fail()) - throw UsageError("Invalid argument for --"+lng); + virtual OptBase &set_help(const std::string &); + virtual OptBase &set_help(const std::string &, const std::string &); + virtual OptBase &bind_seen_count(unsigned &); + 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; } + void process(); + void process(const std::string &); + protected: + virtual void store() = 0; + virtual void store(const std::string &) = 0; + }; - data=tmp; - ++seen_count; - } + template + class SimpleOption: public OptBase + { private: T &data; - void process_() { } + 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(a); + } + catch(const lexical_error &e) + { + throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")"); + } + } }; template class ListOption: public OptBase { + 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 Exception("ListOption with arg_type!=REQUIRED makes no sense"); } + { if(arg_type!=REQUIRED_ARG) throw std::invalid_argument("ListOption arg_type!=REQUIRED"); } - virtual void process() - { - throw UsageError("--"+lng+" requires an argument"); - } + virtual void store() { } - virtual void process(const std::string &a) + virtual void store(const std::string &a) { - typename T::value_type tmp; - std::istringstream ss(a); - ss>>tmp; - if(ss.fail()) - throw UsageError("Invalid argument for --"+lng); - - data.push_back(tmp); - ++seen_count; + try + { + data.push_back(lexical_cast(a)); + } + catch(const lexical_error &e) + { + throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")"); + } } - private: - T &data; }; - std::list opts; - std::list args; + bool help; + std::list opts; + std::vector args; + +public: + GetOpt(); + ~GetOpt(); + + /// Returns any non-option arguments encountered during processing. + const std::vector &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. */ + template + Option &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG) + { return add_option(new SimpleOption(s, l, 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 + Option &add_option(char s, const std::string &l, std::list &d, ArgType a = REQUIRED_ARG) + { return add_option(new ListOption >(s, l, d, a)); } + + /** Adds an option with only a long form. */ + template + Option &add_option(const std::string &l, T &d, ArgType a) + { return add_option(0, l, d, a); } + +private: + OptBase &add_option(OptBase *); OptBase &get_option(char); OptBase &get_option(const std::string &); + +public: + /** Processes argc/argv style command line arguments. The contents of argv + will be unchanged; use get_args to access non-option arguments. */ + void operator()(unsigned, const char *const *); + +private: + /** Processes a long option. Returns the number of arguments eaten. */ unsigned process_long(const char *const *); + + /** Processes short options. Returns the number of arguments eaten. */ unsigned process_short(const char *const *); + +public: + /** Generates a single line that describes known options. */ + std::string generate_usage(const std::string &) const; + + /** Generates help for known options in tabular format, one option per + line. The returned string will have a linefeed at the end. */ + std::string generate_help() const; }; -template<> inline void GetOpt::Option::process_() { data=true; } -template<> inline void GetOpt::Option::process_() { ++data; } +template<> inline void GetOpt::SimpleOption::store() +{ data = true; } + +template<> inline void GetOpt::SimpleOption::store() +{ ++data; } + +template<> inline void GetOpt::SimpleOption::store(const std::string &a) +{ data = a; } + +template<> inline void GetOpt::ListOption >::store(const std::string &a) +{ data.push_back(a); } } // namespace Msp