Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

Last change on this file since 11071 was 11071, checked in by landauf, 8 years ago

merged branch cpp11_v3 back to trunk

  • Property svn:eol-style set to native
File size: 11.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/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    @param bParsingFile
221        Parsing a file or the command line itself
222    */
223    void CommandLineParser::checkFullArgument(const std::string& name, const std::string& value)
224    {
225        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgs_.find(name);
226        if (it == cmdLineArgs_.end())
227            ThrowException(Argument, "Command line argument '" + name + "' does not exist.");
228
229        it->second->parse(value);
230    }
231
232    /**
233    @brief
234        Parses an argument based on its shortcut.
235    @param shortcut
236        Shortcut to the argument
237    @param value
238        String containing the value
239    @param bParsingFile
240        Parsing a file or the command line itself
241    */
242    void CommandLineParser::checkShortcut(const std::string& shortcut, const std::string& value)
243    {
244        std::map<std::string, CommandLineArgument*>::const_iterator it = cmdLineArgsShortcut_.find(shortcut);
245        if (it == cmdLineArgsShortcut_.end())
246            ThrowException(Argument, "Command line shortcut '" + shortcut + "' does not exist.");
247
248        it->second->parse(value);
249    }
250
251    std::string CommandLineParser::getUsageInformation()
252    {
253        CommandLineParser& inst = getInstance();
254        std::ostringstream infoStr;
255
256        // determine maximum name size
257        size_t maxNameSize = 0;
258        for (const auto& mapEntry : inst.cmdLineArgs_)
259        {
260            maxNameSize = std::max(mapEntry.second->getName().size(), maxNameSize);
261        }
262
263        infoStr << endl;
264        infoStr << "Usage: orxonox [options]" << endl;
265        infoStr << "Available options:" << endl;
266
267        for (const auto& mapEntry : inst.cmdLineArgs_)
268        {
269            if (!mapEntry.second->getShortcut().empty())
270                infoStr << " [-" << mapEntry.second->getShortcut() << "] ";
271            else
272                infoStr << "      ";
273            infoStr << "--" << mapEntry.second->getName() << ' ';
274            if (mapEntry.second->getValue().isType<bool>())
275                infoStr << "    ";
276            else
277                infoStr << "ARG ";
278            // fill with the necessary amount of blanks
279            infoStr << std::string(maxNameSize - mapEntry.second->getName().size(), ' ');
280            infoStr << ": " << mapEntry.second->getInformation();
281            infoStr << endl;
282        }
283        return infoStr.str();
284    }
285
286    void CommandLineParser::generateDoc(std::ofstream& file)
287    {
288        file << "/** @page cmdargspage Command Line Arguments Reference" << endl;
289        file << "    @verbatim"; /*no endl*/
290        file << getUsageInformation(); /*no endl*/
291        file << "    @endverbatim" << endl;
292        file << "*/" << endl;
293    }
294
295    /**
296    @brief
297        Retrieves a CommandLineArgument.
298        The method throws an exception if 'name' was not found or the value could not be converted.
299    @note
300        You should of course not call this method before the command line has been parsed.
301    */
302    const CommandLineArgument* CommandLineParser::getArgument(const std::string& name)
303    {
304        std::map<std::string, CommandLineArgument*>::const_iterator it = getInstance().cmdLineArgs_.find(name);
305        if (it == getInstance().cmdLineArgs_.end())
306        {
307            ThrowException(Argument, "Could find command line argument '" + name + "'.");
308        }
309        else
310        {
311            return it->second;
312        }
313    }
314
315    /**
316    @brief
317        Adds a new CommandLineArgument to the internal map.
318        Note that only such arguments are actually valid.
319    */
320    void CommandLineParser::addArgument(CommandLineArgument* argument)
321    {
322        OrxAssert(!getInstance().existsArgument(argument->getName()),
323            "Cannot add a command line argument with name '" + argument->getName() + "' twice.");
324        OrxAssert(!argument->getDefaultValue().isType<bool>() || argument->getDefaultValue().get<bool>() != true,
325               "Boolean command line arguments with positive default values are not supported." << endl
326            << "Please use SetCommandLineSwitch and adjust your argument: " << argument->getName());
327
328        getInstance().cmdLineArgs_[argument->getName()] = argument;
329    }
330
331    /**
332     * @brief Removes a CommandLineArgument from the internal map.
333     */
334    void CommandLineParser::removeArgument(CommandLineArgument* argument)
335    {
336        getInstance().cmdLineArgs_.erase(argument->getName());
337    }
338}
Note: See TracBrowser for help on using the repository browser.