[libcamera-devel,RFC,1/2] cam: options: add parser for suboptions

Message ID 20190125212154.26950-2-niklas.soderlund@ragnatech.se
State Superseded
Delegated to: Niklas Söderlund
Headers show
Series
  • cam: make the --format option take arguments
Related show

Commit Message

Niklas Söderlund Jan. 25, 2019, 9:21 p.m. UTC
The cam utility will make use of lists of key/value pairs from the
command line arguments. This is needed so that a user can among other
things describe complex stream format descriptions, which consists of a
set of parameters.

Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
---
 src/cam/options.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++
 src/cam/options.h   | 24 ++++++++++++++++
 2 files changed, 92 insertions(+)

Comments

Laurent Pinchart Jan. 26, 2019, 5:09 p.m. UTC | #1
Hi Niklas,

Thank you for the patch.

On Fri, Jan 25, 2019 at 10:21:53PM +0100, Niklas Söderlund wrote:
> The cam utility will make use of lists of key/value pairs from the
> command line arguments. This is needed so that a user can among other
> things describe complex stream format descriptions, which consists of a
> set of parameters.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
> ---
>  src/cam/options.cpp | 68 +++++++++++++++++++++++++++++++++++++++++++++
>  src/cam/options.h   | 24 ++++++++++++++++
>  2 files changed, 92 insertions(+)
> 
> diff --git a/src/cam/options.cpp b/src/cam/options.cpp
> index 55c42540f92478e6..3299cdfed69703fa 100644
> --- a/src/cam/options.cpp
> +++ b/src/cam/options.cpp
> @@ -180,3 +180,71 @@ void OptionsParser::Options::clear()
>  {
>  	values_.clear();
>  }
> +
> +void SubOptionsParser::addToken(const char *name)

I was going to propose calling this KeyValueParser, and then realized
there was a getsubopt() function in glibc. As the class isn't restricted
to suboptions but could parse any kind of key/value pairs, I think the
name KeyValueParser may still be better.

Should this function take a boolean that specifies if the value is
mandatory ? How about a help text, as in the options parser ? You could
even add a new addOption() overload to OptionsParser that would take a
suboption parser as an argument to automate all this ?

> +{
> +	if (!name)
> +		return;
> +
> +	/* Reject duplicate options. */
> +	for (const char *option : options_)
> +		if (!strcmp(name, option))
> +			return;
> +
> +	options_.push_back(name);

You could store them in a map or unordered_set to ensure uniqueness of
the name.

> +}
> +
> +SubOptionsParser::Options SubOptionsParser::parse(const char *argc)

s/argc/options/ ?

> +{
> +	const char **tokens;
> +	char *dupe, *subs, *value;
> +	Options options;
> +
> +	tokens = new const char *[options_.size() + 1]();
> +	for (unsigned int i = 0; i < options_.size(); i++)
> +		tokens[i] = options_.at(i);
> +
> +	dupe = strdup(argc);
> +	subs = dupe;
> +	while (*subs != '\0') {
> +		int opt = getsubopt(&subs, (char *const *)tokens, &value);
> +
> +		if (opt == -1 || !value || value[0] == '\0') {
> +			if (opt == -1)
> +				std::cerr << "Invalid option " << value << std::endl;
> +			else
> +				std::cerr << "Missing argument for option "
> +					  << tokens[opt] << std::endl;
> +
> +			options.clear();
> +			break;
> +		}
> +
> +		options.values_[tokens[opt]] = value;
> +	}
> +
> +	free(dupe);
> +	delete[] tokens;

This looks pretty complex. Wouldn't it be simpler to hand-code this ?
You could copy the code from Logger::parseLogLevels().

        for (const char *pair = options; *options != '\0'; pair = options) {
                const char *comma = strchrnul(options, ',');
                size_t len = comma - pair;

                /* Skip over the comma. */
                options = *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 *colon = static_cast<const char *>(memchr(pair, ':', len));
                if (!colon) {
                        key = std::string(pair, len);
                        value = "";
                } else {
                        key = std::string(pair, colon - pair);
                        value = std::string(colon + 1, comma - colon - 1);
                }

                /* The key is mandatory, the value is optional. */
                if (key.empty())
                        continue;

                option.values_[key] = value;
        }

(You probably want to return an error instead of continuing if the string
isn't valid)

Up to you.

> +	return options;
> +}
> +
> +bool SubOptionsParser::Options::valid() const
> +{
> +	return !values_.empty();
> +}
> +
> +bool SubOptionsParser::Options::isSet(std::string opt) const
> +{
> +	return values_.find(opt) != values_.end();
> +}
> +
> +const std::string &SubOptionsParser::Options::operator[](std::string opt) const
> +{
> +	return values_.find(opt)->second;
> +}
> +
> +void SubOptionsParser::Options::clear()
> +{
> +	values_.clear();
> +}
> diff --git a/src/cam/options.h b/src/cam/options.h
> index f99ea7300a71c24f..653dda0c76e52251 100644
> --- a/src/cam/options.h
> +++ b/src/cam/options.h
> @@ -57,4 +57,28 @@ private:
>  	std::map<unsigned int, Option *> optionsMap_;
>  };
>  
> +class SubOptionsParser
> +{
> +public:
> +	class Options
> +	{
> +	public:
> +		bool valid() const;
> +		bool isSet(std::string opt) const;

const std::string &opt

> +		const std::string &operator[](std::string opt) const;

const std::string &opt

> +
> +	private:
> +		friend class SubOptionsParser;
> +		std::map<std::string, std::string> values_;
> +		void clear();
> +	};
> +
> +	void addToken(const char *token);
> +
> +	Options parse(const char *argc);
> +
> +private:
> +	std::vector<const char *> options_;
> +};
> +
>  #endif /* __CAM_OPTIONS_H__ */

Patch

diff --git a/src/cam/options.cpp b/src/cam/options.cpp
index 55c42540f92478e6..3299cdfed69703fa 100644
--- a/src/cam/options.cpp
+++ b/src/cam/options.cpp
@@ -180,3 +180,71 @@  void OptionsParser::Options::clear()
 {
 	values_.clear();
 }
+
+void SubOptionsParser::addToken(const char *name)
+{
+	if (!name)
+		return;
+
+	/* Reject duplicate options. */
+	for (const char *option : options_)
+		if (!strcmp(name, option))
+			return;
+
+	options_.push_back(name);
+}
+
+SubOptionsParser::Options SubOptionsParser::parse(const char *argc)
+{
+	const char **tokens;
+	char *dupe, *subs, *value;
+	Options options;
+
+	tokens = new const char *[options_.size() + 1]();
+	for (unsigned int i = 0; i < options_.size(); i++)
+		tokens[i] = options_.at(i);
+
+	dupe = strdup(argc);
+	subs = dupe;
+	while (*subs != '\0') {
+		int opt = getsubopt(&subs, (char *const *)tokens, &value);
+
+		if (opt == -1 || !value || value[0] == '\0') {
+			if (opt == -1)
+				std::cerr << "Invalid option " << value << std::endl;
+			else
+				std::cerr << "Missing argument for option "
+					  << tokens[opt] << std::endl;
+
+			options.clear();
+			break;
+		}
+
+		options.values_[tokens[opt]] = value;
+	}
+
+	free(dupe);
+	delete[] tokens;
+
+	return options;
+}
+
+bool SubOptionsParser::Options::valid() const
+{
+	return !values_.empty();
+}
+
+bool SubOptionsParser::Options::isSet(std::string opt) const
+{
+	return values_.find(opt) != values_.end();
+}
+
+const std::string &SubOptionsParser::Options::operator[](std::string opt) const
+{
+	return values_.find(opt)->second;
+}
+
+void SubOptionsParser::Options::clear()
+{
+	values_.clear();
+}
diff --git a/src/cam/options.h b/src/cam/options.h
index f99ea7300a71c24f..653dda0c76e52251 100644
--- a/src/cam/options.h
+++ b/src/cam/options.h
@@ -57,4 +57,28 @@  private:
 	std::map<unsigned int, Option *> optionsMap_;
 };
 
+class SubOptionsParser
+{
+public:
+	class Options
+	{
+	public:
+		bool valid() const;
+		bool isSet(std::string opt) const;
+		const std::string &operator[](std::string opt) const;
+
+	private:
+		friend class SubOptionsParser;
+		std::map<std::string, std::string> values_;
+		void clear();
+	};
+
+	void addToken(const char *token);
+
+	Options parse(const char *argc);
+
+private:
+	std::vector<const char *> options_;
+};
+
 #endif /* __CAM_OPTIONS_H__ */