Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/doc/src/libraries/core/CommandLineParser.cc @ 7335

Last change on this file since 7335 was 7335, checked in by rgrieder, 14 years ago

Added separate page for a commandline argument reference.
It's not too useful, but better than nothing.

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