Implement ArgumentsParser::parseArguments and add tests.
Bug: 10059681 Change-Id: I6511a46c879d7a52d0bb4fcab445a66bc40db98c
This commit is contained in:
parent
681dbc295b
commit
1f8d4f47e4
5 changed files with 195 additions and 24 deletions
|
@ -24,6 +24,12 @@ namespace dicttoolkit {
|
|||
const char *const MakedictExecutor::COMMAND_NAME = "makedict";
|
||||
|
||||
/* static */ int MakedictExecutor::run(const int argc, char **argv) {
|
||||
const ArgumentsAndOptions argumentsAndOptions =
|
||||
getArgumentsParser().parseArguments(argc, argv, true /* printErrorMessages */);
|
||||
if (!argumentsAndOptions.isValid()) {
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Command '%s' has not been implemented yet.\n", COMMAND_NAME);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,29 @@ class ArgumentsAndOptions {
|
|||
return mOptions.find(optionName) != mOptions.end();
|
||||
}
|
||||
|
||||
const std::string &getOptionValue(const std::string &optionName) const {
|
||||
const auto &it = mOptions.find(optionName);
|
||||
ASSERT(it != mOptions.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
bool hasArgument(const std::string &name) const {
|
||||
const auto &it = mArguments.find(name);
|
||||
return it != mArguments.end() && !it->second.empty();
|
||||
}
|
||||
|
||||
const std::string &getSingleArgument(const std::string &name) const {
|
||||
const auto &it = mArguments.find(name);
|
||||
ASSERT(it != mArguments.end() && !it->second.empty());
|
||||
return it->second.front();
|
||||
}
|
||||
|
||||
const std::vector<std::string> &getVariableLengthArguments(const std::string &name) const {
|
||||
const auto &it = mArguments.find(name);
|
||||
ASSERT(it != mArguments.end());
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_ASSIGNMENT_OPERATOR(ArgumentsAndOptions);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
namespace latinime {
|
||||
namespace dicttoolkit {
|
||||
|
||||
const int ArgumentSpec::UNLIMITED_COUNT = -1;
|
||||
const size_t ArgumentSpec::UNLIMITED_COUNT = S_INT_MAX;
|
||||
|
||||
bool ArgumentsParser::validateSpecs() const {
|
||||
std::unordered_set<std::string> argumentNameSet;
|
||||
|
@ -53,7 +53,7 @@ void ArgumentsParser::printUsage(const std::string &commandName,
|
|||
const std::string &optionName = option.first;
|
||||
const OptionSpec &spec = option.second;
|
||||
printf(" [-%s", optionName.c_str());
|
||||
if (spec.takeValue()) {
|
||||
if (spec.needsValue()) {
|
||||
printf(" <%s>", spec.getValueName().c_str());
|
||||
}
|
||||
printf("]");
|
||||
|
@ -74,11 +74,11 @@ void ArgumentsParser::printUsage(const std::string &commandName,
|
|||
const std::string &optionName = option.first;
|
||||
const OptionSpec &spec = option.second;
|
||||
printf(" -%s", optionName.c_str());
|
||||
if (spec.takeValue()) {
|
||||
if (spec.needsValue()) {
|
||||
printf(" <%s>", spec.getValueName().c_str());
|
||||
}
|
||||
printf("\t\t\t%s", spec.getDescription().c_str());
|
||||
if (spec.takeValue() && !spec.getDefaultValue().empty()) {
|
||||
if (spec.needsValue() && !spec.getDefaultValue().empty()) {
|
||||
printf("\tdefault: %s", spec.getDefaultValue().c_str());
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -89,10 +89,77 @@ void ArgumentsParser::printUsage(const std::string &commandName,
|
|||
printf("\n\n");
|
||||
}
|
||||
|
||||
const ArgumentsAndOptions ArgumentsParser::parseArguments(const int argc, char **argv) const {
|
||||
// TODO: Implement
|
||||
const ArgumentsAndOptions ArgumentsParser::parseArguments(const int argc, char **argv,
|
||||
const bool printErrorMessage) const {
|
||||
if (argc <= 0) {
|
||||
AKLOGE("Invalid argc (%d).", argc);
|
||||
ASSERT(false);
|
||||
return ArgumentsAndOptions();
|
||||
}
|
||||
std::unordered_map<std::string, std::string> options;
|
||||
for (const auto &entry : mOptionSpecs) {
|
||||
const std::string &optionName = entry.first;
|
||||
const OptionSpec &optionSpec = entry.second;
|
||||
if (optionSpec.needsValue() && !optionSpec.getDefaultValue().empty()) {
|
||||
// Set default value.
|
||||
options[optionName] = optionSpec.getDefaultValue();
|
||||
}
|
||||
}
|
||||
std::unordered_map<std::string, std::vector<std::string>> arguments;
|
||||
auto argumentSpecIt = mArgumentSpecs.cbegin();
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
if (arg.length() > 1 && arg[0] == '-') {
|
||||
// option
|
||||
const std::string optionName = arg.substr(1);
|
||||
const auto it = mOptionSpecs.find(optionName);
|
||||
if (it == mOptionSpecs.end()) {
|
||||
if (printErrorMessage) {
|
||||
fprintf(stderr, "Unknown option: '%s'\n", optionName.c_str());
|
||||
}
|
||||
return ArgumentsAndOptions();
|
||||
}
|
||||
std::string optionValue;
|
||||
if (it->second.needsValue()) {
|
||||
++i;
|
||||
if (i >= argc) {
|
||||
if (printErrorMessage) {
|
||||
fprintf(stderr, "Missing argument for option '%s'\n", optionName.c_str());
|
||||
}
|
||||
return ArgumentsAndOptions();
|
||||
}
|
||||
optionValue = argv[i];
|
||||
}
|
||||
options[optionName] = optionValue;
|
||||
} else {
|
||||
// argument
|
||||
if (argumentSpecIt == mArgumentSpecs.end()) {
|
||||
if (printErrorMessage) {
|
||||
fprintf(stderr, "Too many arguments.\n");
|
||||
}
|
||||
return ArgumentsAndOptions();
|
||||
}
|
||||
arguments[argumentSpecIt->getName()].push_back(arg);
|
||||
if (arguments[argumentSpecIt->getName()].size() >= argumentSpecIt->getMaxCount()) {
|
||||
++argumentSpecIt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (argumentSpecIt != mArgumentSpecs.end()) {
|
||||
const auto &it = arguments.find(argumentSpecIt->getName());
|
||||
const size_t minCount = argumentSpecIt->getMinCount();
|
||||
const size_t actualcount = it == arguments.end() ? 0 : it->second.size();
|
||||
if (minCount > actualcount) {
|
||||
if (printErrorMessage) {
|
||||
fprintf(stderr, "Not enough arguments. %zd argumant(s) required for <%s>\n",
|
||||
minCount, argumentSpecIt->getName().c_str());
|
||||
}
|
||||
return ArgumentsAndOptions();
|
||||
}
|
||||
}
|
||||
return ArgumentsAndOptions(std::move(options), std::move(arguments));
|
||||
}
|
||||
|
||||
} // namespace dicttoolkit
|
||||
} // namespace latinime
|
||||
|
|
|
@ -35,29 +35,29 @@ class OptionSpec {
|
|||
|
||||
static OptionSpec keyValueOption(const std::string &valueName, const std::string &defaultValue,
|
||||
const std::string &description) {
|
||||
return OptionSpec(true /* takeValue */, valueName, defaultValue, description);
|
||||
return OptionSpec(true /* needsValue */, valueName, defaultValue, description);
|
||||
}
|
||||
|
||||
static OptionSpec switchOption(const std::string &description) {
|
||||
return OptionSpec(false /* takeValue */, "" /* valueName */, "" /* defaultValue */,
|
||||
return OptionSpec(false /* needsValue */, "" /* valueName */, "" /* defaultValue */,
|
||||
description);
|
||||
}
|
||||
|
||||
bool takeValue() const { return mTakeValue; }
|
||||
bool needsValue() const { return mNeedsValue; }
|
||||
const std::string &getValueName() const { return mValueName; }
|
||||
const std::string &getDefaultValue() const { return mDefaultValue; }
|
||||
const std::string &getDescription() const { return mDescription; }
|
||||
|
||||
private:
|
||||
OptionSpec(const bool takeValue, const std::string &valueName, const std::string &defaultValue,
|
||||
OptionSpec(const bool needsValue, const std::string &valueName, const std::string &defaultValue,
|
||||
const std::string &description)
|
||||
: mTakeValue(takeValue), mValueName(valueName), mDefaultValue(defaultValue),
|
||||
: mNeedsValue(needsValue), mValueName(valueName), mDefaultValue(defaultValue),
|
||||
mDescription(description) {}
|
||||
|
||||
// Whether the option have to be used with a value or just a switch.
|
||||
// e.g. 'f' in "command -f /path/to/file" is mTakeValue == true.
|
||||
// 'f' in "command -f -t" is mTakeValue == false.
|
||||
bool mTakeValue;
|
||||
// e.g. 'f' in "command -f /path/to/file" is mNeedsValue == true.
|
||||
// 'f' in "command -f -t" is mNeedsValue == false.
|
||||
bool mNeedsValue;
|
||||
// Name of the value used to show usage.
|
||||
std::string mValueName;
|
||||
std::string mDefaultValue;
|
||||
|
@ -66,32 +66,32 @@ class OptionSpec {
|
|||
|
||||
class ArgumentSpec {
|
||||
public:
|
||||
static const int UNLIMITED_COUNT;
|
||||
static const size_t UNLIMITED_COUNT;
|
||||
|
||||
static ArgumentSpec singleArgument(const std::string &name, const std::string &description) {
|
||||
return ArgumentSpec(name, 1 /* minCount */, 1 /* maxCount */, description);
|
||||
}
|
||||
|
||||
static ArgumentSpec variableLengthArguments(const std::string &name, const int minCount,
|
||||
const int maxCount, const std::string &description) {
|
||||
static ArgumentSpec variableLengthArguments(const std::string &name, const size_t minCount,
|
||||
const size_t maxCount, const std::string &description) {
|
||||
return ArgumentSpec(name, minCount, maxCount, description);
|
||||
}
|
||||
|
||||
const std::string &getName() const { return mName; }
|
||||
int getMinCount() const { return mMinCount; }
|
||||
int getMaxCount() const { return mMaxCount; }
|
||||
size_t getMinCount() const { return mMinCount; }
|
||||
size_t getMaxCount() const { return mMaxCount; }
|
||||
const std::string &getDescription() const { return mDescription; }
|
||||
|
||||
private:
|
||||
DISALLOW_DEFAULT_CONSTRUCTOR(ArgumentSpec);
|
||||
|
||||
ArgumentSpec(const std::string &name, const int minCount, const int maxCount,
|
||||
ArgumentSpec(const std::string &name, const size_t minCount, const size_t maxCount,
|
||||
const std::string &description)
|
||||
: mName(name), mMinCount(minCount), mMaxCount(maxCount), mDescription(description) {}
|
||||
|
||||
const std::string mName;
|
||||
const int mMinCount;
|
||||
const int mMaxCount;
|
||||
const size_t mMinCount;
|
||||
const size_t mMaxCount;
|
||||
const std::string mDescription;
|
||||
};
|
||||
|
||||
|
@ -101,7 +101,8 @@ class ArgumentsParser {
|
|||
const std::vector<ArgumentSpec> &&argumentSpecs)
|
||||
: mOptionSpecs(std::move(optionSpecs)), mArgumentSpecs(std::move(argumentSpecs)) {}
|
||||
|
||||
const ArgumentsAndOptions parseArguments(const int argc, char **argv) const;
|
||||
const ArgumentsAndOptions parseArguments(const int argc, char **argv,
|
||||
const bool printErrorMessage) const;
|
||||
bool validateSpecs() const;
|
||||
void printUsage(const std::string &commandName, const std::string &description) const;
|
||||
|
||||
|
|
|
@ -68,6 +68,80 @@ TEST(ArgumentsParserTests, TestValitadeSpecs) {
|
|||
}
|
||||
}
|
||||
|
||||
int initArgv(char *mutableCommandLine, char **argv) {
|
||||
bool readingSeparator = false;
|
||||
int argc = 1;
|
||||
argv[0] = mutableCommandLine;
|
||||
const size_t length = strlen(mutableCommandLine);
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (mutableCommandLine[i] != ' ' && readingSeparator) {
|
||||
readingSeparator = false;
|
||||
argv[argc] = mutableCommandLine + i;
|
||||
++argc;
|
||||
} else if (mutableCommandLine[i] == ' ' && !readingSeparator) {
|
||||
readingSeparator = true;
|
||||
mutableCommandLine[i] = '\0';
|
||||
}
|
||||
}
|
||||
argv[argc] = nullptr;
|
||||
return argc;
|
||||
}
|
||||
|
||||
TEST(ArgumentsParserTests, TestParseArguments) {
|
||||
std::unordered_map<std::string, OptionSpec> optionSpecs;
|
||||
optionSpecs["a"] = OptionSpec::switchOption("description");
|
||||
optionSpecs["b"] = OptionSpec::keyValueOption("valueName", "default", "description");
|
||||
const std::vector<ArgumentSpec> argumentSpecs = {
|
||||
ArgumentSpec::singleArgument("arg0", "description"),
|
||||
ArgumentSpec::variableLengthArguments("arg1", 0 /* minCount */, 2 /* maxCount */,
|
||||
"description"),
|
||||
};
|
||||
const ArgumentsParser parser =
|
||||
ArgumentsParser(std::move(optionSpecs), std::move(argumentSpecs));
|
||||
|
||||
{
|
||||
char kMutableCommandLine[1024] = "command arg";
|
||||
char *argv[128] = {};
|
||||
const int argc = initArgv(kMutableCommandLine, argv);
|
||||
ASSERT_EQ(2, argc);
|
||||
const ArgumentsAndOptions argumentsAndOptions = parser.parseArguments(
|
||||
argc, argv, false /* printErrorMessages */);
|
||||
EXPECT_FALSE(argumentsAndOptions.hasOption("a"));
|
||||
EXPECT_EQ("default", argumentsAndOptions.getOptionValue("b"));
|
||||
EXPECT_EQ("arg", argumentsAndOptions.getSingleArgument("arg0"));
|
||||
EXPECT_FALSE(argumentsAndOptions.hasArgument("arg1"));
|
||||
}
|
||||
{
|
||||
char kArgumentBuffer[1024] = "command -a arg arg";
|
||||
char *argv[128] = {};
|
||||
const int argc = initArgv(kArgumentBuffer, argv);
|
||||
ASSERT_EQ(4, argc);
|
||||
const ArgumentsAndOptions argumentsAndOptions = parser.parseArguments(
|
||||
argc, argv, false /* printErrorMessages */);
|
||||
EXPECT_TRUE(argumentsAndOptions.hasOption("a"));
|
||||
EXPECT_EQ("default", argumentsAndOptions.getOptionValue("b"));
|
||||
EXPECT_EQ("arg", argumentsAndOptions.getSingleArgument("arg0"));
|
||||
EXPECT_TRUE(argumentsAndOptions.hasArgument("arg1"));
|
||||
EXPECT_EQ(1u, argumentsAndOptions.getVariableLengthArguments("arg1").size());
|
||||
}
|
||||
{
|
||||
char kArgumentBuffer[1024] = "command -b value arg arg1 arg2";
|
||||
char *argv[128] = {};
|
||||
const int argc = initArgv(kArgumentBuffer, argv);
|
||||
ASSERT_EQ(6, argc);
|
||||
const ArgumentsAndOptions argumentsAndOptions = parser.parseArguments(
|
||||
argc, argv, false /* printErrorMessages */);
|
||||
EXPECT_FALSE(argumentsAndOptions.hasOption("a"));
|
||||
EXPECT_EQ("value", argumentsAndOptions.getOptionValue("b"));
|
||||
EXPECT_EQ("arg", argumentsAndOptions.getSingleArgument("arg0"));
|
||||
const std::vector<std::string> &arg1 =
|
||||
argumentsAndOptions.getVariableLengthArguments("arg1");
|
||||
EXPECT_EQ(2u, arg1.size());
|
||||
EXPECT_EQ("arg1", arg1[0]);
|
||||
EXPECT_EQ("arg2", arg1[1]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace dicttoolkit
|
||||
} // namespace latinime
|
||||
|
|
Loading…
Reference in a new issue