/** Utility class for parsing the commandline passed to WinMain and extracting options and option arguments Author: Martin Szydlowski */ #include #include //using namespace std; static enum arg_types { ARGUMENT_NONE, ARGUMENT_REQUIRED, ARGUMENT_OPTIONAL }; class WinCmdLineParser { public: // describes one option struct option_info { option_info(): shortname(""), longname(""), argtype(ARGUMENT_NONE) { } option_info(const std::string& sname, const std::string& lname, arg_types atype): shortname(sname), longname(lname), argtype(atype) { } bool operator == (const option_info& rhs) const { if ((!(shortname.empty() || rhs.shortname.empty()) && shortname == rhs.shortname) || (!(longname.empty() || rhs.longname.empty()) && longname == rhs.longname)) return true; else return false; } std::string shortname; std::string longname; arg_types argtype; }; struct option_found { option_found(): shortname(""), longname(""), optarg("") { } option_found(const std::string& sname, const std::string& lname, const std::string& arg): shortname(sname), longname(lname), optarg(arg) { } bool operator == (const option_found& rhs) const { if ((!(shortname.empty() || rhs.shortname.empty()) && shortname == rhs.shortname) || (!(longname.empty() || rhs.longname.empty()) && longname == rhs.longname)) return true; else return false; } std::string shortname; std::string longname; std::string optarg; }; WinCmdLineParser(const char * strCmdLine, bool caseSensitive = false): case_sensitive(caseSensitive), reparse(true) { op_case_transform = tolower; splitStrCmdLine(strCmdLine, m_argv); } ~WinCmdLineParser() { } String dumpOptSet() { std::string sDump; for (std::vector::iterator it = m_optset.begin(); it != m_optset.end(); it ++) { sDump += it->shortname + ", " + it->longname + "\n"; } return sDump; } // add an option with short & long name (each optional, but one mandatory) // only alphanumeric chars valid WinCmdLineParser& addOpt(const std::string& sname, const std::string& lname, arg_types argtype) { std::string shortname = sname; std::string longname = lname; //bool invalid = false; // check sanity: presence & length if (shortname.size() == 0 && longname.size() == 0) throw std::string("Invalid option specification: Specify at least a short name or a long name"); //invalid = true; if (shortname.size() > 1) throw std::string("Invalid option specification: Short name too long, must be one character"); //invalid = true; if (longname.size() == 1) throw std::string("Invalid option specification: Long name too short, must be at least two characters"); //invalid = true; // check sanity: valid characters static std::string valid_chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); for (size_t i = 0; i < shortname.length(); i ++) if (valid_chars.find(shortname[i]) == std::string::npos) throw std::string("Invalid option specification: Invalid character in short name: " + shortname); //invalid = true; for (size_t i = 0; i < longname.length(); i ++) if (valid_chars.find(longname[i]) == std::string::npos) throw std::string("Invalid option specification: Invalid character in long name: " + longname); //invalid = true; // if all is well add option to list //if (!invalid) //{ // convert to all upper case when case-insensitive options if (!case_sensitive) { std::transform(shortname.begin(), shortname.end(), shortname.begin(), op_case_transform); std::transform(longname.begin(), longname.end(), longname.begin(), op_case_transform); } // try inserting std::pair res = insertOpt(option_info(shortname, longname, argtype), m_optset); if (!res.second) { throw std::string("Conflicting option specification: (" + (shortname.empty() ? "" : " -" + shortname) + (longname.empty() ? "" : " --" + longname) + ") conflicts with (" + (res.first.shortname.empty() ? "" : " -" + res.first.shortname) + (res.first.longname.empty() ? "" : " --" + res.first.longname) + ")"); } else { reparse = true; } //} return *this; } // returns true when option present, false otherwise bool getOpt(const std::string& opt) { // dummy string std::string optarg; return getOpt(opt, optarg); } // returns true when option was present in the cmd-line and puts the argument // in optarg, when present bool getOpt(const std::string& opt, std::string& optarg) { if (reparse) { parseOpt(m_argv, m_optset, m_optfound, m_noopt); reparse = false; } optarg.clear(); std::string option = opt; option_found of; if (!case_sensitive) std::transform(option.begin(), option.end(), option.begin(), op_case_transform); if (option.length() == 1) // short option of.shortname = option; else if (option.length() > 1) // long option of.longname = option; else // don't mess with me return false; if (findOptArg(m_optfound, of)) { optarg = of.optarg; return true; } else { return false; } } // returns true when any non-option arguments were in the cmd line // and places them in the arguments vector bool getArgs(std::vector& arguments) { if (reparse) { parseOpt(m_argv, m_optset, m_optfound, m_noopt); reparse = false; } arguments.clear(); arguments = m_noopt; if (m_noopt.size() > 0) return true; else return false; } protected: // split a space-separated string argument in an array of strings // sensitive to ", the only windows way to pass strings with embedded spaces void splitStrCmdLine(const char *strCmdLine, std::vector& argv) { std::string cmdline(strCmdLine); argv.clear(); std::string tmp; unsigned int strpos = 0; bool skipspace = false; while (strpos < cmdline.length()) { while (strpos < cmdline.length() && cmdline[strpos] == ' ') strpos++; tmp.clear(); while (strpos < cmdline.length() && (cmdline[strpos] != ' ' || skipspace)) { if (cmdline[strpos] == '"') { ++ strpos; skipspace = !skipspace; if (!skipspace) continue; } tmp += cmdline[strpos ++]; } argv.push_back(tmp); } //std::ofstream testout("test.txt"); //testout << cmdline << "\n\n"; //std::vector::iterator it = argv.begin(); //while (it != argv.end()) //{ // testout << *it << "\n"; // ++ it; //} //testout.close(); } void parseOpt(const std::vector& argv, const std::vector& optset, std::vector& optfound, std::vector& noopt) { // reset old options list optfound.clear(); // bail out when nothing to parse if (argv.empty() || optset.empty()) return; // iterate through argv and create opt_founds size_t item = 0; while (item < argv.size()) { // dummy info for lookup std::string optstr; option_info info; // skip empty items, shold not happen actually ... if (argv[item].length() > 0) { // check if item might be option (starts with /, - or --) if (argv[item][0] == '-') { // unix-style option, one dash - long or short option // two dashes - long option optstr = argv[item].substr(1, argv[item].length()); if (optstr.length() > 1 && optstr[0] == '-') // long option { optstr = optstr.substr(1, optstr.length()); if (optstr.length() == 1) // invalid optstr = ""; } } else if (argv[item][0] == '/') { // windows-style option, long or short optstr = argv[item].substr(1, argv[item].length()); } else { // no option and no option argument since these are skipped // -> must be a program argument noopt.push_back(argv[item]); ++ item; continue; } if (!case_sensitive) std::transform(optstr.begin(), optstr.end(), optstr.begin(), op_case_transform); if (optstr.length() == 1) // short option info.shortname = optstr; else if (optstr.length() > 1) // long option info.longname = optstr; // lookup if (findOpt(optset, info)) { // find optarg std::string optarg; if (item + 1 < argv.size()) optarg = argv[item + 1]; std::pair res; switch (info.argtype) { case ARGUMENT_NONE: res = insertOptArg(option_found(info.shortname, info.longname, ""), optfound); if (!res.second) { throw std::string("Duplicate option: (" + (res.first.shortname.empty() ? "" : " -" + res.first.shortname) + (res.first.longname.empty() ? "" : " --" + res.first.longname) + ") allready specified"); } break; case ARGUMENT_REQUIRED: if (optarg.length() > 0 && optarg[0] != '-' && optarg[0] != '/') { res = insertOptArg(option_found(info.shortname, info.longname, optarg), optfound); if (!res.second) { throw std::string("Duplicate option: (" + (res.first.shortname.empty() ? "" : " -" + res.first.shortname) + (res.first.longname.empty() ? "" : " --" + res.first.longname) + ") allready specified with argument: " + res.first.optarg); } ++ item; } else { throw std::string("Missing argument for option: " + argv[item]); } break; case ARGUMENT_OPTIONAL: if (optarg.length() > 0 && optarg[0] != '-' && optarg[0] != '/') { res = insertOptArg(option_found(info.shortname, info.longname, optarg), optfound); if (!res.second) { throw std::string("Duplicate option: (" + (res.first.shortname.empty() ? "" : " -" + res.first.shortname) + (res.first.longname.empty() ? "" : " --" + res.first.longname) + ") allready specified with argument: " + res.first.optarg); } ++ item; } else { res = insertOptArg(option_found(info.shortname, info.longname, ""), optfound); if (!res.second) { throw std::string("Duplicate option: (" + (res.first.shortname.empty() ? "" : " -" + res.first.shortname) + (res.first.longname.empty() ? "" : " --" + res.first.longname) + ") allready specified"); } } break; default: break; } } // otherwise panic else { throw std::string("Invalid option: " + argv[item]); } } ++ item; } } std::pair insertOpt(const option_info& optinfo, std::vector& optset) { // check for conflicts for (size_t i = 0; i < optset.size(); i ++) { if (optinfo == optset[i]) { return std::pair(optset[i], false); } } // no conflicts - insert optset.push_back(optinfo); return std::pair(optinfo, true); } bool findOpt(const std::vector& optset, option_info& optinfo) { // find entry for (size_t i = 0; i < optset.size(); i ++) { if (optinfo == optset[i]) { optinfo = optset[i]; return true; } } // not found - reset return false; } std::pair insertOptArg(const option_found& of, std::vector& optfound) { // check for conflicts for (size_t i = 0; i < optfound.size(); i ++) { if (of == optfound[i]) { return std::pair(optfound[i], false); } } // no conflicts - insert optfound.push_back(of); return std::pair(of, true); } bool findOptArg(const std::vector& optfound, option_found& of) { // find entry for (size_t i = 0; i < optfound.size(); i ++) { if (of == optfound[i]) { // found - set values of = optfound[i]; return true; } } // not found return false; } // holds the elements of the command line std::vector m_argv; // holds the elements of the command line which are not options or option arguments std::vector m_noopt; // all options we recognize std::vector m_optset; // all options we have found so far std::vector m_optfound; // parse options case sensitive? bool case_sensitive; // reparse list for next getOpt bool reparse; // poiter to unary function for uppercase transform; int (*op_case_transform)(int); };