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 #include <utilib/OptionParser.h>
13 #include <utilib/TypeManager.h>
14 #include <utilib/sort.h>
15 #include <utilib/TinyXML_helper.h>
16 #include <utilib/Property.h>
17
18 #include <sstream>
19 #include <set>
20
21 using std::map;
22 using std::string;
23
24 namespace utilib
25 {
26
merge_options(const OptionParser & options)27 void OptionParser::merge_options(const OptionParser& options)
28 {
29 std::set<data_t>::const_iterator curr = options.parameter_data.begin();
30 std::set<data_t>::const_iterator end = options.parameter_data.end();
31 while (curr != end) {
32 add_parameter(*curr);
33 curr++;
34 }
35 }
36
37
categorize(const std::string & _name,const std::string & category)38 void OptionParser::categorize(const std::string& _name, const std::string& category)
39 {
40 map<string, std::set<data_t> >::iterator curr = categories.find(category);
41 if (curr == categories.end())
42 {
43 categories[category] = std::set<data_t>();
44 curr = categories.find(category);
45 }
46 std::string name = standardize(_name);
47 bool posix = name.size() == 1;
48 data_t tmp = get_param_any(name.c_str(),posix);
49 tmp().categories.insert(category);
50 curr->second.insert(tmp);
51 }
52
53
alias(const string & _name,const string & _alias)54 void OptionParser::alias(const string& _name, const string& _alias)
55 {
56 std::string name = standardize(_name);
57 std::string alias = standardize(_alias);
58 bool posix = name.size() == 1;
59 data_t tmp = get_param_any(name.c_str(),posix);
60
61 if (alias.size() == 1)
62 posix_parameters[alias[0]] = tmp;
63 else
64 parameters[alias] = tmp;
65 tmp().aliases.insert(alias);
66 }
67
68
initialized(const std::string & _name)69 bool OptionParser::initialized(const std::string& _name)
70 {
71 std::string name = standardize(_name);
72 bool posix = name.size() == 1;
73 data_t tmp = get_param_any(name.c_str(),posix);
74 return tmp().initialized;
75 }
76
77
disable(std::string _name)78 void OptionParser::disable(std::string _name)
79 {
80 std::string name = standardize(_name);
81 bool posix = name.size() == 1;
82 data_t tmp = get_param_any(name.c_str(),posix,false);
83 tmp().disabled = true;
84 }
85
86
enable(std::string _name)87 void OptionParser::enable(std::string _name)
88 {
89 std::string name = standardize(_name);
90 bool posix = name.size() == 1;
91 data_t tmp = get_param_any(name.c_str(),posix,false);
92 tmp().disabled = false;
93 }
94
95
remove(std::string _name)96 void OptionParser::remove(std::string _name)
97 {
98 std::string name = standardize(_name);
99 bool posix = name.size() == 1;
100 data_t tmp = get_param_any(name.c_str(),posix);
101 Parameter& param = tmp();
102
103 if (param.name.size() == 1)
104 posix_parameters.erase(param.short_name);
105 else
106 parameters.erase(param.name);
107 parameter_data.erase(tmp);
108 //
109 // Remove category data
110 //
111 std::set<std::string>::iterator curr = param.categories.begin();
112 std::set<std::string>::iterator end = param.categories.end();
113 while (curr != end) {
114 categories[name].erase(tmp);
115 curr++;
116 }
117 }
118
119
remove(const Parameter & param)120 void OptionParser::remove(const Parameter& param)
121 {
122 if (param.short_name != 0)
123 {
124 map<char, data_t>::iterator s_it = posix_parameters.find(param.short_name);
125 if (s_it != posix_parameters.end()) posix_parameters.erase(s_it);
126 else EXCEPTION_MNGR(std::runtime_error, "Expected posix parameter " << param.short_name);
127 }
128 if (param.aliases.size() > 0)
129 {
130 std::set<std::string>::const_iterator curr = param.aliases.begin();
131 std::set<std::string>::const_iterator end = param.aliases.end();
132 while (curr != end)
133 {
134 map<std::string, data_t>::iterator s_it = parameters.find(*curr);
135 if (s_it != parameters.end()) parameters.erase(s_it);
136 else EXCEPTION_MNGR(std::runtime_error, "Expected alias parameter " << *curr);
137 curr++;
138 }
139 }
140 //
141 // Erase parameter with long_name
142 //
143 {
144 map<std::string, data_t>::iterator s_it = parameters.find(param.name);
145 if (s_it != parameters.end()) parameters.erase(s_it);
146 else EXCEPTION_MNGR(std::runtime_error, "Expected parameter " << param.name);
147 }
148 //
149 // Erase parameter data
150 //
151 #if 0
152 WEH - TODO
153 {
154 std::set<data_t>::iterator s_it = parameter_data.find(param.name);
155 if (s_it != parameter_data.end()) parameter_data.erase(s_it);
156 else EXCEPTION_MNGR(std::runtime_error, "Expected parameter " << param.name);
157 }
158 #endif
159 }
160
161
162 #ifdef UTILIB_HAVE_TINYXML
process_xml(TiXmlElement * node,bool describe)163 void OptionParser::process_xml( TiXmlElement* node, bool describe )
164 {
165 if ( describe )
166 {
167 TiXmlElement *opt = new TiXmlElement("Option");
168 opt->SetAttribute("name", "string");
169 node->LinkEndChild(opt);
170 return;
171 }
172
173 TiXmlElement* n = node->FirstChildElement();
174 for( ; n != NULL; n = n->NextSiblingElement() )
175 {
176 if ( n->ValueStr().compare("Option") != 0 )
177 EXCEPTION_MNGR(std::runtime_error, "OptionParser:process_xml - invalid element "
178 << n->ValueStr() << " in " << get_element_info(n));
179 //std::cerr << "Elment info: " << n->ValueStr() << " in " << get_element_info(n) << std::endl;
180 string name = "";
181 get_string_attribute(n, "name", name);
182 const char* elt_data = n->GetText();
183 if (elt_data != 0)
184 set_parameter(name, elt_data);
185 else
186 set_parameter(name, "");
187 }
188 }
189 #endif
190
191
write(std::ostream & os,const std::set<std::string> & categories_requested,bool categorized) const192 void OptionParser::write(std::ostream& os, const std::set<std::string>& categories_requested, bool categorized) const
193 {
194 //
195 // Print usage
196 //
197 std::string indent = " ";
198 {
199 std::list<std::string>::const_iterator curr = usage.begin();
200 std::list<std::string>::const_iterator end = usage.end();
201 while (curr != end)
202 {
203 std::string _usage = "Usage: ";
204 _usage += *curr;
205 wordwrap_printline(os, _usage, indent);
206 curr++;
207 }
208 wordwrap_printline(os, description, "");
209 os << std::endl;
210 }
211 if (categorized && (categories.size() > 0))
212 {
213 //
214 // Print options, grouped by categories
215 //
216 std::vector<std::string> cat;
217 cat.reserve(categories.size());
218 {
219 //
220 // Get the names of the categories
221 //
222 std::map<std::string, std::set<data_t> >::const_iterator curr = categories.begin();
223 std::map<std::string, std::set<data_t> >::const_iterator end = categories.end();
224 while (curr != end)
225 {
226 if ((categories_requested.size() == 0) || (categories_requested.find(curr->first) != categories_requested.end())) {
227 if (curr->second.size() > 0)
228 cat.push_back(curr->first);
229 }
230 curr++;
231 }
232 }
233 sort(cat);
234 {
235 std::vector<std::string>::iterator curr = cat.begin();
236 std::vector<std::string>::iterator end = cat.end();
237 while (curr != end)
238 {
239 os << " " << *curr << ":" << std::endl;
240 std::map<std::string, std::set<data_t> >::const_iterator tmp = categories.find(*curr);
241 write_parameter_set(os, tmp->second, indent);
242 os << std::endl;
243 curr++;
244 }
245 }
246 if (categories_requested.size() == 0) {
247 //
248 // If no categories are requested, then print an 'uncategorized options' category
249 //
250 std::set<data_t> uncat;
251 std::set<data_t>::const_iterator pcurr = parameter_data.begin();
252 std::set<data_t>::const_iterator pend = parameter_data.end();
253 while (pcurr != pend) {
254 const Parameter& param = (*pcurr)();
255 if (param.categories.size() == 0)
256 uncat.insert(*pcurr);
257 pcurr++;
258 }
259 os << " uncategorized options:" << std::endl;
260 write_parameter_set(os, uncat, indent);
261 os << std::endl;
262 }
263
264 }
265 else
266 {
267 // WEH - is this always an error?
268 if (categories_requested.size() > 0)
269 EXCEPTION_MNGR(std::runtime_error, "Although categories were specified for the output, the OptionParser object does not contain categorized options!");
270 //
271 // Print all options, without categories
272 //
273 os << "options:" << std::endl << std::endl;
274 write_parameter_set(os, parameter_data, indent);
275 os << std::endl;
276 }
277 if (arg_definitions.size() > 0)
278 {
279 os << "arguments:" << std::endl << std::endl;
280 {
281 std::list<std::pair<std::string, std::string> >::const_iterator curr = arg_definitions.begin();
282 std::list<std::pair<std::string, std::string> >::const_iterator end = arg_definitions.end();
283 while (curr != end)
284 {
285 std::string tmp;
286 tmp = " ";
287 tmp += curr->first;
288 tmp += ": ";
289 tmp += curr->second;
290 wordwrap_printline(os, tmp, " ");
291 os << std::endl;
292 curr++;
293 }
294 }
295 }
296
297 if (epilog.size() > 0)
298 {
299 wordwrap_printline(os, epilog, "");
300 }
301 }
302
303
write_parameter_set(std::ostream & os,const std::set<data_t> & parameters,const string & indent) const304 void OptionParser::write_parameter_set(std::ostream& os, const std::set<data_t>& parameters, const string& indent) const
305 {
306 std::set<data_t>::const_iterator curr = parameters.begin();
307 std::set<data_t>::const_iterator end = parameters.end();
308 while (curr != end)
309 {
310 const Parameter& param = (*curr)();
311 std::ostringstream tmp;
312 char sname = param.short_name;
313 if (sname == (char)0)
314 tmp << " ";
315 else
316 tmp << " -" << sname;
317 if (param.name != "")
318 {
319 if (sname == (char)0)
320 tmp << " --" << param.name;
321 else
322 tmp << ", --" << param.name;
323 }
324 string str = tmp.str();
325 int tmplen = static_cast<int>(str.size());
326 if (tmplen < 30)
327 {
328 std::string line;
329 line = str;
330 for (int i = tmplen; i < 30; i++)
331 line += " ";
332 line += param.description;
333 wordwrap_printline(os, line, indent);
334 }
335 else
336 {
337 os << str << std::endl << indent;
338 wordwrap_printline(os, param.description, indent);
339 }
340 if (param.aliases.size() > 0) {
341 std::string line(30,' ');
342 line += "aliases:";
343 std::set<std::string>::const_iterator acurr = param.aliases.begin();
344 std::set<std::string>::const_iterator aend = param.aliases.end();
345 while (acurr != aend) {
346 if (acurr->size() == 1)
347 line += " -";
348 else
349 line += " --";
350 line += *acurr;
351 acurr++;
352 }
353 wordwrap_printline(os, line, indent);
354 }
355 curr++;
356 }
357 }
358
359
360 //
write_xml(std::ostream &) const361 void OptionParser::write_xml(std::ostream& ) const
362 {
363 #ifdef UTILIB_HAVE_TINYXML
364
365 /// TODO - how is this different from the write_values_xml() output?
366
367 #else
368
369 EXCEPTION_MNGR(std::runtime_error, "Cannot print XML information unless UTILIB is configured with TinyXML");
370
371 #endif
372 }
373
374
375 //============================================================================
376 //
377 //
write_values(std::ostream & os,const std::string & opt_label) const378 void OptionParser::write_values(std::ostream& os, const std::string& opt_label) const
379 {
380 if (opt_label != "")
381 os << "# ---- Options for " << opt_label << " ----" << std::endl;
382 //
383 // Get the length of the longest parameter name
384 //
385 size_type len = 0;
386 std::set<data_t>::const_iterator tcurr = parameter_data.begin();
387 std::set<data_t>::const_iterator tlast = parameter_data.end();
388 while (tcurr != tlast)
389 {
390 const Parameter& param = (*tcurr)();
391 if (len < param.name.size())
392 len = param.name.size();
393 tcurr++;
394 }
395 if (len < 10) len = 10;
396 char tformat[32];
397 #ifdef _MSC_VER
398 sprintf_s(tformat, 32, "%%-%lds", (long int)len);
399 #else
400 sprintf(tformat, "%%-%lds", (long int)len);
401 #endif
402 //
403 //
404 //
405 std::set<data_t>::const_iterator curr = parameter_data.begin();
406 std::set<data_t>::const_iterator last = parameter_data.end();
407
408 char tmp[128];
409 while (curr != last)
410 {
411 const Parameter& param = (*curr)();
412 if (!(param.disabled))
413 {
414 if (param.name.size() > 0)
415 {
416 #ifdef _MSC_VER
417 sprintf_s(tmp, 128, tformat, param.name.data());
418 #else
419 sprintf(tmp, tformat, param.name.data());
420 #endif
421 }
422 else
423 {
424 std::string tstr;
425 tstr += param.short_name;
426 tstr += "_option";
427 #ifdef _MSC_VER
428 sprintf_s(tmp, 128, tformat, tstr.c_str());
429 #else
430 sprintf(tmp, tformat, tstr.c_str());
431 #endif
432 }
433 os << tmp << " ";
434 std::ostringstream ostr;
435 ostr << param.info;
436 const std::string& output = ostr.str();
437 if (output.size() == 0)
438 os << "\"\"";
439 else if ( output.find(" ") != string::npos &&
440 ! ( (output[0] == '"' && *output.rbegin() == '"') ||
441 (output[0] == '[' && *output.rbegin() == ']')
442 )
443 )
444 // Instead of explicitly testing for std::string and
445 // CharString, we will simply look for data with a space
446 // that is not already within quotes or look like a vector.
447 os << "\"" << output << "\"";
448 else
449 os << output;
450 if (!param.initialized)
451 os << "\t# default" << std::endl;
452 else
453 os << std::endl;
454 }
455 curr++;
456 }
457 }
458
459
460 #ifdef UTILIB_HAVE_TINYXML
write_values_xml(std::ostream & os) const461 void OptionParser::write_values_xml(std::ostream& os) const
462 {
463
464 TiXmlElement* root = new TiXmlElement("Options");
465 std::set<data_t>::const_iterator curr = parameter_data.begin();
466 std::set<data_t>::const_iterator end = parameter_data.end();
467 while (curr != end) {
468 const Parameter& param = (*curr)();
469 if (!(param.disabled)) {
470 TiXmlElement* option = new TiXmlElement("Option");
471 if (param.name != "")
472 option->SetAttribute("name",param.name);
473 else {
474 std::string tmp;
475 tmp += param.short_name;
476 option->SetAttribute("name",tmp);
477 }
478 int default_val = !(param.initialized);
479 option->SetAttribute("default",default_val);
480 //option->SetAttribute("type",param.info.type().name());
481 std::ostringstream ostr;
482 ostr << param.info;
483 option->LinkEndChild(new TiXmlText(ostr.str()));
484 root->LinkEndChild(option);
485 }
486 curr++;
487 }
488
489 os << *root;
490 delete root;
491 }
492 #else
write_values_xml(std::ostream &) const493 void OptionParser::write_values_xml(std::ostream& ) const
494 {
495 EXCEPTION_MNGR(std::runtime_error, "Cannot print XML information unless UTILIB is configured with TinyXML");
496 }
497 #endif
498
499
500
501 //
502 // 1. Flag errors if an option appears after the first non-option?
503 // 2. Flag error when invalid value is given
504 //
parse_args(int argc,char * _argv[])505 OptionParser::args_t& OptionParser::parse_args(int argc, char* _argv[])
506 {
507 std::vector<std::string> argv(_argv, _argv + argc);
508 int argc_limit = argc - (int)min_num_required_args;
509 //
510 // Go through the argument list
511 //
512 int i = 1;
513 while ((i < argc_limit) && (argv[i][0] == '-'))
514 {
515 //
516 // Split the current string at the '=' character
517 //
518 char* tmp = const_cast<char*>(strchr(argv[i].c_str(), '='));
519 bool using_equal = false;
520 if (tmp)
521 {
522 using_equal = true;
523 tmp++;
524 *(tmp - 1) = '\000';
525 }
526 bool posix = argv[i][1] != '-';
527 Parameter& param = get_param(argv[i].c_str(),posix);
528 if (param.is_bool)
529 {
530 if (strlen(tmp) >0)
531 param.set_value_with_string(tmp);
532 else
533 param.set_value_with_string("");
534 }
535 else
536 {
537 if (!using_equal && required_equals)
538 EXCEPTION_MNGR(std::runtime_error, "Nonboolean parameter '" << argv[i] << "' specified without required argument. Option parsing configured to require --option=value syntax.");
539 if (strlen(tmp) > 0)
540 param.set_value_with_string(tmp);
541 else
542 {
543 i++;
544 if (i == argc_limit)
545 EXCEPTION_MNGR(std::runtime_error, "Expected argument for parameter '" << argv[i-1] << "' but ran out of available arguments");
546 if ((argv[i][0] == '-') && (argv[i].size() > 1) && (isalpha(argv[i][1])))
547 EXCEPTION_MNGR(std::runtime_error, "Expected argument for parameter '" << argv[i-1] << "' but the next argument is an option.");
548 param.set_value_with_string(argv[i]);
549 }
550 }
551 i++;
552 }
553 //
554 // Check to ensure that first 'required arg' does not look like a parameter
555 //
556 if ((i < argc) && (argv[i][0] == '-'))
557 {
558 std::string tmp = argv[i];
559 if ((tmp == "--help") || (tmp == "--version"))
560 {
561 bool posix = argv[i][1] != '-';
562 Parameter& param = get_param(argv[i].c_str(), posix);
563 param.set_value_with_string("");
564 }
565 else if ((argv[i].size() > 1) && (isalpha(argv[i][1])))
566 EXCEPTION_MNGR(runtime_error,
567 "OptionParser::parse_args "
568 "- first required argument looks "
569 "like a parameter flag: " << argv[i]);
570 }
571 #if 0
572 // WEH - This error checking seems useful, but it can get in the way.
573 // There are weird contexts where we should expect to find an option
574 // after a non-option. For example, the command-line for an AMPL
575 // solver has the syntax: <solver> <nl-file> -AMPL
576 int j = i + 1;
577 while (j < argc)
578 {
579 if (argv[j][0] == '-')
580 EXCEPTION_MNGR(runtime_error,
581 "OptionParser::parse_args "
582 "- argument '" << argv[j] << "' looks "
583 "like a parameter flag after all parameters have been parsed. Argument '" << argv[i] << "' is the first argument.");
584 j++;
585 }
586 #endif
587
588 //
589 // Collect the remaining arguments
590 //
591 processed_args.push_back(argv[0]);
592 while (i < argc)
593 {
594 processed_args.push_back(argv[i++]);
595 }
596
597 //
598 // Return the list of arguments
599 //
600 return processed_args;
601 }
602
603
set_parameter(std::string _name,const std::string & value)604 void OptionParser::set_parameter(std::string _name, const std::string& value)
605 {
606 std::string name = standardize(_name);
607 bool posix= name.size() == 1;
608 Parameter& param = get_param(name.c_str(), posix);
609 param.set_value_with_string(value);
610 }
611
set_parameter(std::string _name,Any value)612 void OptionParser::set_parameter(std::string _name, Any value)
613 {
614 std::string name = standardize(_name);
615 bool posix= name.size() == 1;
616 Parameter& param = get_param(name.c_str(), posix);
617 if ( param.info.is_type<Property>() )
618 param.info.expose<Property>() = value;
619 else if ( param.info.is_type<Privileged_Property>() )
620 param.info.expose<Privileged_Property>() = value;
621 else
622 TypeManager()->lexical_cast(value, param.info);
623 }
624
has_parameter(std::string _name)625 int OptionParser::has_parameter(std::string _name)
626 {
627 if ( _name.empty() )
628 return 0;
629 const char* c = _name.c_str();
630 if ( c[0] == '-') c++;
631 if ( c[0] == '-') c++;
632 std::string name = standardize(c);
633
634 // as with get_param_any(), we will assume all single-character parameters are POSIX.
635 if ( name.size() == 1 )
636 {
637 std::map<char, data_t>::iterator curr = posix_parameters.find(name[0]);
638 if ( curr == posix_parameters.end() )
639 return 0;
640 else
641 return curr->second().disabled ? -1 : 1;
642 }
643 else
644 {
645 std::map<std::string, data_t>::iterator curr = parameters.find(name);
646 if ( curr == parameters.end() )
647 return 0;
648 else
649 return curr->second().disabled ? -1 : 1;
650 }
651 }
652
653
get_param(const char * name,bool posix)654 Parameter& OptionParser::get_param(const char* name, bool posix)
655 {
656 data_t tmp = get_param_any(name,posix);
657 return tmp();
658 }
659
660
get_param_any(const char * name,bool posix,bool test_if_enabled)661 OptionParser::data_t OptionParser::get_param_any(const char* name, bool posix, bool test_if_enabled)
662 {
663 if ((name == 0) || (name[0] == '\000'))
664 EXCEPTION_MNGR(std::runtime_error, "OptionParser - "
665 "cannot access a parameter with an empty name.");
666 if (name[0] == '-') name++;
667 if (name[0] == '-') name++;
668
669 std::string param = name;
670 if (posix) {
671 if (param.size() > 1)
672 EXCEPTION_MNGR(std::runtime_error, "Multiple posix options cannot be specified at once.");
673 std::map<char, data_t>::iterator curr = posix_parameters.find(param[0]);
674 if (curr == posix_parameters.end())
675 EXCEPTION_MNGR(std::runtime_error, "Unknown posix parameter '" << param << "'");
676 if (curr->second().disabled && test_if_enabled)
677 EXCEPTION_MNGR(std::runtime_error, "Parameter '" << param << "' is disabled.");
678 return curr->second;
679 }
680 else
681 {
682 std::map<std::string, data_t>::iterator curr = parameters.find(param);
683 if (curr == parameters.end())
684 EXCEPTION_MNGR(std::runtime_error, "Unknown parameter '" << param << "'");
685 if (curr->second().disabled && test_if_enabled)
686 EXCEPTION_MNGR(std::runtime_error, "Parameter '" << param << "' is disabled.");
687 return curr->second;
688 }
689
690 }
691
692
add_parameter(data_t any_param)693 void OptionParser::add_parameter(data_t any_param)
694 {
695 Parameter& param = any_param();
696 //
697 // Setup parameters, posix_parameters and parameter_data
698 //
699 if (param.name != "")
700 parameters[param.name] = any_param;
701 if (param.short_name != 0)
702 {
703 posix_parameters[param.short_name] = any_param;
704 if (param.name == "")
705 {
706 std::string tmp;
707 tmp += std::string("_") + param.short_name;
708 parameters[tmp] = any_param;
709 }
710 }
711 parameter_data.insert(any_param);
712 //
713 // Insert aliases
714 //
715 {
716 std::set<std::string>::iterator curr = param.aliases.begin();
717 std::set<std::string>::iterator end = param.aliases.end();
718 while (curr != end) {
719 if (curr->size() == 1)
720 posix_parameters[(*curr)[0]] = any_param;
721 else
722 parameters[*curr] = any_param;
723 curr++;
724 }
725 }
726 //
727 // Setup categories
728 //
729 {
730 std::set<std::string>::iterator curr = param.categories.begin();
731 std::set<std::string>::iterator end = param.categories.end();
732 while (curr != end) {
733 if (param.name == "") {
734 std::string tmp;
735 tmp += param.short_name;
736 categorize(tmp, *curr);
737 }
738 else
739 categorize(param.name, *curr);
740 curr++;
741 }
742 }
743 }
744
standardize(const std::string & _name)745 std::string OptionParser::standardize(const std::string& _name)
746 {
747 std::string name = _name;
748 std::string::iterator curr = name.begin();
749 std::string::iterator end = name.end();
750 while (curr != end) {
751 if (*curr == '_')
752 *curr = '-';
753 curr++;
754 }
755 return name;
756 }
757
758 //template <>
759 //bool CachedAllocator<SmartPtrInfo<Parameter> >::cache_enabled = false;
760
761 }
762