PageRenderTime 44ms CodeModel.GetById 8ms app.highlight 30ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llcommandlineparser.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 573 lines | 470 code | 46 blank | 57 comment | 19 complexity | 7b571d9a7c98b6d3413123b090f0151c MD5 | raw file
  1/**
  2 * @file llcommandlineparser.cpp
  3 * @brief The LLCommandLineParser class definitions
  4 *
  5 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
  6 * Second Life Viewer Source Code
  7 * Copyright (C) 2010, Linden Research, Inc.
  8 * 
  9 * This library is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU Lesser General Public
 11 * License as published by the Free Software Foundation;
 12 * version 2.1 of the License only.
 13 * 
 14 * This library is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 17 * Lesser General Public License for more details.
 18 * 
 19 * You should have received a copy of the GNU Lesser General Public
 20 * License along with this library; if not, write to the Free Software
 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 22 * 
 23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 24 * $/LicenseInfo$
 25 */ 
 26
 27#include "llviewerprecompiledheaders.h"
 28#include "llcommandlineparser.h"
 29
 30// *NOTE: The boost::lexical_cast generates 
 31// the warning C4701(local used with out assignment) in VC7.1.
 32// Disable the warning for the boost includes.
 33#if _MSC_VER
 34#   pragma warning(push)
 35#   pragma warning( disable : 4701 )
 36#else
 37// NOTE: For the other platforms?
 38#endif
 39
 40#include <boost/program_options.hpp>
 41#include <boost/bind.hpp>
 42#include<boost/tokenizer.hpp>
 43
 44#if _MSC_VER
 45#   pragma warning(pop)
 46#endif
 47
 48#include "llsdserialize.h"
 49#include <iostream>
 50#include <sstream>
 51
 52#include "llcontrol.h"
 53
 54namespace po = boost::program_options;
 55
 56// *NOTE:MEP - Currently the boost object reside in file scope.
 57// This has a couple of negatives, they are always around and 
 58// there can be only one instance of each. 
 59// The plus is that the boost-ly-ness of this implementation is 
 60// hidden from the rest of the world. 
 61// Its importatnt to realize that multiple LLCommandLineParser objects 
 62// will all have this single repository of option escs and parsed options.
 63// This could be good or bad, and probably won't matter for most use cases.
 64namespace 
 65{
 66    po::options_description gOptionsDesc;
 67    po::positional_options_description gPositionalOptions;
 68	po::variables_map gVariableMap;
 69    
 70    const LLCommandLineParser::token_vector_t gEmptyValue;
 71
 72    void read_file_into_string(std::string& str, const std::basic_istream < char >& file)
 73    {
 74	    std::ostringstream oss;
 75	    oss << file.rdbuf();
 76	    str = oss.str();
 77    }
 78
 79    bool gPastLastOption = false;
 80}
 81
 82class LLCLPError : public std::logic_error {
 83public:
 84    LLCLPError(const std::string& what) : std::logic_error(what) {}
 85};
 86
 87class LLCLPLastOption : public std::logic_error {
 88public:
 89    LLCLPLastOption(const std::string& what) : std::logic_error(what) {}
 90};
 91
 92class LLCLPValue : public po::value_semantic_codecvt_helper<char> 
 93{
 94    unsigned mMinTokens;
 95    unsigned mMaxTokens;
 96    bool mIsComposing;
 97    typedef boost::function1<void, const LLCommandLineParser::token_vector_t&> notify_callback_t;
 98    notify_callback_t mNotifyCallback;
 99    bool mLastOption;
100
101public:
102    LLCLPValue() :
103        mMinTokens(0),
104        mMaxTokens(0),
105        mIsComposing(false),
106        mLastOption(false)
107        {}
108      
109    virtual ~LLCLPValue() {};
110
111    void setMinTokens(unsigned c) 
112    {
113        mMinTokens = c;
114    }
115
116    void setMaxTokens(unsigned c) 
117    {
118        mMaxTokens = c;
119    }
120
121    void setComposing(bool c)
122    {
123        mIsComposing = c;
124    }
125
126    void setLastOption(bool c)
127    {
128        mLastOption = c;
129    }
130
131    void setNotifyCallback(notify_callback_t f)
132    {
133        mNotifyCallback = f;
134    }
135
136    // Overrides to support the value_semantic interface.
137    virtual std::string name() const 
138    { 
139        const std::string arg("arg");
140        const std::string args("args");
141        return (max_tokens() > 1) ? args : arg; 
142    }
143
144    virtual unsigned min_tokens() const
145    {
146        return mMinTokens;
147    }
148
149    virtual unsigned max_tokens() const 
150    {
151        return mMaxTokens;
152    }
153
154    virtual bool is_composing() const 
155    {
156        return mIsComposing;
157    }
158
159	// Needed for boost 1.42
160	virtual bool is_required() const
161	{
162		return false; // All our command line options are optional.
163	}
164
165    virtual bool apply_default(boost::any& value_store) const
166    {
167        return false; // No defaults.
168    }
169
170    virtual void notify(const boost::any& value_store) const
171    {
172        const LLCommandLineParser::token_vector_t* value =
173            boost::any_cast<const LLCommandLineParser::token_vector_t>(&value_store);
174        if(mNotifyCallback) 
175        {
176           mNotifyCallback(*value);
177        }
178    }
179
180protected:
181    void xparse(boost::any& value_store,
182         const std::vector<std::string>& new_tokens) const
183    {
184        if(gPastLastOption)
185        {
186            throw(LLCLPLastOption("Don't parse no more!"));
187        }
188
189        // Error checks. Needed?
190        if (!value_store.empty() && !is_composing()) 
191        {
192            throw(LLCLPError("Non composing value with multiple occurences."));
193        }
194        if (new_tokens.size() < min_tokens() || new_tokens.size() > max_tokens())
195        {
196            throw(LLCLPError("Illegal number of tokens specified."));
197        }
198        
199        if(value_store.empty())
200        {
201            value_store = boost::any(LLCommandLineParser::token_vector_t());
202        }
203        LLCommandLineParser::token_vector_t* tv = 
204            boost::any_cast<LLCommandLineParser::token_vector_t>(&value_store); 
205       
206        for(unsigned i = 0; i < new_tokens.size() && i < mMaxTokens; ++i)
207        {
208            tv->push_back(new_tokens[i]);
209        }
210
211        if(mLastOption)
212        {
213            gPastLastOption = true;
214        }
215    }
216};
217
218//----------------------------------------------------------------------------
219// LLCommandLineParser defintions
220//----------------------------------------------------------------------------
221void LLCommandLineParser::addOptionDesc(const std::string& option_name, 
222                                        boost::function1<void, const token_vector_t&> notify_callback,
223                                        unsigned int token_count,
224                                        const std::string& description,
225                                        const std::string& short_name,
226                                        bool composing,
227                                        bool positional,
228                                        bool last_option)
229{
230    // Compose the name for boost::po. 
231    // It takes the format "long_name, short name"
232    const std::string comma(",");
233    std::string boost_option_name = option_name;
234    if(short_name != LLStringUtil::null)
235    {
236        boost_option_name += comma;
237        boost_option_name += short_name;
238    }
239   
240    LLCLPValue* value_desc = new LLCLPValue();
241    value_desc->setMinTokens(token_count);
242    value_desc->setMaxTokens(token_count);
243    value_desc->setComposing(composing);
244    value_desc->setLastOption(last_option);
245
246    boost::shared_ptr<po::option_description> d(
247            new po::option_description(boost_option_name.c_str(), 
248                                    value_desc, 
249                                    description.c_str()));
250
251    if(!notify_callback.empty())
252    {
253        value_desc->setNotifyCallback(notify_callback);
254    }
255
256    gOptionsDesc.add(d);
257
258    if(positional)
259    {
260        gPositionalOptions.add(boost_option_name.c_str(), token_count);
261    }
262}
263
264bool LLCommandLineParser::parseAndStoreResults(po::command_line_parser& clp)
265{
266    try
267    {
268        clp.options(gOptionsDesc);
269        clp.positional(gPositionalOptions);
270		// SNOW-626: Boost 1.42 erroneously added allow_guessing to the default style
271		// (see http://groups.google.com/group/boost-list/browse_thread/thread/545d7bf98ff9bb16?fwc=2&pli=1)
272		// Remove allow_guessing from the default style, because that is not allowed
273		// when we have options that are a prefix of other options (aka, --help and --helperuri).
274        clp.style((po::command_line_style::default_style & ~po::command_line_style::allow_guessing)
275                  | po::command_line_style::allow_long_disguise);
276		if(mExtraParser)
277		{
278			clp.extra_parser(mExtraParser);
279		}
280			
281        po::basic_parsed_options<char> opts = clp.run();
282        po::store(opts, gVariableMap);
283    }
284    catch(po::error& e)
285    {
286        llwarns << "Caught Error:" << e.what() << llendl;
287		mErrorMsg = e.what();
288        return false;
289    }
290    catch(LLCLPError& e)
291    {
292        llwarns << "Caught Error:" << e.what() << llendl;
293		mErrorMsg = e.what();
294        return false;
295    }
296    catch(LLCLPLastOption&) 
297    {
298		// This exception means a token was read after an option 
299		// that must be the last option was reached (see url and slurl options)
300
301        // boost::po will have stored a malformed option. 
302        // All such options will be removed below.
303		// The last option read, the last_option option, and its value
304		// are put into the error message.
305		std::string last_option;
306		std::string last_value;
307        for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end();)
308        {
309            po::variables_map::iterator tempI = i++;
310            if(tempI->second.empty())
311            {
312                gVariableMap.erase(tempI);
313            }
314			else
315			{
316				last_option = tempI->first;
317		        LLCommandLineParser::token_vector_t* tv = 
318				    boost::any_cast<LLCommandLineParser::token_vector_t>(&(tempI->second.value())); 
319				if(!tv->empty())
320				{
321					last_value = (*tv)[tv->size()-1];
322				}
323			}
324        }
325
326		// Continue without parsing.
327		std::ostringstream msg;
328		msg << "Caught Error: Found options after last option: " 
329			<< last_option << " "
330			<< last_value;
331
332        llwarns << msg.str() << llendl;
333		mErrorMsg = msg.str();
334        return false;
335    } 
336    return true;
337}
338
339bool LLCommandLineParser::parseCommandLine(int argc, char **argv)
340{
341    po::command_line_parser clp(argc, argv);
342    return parseAndStoreResults(clp);
343}
344
345bool LLCommandLineParser::parseCommandLineString(const std::string& str)
346{
347    // Split the string content into tokens
348	const char* escape_chars = "\\";
349	const char* separator_chars = "\r\n ";
350	const char* quote_chars = "\"'";
351    boost::escaped_list_separator<char> sep(escape_chars, separator_chars, quote_chars);
352    boost::tokenizer< boost::escaped_list_separator<char> > tok(str, sep);
353    std::vector<std::string> tokens;
354    // std::copy(tok.begin(), tok.end(), std::back_inserter(tokens));
355    for(boost::tokenizer< boost::escaped_list_separator<char> >::iterator i = tok.begin();
356        i != tok.end();
357        ++i)
358    {
359        if(0 != i->size())
360        {
361            tokens.push_back(*i);
362        }
363    }
364
365    po::command_line_parser clp(tokens);
366    return parseAndStoreResults(clp);
367        
368}
369
370bool LLCommandLineParser::parseCommandLineFile(const std::basic_istream < char >& file)
371{
372    std::string args;
373    read_file_into_string(args, file);
374
375    return parseCommandLineString(args);
376}
377
378void LLCommandLineParser::notify()
379{
380    po::notify(gVariableMap);    
381}
382
383void LLCommandLineParser::printOptions() const
384{
385    for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end(); ++i)
386    {
387        std::string name = i->first;
388        token_vector_t values = i->second.as<token_vector_t>();
389        std::ostringstream oss;
390        oss << name << ": ";
391        for(token_vector_t::iterator t_itr = values.begin(); t_itr != values.end(); ++t_itr)
392        {
393            oss << t_itr->c_str() << " ";
394        }
395        llinfos << oss.str() << llendl;
396    }
397}
398
399std::ostream& LLCommandLineParser::printOptionsDesc(std::ostream& os) const
400{
401    return os << gOptionsDesc;
402}
403
404bool LLCommandLineParser::hasOption(const std::string& name) const
405{
406    return gVariableMap.count(name) > 0;
407}
408
409const LLCommandLineParser::token_vector_t& LLCommandLineParser::getOption(const std::string& name) const
410{
411    if(hasOption(name))
412    {
413        return gVariableMap[name].as<token_vector_t>();
414    }
415
416    return gEmptyValue;
417}
418
419//----------------------------------------------------------------------------
420// LLControlGroupCLP defintions
421//----------------------------------------------------------------------------
422void setControlValueCB(const LLCommandLineParser::token_vector_t& value, 
423                       const std::string& opt_name, 
424                       LLControlGroup* ctrlGroup)
425{
426    // *FIX: Do sematic conversion here.
427    // LLSD (ImplString) Is no good for doing string to type conversion for...
428    // booleans
429    // compound types
430    // ?...
431
432    LLControlVariable* ctrl = ctrlGroup->getControl(opt_name);
433    if(NULL != ctrl)
434    {
435        switch(ctrl->type())
436        {
437        case TYPE_BOOLEAN:
438            if(value.size() > 1)
439            {
440                llwarns << "Ignoring extra tokens." << llendl; 
441            }
442              
443            if(value.size() > 0)
444            {
445                // There's a token. check the string for true/false/1/0 etc.
446                BOOL result = false;
447                BOOL gotSet = LLStringUtil::convertToBOOL(value[0], result);
448                if(gotSet)
449                {
450                    ctrl->setValue(LLSD(result), false);
451                }
452            }
453            else
454            {
455                ctrl->setValue(LLSD(true), false);
456            }
457            break;
458
459        default:
460            {
461                // For the default types, let llsd do the conversion.
462                if(value.size() > 1 && ctrl->isType(TYPE_LLSD))
463                {
464                    // Assume its an array...
465                    LLSD llsdArray;
466                    for(unsigned int i = 0; i < value.size(); ++i)
467                    {
468                        LLSD llsdValue;
469                        llsdValue.assign(LLSD::String(value[i]));
470                        llsdArray.set(i, llsdValue);
471                    }
472
473                    ctrl->setValue(llsdArray, false);
474                }
475                else if(value.size() > 0)
476                {
477					if(value.size() > 1)
478					{
479						llwarns << "Ignoring extra tokens mapped to the setting: " << opt_name << "." << llendl; 
480					}
481
482                    LLSD llsdValue;
483                    llsdValue.assign(LLSD::String(value[0]));
484                    ctrl->setValue(llsdValue, false);
485                }
486            }
487            break;
488        }
489    }
490    else
491    {
492        llwarns << "Command Line option mapping '" 
493            << opt_name 
494            << "' not found! Ignoring." 
495            << llendl;
496    }
497}
498
499void LLControlGroupCLP::configure(const std::string& config_filename, LLControlGroup* controlGroup)
500{
501    // This method reads the llsd based config file, and uses it to set 
502    // members of a control group.
503    LLSD clpConfigLLSD;
504    
505    llifstream input_stream;
506    input_stream.open(config_filename, std::ios::in | std::ios::binary);
507
508    if(input_stream.is_open())
509    {
510        LLSDSerialize::fromXML(clpConfigLLSD, input_stream);
511        for(LLSD::map_iterator option_itr = clpConfigLLSD.beginMap(); 
512            option_itr != clpConfigLLSD.endMap(); 
513            ++option_itr)
514        {
515            LLSD::String long_name = option_itr->first;
516            LLSD option_params = option_itr->second;
517            
518            std::string desc("n/a");
519            if(option_params.has("desc"))
520            {
521                desc = option_params["desc"].asString();
522            }
523            
524            std::string short_name = LLStringUtil::null;
525            if(option_params.has("short"))
526            {
527                short_name = option_params["short"].asString();
528            }
529
530            unsigned int token_count = 0;
531            if(option_params.has("count"))
532            {
533                token_count = option_params["count"].asInteger();
534            }
535
536            bool composing = false;
537            if(option_params.has("compose"))
538            {
539                composing = option_params["compose"].asBoolean();
540            }
541
542            bool positional = false;
543            if(option_params.has("positional"))
544            {
545                positional = option_params["positional"].asBoolean();
546            }
547
548            bool last_option = false;
549            if(option_params.has("last_option"))
550            {
551                last_option = option_params["last_option"].asBoolean();
552            }
553
554            boost::function1<void, const token_vector_t&> callback;
555            if(option_params.has("map-to") && (NULL != controlGroup))
556            {
557                std::string controlName = option_params["map-to"].asString();
558                callback = boost::bind(setControlValueCB, _1, 
559                                       controlName, controlGroup);
560            }
561
562            this->addOptionDesc(
563                long_name, 
564                callback,
565                token_count, 
566                desc, 
567                short_name, 
568                composing,
569                positional,
570                last_option);
571        }
572    }
573}