diff options
-rw-r--r-- | src/cam/options.cpp | 176 | ||||
-rw-r--r-- | src/cam/options.h | 30 |
2 files changed, 203 insertions, 3 deletions
diff --git a/src/cam/options.cpp b/src/cam/options.cpp index 204081f3..4c9f3a36 100644 --- a/src/cam/options.cpp +++ b/src/cam/options.cpp @@ -27,6 +27,9 @@ const char *Option::typeName() const case OptionString: return "string"; + + case OptionKeyValue: + return "key=value"; } return "unknown"; @@ -82,6 +85,15 @@ bool OptionsBase<T>::parseValue(const T &opt, const Option &option, case OptionString: value = OptionValue(optarg ? optarg : ""); break; + + case OptionKeyValue: + KeyValueParser *kvParser = option.keyValueParser; + KeyValueParser::Options keyValues = kvParser->parse(optarg); + if (!keyValues.valid()) + return false; + + value = OptionValue(keyValues); + break; } values_[opt] = value; @@ -95,6 +107,141 @@ void OptionsBase<T>::clear() } template class OptionsBase<int>; +template class OptionsBase<std::string>; + +/* ----------------------------------------------------------------------------- + * KeyValueParser + */ + +bool KeyValueParser::addOption(const char *name, OptionType type, + const char *help, OptionArgument argument) +{ + if (!name) + return false; + if (!help || help[0] == '\0') + return false; + if (argument != ArgumentNone && type == OptionNone) + return false; + + /* Reject duplicate options. */ + if (optionsMap_.find(name) != optionsMap_.end()) + return false; + + optionsMap_[name] = Option({ 0, type, name, argument, nullptr, + help, nullptr }); + return true; +} + +KeyValueParser::Options KeyValueParser::parse(const char *arguments) +{ + Options options; + + for (const char *pair = arguments; *arguments != '\0'; pair = arguments) { + const char *comma = strchrnul(arguments, ','); + size_t len = comma - pair; + + /* Skip over the comma. */ + arguments = *comma == ',' ? comma + 1 : comma; + + /* Skip to the next pair if the pair is empty. */ + if (!len) + continue; + + std::string key; + std::string value; + + const char *separator = static_cast<const char *>(memchr(pair, '=', len)); + if (!separator) { + key = std::string(pair, len); + value = ""; + } else { + key = std::string(pair, separator - pair); + value = std::string(separator + 1, comma - separator - 1); + } + + /* The key is mandatory, the value might be optional. */ + if (key.empty()) + continue; + + if (optionsMap_.find(key) == optionsMap_.end()) { + std::cerr << "Invalid option " << key << std::endl; + options.clear(); + break; + } + + OptionArgument arg = optionsMap_[key].argument; + if (value.empty() && arg == ArgumentRequired) { + std::cerr << "Option " << key << " requires an argument" + << std::endl; + options.clear(); + break; + } else if (!value.empty() && arg == ArgumentNone) { + std::cerr << "Option " << key << " takes no argument" + << std::endl; + options.clear(); + break; + } + + const Option &option = optionsMap_[key]; + if (!options.parseValue(key, option, value.c_str())) { + std::cerr << "Failed to parse '" << value << "' as " + << option.typeName() << " for option " << key + << std::endl; + options.clear(); + break; + } + } + + return options; +} + +void KeyValueParser::usage(int indent) +{ + unsigned int space = 0; + + for (auto const &iter : optionsMap_) { + const Option &option = iter.second; + unsigned int length = 14; + if (option.argument != ArgumentNone) + length += 1 + strlen(option.typeName()); + if (option.argument == ArgumentOptional) + length += 2; + + if (length > space) + space = length; + } + + space = (space + 7) / 8 * 8; + + for (auto const &iter : optionsMap_) { + const Option &option = iter.second; + std::string argument = option.name; + + if (option.argument != ArgumentNone) { + if (option.argument == ArgumentOptional) + argument += "[="; + else + argument += "="; + argument += option.typeName(); + if (option.argument == ArgumentOptional) + argument += "]"; + } + + std::cerr << std::setw(indent) << std::right << " " + << std::setw(space) << std::left << argument; + + for (const char *help = option.help, *end = help; end;) { + end = strchr(help, '\n'); + if (end) { + std::cerr << std::string(help, end - help + 1); + std::cerr << std::setw(indent + space) << " "; + help = end + 1; + } else { + std::cerr << help << std::endl; + } + } + } +} /* ----------------------------------------------------------------------------- * OptionValue @@ -120,6 +267,11 @@ OptionValue::OptionValue(const std::string &value) { } +OptionValue::OptionValue(const KeyValueParser::Options &value) + : type_(OptionKeyValue), keyValues_(value) +{ +} + OptionValue::operator int() const { if (type_ != OptionInteger) @@ -136,6 +288,14 @@ OptionValue::operator std::string() const return string_; } +OptionValue::operator KeyValueParser::Options() const +{ + if (type_ != OptionKeyValue) + return KeyValueParser::Options(); + + return keyValues_; +} + /* ----------------------------------------------------------------------------- * OptionsParser */ @@ -160,11 +320,22 @@ bool OptionsParser::addOption(int opt, OptionType type, const char *help, return false; options_.push_back(Option({ opt, type, name, argument, argumentName, - help })); + help, nullptr })); optionsMap_[opt] = &options_.back(); return true; } +bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help, + const char *name) +{ + if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired, + "key=value[,key=value,...]")) + return false; + + options_.back().keyValueParser = parser; + return true; +} + OptionsParser::Options OptionsParser::parse(int argc, char **argv) { OptionsParser::Options options; @@ -301,6 +472,9 @@ void OptionsParser::usage() std::cerr << help << std::endl; } } + + if (option.keyValueParser) + option.keyValueParser->usage(indent); } } diff --git a/src/cam/options.h b/src/cam/options.h index 8b611d37..e1fd62ec 100644 --- a/src/cam/options.h +++ b/src/cam/options.h @@ -11,6 +11,9 @@ #include <list> #include <map> +class KeyValueParser; +class OptionValue; + enum OptionArgument { ArgumentNone, ArgumentRequired, @@ -21,6 +24,7 @@ enum OptionType { OptionNone, OptionInteger, OptionString, + OptionKeyValue, }; struct Option { @@ -30,14 +34,13 @@ struct Option { OptionArgument argument; const char *argumentName; const char *help; + KeyValueParser *keyValueParser; bool hasShortOption() const { return isalnum(opt); } bool hasLongOption() const { return name != nullptr; } const char *typeName() const; }; -class OptionValue; - template <typename T> class OptionsBase { @@ -47,6 +50,7 @@ public: const OptionValue &operator[](const T &opt) const; private: + friend class KeyValueParser; friend class OptionsParser; bool parseValue(const T &opt, const Option &option, const char *value); @@ -55,6 +59,23 @@ private: std::map<T, OptionValue> values_; }; +class KeyValueParser +{ +public: + class Options : public OptionsBase<std::string> + { + }; + + bool addOption(const char *name, OptionType type, const char *help, + OptionArgument argument = ArgumentNone); + + Options parse(const char *arguments); + void usage(int indent); + +private: + std::map<std::string, Option> optionsMap_; +}; + class OptionValue { public: @@ -62,16 +83,19 @@ public: OptionValue(int value); OptionValue(const char *value); OptionValue(const std::string &value); + OptionValue(const KeyValueParser::Options &value); OptionType type() const { return type_; } operator int() const; operator std::string() const; + operator KeyValueParser::Options() const; private: OptionType type_; int integer_; std::string string_; + KeyValueParser::Options keyValues_; }; class OptionsParser @@ -85,6 +109,8 @@ public: const char *name = nullptr, OptionArgument argument = ArgumentNone, const char *argumentName = nullptr); + bool addOption(int opt, KeyValueParser *parser, const char *help, + const char *name = nullptr); Options parse(int argc, char *argv[]); void usage(); |