/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Reto Grieder * Co-authors: * ... * */ #include "CommandLineParser.h" #include #include #include "util/Convert.h" #include "util/Output.h" #include "util/Exception.h" #include "util/StringUtils.h" #include "util/SubString.h" namespace orxonox { CommandLineParser* CommandLineParser::singletonPtr_s = nullptr; /** @brief Parses a value string for a command line argument. It simply uses convertValue(Output, Input) to do that. Bools are treated specially. That is necessary so that you can have simple command line switches. */ void CommandLineArgument::parse(const std::string& value) { if (value_.isType()) { // simulate command line switch bool temp; if (convertValue(&temp, value)) { this->bHasDefaultValue_ = false; this->value_ = temp; } else if (value.empty()) { this->bHasDefaultValue_ = false; this->value_ = true; } else ThrowException(Argument, "Could not read command line argument '" + getName() + "'."); } else { if (!value_.set(value)) { value_.set(defaultValue_); ThrowException(Argument, "Could not read command line argument '" + getName() + "'."); } else this->bHasDefaultValue_ = false; } } /** @brief Destructor destroys all CommandLineArguments with it. */ CommandLineParser::~CommandLineParser() { } /** Parses the command line string for arguments and stores these. @note The reason that you have to provide the string to be parsed as space separated list is because of argc and argv. If you only have a whole string, simply use getAllStrings() of SubString. @param cmdLine Command line string WITHOUT the execution path. */ void CommandLineParser::_parse(const std::string& cmdLine) { std::vector arguments; SubString tokens(cmdLine, " ", " ", false, '\\', true, '"', true, '\0', '\0', false); for (unsigned i = 0; i < tokens.size(); ++i) arguments.push_back(tokens[i]); try { // why this? See bFirstTimeParse_ declaration. if (bFirstTimeParse_) { // first shove all the shortcuts in a map for (const auto& mapEntry : cmdLineArgs_) { OrxAssert(cmdLineArgsShortcut_.find(mapEntry.second->getShortcut()) == cmdLineArgsShortcut_.end(), "Cannot have two command line shortcut with the same name."); if (!mapEntry.second->getShortcut().empty()) cmdLineArgsShortcut_[mapEntry.second->getShortcut()] = mapEntry.second; } bFirstTimeParse_ = false; } std::string name; std::string shortcut; std::string value; for (const std::string& argument : arguments) { if (argument.size() != 0) { // sure not "" if (argument[0] == '-') { // start with "-" if (argument.size() == 1) { // argument[i] is "-", probably a minus sign value += "- "; } else if (argument[1] <= 57 && argument[1] >= 48) { // negative number as a value value += argument + ' '; } else { // can be shortcut or full name argument // save old data first value = removeTrailingWhitespaces(value); if (!name.empty()) { checkFullArgument(name, value); name.clear(); assert(shortcut.empty()); } else if (!shortcut.empty()) { checkShortcut(shortcut, value); shortcut.clear(); assert(name.empty()); } if (argument[1] == '-') { // full name argument with "--name" name = argument.substr(2); } else { // shortcut with "-s" shortcut = argument.substr(1); } // reset value string value.clear(); } } else { // value string if (name.empty() && shortcut.empty()) { ThrowException(Argument, "Expected \"-\" or \"-\" in command line arguments.\n"); } // Concatenate strings as long as there's no new argument by "-" or "--" value += argument + ' '; } } } // parse last argument value = removeTrailingWhitespaces(value); if (!name.empty()) { checkFullArgument(name, value); assert(shortcut.empty()); } else if (!shortcut.empty()) { checkShortcut(shortcut, value); assert(name.empty()); } } catch (const ArgumentException& ex) { orxout(user_error) << "Could not parse command line: " << ex.what() << endl; orxout(user_error) << CommandLineParser::getUsageInformation() << endl; throw GeneralException(""); } } /** @brief Parses an argument based on its full name. @param name Full name of the argument @param value String containing the value */ void CommandLineParser::checkFullArgument(const std::string& name, const std::string& value) { std::map::const_iterator it = cmdLineArgs_.find(name); if (it == cmdLineArgs_.end()) ThrowException(Argument, "Command line argument '" + name + "' does not exist."); it->second->parse(value); } /** @brief Parses an argument based on its shortcut. @param shortcut Shortcut to the argument @param value String containing the value */ void CommandLineParser::checkShortcut(const std::string& shortcut, const std::string& value) { std::map::const_iterator it = cmdLineArgsShortcut_.find(shortcut); if (it == cmdLineArgsShortcut_.end()) ThrowException(Argument, "Command line shortcut '" + shortcut + "' does not exist."); it->second->parse(value); } std::string CommandLineParser::getUsageInformation() { CommandLineParser& inst = getInstance(); std::ostringstream infoStr; // determine maximum name size size_t maxNameSize = 0; for (const auto& mapEntry : inst.cmdLineArgs_) { maxNameSize = std::max(mapEntry.second->getName().size(), maxNameSize); } infoStr << endl; infoStr << "Usage: orxonox [options]" << endl; infoStr << "Available options:" << endl; for (const auto& mapEntry : inst.cmdLineArgs_) { if (!mapEntry.second->getShortcut().empty()) infoStr << " [-" << mapEntry.second->getShortcut() << "] "; else infoStr << " "; infoStr << "--" << mapEntry.second->getName() << ' '; if (mapEntry.second->getValue().isType()) infoStr << " "; else infoStr << "ARG "; // fill with the necessary amount of blanks infoStr << std::string(maxNameSize - mapEntry.second->getName().size(), ' '); infoStr << ": " << mapEntry.second->getInformation(); infoStr << endl; } return infoStr.str(); } void CommandLineParser::generateDoc(std::ofstream& file) { file << "/** @page cmdargspage Command Line Arguments Reference" << endl; file << " @verbatim"; /*no endl*/ file << getUsageInformation(); /*no endl*/ file << " @endverbatim" << endl; file << "*/" << endl; } /** @brief Retrieves a CommandLineArgument. The method throws an exception if 'name' was not found or the value could not be converted. @note You should of course not call this method before the command line has been parsed. */ const CommandLineArgument* CommandLineParser::getArgument(const std::string& name) { std::map::const_iterator it = getInstance().cmdLineArgs_.find(name); if (it == getInstance().cmdLineArgs_.end()) { ThrowException(Argument, "Could find command line argument '" + name + "'."); } else { return it->second; } } /** @brief Adds a new CommandLineArgument to the internal map. Note that only such arguments are actually valid. */ void CommandLineParser::addArgument(CommandLineArgument* argument) { OrxAssert(!getInstance().existsArgument(argument->getName()), "Cannot add a command line argument with name '" + argument->getName() + "' twice."); OrxAssert(!argument->getDefaultValue().isType() || argument->getDefaultValue().get() != true, "Boolean command line arguments with positive default values are not supported." << endl << "Please use SetCommandLineSwitch and adjust your argument: " << argument->getName()); getInstance().cmdLineArgs_[argument->getName()] = argument; } /** * @brief Removes a CommandLineArgument from the internal map. */ void CommandLineParser::removeArgument(CommandLineArgument* argument) { getInstance().cmdLineArgs_.erase(argument->getName()); } }