Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/core7/src/libraries/core/commandline/CommandLineParser.cc @ 10520

Last change on this file since 10520 was 10520, checked in by landauf, 9 years ago

StaticallyInitializedInstances are now responsible to delete the wrapped object.

  • Property svn:eol-style set to native
File size: 12.1 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/Output.h"
36#include "util/Exception.h"
37#include "util/StringUtils.h"
38#include "util/SubString.h"
39
40namespace orxonox
41{
42    /**
43    @brief
44        Parses a value string for a command line argument.
45        It simply uses convertValue(Output, Input) to do that.
46        Bools are treated specially. That is necessary
47        so that you can have simple command line switches.
48    */
49    void CommandLineArgument::parse(const std::string& value)
50    {
51        if (value_.isType<bool>())
52        {
53            // simulate command line switch
54            bool temp;
55            if (convertValue(&temp, value))
56            {
57                this->bHasDefaultValue_ = false;
58                this->value_ = temp;
59            }
60            else if (value.empty())
61            {
62                this->bHasDefaultValue_ = false;
63                this->value_ = true;
64            }
65            else
66                ThrowException(Argument, "Could not read command line argument '" + getName() + "'.");
67        }
68        else
69        {
70            if (!value_.set(value))
71            {
72                value_.set(defaultValue_);
73                ThrowException(Argument, "Could not read command line argument '" + getName() + "'.");
74            }
75            else
76                this->bHasDefaultValue_ = false;
77        }
78    }
79
80
81    /**
82    @brief
83        Destructor destroys all CommandLineArguments with it.
84    */
85    CommandLineParser::~CommandLineParser()
86    {
87    }
88
89    /**
90    @brief
91        Returns a unique instance (Meyers Singleton).
92    */
93    CommandLineParser& CommandLineParser::_getInstance()
94    {
95        static CommandLineParser instance;
96        return instance;
97    }
98
99    /** Parses the command line string for arguments and stores these.
100    @note
101        The reason that you have to provide the string to be parsed as
102        space separated list is because of argc and argv. If you only have
103        a whole string, simply use getAllStrings() of SubString.
104    @param cmdLine
105        Command line string WITHOUT the execution path.
106    */
107    void CommandLineParser::_parse(const std::string& cmdLine)
108    {
109        std::vector<std::string> arguments;
110        SubString tokens(cmdLine, " ", " ", false, '\\', true, '"', true, '\0', '\0', false);
111        for (unsigned i = 0; i < tokens.size(); ++i)
112            arguments.push_back(tokens[i]);
113
114        try
115        {
116            // why this? See bFirstTimeParse_ declaration.
117            if (bFirstTimeParse_)
118            {
119                // first shove all the shortcuts in a map
120                for (std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.begin();
121                    it != cmdLineArgs_.end(); ++it)
122                {
123                    OrxAssert(cmdLineArgsShortcut_.find(it->second->getShortcut()) == cmdLineArgsShortcut_.end(),
124                        "Cannot have two command line shortcut with the same name.");
125                    if (!it->second->getShortcut().empty())
126                        cmdLineArgsShortcut_[it->second->getShortcut()] = it->second;
127                }
128                bFirstTimeParse_ = false;
129            }
130
131            std::string name;
132            std::string shortcut;
133            std::string value;
134            for (unsigned int i = 0; i < arguments.size(); ++i)
135            {
136                if (arguments[i].size() != 0)
137                {
138                    // sure not ""
139                    if (arguments[i][0] == '-')
140                    {
141                        // start with "-"
142                        if (arguments[i].size() == 1)
143                        {
144                            // argument[i] is "-", probably a minus sign
145                            value += "- ";
146                        }
147                        else if (arguments[i][1] <= 57 && arguments[i][1] >= 48)
148                        {
149                            // negative number as a value
150                            value += arguments[i] + ' ';
151                        }
152                        else
153                        {
154                            // can be shortcut or full name argument
155
156                            // save old data first
157                            value = removeTrailingWhitespaces(value);
158                            if (!name.empty())
159                            {
160                                checkFullArgument(name, value);
161                                name.clear();
162                                assert(shortcut.empty());
163                            }
164                            else if (!shortcut.empty())
165                            {
166                                checkShortcut(shortcut, value);
167                                shortcut.clear();
168                                assert(name.empty());
169                            }
170
171                            if (arguments[i][1] == '-')
172                            {
173                                // full name argument with "--name"
174                                name = arguments[i].substr(2);
175                            }
176                            else
177                            {
178                                // shortcut with "-s"
179                                shortcut = arguments[i].substr(1);
180                            }
181
182                            // reset value string
183                            value.clear();
184                        }
185                    }
186                    else
187                    {
188                        // value string
189
190                        if (name.empty() && shortcut.empty())
191                        {
192                            ThrowException(Argument, "Expected \"-\" or \"-\" in command line arguments.\n");
193                        }
194
195                        // Concatenate strings as long as there's no new argument by "-" or "--"
196                        value += arguments[i] + ' ';
197                    }
198                }
199            }
200
201            // parse last argument
202            value = removeTrailingWhitespaces(value);
203            if (!name.empty())
204            {
205                checkFullArgument(name, value);
206                assert(shortcut.empty());
207            }
208            else if (!shortcut.empty())
209            {
210                checkShortcut(shortcut, value);
211                assert(name.empty());
212            }
213        }
214        catch (const ArgumentException& ex)
215        {
216            orxout(user_error) << "Could not parse command line: " << ex.what() << endl;
217            orxout(user_error) << CommandLineParser::getUsageInformation() << endl;
218            throw GeneralException("");
219        }
220    }
221
222    /**
223    @brief
224        Parses an argument based on its full name.
225    @param name
226        Full name of the argument
227    @param value
228        String containing the value
229    @param bParsingFile
230        Parsing a file or the command line itself
231    */
232    void CommandLineParser::checkFullArgument(const std::string& name, const std::string& value)
233    {
234        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.find(name);
235        if (it == cmdLineArgs_.end())
236            ThrowException(Argument, "Command line argument '" + name + "' does not exist.");
237
238        it->second->parse(value);
239    }
240
241    /**
242    @brief
243        Parses an argument based on its shortcut.
244    @param shortcut
245        Shortcut to 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::checkShortcut(const std::string& shortcut, const std::string& value)
252    {
253        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgsShortcut_.find(shortcut);
254        if (it == cmdLineArgsShortcut_.end())
255            ThrowException(Argument, "Command line shortcut '" + shortcut + "' does not exist.");
256
257        it->second->parse(value);
258    }
259
260    std::string CommandLineParser::getUsageInformation()
261    {
262        CommandLineParser& inst = _getInstance();
263        std::ostringstream infoStr;
264
265        // determine maximum name size
266        size_t maxNameSize = 0;
267        for (std::map<std::string, CommandLineArgument*>::const_iterator it = inst.cmdLineArgs_.begin();
268            it != inst.cmdLineArgs_.end(); ++it)
269        {
270            maxNameSize = std::max(it->second->getName().size(), maxNameSize);
271        }
272
273        infoStr << endl;
274        infoStr << "Usage: orxonox [options]" << endl;
275        infoStr << "Available options:" << endl;
276
277        for (std::map<std::string, CommandLineArgument*>::const_iterator it = inst.cmdLineArgs_.begin();
278            it != inst.cmdLineArgs_.end(); ++it)
279        {
280            if (!it->second->getShortcut().empty())
281                infoStr << " [-" << it->second->getShortcut() << "] ";
282            else
283                infoStr << "      ";
284            infoStr << "--" << it->second->getName() << ' ';
285            if (it->second->getValue().isType<bool>())
286                infoStr << "    ";
287            else
288                infoStr << "ARG ";
289            // fill with the necessary amount of blanks
290            infoStr << std::string(maxNameSize - it->second->getName().size(), ' ');
291            infoStr << ": " << it->second->getInformation();
292            infoStr << endl;
293        }
294        return infoStr.str();
295    }
296
297    void CommandLineParser::generateDoc(std::ofstream& file)
298    {
299        file << "/** @page cmdargspage Command Line Arguments Reference" << endl;
300        file << "    @verbatim"; /*no endl*/
301        file << getUsageInformation(); /*no endl*/
302        file << "    @endverbatim" << endl;
303        file << "*/" << endl;
304    }
305
306    /**
307    @brief
308        Retrieves a CommandLineArgument.
309        The method throws an exception if 'name' was not found or the value could not be converted.
310    @note
311        You should of course not call this method before the command line has been parsed.
312    */
313    const CommandLineArgument* CommandLineParser::getArgument(const std::string& name)
314    {
315        std::map<std::string, CommandLineArgument*>::const_iterator it = _getInstance().cmdLineArgs_.find(name);
316        if (it == _getInstance().cmdLineArgs_.end())
317        {
318            ThrowException(Argument, "Could find command line argument '" + name + "'.");
319        }
320        else
321        {
322            return it->second;
323        }
324    }
325
326    /**
327    @brief
328        Adds a new CommandLineArgument to the internal map.
329        Note that only such arguments are actually valid.
330    */
331    void CommandLineParser::addArgument(CommandLineArgument* argument)
332    {
333        OrxAssert(!_getInstance().existsArgument(argument->getName()),
334            "Cannot add a command line argument with name '" + argument->getName() + "' twice.");
335        OrxAssert(!argument->getDefaultValue().isType<bool>() || argument->getDefaultValue().get<bool>() != true,
336               "Boolean command line arguments with positive default values are not supported." << endl
337            << "Please use SetCommandLineSwitch and adjust your argument: " << argument->getName());
338
339        _getInstance().cmdLineArgs_[argument->getName()] = argument;
340    }
341
342    /**
343     * @brief Removes a CommandLineArgument from the internal map.
344     */
345    void CommandLineParser::removeArgument(CommandLineArgument* argument)
346    {
347        _getInstance().cmdLineArgs_.erase(argument->getName());
348    }
349}
Note: See TracBrowser for help on using the repository browser.