diff options
Diffstat (limited to 'cpp/src/IceUtil/Options.cpp')
-rw-r--r-- | cpp/src/IceUtil/Options.cpp | 1049 |
1 files changed, 1049 insertions, 0 deletions
diff --git a/cpp/src/IceUtil/Options.cpp b/cpp/src/IceUtil/Options.cpp new file mode 100644 index 00000000000..57c49ecdaf1 --- /dev/null +++ b/cpp/src/IceUtil/Options.cpp @@ -0,0 +1,1049 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +#include <IceUtil/Options.h> +#include <IceUtil/StringUtil.h> +#include <iostream> +#include <set> + +using namespace std; +using namespace IceUtil; + +IceUtilInternal::APIException::APIException(const char* file, int line, const string& r) + : IceUtil::Exception(file, line), reason(r) +{ +} + +IceUtilInternal::APIException::~APIException() throw() +{ +} + +const char* IceUtilInternal::APIException::_name = "IceUtilInternal::APIException"; + +string +IceUtilInternal::APIException::ice_name() const +{ + return _name; +} + +void +IceUtilInternal::APIException::ice_print(ostream& out) const +{ + Exception::ice_print(out); + if(!reason.empty()) + { + out << ": " << reason; + } +} + +IceUtil::Exception* +IceUtilInternal::APIException::ice_clone() const +{ + return new APIException(*this); +} + +void +IceUtilInternal::APIException::ice_throw() const +{ + throw *this; +} + +ostream& +IceUtilInternal::operator<<(ostream& out, const IceUtilInternal::APIException& ex) +{ + ex.ice_print(out); + return out; +} + +IceUtilInternal::BadOptException::BadOptException(const char* file, int line, const string& r) + : IceUtil::Exception(file, line), reason(r) +{ +} + +IceUtilInternal::BadOptException::~BadOptException() throw() +{ +} + +const char* IceUtilInternal::BadOptException::_name = "IceUtilInternal::BadOptException"; + +string +IceUtilInternal::BadOptException::ice_name() const +{ + return _name; +} + +void +IceUtilInternal::BadOptException::ice_print(ostream& out) const +{ + Exception::ice_print(out); + if(!reason.empty()) + { + out << ": " << reason; + } +} + +IceUtil::Exception* +IceUtilInternal::BadOptException::ice_clone() const +{ + return new BadOptException(*this); +} + +void +IceUtilInternal::BadOptException::ice_throw() const +{ + throw *this; +} + +ostream& +IceUtilInternal::operator<<(ostream& out, const IceUtilInternal::BadOptException& ex) +{ + ex.ice_print(out); + return out; +} + +IceUtilInternal::Options::Options() + : parseCalled(false) +{ +} + +void +IceUtilInternal::Options::checkArgs(const string& shortOpt, const string& longOpt, bool needArg, const string& dflt) +{ + if(shortOpt.empty() && longOpt.empty()) + { + throw IllegalArgumentException(__FILE__, __LINE__, "short and long option cannot both be empty"); + } + + if(!shortOpt.empty()) + { + if(shortOpt.size() != 1) + { + string err = "`"; + err += shortOpt; + err += "': a short option cannot specify more than one option"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + if(shortOpt.find_first_of(" \t\n\r\f\v") != string::npos) + { + string err = "`"; + err += shortOpt; + err += "': a short option cannot be whitespace"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + if(shortOpt[0] == '-') + { + string err = "`"; + err += shortOpt; + err += "': a short option cannot be `-'"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + } + + if(!longOpt.empty()) + { + if(longOpt.find_first_of(" \t\n\r\f\v") != string::npos) + { + string err = "`"; + err += longOpt; + err += "': a long option cannot contain whitespace"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + if(longOpt[0] == '-') + { + string err = "`"; + err += longOpt; + err += "': a long option must not contain a leading `-'"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + } + + if(!needArg && !dflt.empty()) + { + throw IllegalArgumentException(__FILE__, __LINE__, + "a default value can be specified only for options requiring an argument"); + } +} + +void +IceUtilInternal::Options::addOpt(const string& shortOpt, const string& longOpt, ArgType at, string dflt, RepeatType rt) +{ + RecMutex::Lock sync(_m); + + if(parseCalled) + { + throw APIException(__FILE__, __LINE__, "cannot add options after parse() was called"); + } + + checkArgs(shortOpt, longOpt, at == NeedArg, dflt); + + addValidOpt(shortOpt, longOpt, at, dflt, rt); +} + +// +// Split a command line into argv-style arguments, applying +// bash quoting rules. The return value is the arguments +// in the command line, with all shell escapes applied, and +// quotes removed. +// + +IceUtilInternal::Options::StringVector +IceUtilInternal::Options::split(const string& line) +{ + const string IFS = " \t\n"; // Internal Field Separator. + + // + // Strip leading and trailing whitespace. + // + string::size_type start = line.find_first_not_of(IFS); + if(start == string::npos) + { + return StringVector(); + } + string::size_type end = line.find_last_not_of(IFS); + assert(end != string::npos); + + string l(line, start, end - start + 1); + + StringVector vec; + + enum ParseState { Normal, DoubleQuote, SingleQuote, ANSIQuote }; + ParseState state = Normal; + + string arg; + + for(string::size_type i = 0; i < l.size(); ++i) + { + char c = l[i]; + switch(state) + { + case Normal: + { + switch(c) + { + case '\\': + { + // + // Ignore a backslash at the end of the string, + // and strip backslash-newline pairs. If a + // backslash is followed by a space, single quote, + // double quote, or dollar sign, we drop the backslash + // and write the space, single quote, double quote, + // or dollar sign. This is necessary to allow quotes + // to be escaped. Dropping the backslash preceding a + // space deviates from bash quoting rules, but is + // necessary so we don't drop backslashes from Windows + // path names.) + // + if(i < l.size() - 1 && l[++i] != '\n') + { + switch(l[i]) + { + case ' ': + case '$': + case '\'': + case '"': + { + arg.push_back(l[i]); + break; + } + default: + { + arg.push_back('\\'); + arg.push_back(l[i]); + break; + } + } + } + break; + } + case '\'': + { + state = SingleQuote; + break; + } + case '"': + { + state = DoubleQuote; + break; + } + case '$': + { + if(i < l.size() - 1 && l[i + 1] == '\'') + { + state = ANSIQuote; // Bash uses $'<text>' to allow ANSI escape sequences within <text>. + ++i; + } + else + { + arg.push_back('$'); + } + break; + } + default: + { + if(IFS.find(l[i]) != string::npos) + { + vec.push_back(arg); + arg.clear(); + + // + // Move to start of next argument. + // + while(++i < l.size() && IFS.find(l[i]) != string::npos) + { + ; + } + --i; + } + else + { + arg.push_back(l[i]); + } + break; + } + } + break; + } + case DoubleQuote: + { + // + // Within double quotes, only backslash retains its special + // meaning, and only if followed by double quote, backslash, + // or newline. If not followed by one of these characters, + // both the backslash and the character are preserved. + // + if(c == '\\' && i < l.size() - 1) + { + switch(c = l[++i]) + { + case '"': + case '\\': + case '\n': + { + arg.push_back(c); + break; + } + default: + { + arg.push_back('\\'); + arg.push_back(c); + break; + } + } + } + else if(c == '"') // End of double-quote mode. + { + state = Normal; + } + else + { + arg.push_back(c); // Everything else is taken literally. + } + break; + } + case SingleQuote: + { + if(c == '\'') // End of single-quote mode. + { + state = Normal; + } + else + { + arg.push_back(c); // Everything else is taken literally. + } + break; + } + case ANSIQuote: + { + switch(c) + { + case '\\': + { + if(i == l.size() - 1) + { + break; + } + switch(c = l[++i]) + { + // + // Single-letter escape sequences. + // + case 'a': + { + arg.push_back('\a'); + break; + } + case 'b': + { + arg.push_back('\b'); + break; + } + case 'f': + { + arg.push_back('\f'); + break; + } + case 'n': + { + arg.push_back('\n'); + break; + } + case 'r': + { + arg.push_back('\r'); + break; + } + case 't': + { + arg.push_back('\t'); + break; + } + case 'v': + { + arg.push_back('\v'); + break; + } + case '\\': + { + arg.push_back('\\'); + break; + } + case '\'': + { + arg.push_back('\''); + break; + } + case 'e': // Not ANSI-C, but used by bash. + { + arg.push_back('\033'); + break; + } + + // + // Process up to three octal digits. + // + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + static const string octalDigits = "01234567"; + unsigned short us = 0; + string::size_type j; + for(j = i; + j < i + 3 && j < l.size() && octalDigits.find_first_of(c = l[j]) != string::npos; + ++j) + { + us = us * 8 + c - '0'; + } + i = j - 1; + arg.push_back(static_cast<char>(us)); + break; + } + + // + // Process up to two hex digits. + // + case 'x': + { + if(i < l.size() - 1 && !isxdigit(static_cast<unsigned char>(l[i + 1]))) + { + arg.push_back('\\'); + arg.push_back('x'); + break; + } + + Int64 ull = 0; + string::size_type j; + for(j = i + 1; j < i + 3 && j < l.size() && + isxdigit(static_cast<unsigned char>(c = l[j])); ++j) + { + ull *= 16; + if(isdigit(static_cast<unsigned char>(c))) + { + ull += c - '0'; + } + else if(islower(static_cast<unsigned char>(c))) + { + ull += c - 'a' + 10; + } + else + { + ull += c - 'A' + 10; + } + } + i = j - 1; + arg.push_back(static_cast<char>(ull)); + break; + } + + // + // Process control-chars. + // + case 'c': + { + c = l[++i]; + if(IceUtilInternal::isAlpha(c) || c == '@' || (c >= '[' && c <= '_')) + { + arg.push_back(static_cast<char>(toupper(static_cast<unsigned char>(c)) - '@')); + } + else + { + // + // Bash does not define what should happen if a \c + // is not followed by a recognized control character. + // We simply treat this case like other unrecognized + // escape sequences, that is, we preserve the escape + // sequence unchanged. + // + arg.push_back('\\'); + arg.push_back('c'); + arg.push_back(c); + } + break; + } + + // + // If inside an ANSI-quoted string, a backslash isn't followed by + // one of the recognized characters, both the backslash and the + // character are preserved. + // + default: + { + arg.push_back('\\'); + arg.push_back(c); + break; + } + } + break; + } + case '\'': // End of ANSI-quote mode. + { + state = Normal; + break; + } + default: + { + arg.push_back(c); // Everything else is taken literally. + break; + } + } + break; + } + default: + { + assert(!"Impossible parse state"); + break; + } + } + } + + switch(state) + { + case Normal: + { + vec.push_back(arg); + break; + } + case SingleQuote: + { + throw BadOptException(__FILE__, __LINE__, "missing closing single quote"); + break; + } + case DoubleQuote: + { + throw BadOptException(__FILE__, __LINE__, "missing closing double quote"); + break; + } + case ANSIQuote: + { + throw BadOptException(__FILE__, __LINE__, "unterminated $' quote"); + break; + } + default: + { + assert(!"Impossible parse state"); + break; + } + } + + return vec; +} + +// +// Parse a vector of arguments and return the non-option +// arguments as the return value. Throw BadOptException if any of the +// options are invalid. +// Note that args[0] is ignored because that is the name +// of the executable. +// + +IceUtilInternal::Options::StringVector +IceUtilInternal::Options::parse(const StringVector& args) +{ + RecMutex::Lock sync(_m); + + if(parseCalled) + { + throw APIException(__FILE__, __LINE__, "cannot call parse() more than once on the same Option instance"); + } + parseCalled = true; + + set<string> seenNonRepeatableOpts; // To catch repeated non-repeatable options. + + StringVector result; + + string::size_type i; + for(i = 1; i < args.size(); ++i) + { + if(args[i] == "-" || args[i] == "--") + { + ++i; + break; // "-" and "--" indicate end of options. + } + + string opt; + ValidOpts::iterator pos; + bool argDone = false; + + if(args[i].compare(0, 2, "--") == 0) + { + // + // Long option. If the option has an argument, it can either be separated by '=' + // or appear as a separate argument. For example, "--name value" is the same + // as "--name=value". + // + string::size_type p = args[i].find('=', 2); + if(p != string::npos) + { + opt = args[i].substr(2, p - 2); + } + else + { + opt = args[i].substr(2); + } + + pos = checkOpt(opt, LongOpt); + + if(pos->second->repeat == NoRepeat) + { + set<string>::iterator seenPos = seenNonRepeatableOpts.find(opt); + if(seenPos != seenNonRepeatableOpts.end()) + { + string err = "`--"; + err += opt + ":' option cannot be repeated"; + throw BadOptException(__FILE__, __LINE__, err); + } + seenNonRepeatableOpts.insert(seenPos, opt); + string synonym = getSynonym(opt); + if(!synonym.empty()) + { + seenNonRepeatableOpts.insert(synonym); + } + } + + if(p != string::npos) + { + if(pos->second->arg == NoArg && p != args[i].size() - 1) + { + string err = "`"; + err += args[i]; + err += "': option does not take an argument"; + throw BadOptException(__FILE__, __LINE__, err); + } + setOpt(opt, "", args[i].substr(p + 1), pos->second->repeat); + argDone = true; + } + } + else if(!args[i].empty() && args[i][0] == '-') + { + // + // Short option. + // + for(string::size_type p = 1; p < args[i].size(); ++p) + { + opt.clear(); + opt.push_back(args[i][p]); + pos = checkOpt(opt, ShortOpt); + + if(pos->second->repeat == NoRepeat) + { + set<string>::iterator seenPos = seenNonRepeatableOpts.find(opt); + if(seenPos != seenNonRepeatableOpts.end()) + { + string err = "`-"; + err += opt + ":' option cannot be repeated"; + throw BadOptException(__FILE__, __LINE__, err); + } + seenNonRepeatableOpts.insert(seenPos, opt); + string synonym = getSynonym(opt); + if(!synonym.empty()) + { + seenNonRepeatableOpts.insert(synonym); + } + } + + if(pos->second->arg == NeedArg && p != args[i].size() - 1) + { + string optArg = args[i].substr(p + 1); + setOpt(opt, "", optArg, pos->second->repeat); + argDone = true; + break; + } + } + } + else + { + // + // Not an option or option argument. + // + result.push_back(args[i]); + argDone = true; + } + + if(!argDone) + { + if(pos->second->arg == NeedArg) // Need an argument that is separated by whitespace. + { + if(i == args.size() - 1) + { + string err = "`-"; + if(opt.size() != 1) + { + err += "-"; + } + err += opt; + err += "' option requires an argument"; + throw BadOptException(__FILE__, __LINE__, err); + } + setOpt(opt, "", args[++i], pos->second->repeat); + } + else + { + setOpt(opt, "", "1", pos->second->repeat); + } + } + } + + _synonyms.clear(); // Don't need the contents anymore. + + while(i < args.size()) + { + result.push_back(args[i++]); + } + + return result; +} + +// +// Parse a normal argc/argv pair and return the non-option +// arguments as the return value. +// + +IceUtilInternal::Options::StringVector +IceUtilInternal::Options::parse(int argc, const char* const argv[]) +{ + StringVector vec; + for(int i = 0; i < argc; ++i) + { + vec.push_back(argv[i]); + } + return parse(vec); +} + +bool +IceUtilInternal::Options::isSet(const string& opt) const +{ + RecMutex::Lock sync(_m); + + if(!parseCalled) + { + throw APIException(__FILE__, __LINE__, "cannot lookup options before calling parse()"); + } + + ValidOpts::const_iterator pos = checkOptIsValid(opt); + return pos->second->repeat == NoRepeat ? _opts.find(opt) != _opts.end() : _ropts.find(opt) != _ropts.end(); +} + +string +IceUtilInternal::Options::optArg(const string& opt) const +{ + RecMutex::Lock sync(_m); + + if(!parseCalled) + { + throw APIException(__FILE__, __LINE__, "cannot lookup options before calling parse()"); + } + + ValidOpts::const_iterator pos = checkOptHasArg(opt); + + if(pos->second->repeat == Repeat) + { + string err = "`-"; + if(pos->second->length == LongOpt) + { + err.push_back('-'); + } + err += opt; + err += "': is a repeating option -- use argVec() to get its arguments"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + + Opts::const_iterator p = _opts.find(opt); + if(p == _opts.end()) + { + return ""; + } + return p->second->val; +} + +IceUtilInternal::Options::StringVector +IceUtilInternal::Options::argVec(const string& opt) const +{ + RecMutex::Lock sync(_m); + + if(!parseCalled) + { + throw APIException(__FILE__, __LINE__, "cannot lookup options before calling parse()"); + } + + ValidOpts::const_iterator pos = checkOptHasArg(opt); + + if(pos->second->repeat == NoRepeat) + { + string err = "`-"; + if(pos->second->length == LongOpt) + { + err.push_back('-'); + } + err += opt + "': is a non-repeating option -- use optArg() to get its argument"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + + ROpts::const_iterator p = _ropts.find(opt); + return p == _ropts.end() ? StringVector() : p->second->vals; +} + +void +IceUtilInternal::Options::addValidOpt(const string& shortOpt, const string& longOpt, + ArgType at, const string& dflt, RepeatType rt) +{ + if(!shortOpt.empty() && _validOpts.find(shortOpt) != _validOpts.end()) + { + string err = "`"; + err += shortOpt; + err += "': duplicate option"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + if(!longOpt.empty() && _validOpts.find(longOpt) != _validOpts.end()) + { + string err = "`"; + err += longOpt; + err += "': duplicate option"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + + ODPtr odp = new OptionDetails; + odp->arg = at; + odp->repeat = rt; + odp->hasDefault = !dflt.empty(); + + if(!shortOpt.empty()) + { + odp->length = ShortOpt; + _validOpts[shortOpt] = odp; + } + if(!longOpt.empty()) + { + odp->length = LongOpt; + _validOpts[longOpt] = odp; + } + + updateSynonyms(shortOpt, longOpt); + + if(at == NeedArg && !dflt.empty()) + { + setOpt(shortOpt, longOpt, dflt, rt); + } +} + +IceUtilInternal::Options::ValidOpts::iterator +IceUtilInternal::Options::checkOpt(const string& opt, LengthType lt) +{ + ValidOpts::iterator pos = _validOpts.find(opt); + if(pos == _validOpts.end()) + { + string err = "invalid option: `-"; + if(lt == LongOpt) + { + err.push_back('-'); + } + err += opt; + err.push_back('\''); + throw BadOptException(__FILE__, __LINE__, err); + } + return pos; +} + +void +IceUtilInternal::Options::setOpt(const string& opt1, const string& opt2, const string& val, RepeatType rt) +{ + // + // opt1 and opt2 (short and long opt) can't both be empty. + // + assert(!(opt1.empty() && opt2.empty())); + + if(rt == NoRepeat) + { + setNonRepeatingOpt(opt1, val); + setNonRepeatingOpt(opt2, val); + } + else + { + setRepeatingOpt(opt1, val); + setRepeatingOpt(opt2, val); + } +} + +void +IceUtilInternal::Options::setNonRepeatingOpt(const string& opt, const string& val) +{ + if(opt.empty()) + { + return; + } + + // + // The option must not have been set before or, if it was set, it must have + // been because of a default value. + // + assert(_opts.find(opt) == _opts.end() || _validOpts.find(opt)->second->hasDefault); + + OValPtr ovp = new OptionValue; + ovp->val = val; + _opts[opt] = ovp; + + const string synonym = getSynonym(opt); + if(!synonym.empty()) + { + _opts[synonym] = ovp; + } +} + +void +IceUtilInternal::Options::setRepeatingOpt(const string& opt, const string& val) +{ + if(opt.empty()) + { + return; + } + + ValidOpts::const_iterator vpos = _validOpts.find(opt); + assert(vpos != _validOpts.end()); + + ROpts::iterator pos = _ropts.find(opt); + const string synonym = getSynonym(opt); + ROpts::iterator spos = _ropts.find(synonym); + + if(pos != _ropts.end()) + { + assert(_validOpts.find(opt) != _validOpts.end()); + assert(vpos->second->repeat == Repeat); + + _ropts[opt] = pos->second; + if(vpos->second->hasDefault && pos->second->vals.size() == 1) + { + pos->second->vals[0] = val; + vpos->second->hasDefault = false; + } + else + { + pos->second->vals.push_back(val); + } + } + else if(spos != _ropts.end()) + { + assert(_validOpts.find(synonym) != _validOpts.end()); + assert(_validOpts.find(synonym)->second->repeat == Repeat); + + _ropts[synonym] = spos->second; + if(vpos->second->hasDefault && spos->second->vals.size() == 1) + { + spos->second->vals[0] = val; + vpos->second->hasDefault = false; + } + else + { + spos->second->vals.push_back(val); + } + } + else + { + OVecPtr ovp = new OptionValueVector; + ovp->vals.push_back(val); + _ropts[opt] = ovp; + if(!synonym.empty()) + { + _ropts[synonym] = ovp; + } + } +} + +IceUtilInternal::Options::ValidOpts::const_iterator +IceUtilInternal::Options::checkOptIsValid(const string& opt) const +{ + ValidOpts::const_iterator pos = _validOpts.find(opt); + if(pos == _validOpts.end()) + { + string err = "`"; + err += opt; + err += "': invalid option"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + return pos; +} + +IceUtilInternal::Options::ValidOpts::const_iterator +IceUtilInternal::Options::checkOptHasArg(const string& opt) const +{ + ValidOpts::const_iterator pos = checkOptIsValid(opt); + if(pos->second->arg == NoArg) + { + string err = "`-"; + if(pos->second->length == LongOpt) + { + err.push_back('-'); + } + err += opt; + err += "': option does not take arguments"; + throw IllegalArgumentException(__FILE__, __LINE__, err); + } + return pos; +} + +void +IceUtilInternal::Options::updateSynonyms(const ::std::string& shortOpt, const ::std::string& longOpt) +{ + if(!shortOpt.empty() && !longOpt.empty()) + { + _synonyms[shortOpt] = longOpt; + _synonyms[longOpt] = shortOpt; + } +} + +string +IceUtilInternal::Options::getSynonym(const ::std::string& optName) const +{ + Synonyms::const_iterator pos = _synonyms.find(optName); + return pos != _synonyms.end() ? pos->second : string(""); +} |