1 /*  _________________________________________________________________________
2  *
3  *  UTILIB: A utility library for developing portable C++ codes.
4  *  Copyright (c) 2008 Sandia Corporation.
5  *  This software is distributed under the BSD License.
6  *  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7  *  the U.S. Government retains certain rights in this software.
8  *  For more information, see the README file in the top UTILIB directory.
9  *  _________________________________________________________________________
10  */
11 
12 #ifndef utilib_OptionParser_h
13 #define utilib_OptionParser_h
14 
15 #include <utilib/string_ops.h>
16 #include <utilib/Parameter.h>
17 #include <utilib/SmartHandle.h>
18 
19 #ifdef UTILIB_HAVE_TINYXML
20 #include <tinyxml/tinyxml.h>
21 #endif
22 #include <vector>
23 #include <utility>
24 #include <string>
25 
26 namespace utilib
27 {
28 
29 ///
30 /// This is a simple option parser that is based on the UTILI ParameterSet
31 /// class.  This provides a simpler API for setting up command-line options.
32 ///
33 class OptionParser
34 {
35 public:
36 
37    ///
38    typedef SmartHandle<Parameter> data_t;
39 
40    ///
41    typedef std::vector<std::string> args_t;
42 
43    /// Constructor
44    OptionParser(const char* usage_ = 0, const char* description_ = 0)
45    {
46       _help_option = false;
47       _version_option = false;
48       min_num_required_args = 0;
49       required_equals = false;
50 
51       if (usage_ != 0)
52          add_usage(usage_);
53       if (description_ != 0)
54          description = description_;
55       add('h', "help", _help_option, "Display usage information");
56    }
57 
58    /// Merge options from an OptionParser object into the current
59    /// OptionParser.  This merge ignores arguments, the usage, etc.
60    void merge_options(const OptionParser& options);
61 
62    ///
63    unsigned int min_num_required_args;
64 
65    ///
66    bool required_equals;
67 
68    ///
69    std::string description;
70 
71    ///
72    std::string epilog;
73 
74    ///
add_usage(const std::string & usage_)75    void add_usage(const std::string& usage_)
76     {
77     usage.push_back(usage_);
78     }
79 
80    ///
version(const std::string & _version)81    void version(const std::string& _version)
82     {
83     std::map<std::string,data_t>::iterator curr = parameters.find(_version);
84     if (curr == parameters.end())
85        add("version", _version_option, "Display version information");
86     _version_text = _version;
87     }
88 
89    // - Add standard parameters
90 
91    ///
92    template <class T>
93    void add(const char& short_name, const std::string& long_name, T& data, const std::string& description_="", Parameter::action_t action = Parameter::store_value)
94    { add_parameter(short_name, long_name, data, description_, action); }
95 
96    ///
97    template <class T>
98    void add(const char& short_name, T& data, const std::string& description_="", Parameter::action_t action = Parameter::store_value)
99    {
100       std::string tmp = "";
101       add_parameter(short_name, tmp, data, description_, action);
102    }
103 
104    ///
105    template <class T>
106    void add(const std::string& long_name, T& data, const std::string& description_="", Parameter::action_t action = Parameter::store_value)
107    {
108       char tmp = 0;
109       add_parameter(tmp, long_name, data, description_, action);
110    }
111 
112    // - Add list parameters
113 
114    ///
115    template <class T>
116    void add(const char& short_name, const std::string& long_name, std::list<T>& data, const std::string& description_="")
117    { add_parameter(short_name, long_name, data, description_, Parameter::append_value); }
118 
119    ///
120    template <class T>
121    void add(const char& short_name, std::list<T>& data, const std::string& description_="")
122    {
123       std::string tmp = "";
124       add_parameter(short_name, tmp, data, description_, Parameter::append_value);
125    }
126 
127    ///
128    template <class T>
129    void add(const std::string& long_name, std::list<T>& data, const std::string& description_="")
130    {
131       char tmp = 0;
132       add_parameter(tmp, long_name, data, description_, Parameter::append_value);
133    }
134 
135    // - Add boolean parameters
136 
137    ///
138    void add(const char& short_name, const std::string& long_name, bool& data, const std::string& description_="", Parameter::action_t action = Parameter::store_true)
139    { add_parameter(short_name, long_name, data, description_, action); }
140 
141    ///
142    void add(const char& short_name, bool& data, const std::string& description_="", Parameter::action_t action = Parameter::store_true)
143    {
144       std::string tmp = "";
145       add_parameter(short_name, tmp, data, description_, action);
146    }
147 
148    ///
149    void add(const std::string& long_name, bool& data, const std::string& description_="", Parameter::action_t action = Parameter::store_true)
150    {
151       char tmp = 0;
152       add_parameter(tmp, long_name, data, description_, action);
153    }
154 
155    ///
156    void categorize(const std::string& name, const std::string& category);
157 
158    ///
categorize(char short_name,const std::string & category)159    void categorize(char short_name, const std::string& category)
160     {
161     std::string tmp;
162     tmp += short_name;
163     categorize(tmp,category);
164     }
165 
166    ///
167    template <class Type, class FuncT>
validate(const std::string & name_,const Type &,FuncT fn)168    void validate(const std::string& name_, const Type&, FuncT fn)
169     {
170     std::string name = standardize(name_);
171     bool posix = name.size() == 1;
172     Parameter& param = get_param(name.c_str(),posix);
173     param.validator = new ParameterValidator<Type, FuncT>(fn);
174     }
175 
176    /// Disable parameter, but don't delete it from
177    void disable(std::string name);
178 
179    /// Enable a parameter that was previously disabled
180    void enable(std::string name);
181 
182    /// Delete parameter
183    void remove(std::string name);
184 
185    ///
186    bool initialized(const std::string& name);
187 
188    #ifdef UTILIB_HAVE_TINYXML
189    ///
190    void process_xml(TiXmlElement* elt, bool describe=false);
191    #endif
192 
193    ///
194    void write(std::ostream& os, bool categorized=true) const
195     {
196     std::set<std::string> tmp;
197     write(os, tmp, categorized);
198     }
199 
200    ///
201    void write(std::ostream& os, const std::set<std::string>& categories_requested, bool categorized=true) const;
202 
203    ///
204    void write_xml(std::ostream& os) const;
205 
206    ///
207    void write_values(std::ostream& os, const std::string& opt_label="") const;
208 
209    ///
210    void write_values_xml(std::ostream& os) const;
211 
212    ///
write_parameters(std::ostream & os)213    void write_parameters(std::ostream& os) const
214         {
215         std::string indent = "                              ";
216         write_parameter_set(os, parameter_data, indent);
217         }
218 
219    ///
print_version(std::ostream & os)220    void print_version(std::ostream& os) const
221       { wordwrap_printline(os, _version_text, ""); }
222 
223    ///
add_argument(const std::string & name_,const std::string & description_)224    void add_argument(const std::string& name_, const std::string& description_)
225    {
226     std::string name = standardize(name_);
227     arg_definitions.push_back(std::pair<std::string, std::string>(name, description_));
228     }
229 
230    ///
231    args_t& parse_args(int argc, char* argv[]);
232 
233    ///
234    void set_parameter(std::string name, Any value);
235 
236    ///
237    void set_parameter(std::string name, const std::string& value);
238 
239    ///
set_parameter(std::string name,const char * const & value)240    void set_parameter(std::string name, const char * const &value)
241     {
242     if ( value == NULL )
243     {
244        //EXCEPTION_MNGR(std::logic_error, "OptionParser::set_parameter(): "
245        //               "called with NULL char*");
246        set_parameter(name, Any(false));
247        return;
248     }
249     std::string tmp = value;
250     set_parameter(name,tmp);
251     }
252 
253    /// returns 0 if DNE, 1 if enabled, -1 if disabled
254    int has_parameter(std::string name);
255 
256    ///
args()257    args_t& args()
258    { return processed_args; }
259 
260    ///
args()261    const args_t& args() const
262       { return processed_args; }
263 
264    ///
265    void alias(const string& name, const string& alias);
266 
267    ///
help_option()268    bool help_option() const
269    { return _help_option; }
270 
271    ///
version_option()272    bool version_option() const
273       { return _version_option; }
274 
275 protected:
276 
277    ///
278    template <class T>
add_parameter(const char & short_name,const std::string & long_name_,T & data,const std::string & description_,Parameter::action_t action)279    void add_parameter(const char& short_name, const std::string& long_name_, T& data, const std::string& description_, Parameter::action_t action)
280    {
281       std::string long_name = standardize(long_name_);
282       if (long_name != "")
283       {
284          std::map<std::string,data_t>::iterator curr = parameters.find(long_name);
285          if (curr != parameters.end())
286             EXCEPTION_MNGR(runtime_error,
287                            "OptionParser::add_parameter - parameter \"" << long_name << "\" already exists!");
288          if (long_name.size() == 1) {
289             EXCEPTION_MNGR(runtime_error, "OptionParser::add_parameter - cannot specify a non-posix option with a single-character name: " << long_name);
290             }
291       }
292       if (short_name != 0)
293       {
294          std::map<char,data_t>::iterator curr = posix_parameters.find(short_name);
295          if (curr != posix_parameters.end())
296             EXCEPTION_MNGR(runtime_error,
297                            "OptionParser::add_parameter - posix parameter \"" << short_name << "\" already exists!");
298       }
299 
300       std::ostringstream buf;
301       buf << data;
302       data_t param_handle(true);
303       Parameter& param = param_handle();
304       param.name = long_name.c_str();
305       param.short_name = short_name;
306       param.setup_comp_name();
307       param.initialize(data);
308       param.syntax = typeid(data).name();
309       param.default_value = buf.str();
310       param.description = description_;
311       param.action = action;
312       param.is_bool = param.info.is_type(typeid(bool));
313       add_parameter(param_handle);
314       }
315 
316    ///
317    void add_parameter(data_t any_param);
318 
319    ///
320    void remove(const Parameter& param);
321 
322    /// A utility function for finding a parameter given a string,
323    /// which may contain one or more characters.
324    Parameter& get_param(const char* name, bool posix);
325 
326    /// Return the data_t that contains a parameter.
327    data_t get_param_any(const char* name, bool posix, bool test_if_enabled=true);
328 
329    ///
330    void write_parameter_set(std::ostream& os, const std::set<data_t>& parameters, const std::string& indent) const;
331 
332    /// Standardize the parameter name, by replacing '_' chars with '-'
333    std::string standardize(const std::string& name_);
334 
335    /// This is where the parameter data is actually stored
336    std::set<data_t> parameter_data;
337 
338    /// Category info for parameters.
339    std::map<std::string,std::set<data_t> > categories;
340 
341    /// A map from a string to a parameter
342    std::map<std::string, data_t> parameters;
343 
344    /// A map from a single character to a parameter
345    std::map<char, data_t> posix_parameters;
346 
347    /// A list of argument/description pairs that describe the
348    /// arguments of a command
349    std::list< std::pair<std::string, std::string> > arg_definitions;
350 
351    /// The list of arguments returned after parsing a command line
352    /// NOTE: the first argument is the name of the executable that was
353    /// run.
354    args_t processed_args;
355 
356    /// The bool used to define the 'help' option
357    bool _help_option;
358 
359    /// The bool used to define the 'version' option
360    bool _version_option;
361 
362    ///
363    std::string _version_text;
364 
365    ///
366    std::list<std::string> usage;
367 
368 };
369 
370 }
371 
372 #endif
373