From a51b0e6f6aa04216b5fb1c90d887d58f7039eec7 Mon Sep 17 00:00:00 2001 From: kervala Date: Thu, 14 Jan 2016 21:26:10 +0100 Subject: [PATCH] Improve CCmdArgs (a generic command line parser in NeL Misc), fixes #257 --- code/nel/include/nel/misc/cmd_args.h | 82 ++++- code/nel/src/misc/cmd_args.cpp | 465 +++++++++++++++++++++---- code/nel/tools/3d/mesh_export/main.cpp | 81 +++-- 3 files changed, 501 insertions(+), 127 deletions(-) diff --git a/code/nel/include/nel/misc/cmd_args.h b/code/nel/include/nel/misc/cmd_args.h index af0fe28e8..26e097eee 100644 --- a/code/nel/include/nel/misc/cmd_args.h +++ b/code/nel/include/nel/misc/cmd_args.h @@ -31,36 +31,86 @@ namespace NLMISC class CCmdArgs { public: - /// Sets the command line and init _Args variable. You must call this before calling main() - void setArgs (int argc, const char **argv); + CCmdArgs(); - /// Sets the command line and init _Args variable. You must call this before calling main() - void setArgs (const char *args); + struct TArg + { + std::string shortName; // short argument. Eg: o for -o + std::string longName; // long argument. Eg: output for --output + + std::string helpName; // name of argument in help. Eg: + std::string helpDescription; // description of argument in help. Eg: Specifies the directory where to write generated files + + bool found; // all values for this argument + std::vector values; // all values for this argument + }; + + typedef std::vector TArgs; + + /// Add a TArg structure to arguments list. + void addArg(const TArg &arg); + + /// Add an full argument to arguments list. + /// shortName is "p" of the argument is -p + /// longName is "print" of the argument is --print + /// helpName is the name that will be displayed in help if it's a required argument + /// helpDescription is the description of the argument that will be displayed in help + void addArg(const std::string &shortName, const std::string &longName, const std::string &helpName, const std::string &helpDescription); + + /// Add a required argument to arguments list. + /// helpName is the name that will be displayed in help if it's a required argument + /// helpDescription is the description of the argument that will be displayed in help + void addArg(const std::string &helpName, const std::string &helpDescription); + + /// Parse the command line from main() parameters argc and argv and process default arguments. + bool parse(int argc, char **argv); + + /// Parse the command line from a std::string and process default arguments. + bool parse(const std::string &args); + + /// Parse the command linefrom a std::vector and process default arguments. + bool parse(const std::vector &args); /// Returns arguments of the program pass from the user to the program using parameters (ie: "myprog param1 param2") - const NLMISC::CVectorSString &getArgs () const { return _Args; } + const TArgs& getArgs() const { return _Args; } - /// Returns true if the argument if present in the command line (ie: haveArg('p') will return true if -p is in the command line) - bool haveArg (char argName) const; + /// Returns true if the argument if present in the command line (ie: haveArg("p") will return true if -p is in the command line) + bool haveArg(const std::string &argName) const; - /** Returns the parameter linked to an option - * getArg('p') will return toto if -ptoto is in the command line - * getArg('p') will return C:\Documents and Settings\toto.tmp if -p"C:\Documents and Settings\toto.tmp" is in the command line - * It'll thrown an Exception if the argName is not found + /** Returns the parameters linked to an option + * getArg("p") will return toto if -ptoto is in the command line + * getArg("p") will return C:\Documents and Settings\toto.tmp if -p"C:\Documents and Settings\toto.tmp" is in the command line */ - std::string getArg (char argName) const; + std::vector getArg(const std::string &argName) const; /// return true if named long arg is present on the commandline /// eg haveLongArg("toto") returns true if "--toto" or "--toto=xxx" can be found on commandline - bool haveLongArg (const char* argName) const; + bool haveLongArg(const std::string &argName) const; - /// returns the value associated with the given named argument + /// returns values associated with the given named argument /// both "--toto=xxx" and "--toto xxx" are acceptable /// quotes round arguments are stripped - std::string getLongArg (const char* argName) const; + std::vector getLongArg(const std::string &argName) const; + + /// return true if there are arguments that are required + bool needRequiredArg() const; + + /// return true if required or optional args are present on the commandline + bool haveRequiredArg() const; + + /// Returns all additional required parameters + std::vector getRequiredArg() const; + + /// Display help of the program. + void displayHelp(); + + /// Display version of the program. + void displayVersion(); protected: + std::string _ProgramName; + /// Array of arguments pass from the command line - NLMISC::CVectorSString _Args; + TArgs _Args; }; // class CCmdArgs }; // NAMESPACE NLMISC diff --git a/code/nel/src/misc/cmd_args.cpp b/code/nel/src/misc/cmd_args.cpp index 2ef32a6c9..39f4cb849 100644 --- a/code/nel/src/misc/cmd_args.cpp +++ b/code/nel/src/misc/cmd_args.cpp @@ -18,12 +18,12 @@ // Includes // #include "stdmisc.h" - -#include "nel/misc/types_nl.h" -#include "nel/misc/sstring.h" - #include "nel/misc/cmd_args.h" +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + #ifdef DEBUG_NEW #define new DEBUG_NEW #endif @@ -31,114 +31,170 @@ namespace NLMISC { -bool CCmdArgs::haveArg(char argName) const +CCmdArgs::CCmdArgs() { - for(uint32 i = 0; i < _Args.size(); i++) + // add help + addArg("h", "help", "", "Display this help"); + + // add version + addArg("v", "version", "", "Display version of this program"); +} + +void CCmdArgs::addArg(const TArg &arg) +{ + _Args.push_back(arg); +} + +void CCmdArgs::addArg(const std::string &shortName, const std::string &longName, const std::string &helpName, const std::string &helpDescription) +{ + TArg arg; + arg.shortName = shortName; + arg.longName = longName; + arg.helpName = helpName; + arg.helpDescription = helpDescription; + arg.found = false; + + addArg(arg); +} + +void CCmdArgs::addArg(const std::string &helpName, const std::string &helpDescription) +{ + TArg arg; + arg.helpName = helpName; + arg.helpDescription = helpDescription; + arg.found = false; + + addArg(arg); +} + +bool CCmdArgs::haveArg(const std::string &argName) const +{ + // process each argument + for(uint i = 0; i < _Args.size(); i) { - if(_Args[i].size() >= 2 && _Args[i][0] == '-') - { - if(_Args[i][1] == argName) - { - return true; - } - } + const TArg &arg = _Args[i]; + + // return true if long arg found + if (arg.shortName == argName) return arg.found; } + return false; } -std::string CCmdArgs::getArg(char argName) const +std::vector CCmdArgs::getArg(const std::string &argName) const { - for(uint32 i = 0; i < _Args.size(); i++) + // process each argument + for(uint i = 0; i < _Args.size(); ++i) { - if(_Args[i].size() >= 2 && _Args[i][0] == '-') - { - if(_Args[i][1] == argName) - { - /* Remove the first and last '"' : - -c"C:\Documents and Settings\toto.tmp" - will return : - C:\Documents and Settings\toto.tmp - */ - uint begin = 2; - if(_Args[i].size() < 3) - return ""; - //throw Exception ("Parameter '-%c' is malformed, missing content", argName); + const TArg &arg = _Args[i]; - if(_Args[i][begin] == '"') - begin++; - - // End - uint size = (uint)_Args[i].size(); - if(size && _Args[i][size-1] == '"') - size--; - size = (uint)(std::max((int)0, (int)size-(int)begin)); - return _Args[i].substr(begin, size); - } - } + // return values if short arg found + if (arg.shortName == argName && arg.found) return arg.values; } - throw Exception("Parameter '-%c' is not found in command line", argName); + + // return an empty vector + return std::vector(); } -bool CCmdArgs::haveLongArg(const char* argName) const +bool CCmdArgs::haveLongArg(const std::string &argName) const { - for(uint32 i = 0; i < _Args.size(); i++) + // process each argument + for(uint i = 0; i < _Args.size(); ++i) { - if(_Args[i].left(2)=="--" && _Args[i].leftCrop(2).splitTo('=')==argName) - { + const TArg &arg = _Args[i]; + + // return true if long arg found + if (arg.longName == argName) return arg.found; + } + + return false; +} + +std::vector CCmdArgs::getLongArg(const std::string &argName) const +{ + // process each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // return values if long arg found + if (arg.longName == argName && arg.found) return arg.values; + } + + // return an empty vector + return std::vector(); +} + +bool CCmdArgs::needRequiredArg() const +{ + // process each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // they don't have any short or long name, but need a name in help + if (arg.shortName.empty() && arg.longName.empty() && !arg.helpName.empty()) return true; - } } + return false; } -std::string CCmdArgs::getLongArg (const char* argName) const +bool CCmdArgs::haveRequiredArg() const { - for (uint32 i = 0; i < _Args.size(); i++) - { - if (_Args[i].left(2)=="--" && _Args[i].leftCrop(2).splitTo('=')==argName) - { - NLMISC::CSString val= _Args[i].splitFrom('='); - if (!val.empty()) - { - return val.unquoteIfQuoted(); - } - if (i+1<_Args.size() && _Args[i+1].c_str()[0]!='-') - { - return _Args[i+1].unquoteIfQuoted(); - } + // process each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; - return std::string(); - } + // they don't have any short or long name, but need a name in help + if (arg.shortName.empty() && arg.longName.empty() && !arg.helpName.empty()) + return !arg.values.empty(); } - return std::string(); + + return false; } -void CCmdArgs::setArgs(const char *args) +std::vector CCmdArgs::getRequiredArg() const { - _Args.push_back (""); + // process each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; - std::string sargs (args); + // they don't have any short or long name, but need a name in help + if (arg.shortName.empty() && arg.longName.empty() && !arg.helpName.empty()) + return arg.values; + } + + // return an empty vector + return std::vector(); +} + +bool CCmdArgs::parse(const std::string &args) +{ + std::vector argv; std::string::size_type pos1 = 0, pos2 = 0; do { // Look for the first non space character - pos1 = sargs.find_first_not_of (" ", pos2); + pos1 = args.find_first_not_of (" ", pos2); if(pos1 == std::string::npos) break; // Look for the first space or " - pos2 = sargs.find_first_of (" \"", pos1); + pos2 = args.find_first_of (" \"", pos1); if(pos2 != std::string::npos) { // " ? - if(sargs[pos2] == '"') + if(args[pos2] == '"') { // Look for the final \" - pos2 = sargs.find_first_of ("\"", pos2+1); + pos2 = args.find_first_of ("\"", pos2+1); if(pos2 != std::string::npos) { // Look for the first space - pos2 = sargs.find_first_of (" ", pos2+1); + pos2 = args.find_first_of (" ", pos2+1); } } } @@ -146,18 +202,277 @@ void CCmdArgs::setArgs(const char *args) // Compute the size of the string to extract std::string::difference_type length = (pos2 != std::string::npos) ? pos2-pos1 : std::string::npos; - std::string tmp = sargs.substr (pos1, length); - _Args.push_back (tmp); + std::string tmp = args.substr (pos1, length); + argv.push_back (tmp); } while(pos2 != std::string::npos); + + return parse(argv); } -void CCmdArgs::setArgs(int argc, const char **argv) +bool CCmdArgs::parse(int argc, char **argv) { - for (sint i = 0; i < argc; i++) + // convert C strings to STL strings + std::vector args; + + for(sint i = 0; i < argc; ++i) { - _Args.push_back (argv[i]); + args.push_back(argv[i]); } + + return parse(args); +} + +bool CCmdArgs::parse(const std::vector &argv) +{ + // no parameters + if (argv.empty()) return false; + + // first argument is always the program name + _ProgramName = CFile::getFilename(argv.front()); + + // arguments count + uint argc = argv.size(); + + // process each argument + for (sint i = 1; i < argc; i++) + { + std::string name = argv[i]; + +#ifdef NL_OS_WINDOWS + // support / and - under Windows, arguments should be at least 2 characters + if (name.size() > 1 && (name[0] == '-' || name[0] == '/')) +#else + if (name.size() > 1 && name[0] == '-') +#endif + { + // it's a long name if using -- + bool useLongName = name[0] == '-' && name[1] == '-'; + + // extract argument name + name = name.substr(useLongName ? 2:1); + + std::string value; + + if (useLongName) + { + // look if using = to define value + std::string::size_type pos = name.find('='); + + if (pos != std::string::npos) + { + // value is second part, name the first one + value = name.substr(pos+1); + name = name.substr(0, pos); + } + } + else if (name.length() > 1) + { + value = name.substr(1); + name = name.substr(0, 1); + } + + // process each argument definition + for(uint j = 0; j < _Args.size(); ++j) + { + TArg &arg = _Args[j]; + + // only process arguments of the right type + if ((useLongName && name != arg.longName) || (!useLongName && name != arg.shortName)) continue; + + // argument is found + arg.found = true; + + // another argument is required + if (!arg.helpName.empty()) + { + // if the value hasn't be specified by = + if (value.empty() && i+1 < argc) + { + // take next argument + value = argv[++i]; + } + + // add argument value if not empty + if (!value.empty()) + { + arg.values.push_back(value); + } + } + + break; + } + } + else + { + // process each argument definition + for(uint j = 0, len = _Args.size(); j < len; ++j) + { + TArg &arg = _Args[j]; + + // only process arguments that don't have a name + if (!arg.shortName.empty() || !arg.longName.empty()) continue; + + // in fact, if there are more than one required arguments, all arguments are added in first one to simplify + arg.values.push_back(name); + + break; + } + } + } + + // process help if requested or if required arguments are missing + if (haveLongArg("help") || (needRequiredArg() && !haveRequiredArg())) + { + displayHelp(); + return false; + } + + // process version + if (haveLongArg("version")) + { + displayVersion(); + return false; + } + + return true; +} + +void CCmdArgs::displayHelp() +{ + // display program name + printf("Usage: %s ", _ProgramName.c_str()); + + // display optional parameters + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // only short argument is displayed + if (!arg.shortName.empty()) + { + printf("[-%s", arg.shortName.c_str()); + + // a parameter is required + if (!arg.helpName.empty()) + { + printf("<%s>", arg.helpName.c_str()); + } + + printf("]"); + } + } + + sint last = -1; + + // look for last required argument + for(uint i = 0; i < _Args.size(); ++i) + { + if (_Args[i].shortName.empty()) last = (sint)i; + } + + // display required arguments + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // they don't have any short or long name, but need a name in help + if (arg.shortName.empty() && arg.longName.empty() && !arg.helpName.empty()) + { + printf(" <%s>", arg.helpName.c_str()); + + // if last argument, it can support additional arguments + if ((sint)i == last) + { + printf(" [%s...]", arg.helpName.c_str()); + } + } + } + + printf("\n"); + + // display details on each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // not an optional argument + if (arg.shortName.empty() && arg.longName.empty()) continue; + + // a tab + printf("\t"); + + // display short argument + if (!arg.shortName.empty()) + { + printf("-%s", arg.shortName.c_str()); + + // and it's required argument + if (!arg.helpName.empty()) + { + printf(" <%s>", arg.helpName.c_str()); + printf(" or -%s<%s>", arg.shortName.c_str(), arg.helpName.c_str()); + } + } + + // display long argument + if (!arg.longName.empty()) + { + if (!arg.helpName.empty()) + { + // prepend a coma if a short argument was already displayed + if (!arg.shortName.empty()) + { + printf(", "); + } + + // display first syntax for long argument, --arg + printf("--%s <%s>", arg.longName.c_str(), arg.helpName.c_str()); + } + + // prepend " or " if 3 formats for argument + if (!arg.shortName.empty() || !arg.helpName.empty()) + { + printf(" or "); + } + + // display second syntax for long argument, --arg= + printf("--%s", arg.longName.c_str()); + + if (!arg.helpName.empty()) + { + printf("=<%s>", arg.helpName.c_str()); + } + } + + // display argument description + if (!arg.helpDescription.empty()) + { + printf(" : %s", arg.helpDescription.c_str()); + } + + printf("\n"); + } + + // process each argument + for(uint i = 0; i < _Args.size(); ++i) + { + const TArg &arg = _Args[i]; + + // only display required arguments + if (arg.shortName.empty() && arg.longName.empty() && !arg.helpName.empty() && !arg.helpDescription.empty()) + { + printf("\t%s : %s\n", arg.helpName.c_str(), arg.helpDescription.c_str()); + } + } +} + +void CCmdArgs::displayVersion() +{ + // display a verbose version string +#ifdef BUILD_DATE + printf("%s %s (built on %s)\nCopyright (C) %s\n", _ProgramName.c_str(), NL_VERSION, BUILD_DATE, COPYRIGHT); +#endif } }; // NAMESPACE NLMISC diff --git a/code/nel/tools/3d/mesh_export/main.cpp b/code/nel/tools/3d/mesh_export/main.cpp index 81b850a23..b135d719f 100644 --- a/code/nel/tools/3d/mesh_export/main.cpp +++ b/code/nel/tools/3d/mesh_export/main.cpp @@ -24,54 +24,63 @@ #include #include -int printHelp(const NLMISC::CCmdArgs &args) -{ - printf("NeL Mesh Export\n"); - return EXIT_SUCCESS; -} - int main(int argc, char *argv[]) { NLMISC::CApplicationContext app; NLMISC::CCmdArgs args; - args.setArgs(argc, (const char **)argv); - if (args.getArgs().size() == 1 - || args.haveArg('h') - || args.haveLongArg("help")) - return printHelp(args); + args.addArg("d", "dst", "destination", "Destination directory path"); + args.addArg("", "dependlog", "log", "Dependencies log path"); + args.addArg("", "errorlog", "log", "Errors log path"); + args.addArg("filename", "Filename of 3D model to convert"); - const NLMISC::CSString &filePath = args.getArgs().back(); - if (!NLMISC::CFile::fileExists(filePath)) - { - printHelp(args); - nlerror("File '%s' does not exist", filePath.c_str()); - return EXIT_FAILURE; - } + if (!args.parse(argc, argv)) return EXIT_SUCCESS; - CMeshUtilsSettings settings; - settings.SourceFilePath = filePath; - - if (args.haveArg('d')) - settings.DestinationDirectoryPath = args.getArg('d'); - if (settings.DestinationDirectoryPath.empty()) - settings.DestinationDirectoryPath = args.getLongArg("dst"); - if (settings.DestinationDirectoryPath.empty()) - settings.DestinationDirectoryPath = filePath + "_export"; - settings.DestinationDirectoryPath += "/"; - - settings.ToolDependLog = args.getLongArg("dependlog"); - if (settings.ToolDependLog.empty()) - settings.ToolDependLog = settings.DestinationDirectoryPath + "depend.log"; - settings.ToolErrorLog = args.getLongArg("errorlog"); - if (settings.ToolErrorLog.empty()) - settings.ToolErrorLog = settings.DestinationDirectoryPath + "error.log"; + const std::vector &filePathes = args.getRequiredArg(); NL3D::CScene::registerBasics(); NL3D::registerSerial3d(); - return exportScene(settings); + sint res = 0; + + for(uint i = 0; i < filePathes.size(); ++i) + { + std::string filePath = filePathes[i]; + + if (!NLMISC::CFile::fileExists(filePath)) + { + nlerror("File '%s' does not exist", filePath.c_str()); + return EXIT_FAILURE; + } + + CMeshUtilsSettings settings; + settings.SourceFilePath = filePath; + + if (args.haveArg("d")) + settings.DestinationDirectoryPath = args.getArg("d").front(); + + if (settings.DestinationDirectoryPath.empty()) + settings.DestinationDirectoryPath = filePath + "_export"; + + settings.DestinationDirectoryPath = NLMISC::standardizePath(settings.DestinationDirectoryPath); + + if (args.haveLongArg("dependlog")) + settings.ToolDependLog = args.getLongArg("dependlog").front(); + + if (settings.ToolDependLog.empty()) + settings.ToolDependLog = settings.DestinationDirectoryPath + "depend.log"; + + if (args.haveLongArg("errorlog")) + settings.ToolErrorLog = args.getLongArg("errorlog").front(); + + if (settings.ToolErrorLog.empty()) + settings.ToolErrorLog = settings.DestinationDirectoryPath + "error.log"; + + res = exportScene(settings); + } + + return res; } /* end of file */