diff options
Diffstat (limited to 'indra/newview/llcommandlineparser.cpp')
-rw-r--r-- | indra/newview/llcommandlineparser.cpp | 1510 |
1 files changed, 755 insertions, 755 deletions
diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp index 62eec9fd5f..5b8d8b5480 100644 --- a/indra/newview/llcommandlineparser.cpp +++ b/indra/newview/llcommandlineparser.cpp @@ -1,755 +1,755 @@ -/**
- * @file llcommandlineparser.cpp
- * @brief The LLCommandLineParser class definitions
- *
- * $LicenseInfo:firstyear=2007&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-#include "llcommandlineparser.h"
-#include "llexception.h"
-
-// *NOTE: The boost::lexical_cast generates
-// the warning C4701(local used with out assignment) in VC7.1.
-// Disable the warning for the boost includes.
-#if _MSC_VER
-# pragma warning(push)
-# pragma warning( disable : 4701 )
-#else
-// NOTE: For the other platforms?
-#endif
-
-#include <boost/program_options.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/tokenizer.hpp>
-#include <boost/assign/list_of.hpp>
-
-#if _MSC_VER
-# pragma warning(pop)
-#endif
-
-#include "llsdserialize.h"
-#include "llerror.h"
-#include "stringize.h"
-#include "llexception.h"
-#include <string>
-#include <set>
-#include <iostream>
-#include <sstream>
-#include <typeinfo>
-
-#include "llcontrol.h"
-
-namespace po = boost::program_options;
-
-// *NOTE:MEP - Currently the boost object reside in file scope.
-// This has a couple of negatives, they are always around and
-// there can be only one instance of each.
-// The plus is that the boost-ly-ness of this implementation is
-// hidden from the rest of the world.
-// Its importatnt to realize that multiple LLCommandLineParser objects
-// will all have this single repository of option escs and parsed options.
-// This could be good or bad, and probably won't matter for most use cases.
-namespace
-{
- // List of command-line switches that can't map-to settings variables.
- // Going forward, we want every new command-line switch to map-to some
- // settings variable. This list is used to validate that.
- const std::set<std::string> unmapped_options = boost::assign::list_of
- ("help")
- ("set")
- ("setdefault")
- ("settings")
- ("sessionsettings")
- ("usersessionsettings")
- ;
-
- po::options_description gOptionsDesc;
- po::positional_options_description gPositionalOptions;
- po::variables_map gVariableMap;
-
- const LLCommandLineParser::token_vector_t gEmptyValue;
-
- void read_file_into_string(std::string& str, const std::basic_istream < char >& file)
- {
- std::ostringstream oss;
- oss << file.rdbuf();
- str = oss.str();
- }
-
- bool gPastLastOption = false;
-}
-
-class LLCLPError : public LLException {
-public:
- LLCLPError(const std::string& what) : LLException(what) {}
-};
-
-class LLCLPLastOption : public LLException {
-public:
- LLCLPLastOption(const std::string& what) : LLException(what) {}
-};
-
-class LLCLPValue : public po::value_semantic_codecvt_helper<char>
-{
- unsigned mMinTokens;
- unsigned mMaxTokens;
- bool mIsComposing;
- typedef boost::function1<void, const LLCommandLineParser::token_vector_t&> notify_callback_t;
- notify_callback_t mNotifyCallback;
- bool mLastOption;
-
-public:
- LLCLPValue() :
- mMinTokens(0),
- mMaxTokens(0),
- mIsComposing(false),
- mLastOption(false)
- {}
-
- virtual ~LLCLPValue() {};
-
- void setMinTokens(unsigned c)
- {
- mMinTokens = c;
- }
-
- void setMaxTokens(unsigned c)
- {
- mMaxTokens = c;
- }
-
- void setComposing(bool c)
- {
- mIsComposing = c;
- }
-
- void setLastOption(bool c)
- {
- mLastOption = c;
- }
-
- void setNotifyCallback(notify_callback_t f)
- {
- mNotifyCallback = f;
- }
-
- // Overrides to support the value_semantic interface.
- virtual std::string name() const
- {
- const std::string arg("arg");
- const std::string args("args");
- return (max_tokens() > 1) ? args : arg;
- }
-
- virtual unsigned min_tokens() const
- {
- return mMinTokens;
- }
-
- virtual unsigned max_tokens() const
- {
- return mMaxTokens;
- }
-
- virtual bool is_composing() const
- {
- return mIsComposing;
- }
-
- // Needed for boost 1.42
- virtual bool is_required() const
- {
- return false; // All our command line options are optional.
- }
-
- virtual bool apply_default(boost::any& value_store) const
- {
- return false; // No defaults.
- }
-
- virtual void notify(const boost::any& value_store) const
- {
- const LLCommandLineParser::token_vector_t* value =
- boost::any_cast<const LLCommandLineParser::token_vector_t>(&value_store);
- if(mNotifyCallback)
- {
- mNotifyCallback(*value);
- }
- }
-
-protected:
- void xparse(boost::any& value_store,
- const std::vector<std::string>& new_tokens) const
- {
- if(gPastLastOption)
- {
- LLTHROW(LLCLPLastOption("Don't parse no more!"));
- }
-
- // Error checks. Needed?
- if (!value_store.empty() && !is_composing())
- {
- LLTHROW(LLCLPError("Non composing value with multiple occurences."));
- }
- if (new_tokens.size() < min_tokens() || new_tokens.size() > max_tokens())
- {
- LLTHROW(LLCLPError("Illegal number of tokens specified."));
- }
-
- if(value_store.empty())
- {
- value_store = boost::any(LLCommandLineParser::token_vector_t());
- }
- LLCommandLineParser::token_vector_t* tv =
- boost::any_cast<LLCommandLineParser::token_vector_t>(&value_store);
-
- for(unsigned i = 0; i < new_tokens.size() && i < mMaxTokens; ++i)
- {
- tv->push_back(new_tokens[i]);
- }
-
- if(mLastOption)
- {
- gPastLastOption = true;
- }
- }
-};
-
-//----------------------------------------------------------------------------
-// LLCommandLineParser defintions
-//----------------------------------------------------------------------------
-void LLCommandLineParser::addOptionDesc(const std::string& option_name,
- boost::function1<void, const token_vector_t&> notify_callback,
- unsigned int token_count,
- const std::string& description,
- const std::string& short_name,
- bool composing,
- bool positional,
- bool last_option)
-{
- // Compose the name for boost::po.
- // It takes the format "long_name, short name"
- const std::string comma(",");
- std::string boost_option_name = option_name;
- if(short_name != LLStringUtil::null)
- {
- boost_option_name += comma;
- boost_option_name += short_name;
- }
-
- LLCLPValue* value_desc = new LLCLPValue();
- value_desc->setMinTokens(token_count);
- value_desc->setMaxTokens(token_count);
- value_desc->setComposing(composing);
- value_desc->setLastOption(last_option);
-
- boost::shared_ptr<po::option_description> d(
- new po::option_description(boost_option_name.c_str(),
- value_desc,
- description.c_str()));
-
- if(!notify_callback.empty())
- {
- value_desc->setNotifyCallback(notify_callback);
- }
-
- gOptionsDesc.add(d);
-
- if(positional)
- {
- gPositionalOptions.add(boost_option_name.c_str(), token_count);
- }
-}
-
-bool LLCommandLineParser::parseAndStoreResults(po::command_line_parser& clp)
-{
- try
- {
- clp.options(gOptionsDesc);
- clp.positional(gPositionalOptions);
- // SNOW-626: Boost 1.42 erroneously added allow_guessing to the default style
- // (see http://groups.google.com/group/boost-list/browse_thread/thread/545d7bf98ff9bb16?fwc=2&pli=1)
- // Remove allow_guessing from the default style, because that is not allowed
- // when we have options that are a prefix of other options (aka, --help and --helperuri).
- clp.style((po::command_line_style::default_style & ~po::command_line_style::allow_guessing)
- | po::command_line_style::allow_long_disguise);
- if(mExtraParser)
- {
- clp.extra_parser(mExtraParser);
- }
-
- po::basic_parsed_options<char> opts = clp.run();
- po::store(opts, gVariableMap);
- }
- catch(po::error& e)
- {
- LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL;
- mErrorMsg = e.what();
- return false;
- }
- catch(LLCLPError& e)
- {
- LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL;
- mErrorMsg = e.what();
- return false;
- }
- catch(LLCLPLastOption&)
- {
- // This exception means a token was read after an option
- // that must be the last option was reached (see url and slurl options)
-
- // boost::po will have stored a malformed option.
- // All such options will be removed below.
- // The last option read, the last_option option, and its value
- // are put into the error message.
- std::string last_option;
- std::string last_value;
- for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end();)
- {
- po::variables_map::iterator tempI = i++;
- if(tempI->second.empty())
- {
- gVariableMap.erase(tempI);
- }
- else
- {
- last_option = tempI->first;
- LLCommandLineParser::token_vector_t* tv =
- boost::any_cast<LLCommandLineParser::token_vector_t>(&(tempI->second.value()));
- if(!tv->empty())
- {
- last_value = (*tv)[tv->size()-1];
- }
- }
- }
-
- // Continue without parsing.
- std::ostringstream msg;
- msg << "Caught Error: Found options after last option: "
- << last_option << " "
- << last_value;
-
- LL_WARNS() << msg.str() << LL_ENDL;
- mErrorMsg = msg.str();
- return false;
- }
- return true;
-}
-
-bool LLCommandLineParser::parseCommandLine(int argc, char **argv)
-{
- po::command_line_parser clp(argc, argv);
- return parseAndStoreResults(clp);
-}
-
-// TODO:
-// - Break out this funky parsing logic into separate method
-// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line
-// overload that supports quoted tokens)
-// - Unless this logic offers significant semantic benefits, replace it with
-// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a
-// string-valued variable to the empty string, because empty strings are
-// eliminated below.
-
-bool LLCommandLineParser::parseCommandLineString(const std::string& str)
-{
- std::string cmd_line_string("");
- if (!str.empty())
- {
- bool add_last_c = true;
- S32 last_c_pos = str.size() - 1; //don't get out of bounds on pos+1, last char will be processed separately
- for (S32 pos = 0; pos < last_c_pos; ++pos)
- {
- cmd_line_string.append(&str[pos], 1);
- if (str[pos] == '\\')
- {
- cmd_line_string.append("\\", 1);
- if (str[pos + 1] == '\\')
- {
- ++pos;
- add_last_c = (pos != last_c_pos);
- }
- }
- }
- if (add_last_c)
- {
- cmd_line_string.append(&str[last_c_pos], 1);
- if (str[last_c_pos] == '\\')
- {
- cmd_line_string.append("\\", 1);
- }
- }
- }
-
- std::vector<std::string> tokens;
- try
- {
- // Split the string content into tokens
- const char* escape_chars = "\\";
- const char* separator_chars = "\r\n ";
- const char* quote_chars = "\"'";
- boost::escaped_list_separator<char> sep(escape_chars, separator_chars, quote_chars);
- boost::tokenizer< boost::escaped_list_separator<char> > tok(cmd_line_string, sep);
- // std::copy(tok.begin(), tok.end(), std::back_inserter(tokens));
- for (boost::tokenizer< boost::escaped_list_separator<char> >::iterator i = tok.begin();
- i != tok.end();
- ++i)
- {
- if (0 != i->size())
- {
- tokens.push_back(*i);
- }
- }
- }
- catch (...)
- {
- CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("Unexpected crash while parsing: " << str));
- }
-
- po::command_line_parser clp(tokens);
- return parseAndStoreResults(clp);
-
-}
-
-bool LLCommandLineParser::parseCommandLineFile(const std::basic_istream < char >& file)
-{
- std::string args;
- read_file_into_string(args, file);
-
- return parseCommandLineString(args);
-}
-
-bool LLCommandLineParser::notify()
-{
- try
- {
- po::notify(gVariableMap);
- return true;
- }
- catch (const LLCLPError& e)
- {
- LL_WARNS() << "Caught Error: " << e.what() << LL_ENDL;
- mErrorMsg = e.what();
- return false;
- }
-}
-
-void LLCommandLineParser::printOptions() const
-{
- for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end(); ++i)
- {
- std::string name = i->first;
- token_vector_t values = i->second.as<token_vector_t>();
- std::ostringstream oss;
- oss << name << ": ";
- for(token_vector_t::iterator t_itr = values.begin(); t_itr != values.end(); ++t_itr)
- {
- oss << t_itr->c_str() << " ";
- }
- LL_INFOS() << oss.str() << LL_ENDL;
- }
-}
-
-std::ostream& LLCommandLineParser::printOptionsDesc(std::ostream& os) const
-{
- return os << gOptionsDesc;
-}
-
-bool LLCommandLineParser::hasOption(const std::string& name) const
-{
- return gVariableMap.count(name) > 0;
-}
-
-const LLCommandLineParser::token_vector_t& LLCommandLineParser::getOption(const std::string& name) const
-{
- if(hasOption(name))
- {
- return gVariableMap[name].as<token_vector_t>();
- }
-
- return gEmptyValue;
-}
-
-//----------------------------------------------------------------------------
-// LLControlGroupCLP defintions
-//----------------------------------------------------------------------------
-namespace {
-LLCommandLineParser::token_vector_t::value_type
-onevalue(const std::string& option,
- const LLCommandLineParser::token_vector_t& value)
-{
- if (value.empty())
- {
- // What does it mean when the user specifies a command-line switch
- // that requires a value, but omits the value? Complain.
- LLTHROW(LLCLPError(STRINGIZE("No value specified for --" << option << "!")));
- }
- else if (value.size() > 1)
- {
- LL_WARNS() << "Ignoring extra tokens specified for --"
- << option << "." << LL_ENDL;
- }
- return value[0];
-}
-
-void badvalue(const std::string& option,
- const std::string& varname,
- const std::string& type,
- const std::string& value)
-{
- // If the user passes an unusable value for a command-line switch, it
- // seems like a really bad idea to just ignore it, even with a log
- // warning.
- LLTHROW(LLCLPError(STRINGIZE("Invalid value specified by command-line switch '" << option
- << "' for variable '" << varname << "' of type " << type
- << ": '" << value << "'")));
-}
-
-template <typename T>
-T convertTo(const std::string& option,
- const std::string& varname,
- const LLCommandLineParser::token_vector_t::value_type& value)
-{
- try
- {
- return boost::lexical_cast<T>(value);
- }
- catch (const boost::bad_lexical_cast&)
- {
- badvalue(option, varname, typeid(T).name(), value);
- // bogus return; compiler unaware that badvalue() won't return
- return T();
- }
-}
-
-void setControlValueCB(const LLCommandLineParser::token_vector_t& value,
- const std::string& option,
- LLControlVariable* ctrl)
-{
- // *FIX: Do semantic conversion here.
- // LLSD (ImplString) Is no good for doing string to type conversion for...
- // booleans
- // compound types
- // ?...
-
- if(NULL != ctrl)
- {
- switch(ctrl->type())
- {
- case TYPE_BOOLEAN:
- if (value.empty())
- {
- // Boolean-valued command-line switches are unusual. If you
- // simply specify the switch without an explicit value, we can
- // infer you mean 'true'.
- ctrl->setValue(LLSD(true), false);
- }
- else
- {
- // Only call onevalue() AFTER handling value.empty() case!
- std::string token(onevalue(option, value));
-
- // There's a token. check the string for true/false/1/0 etc.
- bool result = false;
- bool gotSet = LLStringUtil::convertToBOOL(token, result);
- if (gotSet)
- {
- ctrl->setValue(LLSD(result), false);
- }
- else
- {
- badvalue(option, ctrl->getName(), "bool", token);
- }
- }
- break;
-
- case TYPE_U32:
- {
- std::string token(onevalue(option, value));
- // To my surprise, for an unsigned target, lexical_cast() doesn't
- // complain about an input string such as "-17". In that case, you
- // get a very large positive result. So for U32, make sure there's
- // no minus sign!
- if (token.find('-') == std::string::npos)
- {
- ctrl->setValue(LLSD::Integer(convertTo<U32>(option, ctrl->getName(), token)),
- false);
- }
- else
- {
- badvalue(option, ctrl->getName(), "unsigned", token);
- }
- break;
- }
-
- case TYPE_S32:
- ctrl->setValue(convertTo<S32>(option, ctrl->getName(),
- onevalue(option, value)), false);
- break;
-
- case TYPE_F32:
- ctrl->setValue(convertTo<F32>(option, ctrl->getName(),
- onevalue(option, value)), false);
- break;
-
- // It appears that no one has yet tried to define a command-line
- // switch mapped to a settings variable of TYPE_VEC3, TYPE_VEC3D,
- // TYPE_RECT, TYPE_COL4, TYPE_COL3. Such types would certainly seem to
- // call for a bit of special handling here...
- default:
- {
- // For the default types, let llsd do the conversion.
- if(value.size() > 1 && ctrl->isType(TYPE_LLSD))
- {
- // Assume its an array...
- LLSD llsdArray;
- for(unsigned int i = 0; i < value.size(); ++i)
- {
- LLSD llsdValue;
- llsdValue.assign(LLSD::String(value[i]));
- llsdArray.set(i, llsdValue);
- }
-
- ctrl->setValue(llsdArray, false);
- }
- else
- {
- ctrl->setValue(onevalue(option, value), false);
- }
- }
- break;
- }
- }
- else
- {
- // This isn't anything a user can affect -- it's a misconfiguration on
- // the part of the coder. Rub the coder's nose in the problem right
- // away so even preliminary testing will surface it.
- LL_ERRS() << "Command Line option --" << option
- << " maps to unknown setting!" << LL_ENDL;
- }
-}
-} // anonymous namespace
-
-void LLControlGroupCLP::configure(const std::string& config_filename, LLControlGroup* controlGroup)
-{
- // This method reads the llsd based config file, and uses it to set
- // members of a control group.
- LLSD clpConfigLLSD;
-
- llifstream input_stream;
- input_stream.open(config_filename.c_str(), std::ios::in | std::ios::binary);
-
- if(input_stream.is_open())
- {
- LLSDSerialize::fromXML(clpConfigLLSD, input_stream);
- for(LLSD::map_iterator option_itr = clpConfigLLSD.beginMap();
- option_itr != clpConfigLLSD.endMap();
- ++option_itr)
- {
- LLSD::String long_name = option_itr->first;
- LLSD option_params = option_itr->second;
-
- std::string desc("n/a");
- if(option_params.has("desc"))
- {
- desc = option_params["desc"].asString();
- }
-
- std::string short_name = LLStringUtil::null;
- if(option_params.has("short"))
- {
- short_name = option_params["short"].asString();
- }
-
- unsigned int token_count = 0;
- if(option_params.has("count"))
- {
- token_count = option_params["count"].asInteger();
- }
-
- bool composing = false;
- if(option_params.has("compose"))
- {
- composing = option_params["compose"].asBoolean();
- }
-
- bool positional = false;
- if(option_params.has("positional"))
- {
- positional = option_params["positional"].asBoolean();
- }
-
- bool last_option = false;
- if(option_params.has("last_option"))
- {
- last_option = option_params["last_option"].asBoolean();
- }
-
- boost::function1<void, const token_vector_t&> callback;
- if (! option_params.has("map-to"))
- {
- // If this option isn't mapped to a settings variable, is it
- // one of the ones for which that's unreasonable, or did
- // someone carelessly add a new option? (Make all these
- // configuration errors fatal so a maintainer will catch them
- // right away.)
- std::set<std::string>::const_iterator found = unmapped_options.find(long_name);
- if (found == unmapped_options.end())
- {
- LL_ERRS() << "New command-line option " << long_name
- << " should map-to a variable in settings.xml" << LL_ENDL;
- }
- }
- else // option specifies map-to
- {
- std::string controlName = option_params["map-to"].asString();
- if (! controlGroup)
- {
- LL_ERRS() << "Must pass gSavedSettings to LLControlGroupCLP::configure() for "
- << long_name << " (map-to " << controlName << ")" << LL_ENDL;
- }
-
- LLControlVariable* ctrl = controlGroup->getControl(controlName);
- if (! ctrl)
- {
- LL_ERRS() << "Option " << long_name << " specifies map-to " << controlName
- << " which does not exist" << LL_ENDL;
- }
-
- callback = boost::bind(setControlValueCB, _1, long_name, ctrl);
- }
-
- this->addOptionDesc(
- long_name,
- callback,
- token_count,
- desc,
- short_name,
- composing,
- positional,
- last_option);
- }
- }
-}
+/** + * @file llcommandlineparser.cpp + * @brief The LLCommandLineParser class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcommandlineparser.h" +#include "llexception.h" + +// *NOTE: The boost::lexical_cast generates +// the warning C4701(local used with out assignment) in VC7.1. +// Disable the warning for the boost includes. +#if _MSC_VER +# pragma warning(push) +# pragma warning( disable : 4701 ) +#else +// NOTE: For the other platforms? +#endif + +#include <boost/program_options.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/tokenizer.hpp> +#include <boost/assign/list_of.hpp> + +#if _MSC_VER +# pragma warning(pop) +#endif + +#include "llsdserialize.h" +#include "llerror.h" +#include "stringize.h" +#include "llexception.h" +#include <string> +#include <set> +#include <iostream> +#include <sstream> +#include <typeinfo> + +#include "llcontrol.h" + +namespace po = boost::program_options; + +// *NOTE:MEP - Currently the boost object reside in file scope. +// This has a couple of negatives, they are always around and +// there can be only one instance of each. +// The plus is that the boost-ly-ness of this implementation is +// hidden from the rest of the world. +// Its importatnt to realize that multiple LLCommandLineParser objects +// will all have this single repository of option escs and parsed options. +// This could be good or bad, and probably won't matter for most use cases. +namespace +{ + // List of command-line switches that can't map-to settings variables. + // Going forward, we want every new command-line switch to map-to some + // settings variable. This list is used to validate that. + const std::set<std::string> unmapped_options = boost::assign::list_of + ("help") + ("set") + ("setdefault") + ("settings") + ("sessionsettings") + ("usersessionsettings") + ; + + po::options_description gOptionsDesc; + po::positional_options_description gPositionalOptions; + po::variables_map gVariableMap; + + const LLCommandLineParser::token_vector_t gEmptyValue; + + void read_file_into_string(std::string& str, const std::basic_istream < char >& file) + { + std::ostringstream oss; + oss << file.rdbuf(); + str = oss.str(); + } + + bool gPastLastOption = false; +} + +class LLCLPError : public LLException { +public: + LLCLPError(const std::string& what) : LLException(what) {} +}; + +class LLCLPLastOption : public LLException { +public: + LLCLPLastOption(const std::string& what) : LLException(what) {} +}; + +class LLCLPValue : public po::value_semantic_codecvt_helper<char> +{ + unsigned mMinTokens; + unsigned mMaxTokens; + bool mIsComposing; + typedef boost::function1<void, const LLCommandLineParser::token_vector_t&> notify_callback_t; + notify_callback_t mNotifyCallback; + bool mLastOption; + +public: + LLCLPValue() : + mMinTokens(0), + mMaxTokens(0), + mIsComposing(false), + mLastOption(false) + {} + + virtual ~LLCLPValue() {}; + + void setMinTokens(unsigned c) + { + mMinTokens = c; + } + + void setMaxTokens(unsigned c) + { + mMaxTokens = c; + } + + void setComposing(bool c) + { + mIsComposing = c; + } + + void setLastOption(bool c) + { + mLastOption = c; + } + + void setNotifyCallback(notify_callback_t f) + { + mNotifyCallback = f; + } + + // Overrides to support the value_semantic interface. + virtual std::string name() const + { + const std::string arg("arg"); + const std::string args("args"); + return (max_tokens() > 1) ? args : arg; + } + + virtual unsigned min_tokens() const + { + return mMinTokens; + } + + virtual unsigned max_tokens() const + { + return mMaxTokens; + } + + virtual bool is_composing() const + { + return mIsComposing; + } + + // Needed for boost 1.42 + virtual bool is_required() const + { + return false; // All our command line options are optional. + } + + virtual bool apply_default(boost::any& value_store) const + { + return false; // No defaults. + } + + virtual void notify(const boost::any& value_store) const + { + const LLCommandLineParser::token_vector_t* value = + boost::any_cast<const LLCommandLineParser::token_vector_t>(&value_store); + if(mNotifyCallback) + { + mNotifyCallback(*value); + } + } + +protected: + void xparse(boost::any& value_store, + const std::vector<std::string>& new_tokens) const + { + if(gPastLastOption) + { + LLTHROW(LLCLPLastOption("Don't parse no more!")); + } + + // Error checks. Needed? + if (!value_store.empty() && !is_composing()) + { + LLTHROW(LLCLPError("Non composing value with multiple occurences.")); + } + if (new_tokens.size() < min_tokens() || new_tokens.size() > max_tokens()) + { + LLTHROW(LLCLPError("Illegal number of tokens specified.")); + } + + if(value_store.empty()) + { + value_store = boost::any(LLCommandLineParser::token_vector_t()); + } + LLCommandLineParser::token_vector_t* tv = + boost::any_cast<LLCommandLineParser::token_vector_t>(&value_store); + + for(unsigned i = 0; i < new_tokens.size() && i < mMaxTokens; ++i) + { + tv->push_back(new_tokens[i]); + } + + if(mLastOption) + { + gPastLastOption = true; + } + } +}; + +//---------------------------------------------------------------------------- +// LLCommandLineParser defintions +//---------------------------------------------------------------------------- +void LLCommandLineParser::addOptionDesc(const std::string& option_name, + boost::function1<void, const token_vector_t&> notify_callback, + unsigned int token_count, + const std::string& description, + const std::string& short_name, + bool composing, + bool positional, + bool last_option) +{ + // Compose the name for boost::po. + // It takes the format "long_name, short name" + const std::string comma(","); + std::string boost_option_name = option_name; + if(short_name != LLStringUtil::null) + { + boost_option_name += comma; + boost_option_name += short_name; + } + + LLCLPValue* value_desc = new LLCLPValue(); + value_desc->setMinTokens(token_count); + value_desc->setMaxTokens(token_count); + value_desc->setComposing(composing); + value_desc->setLastOption(last_option); + + boost::shared_ptr<po::option_description> d( + new po::option_description(boost_option_name.c_str(), + value_desc, + description.c_str())); + + if(!notify_callback.empty()) + { + value_desc->setNotifyCallback(notify_callback); + } + + gOptionsDesc.add(d); + + if(positional) + { + gPositionalOptions.add(boost_option_name.c_str(), token_count); + } +} + +bool LLCommandLineParser::parseAndStoreResults(po::command_line_parser& clp) +{ + try + { + clp.options(gOptionsDesc); + clp.positional(gPositionalOptions); + // SNOW-626: Boost 1.42 erroneously added allow_guessing to the default style + // (see http://groups.google.com/group/boost-list/browse_thread/thread/545d7bf98ff9bb16?fwc=2&pli=1) + // Remove allow_guessing from the default style, because that is not allowed + // when we have options that are a prefix of other options (aka, --help and --helperuri). + clp.style((po::command_line_style::default_style & ~po::command_line_style::allow_guessing) + | po::command_line_style::allow_long_disguise); + if(mExtraParser) + { + clp.extra_parser(mExtraParser); + } + + po::basic_parsed_options<char> opts = clp.run(); + po::store(opts, gVariableMap); + } + catch(po::error& e) + { + LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } + catch(LLCLPError& e) + { + LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } + catch(LLCLPLastOption&) + { + // This exception means a token was read after an option + // that must be the last option was reached (see url and slurl options) + + // boost::po will have stored a malformed option. + // All such options will be removed below. + // The last option read, the last_option option, and its value + // are put into the error message. + std::string last_option; + std::string last_value; + for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end();) + { + po::variables_map::iterator tempI = i++; + if(tempI->second.empty()) + { + gVariableMap.erase(tempI); + } + else + { + last_option = tempI->first; + LLCommandLineParser::token_vector_t* tv = + boost::any_cast<LLCommandLineParser::token_vector_t>(&(tempI->second.value())); + if(!tv->empty()) + { + last_value = (*tv)[tv->size()-1]; + } + } + } + + // Continue without parsing. + std::ostringstream msg; + msg << "Caught Error: Found options after last option: " + << last_option << " " + << last_value; + + LL_WARNS() << msg.str() << LL_ENDL; + mErrorMsg = msg.str(); + return false; + } + return true; +} + +bool LLCommandLineParser::parseCommandLine(int argc, char **argv) +{ + po::command_line_parser clp(argc, argv); + return parseAndStoreResults(clp); +} + +// TODO: +// - Break out this funky parsing logic into separate method +// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line +// overload that supports quoted tokens) +// - Unless this logic offers significant semantic benefits, replace it with +// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a +// string-valued variable to the empty string, because empty strings are +// eliminated below. + +bool LLCommandLineParser::parseCommandLineString(const std::string& str) +{ + std::string cmd_line_string(""); + if (!str.empty()) + { + bool add_last_c = true; + S32 last_c_pos = str.size() - 1; //don't get out of bounds on pos+1, last char will be processed separately + for (S32 pos = 0; pos < last_c_pos; ++pos) + { + cmd_line_string.append(&str[pos], 1); + if (str[pos] == '\\') + { + cmd_line_string.append("\\", 1); + if (str[pos + 1] == '\\') + { + ++pos; + add_last_c = (pos != last_c_pos); + } + } + } + if (add_last_c) + { + cmd_line_string.append(&str[last_c_pos], 1); + if (str[last_c_pos] == '\\') + { + cmd_line_string.append("\\", 1); + } + } + } + + std::vector<std::string> tokens; + try + { + // Split the string content into tokens + const char* escape_chars = "\\"; + const char* separator_chars = "\r\n "; + const char* quote_chars = "\"'"; + boost::escaped_list_separator<char> sep(escape_chars, separator_chars, quote_chars); + boost::tokenizer< boost::escaped_list_separator<char> > tok(cmd_line_string, sep); + // std::copy(tok.begin(), tok.end(), std::back_inserter(tokens)); + for (boost::tokenizer< boost::escaped_list_separator<char> >::iterator i = tok.begin(); + i != tok.end(); + ++i) + { + if (0 != i->size()) + { + tokens.push_back(*i); + } + } + } + catch (...) + { + CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("Unexpected crash while parsing: " << str)); + } + + po::command_line_parser clp(tokens); + return parseAndStoreResults(clp); + +} + +bool LLCommandLineParser::parseCommandLineFile(const std::basic_istream < char >& file) +{ + std::string args; + read_file_into_string(args, file); + + return parseCommandLineString(args); +} + +bool LLCommandLineParser::notify() +{ + try + { + po::notify(gVariableMap); + return true; + } + catch (const LLCLPError& e) + { + LL_WARNS() << "Caught Error: " << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } +} + +void LLCommandLineParser::printOptions() const +{ + for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end(); ++i) + { + std::string name = i->first; + token_vector_t values = i->second.as<token_vector_t>(); + std::ostringstream oss; + oss << name << ": "; + for(token_vector_t::iterator t_itr = values.begin(); t_itr != values.end(); ++t_itr) + { + oss << t_itr->c_str() << " "; + } + LL_INFOS() << oss.str() << LL_ENDL; + } +} + +std::ostream& LLCommandLineParser::printOptionsDesc(std::ostream& os) const +{ + return os << gOptionsDesc; +} + +bool LLCommandLineParser::hasOption(const std::string& name) const +{ + return gVariableMap.count(name) > 0; +} + +const LLCommandLineParser::token_vector_t& LLCommandLineParser::getOption(const std::string& name) const +{ + if(hasOption(name)) + { + return gVariableMap[name].as<token_vector_t>(); + } + + return gEmptyValue; +} + +//---------------------------------------------------------------------------- +// LLControlGroupCLP defintions +//---------------------------------------------------------------------------- +namespace { +LLCommandLineParser::token_vector_t::value_type +onevalue(const std::string& option, + const LLCommandLineParser::token_vector_t& value) +{ + if (value.empty()) + { + // What does it mean when the user specifies a command-line switch + // that requires a value, but omits the value? Complain. + LLTHROW(LLCLPError(STRINGIZE("No value specified for --" << option << "!"))); + } + else if (value.size() > 1) + { + LL_WARNS() << "Ignoring extra tokens specified for --" + << option << "." << LL_ENDL; + } + return value[0]; +} + +void badvalue(const std::string& option, + const std::string& varname, + const std::string& type, + const std::string& value) +{ + // If the user passes an unusable value for a command-line switch, it + // seems like a really bad idea to just ignore it, even with a log + // warning. + LLTHROW(LLCLPError(STRINGIZE("Invalid value specified by command-line switch '" << option + << "' for variable '" << varname << "' of type " << type + << ": '" << value << "'"))); +} + +template <typename T> +T convertTo(const std::string& option, + const std::string& varname, + const LLCommandLineParser::token_vector_t::value_type& value) +{ + try + { + return boost::lexical_cast<T>(value); + } + catch (const boost::bad_lexical_cast&) + { + badvalue(option, varname, typeid(T).name(), value); + // bogus return; compiler unaware that badvalue() won't return + return T(); + } +} + +void setControlValueCB(const LLCommandLineParser::token_vector_t& value, + const std::string& option, + LLControlVariable* ctrl) +{ + // *FIX: Do semantic conversion here. + // LLSD (ImplString) Is no good for doing string to type conversion for... + // booleans + // compound types + // ?... + + if(NULL != ctrl) + { + switch(ctrl->type()) + { + case TYPE_BOOLEAN: + if (value.empty()) + { + // Boolean-valued command-line switches are unusual. If you + // simply specify the switch without an explicit value, we can + // infer you mean 'true'. + ctrl->setValue(LLSD(true), false); + } + else + { + // Only call onevalue() AFTER handling value.empty() case! + std::string token(onevalue(option, value)); + + // There's a token. check the string for true/false/1/0 etc. + bool result = false; + bool gotSet = LLStringUtil::convertToBOOL(token, result); + if (gotSet) + { + ctrl->setValue(LLSD(result), false); + } + else + { + badvalue(option, ctrl->getName(), "bool", token); + } + } + break; + + case TYPE_U32: + { + std::string token(onevalue(option, value)); + // To my surprise, for an unsigned target, lexical_cast() doesn't + // complain about an input string such as "-17". In that case, you + // get a very large positive result. So for U32, make sure there's + // no minus sign! + if (token.find('-') == std::string::npos) + { + ctrl->setValue(LLSD::Integer(convertTo<U32>(option, ctrl->getName(), token)), + false); + } + else + { + badvalue(option, ctrl->getName(), "unsigned", token); + } + break; + } + + case TYPE_S32: + ctrl->setValue(convertTo<S32>(option, ctrl->getName(), + onevalue(option, value)), false); + break; + + case TYPE_F32: + ctrl->setValue(convertTo<F32>(option, ctrl->getName(), + onevalue(option, value)), false); + break; + + // It appears that no one has yet tried to define a command-line + // switch mapped to a settings variable of TYPE_VEC3, TYPE_VEC3D, + // TYPE_RECT, TYPE_COL4, TYPE_COL3. Such types would certainly seem to + // call for a bit of special handling here... + default: + { + // For the default types, let llsd do the conversion. + if(value.size() > 1 && ctrl->isType(TYPE_LLSD)) + { + // Assume its an array... + LLSD llsdArray; + for(unsigned int i = 0; i < value.size(); ++i) + { + LLSD llsdValue; + llsdValue.assign(LLSD::String(value[i])); + llsdArray.set(i, llsdValue); + } + + ctrl->setValue(llsdArray, false); + } + else + { + ctrl->setValue(onevalue(option, value), false); + } + } + break; + } + } + else + { + // This isn't anything a user can affect -- it's a misconfiguration on + // the part of the coder. Rub the coder's nose in the problem right + // away so even preliminary testing will surface it. + LL_ERRS() << "Command Line option --" << option + << " maps to unknown setting!" << LL_ENDL; + } +} +} // anonymous namespace + +void LLControlGroupCLP::configure(const std::string& config_filename, LLControlGroup* controlGroup) +{ + // This method reads the llsd based config file, and uses it to set + // members of a control group. + LLSD clpConfigLLSD; + + llifstream input_stream; + input_stream.open(config_filename.c_str(), std::ios::in | std::ios::binary); + + if(input_stream.is_open()) + { + LLSDSerialize::fromXML(clpConfigLLSD, input_stream); + for(LLSD::map_iterator option_itr = clpConfigLLSD.beginMap(); + option_itr != clpConfigLLSD.endMap(); + ++option_itr) + { + LLSD::String long_name = option_itr->first; + LLSD option_params = option_itr->second; + + std::string desc("n/a"); + if(option_params.has("desc")) + { + desc = option_params["desc"].asString(); + } + + std::string short_name = LLStringUtil::null; + if(option_params.has("short")) + { + short_name = option_params["short"].asString(); + } + + unsigned int token_count = 0; + if(option_params.has("count")) + { + token_count = option_params["count"].asInteger(); + } + + bool composing = false; + if(option_params.has("compose")) + { + composing = option_params["compose"].asBoolean(); + } + + bool positional = false; + if(option_params.has("positional")) + { + positional = option_params["positional"].asBoolean(); + } + + bool last_option = false; + if(option_params.has("last_option")) + { + last_option = option_params["last_option"].asBoolean(); + } + + boost::function1<void, const token_vector_t&> callback; + if (! option_params.has("map-to")) + { + // If this option isn't mapped to a settings variable, is it + // one of the ones for which that's unreasonable, or did + // someone carelessly add a new option? (Make all these + // configuration errors fatal so a maintainer will catch them + // right away.) + std::set<std::string>::const_iterator found = unmapped_options.find(long_name); + if (found == unmapped_options.end()) + { + LL_ERRS() << "New command-line option " << long_name + << " should map-to a variable in settings.xml" << LL_ENDL; + } + } + else // option specifies map-to + { + std::string controlName = option_params["map-to"].asString(); + if (! controlGroup) + { + LL_ERRS() << "Must pass gSavedSettings to LLControlGroupCLP::configure() for " + << long_name << " (map-to " << controlName << ")" << LL_ENDL; + } + + LLControlVariable* ctrl = controlGroup->getControl(controlName); + if (! ctrl) + { + LL_ERRS() << "Option " << long_name << " specifies map-to " << controlName + << " which does not exist" << LL_ENDL; + } + + callback = boost::bind(setControlValueCB, _1, long_name, ctrl); + } + + this->addOptionDesc( + long_name, + callback, + token_count, + desc, + short_name, + composing, + positional, + last_option); + } + } +} |