Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 62ac89e1 authored by Keisuke Kuroyanagi's avatar Keisuke Kuroyanagi Committed by Android (Google) Code Review
Browse files

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

parents dd01ed3a 1f8d4f47
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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;
}
+23 −0
Original line number Diff line number Diff line
@@ -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);

+74 −7
Original line number Diff line number Diff line
@@ -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
+18 −17
Original line number Diff line number Diff line
@@ -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;

+74 −0
Original line number Diff line number Diff line
@@ -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