Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/src/libraries/core/CommandLineParser.cc @ 8270

Last change on this file since 8270 was 7401, checked in by landauf, 14 years ago

merged doc branch back to trunk

  • Property svn:eol-style set to native
File size: 13.7 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *   Co-authors:
25 *      ...
26 *
27 */
28
29#include "CommandLineParser.h"
30
31#include <algorithm>
32#include <sstream>
33
34#include "util/Convert.h"
35#include "util/Debug.h"
36#include "util/Exception.h"
37#include "util/StringUtils.h"
38#include "util/SubString.h"
39#include "PathConfig.h"
40
41namespace orxonox
42{
43    SetCommandLineOnlyArgument(optionsFile, "start.ini").shortcut("o");
44
45    /**
46    @brief
47        Parses a value string for a command line argument.
48        It simply uses convertValue(Output, Input) to do that.
49        Bools are treated specially. That is necessary
50        so that you can have simple command line switches.
51    */
52    void CommandLineArgument::parse(const std::string& value, bool bParsingFile)
53    {
54        if (bParsingFile && this->bCommandLineOnly_)
55            ThrowException(Argument, "Command line argument '" + getName() + "' is not allowed in files.");
56        if (value_.getType() == MT_Type::Bool)
57        {
58            // simulate command line switch
59            bool temp;
60            if (convertValue(&temp, value))
61            {
62                this->bHasDefaultValue_ = false;
63                this->value_ = temp;
64            }
65            else if (value.empty())
66            {
67                this->bHasDefaultValue_ = false;
68                this->value_ = true;
69            }
70            else
71                ThrowException(Argument, "Could not read command line argument '" + getName() + "'.");
72        }
73        else
74        {
75            if (!value_.setValue(value))
76            {
77                value_.setValue(defaultValue_);
78                ThrowException(Argument, "Could not read command line argument '" + getName() + "'.");
79            }
80            else
81                this->bHasDefaultValue_ = false;
82        }
83    }
84
85
86    /**
87    @brief
88        Destructor destroys all CommandLineArguments with it.
89    */
90    CommandLineParser::~CommandLineParser()
91    {
92        CommandLineParser::destroyAllArguments();
93    }
94
95    /**
96    @brief
97        Returns a unique instance (Meyers Singleton).
98    */
99    CommandLineParser& CommandLineParser::_getInstance()
100    {
101        static CommandLineParser instance;
102        return instance;
103    }
104
105    /**
106    @brief
107        Destroys all command line arguments. This should be called at the end
108        of main. Do not use before that.
109    */
110    void CommandLineParser::destroyAllArguments()
111    {
112        for (std::map<std::string, CommandLineArgument*>::const_iterator it = _getInstance().cmdLineArgs_.begin();
113            it != _getInstance().cmdLineArgs_.end(); ++it)
114            delete it->second;
115        _getInstance().cmdLineArgs_.clear();
116    }
117
118    /**
119    @brief
120        Reads the command line parses the values of each argument.
121        It is then stored in the corresponding CommandLineArgument.
122    @note
123        The reason that you have to provide the string to be parsed as
124        space separted list is because of argc and argv. If you only have
125        a whole string, simply use getAllStrings() of SubString.
126    @param arguments
127        Vector of space separated strings.
128    @param bParsingFile
129        Parsing a file or the command line itself
130    */
131    void CommandLineParser::_parse(const std::vector<std::string>& arguments, bool bParsingFile)
132    {
133        try
134        {
135            // why this? See bFirstTimeParse_ declaration.
136            if (bFirstTimeParse_)
137            {
138                // first shove all the shortcuts in a map
139                for (std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.begin();
140                    it != cmdLineArgs_.end(); ++it)
141                {
142                    OrxAssert(cmdLineArgsShortcut_.find(it->second->getShortcut()) == cmdLineArgsShortcut_.end(),
143                        "Cannot have two command line shortcut with the same name.");
144                    if (!it->second->getShortcut().empty())
145                        cmdLineArgsShortcut_[it->second->getShortcut()] = it->second;
146                }
147                bFirstTimeParse_ = false;
148            }
149
150            std::string name;
151            std::string shortcut;
152            std::string value;
153            for (unsigned int i = 0; i < arguments.size(); ++i)
154            {
155                if (arguments[i].size() != 0)
156                {
157                    // sure not ""
158                    if (arguments[i][0] == '-')
159                    {
160                        // start with "-"
161                        if (arguments[i].size() == 1)
162                        {
163                            // argument[i] is "-", probably a minus sign
164                            value += "- ";
165                        }
166                        else if (arguments[i][1] <= 57 && arguments[i][1] >= 48)
167                        {
168                            // negative number as a value
169                            value += arguments[i] + ' ';
170                        }
171                        else
172                        {
173                            // can be shortcut or full name argument
174
175                            // save old data first
176                            value = removeTrailingWhitespaces(value);
177                            if (!name.empty())
178                            {
179                                checkFullArgument(name, value, bParsingFile);
180                                name.clear();
181                                assert(shortcut.empty());
182                            }
183                            else if (!shortcut.empty())
184                            {
185                                checkShortcut(shortcut, value, bParsingFile);
186                                shortcut.clear();
187                                assert(name.empty());
188                            }
189
190                            if (arguments[i][1] == '-')
191                            {
192                                // full name argument with "--name"
193                                name = arguments[i].substr(2);
194                            }
195                            else
196                            {
197                                // shortcut with "-s"
198                                shortcut = arguments[i].substr(1);
199                            }
200
201                            // reset value string
202                            value.clear();
203                        }
204                    }
205                    else
206                    {
207                        // value string
208
209                        if (name.empty() && shortcut.empty())
210                        {
211                            ThrowException(Argument, "Expected \"-\" or \"-\" in command line arguments.\n");
212                        }
213
214                        // Concatenate strings as long as there's no new argument by "-" or "--"
215                        value += arguments[i] + ' ';
216                    }
217                }
218            }
219
220            // parse last argument
221            value = removeTrailingWhitespaces(value);
222            if (!name.empty())
223            {
224                checkFullArgument(name, value, bParsingFile);
225                assert(shortcut.empty());
226            }
227            else if (!shortcut.empty())
228            {
229                checkShortcut(shortcut, value, bParsingFile);
230                assert(name.empty());
231            }
232        }
233        catch (const ArgumentException& ex)
234        {
235            COUT(0) << "Could not parse command line (including additional files): " << ex.what() << std::endl;
236            COUT(0) << CommandLineParser::getUsageInformation() << std::endl;
237            throw GeneralException("");
238        }
239    }
240
241    /**
242    @brief
243        Parses an argument based on its full name.
244    @param name
245        Full name of the argument
246    @param value
247        String containing the value
248    @param bParsingFile
249        Parsing a file or the command line itself
250    */
251    void CommandLineParser::checkFullArgument(const std::string& name, const std::string& value, bool bParsingFile)
252    {
253        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.find(name);
254        if (it == cmdLineArgs_.end())
255            ThrowException(Argument, "Command line argument '" + name + "' does not exist.");
256
257        it->second->parse(value, bParsingFile);
258    }
259
260    /**
261    @brief
262        Parses an argument based on its shortcut.
263    @param shortcut
264        Shortcut to the argument
265    @param value
266        String containing the value
267    @param bParsingFile
268        Parsing a file or the command line itself
269    */
270    void CommandLineParser::checkShortcut(const std::string& shortcut, const std::string& value, bool bParsingFile)
271    {
272        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgsShortcut_.find(shortcut);
273        if (it == cmdLineArgsShortcut_.end())
274            ThrowException(Argument, "Command line shortcut '" + shortcut + "' does not exist.");
275
276        it->second->parse(value, bParsingFile);
277    }
278
279    std::string CommandLineParser::getUsageInformation()
280    {
281        CommandLineParser& inst = _getInstance();
282        std::ostringstream infoStr;
283
284        // determine maximum name size
285        size_t maxNameSize = 0;
286        for (std::map<std::string, CommandLineArgument*>::const_iterator it = inst.cmdLineArgs_.begin();
287            it != inst.cmdLineArgs_.end(); ++it)
288        {
289            maxNameSize = std::max(it->second->getName().size(), maxNameSize);
290        }
291
292        infoStr << std::endl;
293        infoStr << "Usage: orxonox [options]" << std::endl;
294        infoStr << "Available options:" << std::endl;
295
296        for (std::map<std::string, CommandLineArgument*>::const_iterator it = inst.cmdLineArgs_.begin();
297            it != inst.cmdLineArgs_.end(); ++it)
298        {
299            if (!it->second->getShortcut().empty())
300                infoStr << " [-" << it->second->getShortcut() << "] ";
301            else
302                infoStr << "      ";
303            infoStr << "--" << it->second->getName() << ' ';
304            if (it->second->getValue().getType() != MT_Type::Bool)
305                infoStr << "ARG ";
306            else
307                infoStr << "    ";
308            // fill with the necessary amount of blanks
309            infoStr << std::string(maxNameSize - it->second->getName().size(), ' ');
310            infoStr << ": " << it->second->getInformation();
311            infoStr << std::endl;
312        }
313        return infoStr.str();
314    }
315
316    void CommandLineParser::generateDoc(std::ofstream& file)
317    {
318        file << "/** @page cmdargspage Command Line Arguments Reference" << endl;
319        file << "    @verbatim"; /*no endl*/
320        file << getUsageInformation(); /*no endl*/
321        file << "    @endverbatim" << endl;
322        file << "*/" << endl;
323    }
324
325    /**
326    @brief
327        Retrieves a CommandLineArgument.
328        The method throws an exception if 'name' was not found or the value could not be converted.
329    @note
330        You should of course not call this method before the command line has been parsed.
331    */
332    const CommandLineArgument* CommandLineParser::getArgument(const std::string& name)
333    {
334        std::map<std::string, CommandLineArgument*>::const_iterator it = _getInstance().cmdLineArgs_.find(name);
335        if (it == _getInstance().cmdLineArgs_.end())
336        {
337            ThrowException(Argument, "Could find command line argument '" + name + "'.");
338        }
339        else
340        {
341            return it->second;
342        }
343    }
344
345    /**
346    @brief
347        Parses only the command line for CommandLineArguments.
348    */
349    void CommandLineParser::_parseCommandLine(const std::string& cmdLine)
350    {
351        std::vector<std::string> args;
352        SubString tokens(cmdLine, " ", " ", false, '\\', true, '"', true, '\0', '\0', false);
353        for (unsigned i = 0; i < tokens.size(); ++i)
354            args.push_back(tokens[i]);
355        this->_parse(args, false);
356    }
357
358    /**
359    @brief
360        Parses start.ini (or the file specified with --optionsFile) for CommandLineArguments.
361    */
362    void CommandLineParser::_parseFile()
363    {
364        const std::string& filename = CommandLineParser::getValue("optionsFile").getString();
365
366        // look for additional arguments in given file or start.ini as default
367        // They will not overwrite the arguments given directly
368        std::ifstream file;
369        file.open((PathConfig::getConfigPathString() + filename).c_str());
370        std::vector<std::string> args;
371        if (file)
372        {
373            while (!file.eof())
374            {
375                std::string line;
376                std::getline(file, line);
377                line = removeTrailingWhitespaces(line);
378                //if (!(line[0] == '#' || line[0] == '%'))
379                //{
380                SubString tokens(line, " ", " ", false, '\\', true, '"', true, '\0', '\0', false, '#');
381                for (unsigned i = 0; i < tokens.size(); ++i)
382                    if (tokens[i][0] != '#')
383                        args.push_back(tokens[i]);
384                //args.insert(args.end(), tokens.getAllStrings().begin(), tokens.getAllStrings().end());
385                //}
386            }
387            file.close();
388        }
389
390        _parse(args, true);
391    }
392}
Note: See TracBrowser for help on using the repository browser.