Merge "Implement ArgumentsParser::parseArguments and add tests."

This commit is contained in:
Keisuke Kuroyanagi 2014-11-25 01:35:27 +00:00 committed by Android (Google) Code Review
commit 62ac89e149
5 changed files with 195 additions and 24 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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,9 +89,76 @@ 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

View file

@ -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;

View file

@ -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