/*------------------------------------------------------------------------- * drawElements C++ Base Library * ----------------------------- * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Command line parser. *//*--------------------------------------------------------------------*/ #include "deCommandLine.hpp" #include #include #include #include #include namespace de { namespace cmdline { namespace { DE_DECLARE_COMMAND_LINE_OPT(Help, bool); } namespace detail { inline const char *getNamedValueName(const void *namedValue) { return static_cast *>(namedValue)->name; } using std::set; TypedFieldMap::TypedFieldMap(void) { } TypedFieldMap::~TypedFieldMap(void) { clear(); } void TypedFieldMap::clear(void) { for (Map::const_iterator iter = m_fields.begin(); iter != m_fields.end(); ++iter) { if (iter->second.value) iter->second.destructor(iter->second.value); } m_fields.clear(); } bool TypedFieldMap::contains(const std::type_info *key) const { return m_fields.find(key) != m_fields.end(); } const TypedFieldMap::Entry &TypedFieldMap::get(const std::type_info *key) const { Map::const_iterator pos = m_fields.find(key); if (pos != m_fields.end()) return pos->second; else throw std::out_of_range("Value not set"); } void TypedFieldMap::set(const std::type_info *key, const Entry &value) { Map::iterator pos = m_fields.find(key); if (pos != m_fields.end()) { pos->second.destructor(pos->second.value); pos->second.value = DE_NULL; pos->second = value; } else m_fields.insert(std::make_pair(key, value)); } Parser::Parser(void) { addOption(Option("h", "help", "Show this help")); } Parser::~Parser(void) { } void Parser::addOption(const OptInfo &option) { m_options.push_back(option); } bool Parser::parse(int numArgs, const char *const *args, CommandLine *dst, std::ostream &err) const { typedef map OptMap; typedef set OptSet; OptMap shortOptMap; OptMap longOptMap; OptSet seenOpts; bool allOk = true; DE_ASSERT(dst->m_args.empty() && dst->m_options.empty()); for (vector::const_iterator optIter = m_options.begin(); optIter != m_options.end(); optIter++) { const OptInfo &opt = *optIter; DE_ASSERT(opt.shortName || opt.longName); if (opt.shortName) { DE_ASSERT(shortOptMap.find(opt.shortName) == shortOptMap.end()); shortOptMap[opt.shortName] = &opt; } if (opt.longName) { DE_ASSERT(longOptMap.find(opt.longName) == longOptMap.end()); longOptMap[opt.longName] = &opt; } // Set default values. if (opt.defaultValue) opt.dispatchParse(&opt, opt.defaultValue, &dst->m_options); else if (opt.setDefault) opt.setDefault(&dst->m_options); } DE_ASSERT(!dst->helpSpecified()); for (int argNdx = 0; argNdx < numArgs; argNdx++) { const char *arg = args[argNdx]; int argLen = (int)strlen(arg); if (arg[0] == '-' && arg[1] == '-' && arg[2] == 0) { // End of option list (--) for (int optNdx = argNdx + 1; optNdx < numArgs; optNdx++) dst->m_args.push_back(args[optNdx]); break; } else if (arg[0] == '-') { const bool isLongName = arg[1] == '-'; const char *nameStart = arg + (isLongName ? 2 : 1); const char *nameEnd = std::find(nameStart, arg + argLen, '='); const bool hasImmValue = nameEnd != (arg + argLen); const OptMap &optMap = isLongName ? longOptMap : shortOptMap; OptMap::const_iterator optPos = optMap.find(string(nameStart, nameEnd)); const OptInfo *opt = optPos != optMap.end() ? optPos->second : DE_NULL; if (!opt) { err << "Unrecognized command line option '" << arg << "'\n"; allOk = false; continue; } if (seenOpts.find(opt) != seenOpts.end()) { err << "Command line option '--" << opt->longName << "' specified multiple times\n"; allOk = false; continue; } seenOpts.insert(opt); if (opt->isFlag) { if (!hasImmValue) { opt->dispatchParse(opt, DE_NULL, &dst->m_options); } else { err << "No value expected for command line option '--" << opt->longName << "'\n"; allOk = false; } } else { const bool hasValue = hasImmValue || (argNdx + 1 < numArgs); if (hasValue) { const char *value = hasValue ? (hasImmValue ? nameEnd + 1 : args[argNdx + 1]) : DE_NULL; if (!hasImmValue) argNdx += 1; // Skip value try { opt->dispatchParse(opt, value, &dst->m_options); } catch (const std::exception &e) { err << "Got error parsing command line option '--" << opt->longName << "': " << e.what() << "\n"; allOk = false; } } else { err << "Expected value for command line option '--" << opt->longName << "'\n"; allOk = false; } } } else { // Not an option dst->m_args.push_back(arg); } } // Help specified? if (dst->helpSpecified()) allOk = false; return allOk; } void Parser::help(std::ostream &str) const { for (vector::const_iterator optIter = m_options.begin(); optIter != m_options.end(); ++optIter) { const OptInfo &opt = *optIter; str << " "; if (opt.shortName) str << "-" << opt.shortName; if (opt.shortName && opt.longName) str << ", "; if (opt.longName) str << "--" << opt.longName; if (opt.namedValues) { str << "=["; for (const void *curValue = opt.namedValues; curValue != opt.namedValuesEnd; curValue = (const void *)((uintptr_t)curValue + opt.namedValueStride)) { if (curValue != opt.namedValues) str << "|"; str << getNamedValueName(curValue); } str << "]"; } else if (!opt.isFlag) str << "="; str << "\n"; if (opt.description) str << " " << opt.description << "\n"; if (opt.defaultValue) str << " default: '" << opt.defaultValue << "'\n"; str << "\n"; } } void CommandLine::clear(void) { m_options.clear(); m_args.clear(); } bool CommandLine::helpSpecified(void) const { return m_options.get(); } const void *findNamedValueMatch(const char *src, const void *namedValues, const void *namedValuesEnd, size_t stride) { std::string srcStr(src); for (const void *curValue = namedValues; curValue != namedValuesEnd; curValue = (const void *)((uintptr_t)curValue + stride)) { if (srcStr == getNamedValueName(curValue)) return curValue; } throw std::invalid_argument("unrecognized value '" + srcStr + "'"); } } // namespace detail // Default / parsing functions template <> void getTypeDefault(bool *dst) { *dst = false; } template <> void parseType(const char *, bool *dst) { *dst = true; } template <> void parseType(const char *src, std::string *dst) { *dst = src; } template <> void parseType(const char *src, int *dst) { std::istringstream str(src); str >> *dst; if (str.bad() || !str.eof()) throw std::invalid_argument("invalid integer literal"); } // Tests DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt, std::string); DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt, std::string); DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt, int); DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt, bool); DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt, uint64_t); void selfTest(void) { // Parsing with no options. { Parser parser; { std::ostringstream err; CommandLine cmdLine; const bool parseOk = parser.parse(0, DE_NULL, &cmdLine, err); DE_TEST_ASSERT(parseOk && err.str().empty()); } { const char *args[] = {"-h"}; std::ostringstream err; CommandLine cmdLine; const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(err.str().empty()); // No message about -h } { const char *args[] = {"--help"}; std::ostringstream err; CommandLine cmdLine; const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(err.str().empty()); // No message about -h } { const char *args[] = {"foo", "bar", "baz baz"}; std::ostringstream err; CommandLine cmdLine; const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(parseOk && err.str().empty()); DE_TEST_ASSERT(cmdLine.getArgs().size() == DE_LENGTH_OF_ARRAY(args)); for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(args); ndx++) DE_TEST_ASSERT(cmdLine.getArgs()[ndx] == args[ndx]); } } // Parsing with options. { Parser parser; static const NamedValue s_namedValues[] = {{"zero", 0}, {"one", 1}, {"huge", ~0ull}}; parser << Option("s", "string", "String option") << Option("x", "xyz", "String option w/ default value", "foo") << Option("i", "int", "Int option") << Option("b", "bool", "Test boolean flag") << Option("n", "named", "Test named opt", DE_ARRAY_BEGIN(s_namedValues), DE_ARRAY_END(s_namedValues), "one"); { std::ostringstream err; DE_TEST_ASSERT(err.str().empty()); parser.help(err); DE_TEST_ASSERT(!err.str().empty()); } // Default values { CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(0, DE_NULL, &cmdLine, err); DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getOption() == 1); DE_TEST_ASSERT(cmdLine.getOption() == false); DE_TEST_ASSERT(cmdLine.getOption() == "foo"); } // Basic parsing { const char *args[] = {"-s", "test value", "-b", "-i=9", "--named=huge"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(cmdLine.getOption() == "test value"); DE_TEST_ASSERT(cmdLine.getOption() == 9); DE_TEST_ASSERT(cmdLine.getOption()); DE_TEST_ASSERT(cmdLine.getOption() == ~0ull); DE_TEST_ASSERT(cmdLine.getOption() == "foo"); } // End of argument list (--) { const char *args[] = {"--string=foo", "-b", "--", "--int=2", "-b"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(cmdLine.getOption() == "foo"); DE_TEST_ASSERT(cmdLine.getOption()); DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getArgs().size() == 2); DE_TEST_ASSERT(cmdLine.getArgs()[0] == "--int=2"); DE_TEST_ASSERT(cmdLine.getArgs()[1] == "-b"); } // Value -- { const char *args[] = {"--string", "--", "-b", "foo"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(cmdLine.getOption() == "--"); DE_TEST_ASSERT(cmdLine.getOption()); DE_TEST_ASSERT(!cmdLine.hasOption()); DE_TEST_ASSERT(cmdLine.getArgs().size() == 1); DE_TEST_ASSERT(cmdLine.getArgs()[0] == "foo"); } // Invalid flag usage { const char *args[] = {"-b=true"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Invalid named option { const char *args[] = {"-n=two"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Unrecognized option (-x) { const char *args[] = {"-x"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Unrecognized option (--xxx) { const char *args[] = {"--xxx"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Invalid int value { const char *args[] = {"--int", "1x"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Arg specified multiple times { const char *args[] = {"-s=2", "-s=3"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Missing value { const char *args[] = {"--int"}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(!parseOk); DE_TEST_ASSERT(!err.str().empty()); } // Empty value --arg= { const char *args[] = {"--string=", "-b", "-x", ""}; CommandLine cmdLine; std::ostringstream err; bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); DE_TEST_ASSERT(parseOk); DE_TEST_ASSERT(err.str().empty()); DE_TEST_ASSERT(cmdLine.getOption() == ""); DE_TEST_ASSERT(cmdLine.getOption() == ""); DE_TEST_ASSERT(cmdLine.getOption()); } } } } // namespace cmdline } // namespace de