Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 11400 was 11099, checked in by muemart, 9 years ago

Fix loads of doxygen warnings and other documentation issues

  • Property svn:eol-style set to native
File size: 11.6 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 = nullptr;
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 (const auto& mapEntry : cmdLineArgs_)
113                {
114                    OrxAssert(cmdLineArgsShortcut_.find(mapEntry.second->getShortcut()) == cmdLineArgsShortcut_.end(),
115                        "Cannot have two command line shortcut with the same name.");
116                    if (!mapEntry.second->getShortcut().empty())
117                        cmdLineArgsShortcut_[mapEntry.second->getShortcut()] = mapEntry.second;
118                }
119                bFirstTimeParse_ = false;
120            }
121
122            std::string name;
123            std::string shortcut;
124            std::string value;
125            for (const std::string& argument : arguments)
126            {
127                if (argument.size() != 0)
128                {
129                    // sure not ""
130                    if (argument[0] == '-')
131                    {
132                        // start with "-"
133                        if (argument.size() == 1)
134                        {
135                            // argument[i] is "-", probably a minus sign
136                            value += "- ";
137                        }
138                        else if (argument[1] <= 57 && argument[1] >= 48)
139                        {
140                            // negative number as a value
141                            value += argument + ' ';
142                        }
143                        else
144                        {
145                            // can be shortcut or full name argument
146
147                            // save old data first
148                            value = removeTrailingWhitespaces(value);
149                            if (!name.empty())
150                            {
151                                checkFullArgument(name, value);
152                                name.clear();
153                                assert(shortcut.empty());
154                            }
155                            else if (!shortcut.empty())
156                            {
157                                checkShortcut(shortcut, value);
158                                shortcut.clear();
159                                assert(name.empty());
160                            }
161
162                            if (argument[1] == '-')
163                            {
164                                // full name argument with "--name"
165                                name = argument.substr(2);
166                            }
167                            else
168                            {
169                                // shortcut with "-s"
170                                shortcut = argument.substr(1);
171                            }
172
173                            // reset value string
174                            value.clear();
175                        }
176                    }
177                    else
178                    {
179                        // value string
180
181                        if (name.empty() && shortcut.empty())
182                        {
183                            ThrowException(Argument, "Expected \"-\" or \"-\" in command line arguments.\n");
184                        }
185
186                        // Concatenate strings as long as there's no new argument by "-" or "--"
187                        value += argument + ' ';
188                    }
189                }
190            }
191
192            // parse last argument
193            value = removeTrailingWhitespaces(value);
194            if (!name.empty())
195            {
196                checkFullArgument(name, value);
197                assert(shortcut.empty());
198            }
199            else if (!shortcut.empty())
200            {
201                checkShortcut(shortcut, value);
202                assert(name.empty());
203            }
204        }
205        catch (const ArgumentException& ex)
206        {
207            orxout(user_error) << "Could not parse command line: " << ex.what() << endl;
208            orxout(user_error) << CommandLineParser::getUsageInformation() << endl;
209            throw GeneralException("");
210        }
211    }
212
213    /**
214    @brief
215        Parses an argument based on its full name.
216    @param name
217        Full name of the argument
218    @param value
219        String containing the value
220    */
221    void CommandLineParser::checkFullArgument(const std::string& name, const std::string& value)
222    {
223        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.find(name);
224        if (it == cmdLineArgs_.end())
225            ThrowException(Argument, "Command line argument '" + name + "' does not exist.");
226
227        it->second->parse(value);
228    }
229
230    /**
231    @brief
232        Parses an argument based on its shortcut.
233    @param shortcut
234        Shortcut to the argument
235    @param value
236        String containing the value
237    */
238    void CommandLineParser::checkShortcut(const std::string& shortcut, const std::string& value)
239    {
240        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgsShortcut_.find(shortcut);
241        if (it == cmdLineArgsShortcut_.end())
242            ThrowException(Argument, "Command line shortcut '" + shortcut + "' does not exist.");
243
244        it->second->parse(value);
245    }
246
247    std::string CommandLineParser::getUsageInformation()
248    {
249        CommandLineParser& inst = getInstance();
250        std::ostringstream infoStr;
251
252        // determine maximum name size
253        size_t maxNameSize = 0;
254        for (const auto& mapEntry : inst.cmdLineArgs_)
255        {
256            maxNameSize = std::max(mapEntry.second->getName().size(), maxNameSize);
257        }
258
259        infoStr << endl;
260        infoStr << "Usage: orxonox [options]" << endl;
261        infoStr << "Available options:" << endl;
262
263        for (const auto& mapEntry : inst.cmdLineArgs_)
264        {
265            if (!mapEntry.second->getShortcut().empty())
266                infoStr << " [-" << mapEntry.second->getShortcut() << "] ";
267            else
268                infoStr << "      ";
269            infoStr << "--" << mapEntry.second->getName() << ' ';
270            if (mapEntry.second->getValue().isType<bool>())
271                infoStr << "    ";
272            else
273                infoStr << "ARG ";
274            // fill with the necessary amount of blanks
275            infoStr << std::string(maxNameSize - mapEntry.second->getName().size(), ' ');
276            infoStr << ": " << mapEntry.second->getInformation();
277            infoStr << endl;
278        }
279        return infoStr.str();
280    }
281
282    void CommandLineParser::generateDoc(std::ofstream& file)
283    {
284        file << "/** @page cmdargspage Command Line Arguments Reference" << endl;
285        file << "    @verbatim"; /*no endl*/
286        file << getUsageInformation(); /*no endl*/
287        file << "    @endverbatim" << endl;
288        file << "*/" << endl;
289    }
290
291    /**
292    @brief
293        Retrieves a CommandLineArgument.
294        The method throws an exception if 'name' was not found or the value could not be converted.
295    @note
296        You should of course not call this method before the command line has been parsed.
297    */
298    const CommandLineArgument* CommandLineParser::getArgument(const std::string& name)
299    {
300        std::map<std::string, CommandLineArgument*>::const_iterator it = getInstance().cmdLineArgs_.find(name);
301        if (it == getInstance().cmdLineArgs_.end())
302        {
303            ThrowException(Argument, "Could find command line argument '" + name + "'.");
304        }
305        else
306        {
307            return it->second;
308        }
309    }
310
311    /**
312    @brief
313        Adds a new CommandLineArgument to the internal map.
314        Note that only such arguments are actually valid.
315    */
316    void CommandLineParser::addArgument(CommandLineArgument* argument)
317    {
318        OrxAssert(!getInstance().existsArgument(argument->getName()),
319            "Cannot add a command line argument with name '" + argument->getName() + "' twice.");
320        OrxAssert(!argument->getDefaultValue().isType<bool>() || argument->getDefaultValue().get<bool>() != true,
321               "Boolean command line arguments with positive default values are not supported." << endl
322            << "Please use SetCommandLineSwitch and adjust your argument: " << argument->getName());
323
324        getInstance().cmdLineArgs_[argument->getName()] = argument;
325    }
326
327    /**
328     * @brief Removes a CommandLineArgument from the internal map.
329     */
330    void CommandLineParser::removeArgument(CommandLineArgument* argument)
331    {
332        getInstance().cmdLineArgs_.erase(argument->getName());
333    }
334}
Note: See TracBrowser for help on using the repository browser.