/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing#kwsys for details. */ #include "kwsysPrivate.h" #include KWSYS_HEADER(CommandLineArguments.hxx) #include KWSYS_HEADER(Configure.hxx) #include KWSYS_HEADER(String.hxx) // Work-around CMake dependency scanning limitation. This must // duplicate the above list of headers. #if 0 # include "CommandLineArguments.hxx.in" # include "Configure.hxx.in" # include "String.hxx.in" #endif #include #include #include #include #include #include #include #include #ifdef _MSC_VER # pragma warning(disable : 4786) #endif #if defined(__sgi) && !defined(__GNUC__) # pragma set woff 1375 /* base class destructor not virtual */ #endif #if 0 # define CommandLineArguments_DEBUG(x) \ std::cout << __LINE__ << " CLA: " << x << std::endl #else # define CommandLineArguments_DEBUG(x) #endif namespace KWSYS_NAMESPACE { struct CommandLineArgumentsCallbackStructure { const char* Argument; int ArgumentType; CommandLineArguments::CallbackType Callback; void* CallData; void* Variable; int VariableType; const char* Help; }; class CommandLineArgumentsVectorOfStrings : public std::vector { }; class CommandLineArgumentsSetOfStrings : public std::set { }; class CommandLineArgumentsMapOfStrucs : public std::map { }; class CommandLineArgumentsInternal { public: CommandLineArgumentsInternal() = default; using VectorOfStrings = CommandLineArgumentsVectorOfStrings; using CallbacksMap = CommandLineArgumentsMapOfStrucs; using String = kwsys::String; using SetOfStrings = CommandLineArgumentsSetOfStrings; VectorOfStrings Argv; String Argv0; CallbacksMap Callbacks; CommandLineArguments::ErrorCallbackType UnknownArgumentCallback{ nullptr }; void* ClientData{ nullptr }; VectorOfStrings::size_type LastArgument{ 0 }; VectorOfStrings UnusedArguments; }; CommandLineArguments::CommandLineArguments() { this->Internals = new CommandLineArguments::Internal; this->Help = ""; this->LineLength = 80; this->StoreUnusedArgumentsFlag = false; } CommandLineArguments::~CommandLineArguments() { delete this->Internals; } void CommandLineArguments::Initialize(int argc, const char* const argv[]) { int cc; this->Initialize(); this->Internals->Argv0 = argv[0]; for (cc = 1; cc < argc; cc++) { this->ProcessArgument(argv[cc]); } } void CommandLineArguments::Initialize(int argc, char* argv[]) { this->Initialize(argc, static_cast(argv)); } void CommandLineArguments::Initialize() { this->Internals->Argv.clear(); this->Internals->LastArgument = 0; } void CommandLineArguments::ProcessArgument(const char* arg) { this->Internals->Argv.push_back(arg); } bool CommandLineArguments::GetMatchedArguments( std::vector* matches, const std::string& arg) { matches->clear(); CommandLineArguments::Internal::CallbacksMap::iterator it; // Does the argument match to any we know about? for (it = this->Internals->Callbacks.begin(); it != this->Internals->Callbacks.end(); it++) { const CommandLineArguments::Internal::String& parg = it->first; CommandLineArgumentsCallbackStructure* cs = &it->second; if (cs->ArgumentType == CommandLineArguments::NO_ARGUMENT || cs->ArgumentType == CommandLineArguments::SPACE_ARGUMENT) { if (arg == parg) { matches->push_back(parg); } } else if (arg.find(parg) == 0) { matches->push_back(parg); } } return !matches->empty(); } int CommandLineArguments::Parse() { std::vector::size_type cc; std::vector matches; if (this->StoreUnusedArgumentsFlag) { this->Internals->UnusedArguments.clear(); } for (cc = 0; cc < this->Internals->Argv.size(); cc++) { const std::string& arg = this->Internals->Argv[cc]; CommandLineArguments_DEBUG("Process argument: " << arg); this->Internals->LastArgument = cc; if (this->GetMatchedArguments(&matches, arg)) { // Ok, we found one or more arguments that match what user specified. // Let's find the longest one. CommandLineArguments::Internal::VectorOfStrings::size_type kk; CommandLineArguments::Internal::VectorOfStrings::size_type maxidx = 0; CommandLineArguments::Internal::String::size_type maxlen = 0; for (kk = 0; kk < matches.size(); kk++) { if (matches[kk].size() > maxlen) { maxlen = matches[kk].size(); maxidx = kk; } } // So, the longest one is probably the right one. Now see if it has any // additional value CommandLineArgumentsCallbackStructure* cs = &this->Internals->Callbacks[matches[maxidx]]; const std::string& sarg = matches[maxidx]; if (cs->Argument != sarg) { abort(); } switch (cs->ArgumentType) { case NO_ARGUMENT: // No value if (!this->PopulateVariable(cs, nullptr)) { return 0; } break; case SPACE_ARGUMENT: if (cc == this->Internals->Argv.size() - 1) { this->Internals->LastArgument--; return 0; } CommandLineArguments_DEBUG("This is a space argument: " << arg << " value: " << this->Internals->Argv[cc + 1]); // Value is the next argument if (!this->PopulateVariable(cs, this->Internals->Argv[cc + 1].c_str())) { return 0; } cc++; break; case EQUAL_ARGUMENT: if (arg.size() == sarg.size() || arg.at(sarg.size()) != '=') { this->Internals->LastArgument--; return 0; } // Value is everythng followed the '=' sign if (!this->PopulateVariable(cs, arg.c_str() + sarg.size() + 1)) { return 0; } break; case CONCAT_ARGUMENT: // Value is whatever follows the argument if (!this->PopulateVariable(cs, arg.c_str() + sarg.size())) { return 0; } break; case MULTI_ARGUMENT: // Suck in all the rest of the arguments CommandLineArguments_DEBUG("This is a multi argument: " << arg); for (cc++; cc < this->Internals->Argv.size(); ++cc) { const std::string& marg = this->Internals->Argv[cc]; CommandLineArguments_DEBUG( " check multi argument value: " << marg); if (this->GetMatchedArguments(&matches, marg)) { CommandLineArguments_DEBUG("End of multi argument " << arg << " with value: " << marg); break; } CommandLineArguments_DEBUG( " populate multi argument value: " << marg); if (!this->PopulateVariable(cs, marg.c_str())) { return 0; } } if (cc != this->Internals->Argv.size()) { CommandLineArguments_DEBUG("Again End of multi argument " << arg); cc--; continue; } break; default: std::cerr << "Got unknown argument type: \"" << cs->ArgumentType << "\"" << std::endl; this->Internals->LastArgument--; return 0; } } else { // Handle unknown arguments if (this->Internals->UnknownArgumentCallback) { if (!this->Internals->UnknownArgumentCallback( arg.c_str(), this->Internals->ClientData)) { this->Internals->LastArgument--; return 0; } return 1; } else if (this->StoreUnusedArgumentsFlag) { CommandLineArguments_DEBUG("Store unused argument " << arg); this->Internals->UnusedArguments.push_back(arg); } else { std::cerr << "Got unknown argument: \"" << arg << "\"" << std::endl; this->Internals->LastArgument--; return 0; } } } return 1; } void CommandLineArguments::GetRemainingArguments(int* argc, char*** argv) { CommandLineArguments::Internal::VectorOfStrings::size_type size = this->Internals->Argv.size() - this->Internals->LastArgument + 1; CommandLineArguments::Internal::VectorOfStrings::size_type cc; // Copy Argv0 as the first argument char** args = new char*[size]; args[0] = new char[this->Internals->Argv0.size() + 1]; strcpy(args[0], this->Internals->Argv0.c_str()); int cnt = 1; // Copy everything after the LastArgument, since that was not parsed. for (cc = this->Internals->LastArgument + 1; cc < this->Internals->Argv.size(); cc++) { args[cnt] = new char[this->Internals->Argv[cc].size() + 1]; strcpy(args[cnt], this->Internals->Argv[cc].c_str()); cnt++; } *argc = cnt; *argv = args; } void CommandLineArguments::GetUnusedArguments(int* argc, char*** argv) { CommandLineArguments::Internal::VectorOfStrings::size_type size = this->Internals->UnusedArguments.size() + 1; CommandLineArguments::Internal::VectorOfStrings::size_type cc; // Copy Argv0 as the first argument char** args = new char*[size]; args[0] = new char[this->Internals->Argv0.size() + 1]; strcpy(args[0], this->Internals->Argv0.c_str()); int cnt = 1; // Copy everything after the LastArgument, since that was not parsed. for (cc = 0; cc < this->Internals->UnusedArguments.size(); cc++) { kwsys::String& str = this->Internals->UnusedArguments[cc]; args[cnt] = new char[str.size() + 1]; strcpy(args[cnt], str.c_str()); cnt++; } *argc = cnt; *argv = args; } void CommandLineArguments::DeleteRemainingArguments(int argc, char*** argv) { int cc; for (cc = 0; cc < argc; ++cc) { delete[](*argv)[cc]; } delete[] * argv; } void CommandLineArguments::AddCallback(const char* argument, ArgumentTypeEnum type, CallbackType callback, void* call_data, const char* help) { CommandLineArgumentsCallbackStructure s; s.Argument = argument; s.ArgumentType = type; s.Callback = callback; s.CallData = call_data; s.VariableType = CommandLineArguments::NO_VARIABLE_TYPE; s.Variable = nullptr; s.Help = help; this->Internals->Callbacks[argument] = s; this->GenerateHelp(); } void CommandLineArguments::AddArgument(const char* argument, ArgumentTypeEnum type, VariableTypeEnum vtype, void* variable, const char* help) { CommandLineArgumentsCallbackStructure s; s.Argument = argument; s.ArgumentType = type; s.Callback = nullptr; s.CallData = nullptr; s.VariableType = vtype; s.Variable = variable; s.Help = help; this->Internals->Callbacks[argument] = s; this->GenerateHelp(); } #define CommandLineArgumentsAddArgumentMacro(type, ctype) \ void CommandLineArguments::AddArgument(const char* argument, \ ArgumentTypeEnum type, \ ctype* variable, const char* help) \ { \ this->AddArgument(argument, type, CommandLineArguments::type##_TYPE, \ variable, help); \ } /* clang-format off */ CommandLineArgumentsAddArgumentMacro(BOOL, bool) CommandLineArgumentsAddArgumentMacro(INT, int) CommandLineArgumentsAddArgumentMacro(DOUBLE, double) CommandLineArgumentsAddArgumentMacro(STRING, char*) CommandLineArgumentsAddArgumentMacro(STL_STRING, std::string) CommandLineArgumentsAddArgumentMacro(VECTOR_BOOL, std::vector) CommandLineArgumentsAddArgumentMacro(VECTOR_INT, std::vector) CommandLineArgumentsAddArgumentMacro(VECTOR_DOUBLE, std::vector) CommandLineArgumentsAddArgumentMacro(VECTOR_STRING, std::vector) CommandLineArgumentsAddArgumentMacro(VECTOR_STL_STRING, std::vector) #ifdef HELP_CLANG_FORMAT ; #endif /* clang-format on */ #define CommandLineArgumentsAddBooleanArgumentMacro(type, ctype) \ void CommandLineArguments::AddBooleanArgument( \ const char* argument, ctype* variable, const char* help) \ { \ this->AddArgument(argument, CommandLineArguments::NO_ARGUMENT, \ CommandLineArguments::type##_TYPE, variable, help); \ } /* clang-format off */ CommandLineArgumentsAddBooleanArgumentMacro(BOOL, bool) CommandLineArgumentsAddBooleanArgumentMacro(INT, int) CommandLineArgumentsAddBooleanArgumentMacro(DOUBLE, double) CommandLineArgumentsAddBooleanArgumentMacro(STRING, char*) CommandLineArgumentsAddBooleanArgumentMacro(STL_STRING, std::string) #ifdef HELP_CLANG_FORMAT ; #endif /* clang-format on */ void CommandLineArguments::SetClientData(void* client_data) { this->Internals->ClientData = client_data; } void CommandLineArguments::SetUnknownArgumentCallback( CommandLineArguments::ErrorCallbackType callback) { this->Internals->UnknownArgumentCallback = callback; } const char* CommandLineArguments::GetHelp(const char* arg) { auto it = this->Internals->Callbacks.find(arg); if (it == this->Internals->Callbacks.end()) { return nullptr; } // Since several arguments may point to the same argument, find the one this // one point to if this one is pointing to another argument. CommandLineArgumentsCallbackStructure* cs = &(it->second); for (;;) { auto hit = this->Internals->Callbacks.find(cs->Help); if (hit == this->Internals->Callbacks.end()) { break; } cs = &(hit->second); } return cs->Help; } void CommandLineArguments::SetLineLength(unsigned int ll) { if (ll < 9 || ll > 1000) { return; } this->LineLength = ll; this->GenerateHelp(); } const char* CommandLineArguments::GetArgv0() { return this->Internals->Argv0.c_str(); } unsigned int CommandLineArguments::GetLastArgument() { return static_cast(this->Internals->LastArgument + 1); } void CommandLineArguments::GenerateHelp() { std::ostringstream str; // Collapse all arguments into the map of vectors of all arguments that do // the same thing. CommandLineArguments::Internal::CallbacksMap::iterator it; using MapArgs = std::map; MapArgs mp; MapArgs::iterator mpit, smpit; for (it = this->Internals->Callbacks.begin(); it != this->Internals->Callbacks.end(); it++) { CommandLineArgumentsCallbackStructure* cs = &(it->second); mpit = mp.find(cs->Help); if (mpit != mp.end()) { mpit->second.insert(it->first); mp[it->first].insert(it->first); } else { mp[it->first].insert(it->first); } } for (it = this->Internals->Callbacks.begin(); it != this->Internals->Callbacks.end(); it++) { CommandLineArgumentsCallbackStructure* cs = &(it->second); mpit = mp.find(cs->Help); if (mpit != mp.end()) { mpit->second.insert(it->first); smpit = mp.find(it->first); CommandLineArguments::Internal::SetOfStrings::iterator sit; for (sit = smpit->second.begin(); sit != smpit->second.end(); sit++) { mpit->second.insert(*sit); } mp.erase(smpit); } else { mp[it->first].insert(it->first); } } // Find the length of the longest string CommandLineArguments::Internal::String::size_type maxlen = 0; for (mpit = mp.begin(); mpit != mp.end(); mpit++) { CommandLineArguments::Internal::SetOfStrings::iterator sit; for (sit = mpit->second.begin(); sit != mpit->second.end(); sit++) { CommandLineArguments::Internal::String::size_type clen = sit->size(); switch (this->Internals->Callbacks[*sit].ArgumentType) { case CommandLineArguments::NO_ARGUMENT: clen += 0; break; case CommandLineArguments::CONCAT_ARGUMENT: clen += 3; break; case CommandLineArguments::SPACE_ARGUMENT: clen += 4; break; case CommandLineArguments::EQUAL_ARGUMENT: clen += 4; break; } if (clen > maxlen) { maxlen = clen; } } } CommandLineArguments::Internal::String::size_type maxstrlen = maxlen; maxlen += 4; // For the space before and after the option // Print help for each option for (mpit = mp.begin(); mpit != mp.end(); mpit++) { CommandLineArguments::Internal::SetOfStrings::iterator sit; for (sit = mpit->second.begin(); sit != mpit->second.end(); sit++) { str << std::endl; std::string argument = *sit; switch (this->Internals->Callbacks[*sit].ArgumentType) { case CommandLineArguments::NO_ARGUMENT: break; case CommandLineArguments::CONCAT_ARGUMENT: argument += "opt"; break; case CommandLineArguments::SPACE_ARGUMENT: argument += " opt"; break; case CommandLineArguments::EQUAL_ARGUMENT: argument += "=opt"; break; case CommandLineArguments::MULTI_ARGUMENT: argument += " opt opt ..."; break; } str << " " << argument.substr(0, maxstrlen) << " "; } const char* ptr = this->Internals->Callbacks[mpit->first].Help; size_t len = strlen(ptr); int cnt = 0; while (len > 0) { // If argument with help is longer than line length, split it on previous // space (or tab) and continue on the next line CommandLineArguments::Internal::String::size_type cc; for (cc = 0; ptr[cc]; cc++) { if (*ptr == ' ' || *ptr == '\t') { ptr++; len--; } } if (cnt > 0) { for (cc = 0; cc < maxlen; cc++) { str << " "; } } CommandLineArguments::Internal::String::size_type skip = len; if (skip > this->LineLength - maxlen) { skip = this->LineLength - maxlen; for (cc = skip - 1; cc > 0; cc--) { if (ptr[cc] == ' ' || ptr[cc] == '\t') { break; } } if (cc != 0) { skip = cc; } } str.write(ptr, static_cast(skip)); str << std::endl; ptr += skip; len -= skip; cnt++; } } /* // This can help debugging help string str << endl; unsigned int cc; for ( cc = 0; cc < this->LineLength; cc ++ ) { str << cc % 10; } str << endl; */ this->Help = str.str(); } void CommandLineArguments::PopulateVariable(bool* variable, const std::string& value) { if (value == "1" || value == "ON" || value == "on" || value == "On" || value == "TRUE" || value == "true" || value == "True" || value == "yes" || value == "Yes" || value == "YES") { *variable = true; } else { *variable = false; } } void CommandLineArguments::PopulateVariable(int* variable, const std::string& value) { char* res = nullptr; *variable = static_cast(strtol(value.c_str(), &res, 10)); // if ( res && *res ) // { // Can handle non-int // } } void CommandLineArguments::PopulateVariable(double* variable, const std::string& value) { char* res = nullptr; *variable = strtod(value.c_str(), &res); // if ( res && *res ) // { // Can handle non-double // } } void CommandLineArguments::PopulateVariable(char** variable, const std::string& value) { delete[] * variable; *variable = new char[value.size() + 1]; strcpy(*variable, value.c_str()); } void CommandLineArguments::PopulateVariable(std::string* variable, const std::string& value) { *variable = value; } void CommandLineArguments::PopulateVariable(std::vector* variable, const std::string& value) { bool val = false; if (value == "1" || value == "ON" || value == "on" || value == "On" || value == "TRUE" || value == "true" || value == "True" || value == "yes" || value == "Yes" || value == "YES") { val = true; } variable->push_back(val); } void CommandLineArguments::PopulateVariable(std::vector* variable, const std::string& value) { char* res = nullptr; variable->push_back(static_cast(strtol(value.c_str(), &res, 10))); // if ( res && *res ) // { // Can handle non-int // } } void CommandLineArguments::PopulateVariable(std::vector* variable, const std::string& value) { char* res = nullptr; variable->push_back(strtod(value.c_str(), &res)); // if ( res && *res ) // { // Can handle non-int // } } void CommandLineArguments::PopulateVariable(std::vector* variable, const std::string& value) { char* var = new char[value.size() + 1]; strcpy(var, value.c_str()); variable->push_back(var); } void CommandLineArguments::PopulateVariable(std::vector* variable, const std::string& value) { variable->push_back(value); } bool CommandLineArguments::PopulateVariable( CommandLineArgumentsCallbackStructure* cs, const char* value) { // Call the callback if (cs->Callback) { if (!cs->Callback(cs->Argument, value, cs->CallData)) { this->Internals->LastArgument--; return false; } } CommandLineArguments_DEBUG("Set argument: " << cs->Argument << " to " << value); if (cs->Variable) { std::string var = "1"; if (value) { var = value; } switch (cs->VariableType) { case CommandLineArguments::INT_TYPE: this->PopulateVariable(static_cast(cs->Variable), var); break; case CommandLineArguments::DOUBLE_TYPE: this->PopulateVariable(static_cast(cs->Variable), var); break; case CommandLineArguments::STRING_TYPE: this->PopulateVariable(static_cast(cs->Variable), var); break; case CommandLineArguments::STL_STRING_TYPE: this->PopulateVariable(static_cast(cs->Variable), var); break; case CommandLineArguments::BOOL_TYPE: this->PopulateVariable(static_cast(cs->Variable), var); break; case CommandLineArguments::VECTOR_BOOL_TYPE: this->PopulateVariable(static_cast*>(cs->Variable), var); break; case CommandLineArguments::VECTOR_INT_TYPE: this->PopulateVariable(static_cast*>(cs->Variable), var); break; case CommandLineArguments::VECTOR_DOUBLE_TYPE: this->PopulateVariable(static_cast*>(cs->Variable), var); break; case CommandLineArguments::VECTOR_STRING_TYPE: this->PopulateVariable(static_cast*>(cs->Variable), var); break; case CommandLineArguments::VECTOR_STL_STRING_TYPE: this->PopulateVariable( static_cast*>(cs->Variable), var); break; default: std::cerr << "Got unknown variable type: \"" << cs->VariableType << "\"" << std::endl; this->Internals->LastArgument--; return false; } } return true; } } // namespace KWSYS_NAMESPACE