| /*------------------------------------------------------------------------- |
| * 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 <set> |
| #include <sstream> |
| #include <cstring> |
| #include <stdexcept> |
| #include <algorithm> |
| |
| namespace de |
| { |
| namespace cmdline |
| { |
| |
| namespace |
| { |
| struct Help { typedef bool ValueType; }; |
| } |
| |
| namespace detail |
| { |
| |
| inline const char* getNamedValueName (const void* namedValue) |
| { |
| return static_cast<const NamedValue<deUint8>*>(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<Help>("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<string, const OptInfo*> OptMap; |
| typedef set<const OptInfo*> OptSet; |
| |
| OptMap shortOptMap; |
| OptMap longOptMap; |
| OptSet seenOpts; |
| bool allOk = true; |
| |
| DE_ASSERT(dst->m_args.empty() && dst->m_options.empty()); |
| |
| for (vector<OptInfo>::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->m_options.get<Help>()); |
| |
| 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->m_options.get<Help>()) |
| allOk = false; |
| |
| return allOk; |
| } |
| |
| void Parser::help (std::ostream& str) const |
| { |
| for (vector<OptInfo>::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*)((deUintptr)curValue + opt.namedValueStride)) |
| { |
| if (curValue != opt.namedValues) |
| str << "|"; |
| str << getNamedValueName(curValue); |
| } |
| |
| str << "]"; |
| } |
| else if (!opt.isFlag) |
| str << "=<value>"; |
| |
| 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(); |
| } |
| |
| 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*)((deUintptr)curValue + stride)) |
| { |
| if (srcStr == getNamedValueName(curValue)) |
| return curValue; |
| } |
| |
| throw std::invalid_argument("unrecognized value '" + srcStr + "'"); |
| } |
| |
| } // detail |
| |
| // Default / parsing functions |
| |
| template<> |
| void getTypeDefault (bool* dst) |
| { |
| *dst = false; |
| } |
| |
| template<> |
| void parseType<bool> (const char*, bool* dst) |
| { |
| *dst = true; |
| } |
| |
| template<> |
| void parseType<std::string> (const char* src, std::string* dst) |
| { |
| *dst = src; |
| } |
| |
| template<> |
| void parseType<int> (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, deUint64); |
| |
| 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<deUint64> s_namedValues[] = |
| { |
| { "zero", 0 }, |
| { "one", 1 }, |
| { "huge", ~0ull } |
| }; |
| |
| parser << Option<TestStringOpt> ("s", "string", "String option") |
| << Option<TestStringDefOpt> ("x", "xyz", "String option w/ default value", "foo") |
| << Option<TestIntOpt> ("i", "int", "Int option") |
| << Option<TestBoolOpt> ("b", "bool", "Test boolean flag") |
| << Option<TestNamedOpt> ("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<TestStringOpt>()); |
| DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>()); |
| DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == 1); |
| DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false); |
| DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "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<TestStringOpt>() == "test value"); |
| DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 9); |
| DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); |
| DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == ~0ull); |
| DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "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<TestStringOpt>() == "foo"); |
| DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); |
| DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>()); |
| |
| 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<TestStringOpt>() == "--"); |
| DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); |
| DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>()); |
| |
| 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<TestStringOpt>() == ""); |
| DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == ""); |
| DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); |
| } |
| } |
| } |
| |
| } // cmdline |
| } // de |