From patchwork Thu Jan 31 23:47:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 467 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 7FF6C60DB4 for ; Fri, 1 Feb 2019 00:47:45 +0100 (CET) Received: from pendragon.ideasonboard.com (85-76-34-136-nat.elisa-mobile.fi [85.76.34.136]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id E49DB41; Fri, 1 Feb 2019 00:47:43 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1548978465; bh=2kwFiiV5dhbZPpqRDRQdFylZbKrvGmQD2D8xsk42WuM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=GXEM+lH6n+ev5j6lbqAy/vaEZ8GfdjMNzp6MBnUjYCmjpTufwZ4f+OZ2yU9fEKS0b cb9bUgzWIP7av6b9jvyXBe6nQHVlp/3K5j1lb8OW23wFMS+AgFGZV3v2Wf7+dzy0KY ASBOzByxG7ho9sTDozHxDU4hrbO5KOE3pp5BEF48= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Fri, 1 Feb 2019 01:47:20 +0200 Message-Id: <20190131234721.22606-8-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20190131234721.22606-1-laurent.pinchart@ideasonboard.com> References: <20190131234721.22606-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 7/8] cam: options: Add a key=value parser X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 31 Jan 2019 23:47:45 -0000 From: Niklas Söderlund Some options passed to the cam utility need to be complex and specify a list of key=value pairs. Add a new parser to deal with these options, usable on its own to parse key=value pairs from any string. Integrate the KeyValueParser into the existing OptionsParser. The cam application can fully describe all its options in one location and perform full parsing of all arguments in one go. The KeyValueParser also integrates itself with the usage() printing of the OptionsParser. Signed-off-by: Niklas Söderlund Signed-off-by: Laurent Pinchart --- Changes since v1: - Remove OptionsParser::Options::isKeyValue() - Return bool from addOption() - Refactor parse() method - Add option type handling - Remove argumentName from addOption() - Cosmetic changes --- src/cam/options.cpp | 176 +++++++++++++++++++++++++++++++++++++++++++- 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 204081f3cd8e..4c9f3a36d435 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::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::clear() } template class OptionsBase; +template class OptionsBase; + +/* ----------------------------------------------------------------------------- + * 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(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 8b611d374fd5..e1fd62ecd369 100644 --- a/src/cam/options.h +++ b/src/cam/options.h @@ -11,6 +11,9 @@ #include #include +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 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 values_; }; +class KeyValueParser +{ +public: + class Options : public OptionsBase + { + }; + + 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 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();