584 lines
14 KiB
C++
584 lines
14 KiB
C++
/*-------------------------------------------------------------------------
|
|
* 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
|