From patchwork Mon Jan 28 00:41:08 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 426 Return-Path: Received: from bin-mail-out-05.binero.net (bin-mail-out-05.binero.net [195.74.38.228]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0065760DC1 for ; Mon, 28 Jan 2019 01:41:34 +0100 (CET) X-Halon-ID: 6853ec7a-2295-11e9-911a-0050569116f7 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (unknown [89.233.230.99]) by bin-vsp-out-03.atm.binero.net (Halon) with ESMTPA id 6853ec7a-2295-11e9-911a-0050569116f7; Mon, 28 Jan 2019 01:41:10 +0100 (CET) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Mon, 28 Jan 2019 01:41:08 +0100 Message-Id: <20190128004109.25860-6-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190128004109.25860-1-niklas.soderlund@ragnatech.se> References: <20190128004109.25860-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 5/6] 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: Mon, 28 Jan 2019 00:41:35 -0000 Some options passed to the cam utility needs to be complex and specify a list of key=value pairs, add a new parser to deal white these options. The new parser is integrated into the existing OptionsParser and the cam application can fully describe all its options in one location and perform full parsing of all arguments in one go. The new key=value parsers also integrates itself with the usage() printing of the OptionsParser. The new parser can also be used on it's own to parse key=value pairs from different data sources then the command line options. Signed-off-by: Niklas Söderlund --- src/cam/options.cpp | 159 +++++++++++++++++++++++++++++++++++++++++++- src/cam/options.h | 35 ++++++++++ 2 files changed, 193 insertions(+), 1 deletion(-) diff --git a/src/cam/options.cpp b/src/cam/options.cpp index d3bff1cd897a5cfb..f63fd3b495e29986 100644 --- a/src/cam/options.cpp +++ b/src/cam/options.cpp @@ -30,11 +30,22 @@ bool OptionsParser::addOption(int opt, const char *help, const char *name, if (optionsMap_.find(opt) != optionsMap_.end()) return false; - optionsMap_[opt] = Option({ opt, name, argument, argumentName, help }); + optionsMap_[opt] = Option({ opt, name, argument, argumentName, help, + nullptr }); return true; } +void OptionsParser::addOption(int opt, const char *help, KeyValueParser &parser, + const char *name) +{ + if (!addOption(opt, help, name, ArgumentRequired, + "key=value[,key=value,...]")) + return; + + optionsMap_[opt].keyValueParser = &parser; +} + OptionsParser::Options OptionsParser::parse(int argc, char **argv) { OptionsParser::Options options; @@ -103,6 +114,16 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv) } options.values_[c] = optarg ? optarg : ""; + + /* Parse argument as key=values if needed. */ + if (optionsMap_[c].keyValueParser) { + options.keyValues_[c] = optionsMap_[c].keyValueParser->parse(options.values_[c].c_str()); + if (!options.keyValues_[c].valid()) { + usage(); + options.clear(); + break; + } + } } return options; @@ -169,5 +190,141 @@ void OptionsParser::usage() std::cerr << help << std::endl; } } + + if (option.keyValueParser) + option.keyValueParser->usage(indent); + } +} + +bool OptionsParser::Options::isKeyValue(int opt) +{ + return keyValues_.find(opt) != keyValues_.end(); +} + +KeyValueParser::Options OptionsParser::Options::keyValues(int opt) +{ + return keyValues_.find(opt)->second; +} + +void KeyValueParser::addOption(const char *name, + const char *help, + OptionArgument argument, + const char *argumentName) +{ + if (!name) + return; + if (!help || help[0] == '\0') + return; + if (argument != ArgumentNone && !argumentName) + return; + + /* Reject duplicate options. */ + if (optionsMap_.find(name) != optionsMap_.end()) + return; + + optionsMap_[name] = Option({ name, help, argument, argumentName }); +} + +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; + } + + if (value == "" && optionsMap_[key].argument == ArgumentRequired) { + std::cerr << "Missing argument for option " << key << std::endl; + options.clear(); + break; + } + + if (value != "" && optionsMap_[key].argument == ArgumentNone) { + std::cerr << "Argument specified for option " << key << std::endl; + options.clear(); + break; + } + + options.values_[key] = value; + } + + 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.argumentName); + 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.argumentName; + 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; + } + } } } diff --git a/src/cam/options.h b/src/cam/options.h index cb7286a0a8005579..91a78406a601d737 100644 --- a/src/cam/options.h +++ b/src/cam/options.h @@ -27,20 +27,54 @@ public: private: friend class OptionsParser; + friend class KeyValueParser; std::map values_; void clear() { values_.clear(); }; }; +class KeyValueParser +{ +public: + class Options : public OptionsBase + { + }; + + void addOption(const char *name, const char *help, + OptionArgument argument = ArgumentNone, + const char *argumentName = nullptr); + + Options parse(const char *arguments); + void usage(int indent); + +private: + struct Option { + const char *name; + const char *help; + OptionArgument argument; + const char *argumentName; + }; + + std::map optionsMap_; +}; + class OptionsParser { public: class Options : public OptionsBase { + public: + bool isKeyValue(int opt); + KeyValueParser::Options keyValues(int opt); + private: + friend class OptionsParser; + std::map keyValues_; }; bool addOption(int opt, const char *help, const char *name = nullptr, OptionArgument argument = ArgumentNone, const char *argumentName = nullptr); + void addOption(int opt, const char *help, KeyValueParser &parser, + const char *name = nullptr); Options parse(int argc, char *argv[]); void usage(); @@ -52,6 +86,7 @@ private: OptionArgument argument; const char *argumentName; const char *help; + KeyValueParser *keyValueParser; bool hasShortOption() const { return isalnum(opt); } bool hasLongOption() const { return name != nullptr; }