Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/commandline/CommandLineParser.cc @ 11015

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

clean and explicit setup/shutdown of singletons that are used by statically initialized instances

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