/* _________________________________________________________________________ * * UTILIB: A utility library for developing portable C++ codes. * Copyright (c) 2008 Sandia Corporation. * This software is distributed under the BSD License. * Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, * the U.S. Government retains certain rights in this software. * For more information, see the README file in the top UTILIB directory. * _________________________________________________________________________ */ #include #include #include #include #include #include #include using std::map; using std::string; namespace utilib { void OptionParser::merge_options(const OptionParser& options) { std::set::const_iterator curr = options.parameter_data.begin(); std::set::const_iterator end = options.parameter_data.end(); while (curr != end) { add_parameter(*curr); curr++; } } void OptionParser::categorize(const std::string& _name, const std::string& category) { map >::iterator curr = categories.find(category); if (curr == categories.end()) { categories[category] = std::set(); curr = categories.find(category); } std::string name = standardize(_name); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix); tmp().categories.insert(category); curr->second.insert(tmp); } void OptionParser::alias(const string& _name, const string& _alias) { std::string name = standardize(_name); std::string alias = standardize(_alias); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix); if (alias.size() == 1) posix_parameters[alias[0]] = tmp; else parameters[alias] = tmp; tmp().aliases.insert(alias); } bool OptionParser::initialized(const std::string& _name) { std::string name = standardize(_name); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix); return tmp().initialized; } void OptionParser::disable(std::string _name) { std::string name = standardize(_name); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix,false); tmp().disabled = true; } void OptionParser::enable(std::string _name) { std::string name = standardize(_name); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix,false); tmp().disabled = false; } void OptionParser::remove(std::string _name) { std::string name = standardize(_name); bool posix = name.size() == 1; data_t tmp = get_param_any(name.c_str(),posix); Parameter& param = tmp(); if (param.name.size() == 1) posix_parameters.erase(param.short_name); else parameters.erase(param.name); parameter_data.erase(tmp); // // Remove category data // std::set::iterator curr = param.categories.begin(); std::set::iterator end = param.categories.end(); while (curr != end) { categories[name].erase(tmp); curr++; } } void OptionParser::remove(const Parameter& param) { if (param.short_name != 0) { map::iterator s_it = posix_parameters.find(param.short_name); if (s_it != posix_parameters.end()) posix_parameters.erase(s_it); else EXCEPTION_MNGR(std::runtime_error, "Expected posix parameter " << param.short_name); } if (param.aliases.size() > 0) { std::set::const_iterator curr = param.aliases.begin(); std::set::const_iterator end = param.aliases.end(); while (curr != end) { map::iterator s_it = parameters.find(*curr); if (s_it != parameters.end()) parameters.erase(s_it); else EXCEPTION_MNGR(std::runtime_error, "Expected alias parameter " << *curr); curr++; } } // // Erase parameter with long_name // { map::iterator s_it = parameters.find(param.name); if (s_it != parameters.end()) parameters.erase(s_it); else EXCEPTION_MNGR(std::runtime_error, "Expected parameter " << param.name); } // // Erase parameter data // #if 0 WEH - TODO { std::set::iterator s_it = parameter_data.find(param.name); if (s_it != parameter_data.end()) parameter_data.erase(s_it); else EXCEPTION_MNGR(std::runtime_error, "Expected parameter " << param.name); } #endif } #ifdef UTILIB_HAVE_TINYXML void OptionParser::process_xml( TiXmlElement* node, bool describe ) { if ( describe ) { TiXmlElement *opt = new TiXmlElement("Option"); opt->SetAttribute("name", "string"); node->LinkEndChild(opt); return; } TiXmlElement* n = node->FirstChildElement(); for( ; n != NULL; n = n->NextSiblingElement() ) { if ( n->ValueStr().compare("Option") != 0 ) EXCEPTION_MNGR(std::runtime_error, "OptionParser:process_xml - invalid element " << n->ValueStr() << " in " << get_element_info(n)); //std::cerr << "Elment info: " << n->ValueStr() << " in " << get_element_info(n) << std::endl; string name = ""; get_string_attribute(n, "name", name); const char* elt_data = n->GetText(); if (elt_data != 0) set_parameter(name, elt_data); else set_parameter(name, ""); } } #endif void OptionParser::write(std::ostream& os, const std::set& categories_requested, bool categorized) const { // // Print usage // std::string indent = " "; { std::list::const_iterator curr = usage.begin(); std::list::const_iterator end = usage.end(); while (curr != end) { std::string _usage = "Usage: "; _usage += *curr; wordwrap_printline(os, _usage, indent); curr++; } wordwrap_printline(os, description, ""); os << std::endl; } if (categorized && (categories.size() > 0)) { // // Print options, grouped by categories // std::vector cat; cat.reserve(categories.size()); { // // Get the names of the categories // std::map >::const_iterator curr = categories.begin(); std::map >::const_iterator end = categories.end(); while (curr != end) { if ((categories_requested.size() == 0) || (categories_requested.find(curr->first) != categories_requested.end())) { if (curr->second.size() > 0) cat.push_back(curr->first); } curr++; } } sort(cat); { std::vector::iterator curr = cat.begin(); std::vector::iterator end = cat.end(); while (curr != end) { os << " " << *curr << ":" << std::endl; std::map >::const_iterator tmp = categories.find(*curr); write_parameter_set(os, tmp->second, indent); os << std::endl; curr++; } } if (categories_requested.size() == 0) { // // If no categories are requested, then print an 'uncategorized options' category // std::set uncat; std::set::const_iterator pcurr = parameter_data.begin(); std::set::const_iterator pend = parameter_data.end(); while (pcurr != pend) { const Parameter& param = (*pcurr)(); if (param.categories.size() == 0) uncat.insert(*pcurr); pcurr++; } os << " uncategorized options:" << std::endl; write_parameter_set(os, uncat, indent); os << std::endl; } } else { // WEH - is this always an error? if (categories_requested.size() > 0) EXCEPTION_MNGR(std::runtime_error, "Although categories were specified for the output, the OptionParser object does not contain categorized options!"); // // Print all options, without categories // os << "options:" << std::endl << std::endl; write_parameter_set(os, parameter_data, indent); os << std::endl; } if (arg_definitions.size() > 0) { os << "arguments:" << std::endl << std::endl; { std::list >::const_iterator curr = arg_definitions.begin(); std::list >::const_iterator end = arg_definitions.end(); while (curr != end) { std::string tmp; tmp = " "; tmp += curr->first; tmp += ": "; tmp += curr->second; wordwrap_printline(os, tmp, " "); os << std::endl; curr++; } } } if (epilog.size() > 0) { wordwrap_printline(os, epilog, ""); } } void OptionParser::write_parameter_set(std::ostream& os, const std::set& parameters, const string& indent) const { std::set::const_iterator curr = parameters.begin(); std::set::const_iterator end = parameters.end(); while (curr != end) { const Parameter& param = (*curr)(); std::ostringstream tmp; char sname = param.short_name; if (sname == (char)0) tmp << " "; else tmp << " -" << sname; if (param.name != "") { if (sname == (char)0) tmp << " --" << param.name; else tmp << ", --" << param.name; } string str = tmp.str(); int tmplen = static_cast(str.size()); if (tmplen < 30) { std::string line; line = str; for (int i = tmplen; i < 30; i++) line += " "; line += param.description; wordwrap_printline(os, line, indent); } else { os << str << std::endl << indent; wordwrap_printline(os, param.description, indent); } if (param.aliases.size() > 0) { std::string line(30,' '); line += "aliases:"; std::set::const_iterator acurr = param.aliases.begin(); std::set::const_iterator aend = param.aliases.end(); while (acurr != aend) { if (acurr->size() == 1) line += " -"; else line += " --"; line += *acurr; acurr++; } wordwrap_printline(os, line, indent); } curr++; } } // void OptionParser::write_xml(std::ostream& ) const { #ifdef UTILIB_HAVE_TINYXML /// TODO - how is this different from the write_values_xml() output? #else EXCEPTION_MNGR(std::runtime_error, "Cannot print XML information unless UTILIB is configured with TinyXML"); #endif } //============================================================================ // // void OptionParser::write_values(std::ostream& os, const std::string& opt_label) const { if (opt_label != "") os << "# ---- Options for " << opt_label << " ----" << std::endl; // // Get the length of the longest parameter name // size_type len = 0; std::set::const_iterator tcurr = parameter_data.begin(); std::set::const_iterator tlast = parameter_data.end(); while (tcurr != tlast) { const Parameter& param = (*tcurr)(); if (len < param.name.size()) len = param.name.size(); tcurr++; } if (len < 10) len = 10; char tformat[32]; #ifdef _MSC_VER sprintf_s(tformat, 32, "%%-%lds", (long int)len); #else sprintf(tformat, "%%-%lds", (long int)len); #endif // // // std::set::const_iterator curr = parameter_data.begin(); std::set::const_iterator last = parameter_data.end(); char tmp[128]; while (curr != last) { const Parameter& param = (*curr)(); if (!(param.disabled)) { if (param.name.size() > 0) { #ifdef _MSC_VER sprintf_s(tmp, 128, tformat, param.name.data()); #else sprintf(tmp, tformat, param.name.data()); #endif } else { std::string tstr; tstr += param.short_name; tstr += "_option"; #ifdef _MSC_VER sprintf_s(tmp, 128, tformat, tstr.c_str()); #else sprintf(tmp, tformat, tstr.c_str()); #endif } os << tmp << " "; std::ostringstream ostr; ostr << param.info; const std::string& output = ostr.str(); if (output.size() == 0) os << "\"\""; else if ( output.find(" ") != string::npos && ! ( (output[0] == '"' && *output.rbegin() == '"') || (output[0] == '[' && *output.rbegin() == ']') ) ) // Instead of explicitly testing for std::string and // CharString, we will simply look for data with a space // that is not already within quotes or look like a vector. os << "\"" << output << "\""; else os << output; if (!param.initialized) os << "\t# default" << std::endl; else os << std::endl; } curr++; } } #ifdef UTILIB_HAVE_TINYXML void OptionParser::write_values_xml(std::ostream& os) const { TiXmlElement* root = new TiXmlElement("Options"); std::set::const_iterator curr = parameter_data.begin(); std::set::const_iterator end = parameter_data.end(); while (curr != end) { const Parameter& param = (*curr)(); if (!(param.disabled)) { TiXmlElement* option = new TiXmlElement("Option"); if (param.name != "") option->SetAttribute("name",param.name); else { std::string tmp; tmp += param.short_name; option->SetAttribute("name",tmp); } int default_val = !(param.initialized); option->SetAttribute("default",default_val); //option->SetAttribute("type",param.info.type().name()); std::ostringstream ostr; ostr << param.info; option->LinkEndChild(new TiXmlText(ostr.str())); root->LinkEndChild(option); } curr++; } os << *root; delete root; } #else void OptionParser::write_values_xml(std::ostream& ) const { EXCEPTION_MNGR(std::runtime_error, "Cannot print XML information unless UTILIB is configured with TinyXML"); } #endif // // 1. Flag errors if an option appears after the first non-option? // 2. Flag error when invalid value is given // OptionParser::args_t& OptionParser::parse_args(int argc, char* _argv[]) { std::vector argv(_argv, _argv + argc); int argc_limit = argc - (int)min_num_required_args; // // Go through the argument list // int i = 1; while ((i < argc_limit) && (argv[i][0] == '-')) { // // Split the current string at the '=' character // char* tmp = const_cast(strchr(argv[i].c_str(), '=')); bool using_equal = false; if (tmp) { using_equal = true; tmp++; *(tmp - 1) = '\000'; } bool posix = argv[i][1] != '-'; Parameter& param = get_param(argv[i].c_str(),posix); if (param.is_bool) { if (strlen(tmp) >0) param.set_value_with_string(tmp); else param.set_value_with_string(""); } else { if (!using_equal && required_equals) EXCEPTION_MNGR(std::runtime_error, "Nonboolean parameter '" << argv[i] << "' specified without required argument. Option parsing configured to require --option=value syntax."); if (strlen(tmp) > 0) param.set_value_with_string(tmp); else { i++; if (i == argc_limit) EXCEPTION_MNGR(std::runtime_error, "Expected argument for parameter '" << argv[i-1] << "' but ran out of available arguments"); if ((argv[i][0] == '-') && (argv[i].size() > 1) && (isalpha(argv[i][1]))) EXCEPTION_MNGR(std::runtime_error, "Expected argument for parameter '" << argv[i-1] << "' but the next argument is an option."); param.set_value_with_string(argv[i]); } } i++; } // // Check to ensure that first 'required arg' does not look like a parameter // if ((i < argc) && (argv[i][0] == '-')) { std::string tmp = argv[i]; if ((tmp == "--help") || (tmp == "--version")) { bool posix = argv[i][1] != '-'; Parameter& param = get_param(argv[i].c_str(), posix); param.set_value_with_string(""); } else if ((argv[i].size() > 1) && (isalpha(argv[i][1]))) EXCEPTION_MNGR(runtime_error, "OptionParser::parse_args " "- first required argument looks " "like a parameter flag: " << argv[i]); } #if 0 // WEH - This error checking seems useful, but it can get in the way. // There are weird contexts where we should expect to find an option // after a non-option. For example, the command-line for an AMPL // solver has the syntax: -AMPL int j = i + 1; while (j < argc) { if (argv[j][0] == '-') EXCEPTION_MNGR(runtime_error, "OptionParser::parse_args " "- argument '" << argv[j] << "' looks " "like a parameter flag after all parameters have been parsed. Argument '" << argv[i] << "' is the first argument."); j++; } #endif // // Collect the remaining arguments // processed_args.push_back(argv[0]); while (i < argc) { processed_args.push_back(argv[i++]); } // // Return the list of arguments // return processed_args; } void OptionParser::set_parameter(std::string _name, const std::string& value) { std::string name = standardize(_name); bool posix= name.size() == 1; Parameter& param = get_param(name.c_str(), posix); param.set_value_with_string(value); } void OptionParser::set_parameter(std::string _name, Any value) { std::string name = standardize(_name); bool posix= name.size() == 1; Parameter& param = get_param(name.c_str(), posix); if ( param.info.is_type() ) param.info.expose() = value; else if ( param.info.is_type() ) param.info.expose() = value; else TypeManager()->lexical_cast(value, param.info); } int OptionParser::has_parameter(std::string _name) { if ( _name.empty() ) return 0; const char* c = _name.c_str(); if ( c[0] == '-') c++; if ( c[0] == '-') c++; std::string name = standardize(c); // as with get_param_any(), we will assume all single-character parameters are POSIX. if ( name.size() == 1 ) { std::map::iterator curr = posix_parameters.find(name[0]); if ( curr == posix_parameters.end() ) return 0; else return curr->second().disabled ? -1 : 1; } else { std::map::iterator curr = parameters.find(name); if ( curr == parameters.end() ) return 0; else return curr->second().disabled ? -1 : 1; } } Parameter& OptionParser::get_param(const char* name, bool posix) { data_t tmp = get_param_any(name,posix); return tmp(); } OptionParser::data_t OptionParser::get_param_any(const char* name, bool posix, bool test_if_enabled) { if ((name == 0) || (name[0] == '\000')) EXCEPTION_MNGR(std::runtime_error, "OptionParser - " "cannot access a parameter with an empty name."); if (name[0] == '-') name++; if (name[0] == '-') name++; std::string param = name; if (posix) { if (param.size() > 1) EXCEPTION_MNGR(std::runtime_error, "Multiple posix options cannot be specified at once."); std::map::iterator curr = posix_parameters.find(param[0]); if (curr == posix_parameters.end()) EXCEPTION_MNGR(std::runtime_error, "Unknown posix parameter '" << param << "'"); if (curr->second().disabled && test_if_enabled) EXCEPTION_MNGR(std::runtime_error, "Parameter '" << param << "' is disabled."); return curr->second; } else { std::map::iterator curr = parameters.find(param); if (curr == parameters.end()) EXCEPTION_MNGR(std::runtime_error, "Unknown parameter '" << param << "'"); if (curr->second().disabled && test_if_enabled) EXCEPTION_MNGR(std::runtime_error, "Parameter '" << param << "' is disabled."); return curr->second; } } void OptionParser::add_parameter(data_t any_param) { Parameter& param = any_param(); // // Setup parameters, posix_parameters and parameter_data // if (param.name != "") parameters[param.name] = any_param; if (param.short_name != 0) { posix_parameters[param.short_name] = any_param; if (param.name == "") { std::string tmp; tmp += std::string("_") + param.short_name; parameters[tmp] = any_param; } } parameter_data.insert(any_param); // // Insert aliases // { std::set::iterator curr = param.aliases.begin(); std::set::iterator end = param.aliases.end(); while (curr != end) { if (curr->size() == 1) posix_parameters[(*curr)[0]] = any_param; else parameters[*curr] = any_param; curr++; } } // // Setup categories // { std::set::iterator curr = param.categories.begin(); std::set::iterator end = param.categories.end(); while (curr != end) { if (param.name == "") { std::string tmp; tmp += param.short_name; categorize(tmp, *curr); } else categorize(param.name, *curr); curr++; } } } std::string OptionParser::standardize(const std::string& _name) { std::string name = _name; std::string::iterator curr = name.begin(); std::string::iterator end = name.end(); while (curr != end) { if (*curr == '_') *curr = '-'; curr++; } return name; } //template <> //bool CachedAllocator >::cache_enabled = false; }