mirror of
https://git.libcamera.org/libcamera/libcamera.git
synced 2025-07-25 01:25:08 +03:00
cam: options: Support parent-child relationship between options
Add support for creating a tree-based hieararchy of options instead of a flat list. This is useful to support options that need to be interpreted in the context of a particular occurrence of an array option. The usage text automatically documents the options in their corresponding context: Options: -c, --camera camera ... Specify which camera to operate on, by id or by index -h, --help Display this help message -I, --info Display information about stream(s) -l, --list List all cameras --list-controls List cameras controls -p, --list-properties List cameras properties -m, --monitor Monitor for hotplug and unplug camera events Options valid in the context of --camera: -C, --capture[=count] Capture until interrupted by user or until <count> frames captured -F, --file[=filename] Write captured frames to disk If the file name ends with a '/', it sets the directory in which to write files, using the default file name. Otherwise it sets the full file path and name. The first '#' character in the file name is expanded to the camera index, stream name and frame sequence number. The default file name is 'frame-#.bin'. -s, --stream key=value[,key=value,...] ... Set configuration of a camera stream height=integer Height in pixels pixelformat=string Pixel format name role=string Role for the stream (viewfinder, video, still, raw) width=integer Width in pixels --strict-formats Do not allow requested stream format(s) to be adjusted --metadata Print the metadata for completed requests Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
This commit is contained in:
parent
b53f68e66c
commit
bb682d2585
2 changed files with 199 additions and 32 deletions
|
@ -79,6 +79,12 @@
|
||||||
* \var Option::isArray
|
* \var Option::isArray
|
||||||
* \brief Whether the option can appear once or multiple times
|
* \brief Whether the option can appear once or multiple times
|
||||||
*
|
*
|
||||||
|
* \var Option::parent
|
||||||
|
* \brief The parent option
|
||||||
|
*
|
||||||
|
* \var Option::children
|
||||||
|
* \brief List of child options, storing all options whose parent is this option
|
||||||
|
*
|
||||||
* \fn Option::hasShortOption()
|
* \fn Option::hasShortOption()
|
||||||
* \brief Tell if the option has a short option specifier (e.g. `-f`)
|
* \brief Tell if the option has a short option specifier (e.g. `-f`)
|
||||||
* \return True if the option has a short option specifier, false otherwise
|
* \return True if the option has a short option specifier, false otherwise
|
||||||
|
@ -96,6 +102,8 @@ struct Option {
|
||||||
const char *help;
|
const char *help;
|
||||||
KeyValueParser *keyValueParser;
|
KeyValueParser *keyValueParser;
|
||||||
bool isArray;
|
bool isArray;
|
||||||
|
Option *parent;
|
||||||
|
std::list<Option> children;
|
||||||
|
|
||||||
bool hasShortOption() const { return isalnum(opt); }
|
bool hasShortOption() const { return isalnum(opt); }
|
||||||
bool hasLongOption() const { return name != nullptr; }
|
bool hasLongOption() const { return name != nullptr; }
|
||||||
|
@ -335,7 +343,7 @@ bool KeyValueParser::addOption(const char *name, OptionType type,
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
|
optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
|
||||||
help, nullptr, false });
|
help, nullptr, false, nullptr, {} });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,6 +480,11 @@ void KeyValueParser::usage(int indent)
|
||||||
* option. It supports empty values, integers, strings, key-value lists, as well
|
* option. It supports empty values, integers, strings, key-value lists, as well
|
||||||
* as arrays of those types. For array values, all array elements shall have the
|
* as arrays of those types. For array values, all array elements shall have the
|
||||||
* same type.
|
* same type.
|
||||||
|
*
|
||||||
|
* OptionValue instances are organized in a tree-based structure that matches
|
||||||
|
* the parent-child relationship of the options added to the parser. Children
|
||||||
|
* are retrieved with the children() function, and are stored as an
|
||||||
|
* OptionsBase<int>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -662,6 +675,15 @@ std::vector<OptionValue> OptionValue::toArray() const
|
||||||
return array_;
|
return array_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Retrieve the list of child values
|
||||||
|
* \return The list of child values
|
||||||
|
*/
|
||||||
|
const OptionsParser::Options &OptionValue::children() const
|
||||||
|
{
|
||||||
|
return children_;
|
||||||
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------------
|
/* -----------------------------------------------------------------------------
|
||||||
* OptionsParser
|
* OptionsParser
|
||||||
*/
|
*/
|
||||||
|
@ -724,6 +746,32 @@ std::vector<OptionValue> OptionValue::toArray() const
|
||||||
* accept an argument, the option value can be access by Options::operator[]()
|
* accept an argument, the option value can be access by Options::operator[]()
|
||||||
* using the option identifier as the key. The order in which different options
|
* using the option identifier as the key. The order in which different options
|
||||||
* are specified on the command line isn't preserved.
|
* are specified on the command line isn't preserved.
|
||||||
|
*
|
||||||
|
* Options can be created with parent-child relationships to organize them as a
|
||||||
|
* tree instead of a flat list. When parsing a command line, the child options
|
||||||
|
* are considered related to the parent option that precedes them. This is
|
||||||
|
* useful when the parent is an array option. The Options values list generated
|
||||||
|
* by the parser then turns into a tree, which each parent value storing the
|
||||||
|
* values of child options that follow that instance of the parent option.
|
||||||
|
* For instance, with a `capture` option specified as a child of a `camera`
|
||||||
|
* array option, parsing the command line
|
||||||
|
*
|
||||||
|
* `--camera 1 --capture=10 --camera 2 --capture=20`
|
||||||
|
*
|
||||||
|
* will return an Options instance containing a single OptionValue instance of
|
||||||
|
* array type, for the `camera` option. The OptionValue will contain two
|
||||||
|
* entries, with the first entry containing the integer value 1 and the second
|
||||||
|
* entry the integer value 2. Each of those entries will in turn store an
|
||||||
|
* Options instance that contains the respective children. The first entry will
|
||||||
|
* store in its children a `capture` option of value 10, and the second entry a
|
||||||
|
* `capture` option of value 20.
|
||||||
|
*
|
||||||
|
* The command line
|
||||||
|
*
|
||||||
|
* `--capture=10 --camera 1`
|
||||||
|
*
|
||||||
|
* would result in a parsing error, as the `capture` option has no preceding
|
||||||
|
* `camera` option on the command line.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -747,13 +795,14 @@ OptionsParser::~OptionsParser() = default;
|
||||||
* mandatory argument, or no argument at all
|
* mandatory argument, or no argument at all
|
||||||
* \param[in] argumentName The argument name used in the help text
|
* \param[in] argumentName The argument name used in the help text
|
||||||
* \param[in] array Whether the option can appear once or multiple times
|
* \param[in] array Whether the option can appear once or multiple times
|
||||||
|
* \param[in] parent The identifier of the parent option (optional)
|
||||||
*
|
*
|
||||||
* \return True if the option was added successfully, false if an error
|
* \return True if the option was added successfully, false if an error
|
||||||
* occurred.
|
* occurred.
|
||||||
*/
|
*/
|
||||||
bool OptionsParser::addOption(int opt, OptionType type, const char *help,
|
bool OptionsParser::addOption(int opt, OptionType type, const char *help,
|
||||||
const char *name, OptionArgument argument,
|
const char *name, OptionArgument argument,
|
||||||
const char *argumentName, bool array)
|
const char *argumentName, bool array, int parent)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Options must have at least a short or long name, and a text message.
|
* Options must have at least a short or long name, and a text message.
|
||||||
|
@ -770,9 +819,31 @@ bool OptionsParser::addOption(int opt, OptionType type, const char *help,
|
||||||
if (optionsMap_.find(opt) != optionsMap_.end())
|
if (optionsMap_.find(opt) != optionsMap_.end())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
options_.push_back(Option({ opt, type, name, argument, argumentName,
|
/*
|
||||||
help, nullptr, array }));
|
* If a parent is specified, create the option as a child of its parent.
|
||||||
optionsMap_[opt] = &options_.back();
|
* Otherwise, create it in the parser's options list.
|
||||||
|
*/
|
||||||
|
Option *option;
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
auto iter = optionsMap_.find(parent);
|
||||||
|
if (iter == optionsMap_.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Option *parentOpt = iter->second;
|
||||||
|
parentOpt->children.push_back({
|
||||||
|
opt, type, name, argument, argumentName, help, nullptr,
|
||||||
|
array, parentOpt, {}
|
||||||
|
});
|
||||||
|
option = &parentOpt->children.back();
|
||||||
|
} else {
|
||||||
|
options_.push_back({ opt, type, name, argument, argumentName,
|
||||||
|
help, nullptr, array, nullptr, {} });
|
||||||
|
option = &options_.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsMap_[opt] = option;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,13 +861,13 @@ bool OptionsParser::addOption(int opt, OptionType type, const char *help,
|
||||||
* occurred.
|
* occurred.
|
||||||
*/
|
*/
|
||||||
bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help,
|
bool OptionsParser::addOption(int opt, KeyValueParser *parser, const char *help,
|
||||||
const char *name, bool array)
|
const char *name, bool array, int parent)
|
||||||
{
|
{
|
||||||
if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired,
|
if (!addOption(opt, OptionKeyValue, help, name, ArgumentRequired,
|
||||||
"key=value[,key=value,...]", array))
|
"key=value[,key=value,...]", array, parent))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
options_.back().keyValueParser = parser;
|
optionsMap_[opt]->keyValueParser = parser;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,26 +892,26 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
|
||||||
* Allocate short and long options arrays large enough to contain all
|
* Allocate short and long options arrays large enough to contain all
|
||||||
* options.
|
* options.
|
||||||
*/
|
*/
|
||||||
char shortOptions[options_.size() * 3 + 2];
|
char shortOptions[optionsMap_.size() * 3 + 2];
|
||||||
struct option longOptions[options_.size() + 1];
|
struct option longOptions[optionsMap_.size() + 1];
|
||||||
unsigned int ids = 0;
|
unsigned int ids = 0;
|
||||||
unsigned int idl = 0;
|
unsigned int idl = 0;
|
||||||
|
|
||||||
shortOptions[ids++] = ':';
|
shortOptions[ids++] = ':';
|
||||||
|
|
||||||
for (const Option &option : options_) {
|
for (const auto [opt, option] : optionsMap_) {
|
||||||
if (option.hasShortOption()) {
|
if (option->hasShortOption()) {
|
||||||
shortOptions[ids++] = option.opt;
|
shortOptions[ids++] = opt;
|
||||||
if (option.argument != ArgumentNone)
|
if (option->argument != ArgumentNone)
|
||||||
shortOptions[ids++] = ':';
|
shortOptions[ids++] = ':';
|
||||||
if (option.argument == ArgumentOptional)
|
if (option->argument == ArgumentOptional)
|
||||||
shortOptions[ids++] = ':';
|
shortOptions[ids++] = ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.hasLongOption()) {
|
if (option->hasLongOption()) {
|
||||||
longOptions[idl].name = option.name;
|
longOptions[idl].name = option->name;
|
||||||
|
|
||||||
switch (option.argument) {
|
switch (option->argument) {
|
||||||
case ArgumentNone:
|
case ArgumentNone:
|
||||||
longOptions[idl].has_arg = no_argument;
|
longOptions[idl].has_arg = no_argument;
|
||||||
break;
|
break;
|
||||||
|
@ -853,7 +924,7 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
longOptions[idl].flag = 0;
|
longOptions[idl].flag = 0;
|
||||||
longOptions[idl].val = option.opt;
|
longOptions[idl].val = option->opt;
|
||||||
idl++;
|
idl++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -881,10 +952,7 @@ OptionsParser::Options OptionsParser::parse(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Option &option = *optionsMap_[c];
|
const Option &option = *optionsMap_[c];
|
||||||
if (!options.parseValue(c, option, optarg)) {
|
if (!parseValue(option, optarg, &options)) {
|
||||||
std::cerr << "Can't parse " << option.typeName()
|
|
||||||
<< " argument for option " << option.optionName()
|
|
||||||
<< std::endl;
|
|
||||||
usage();
|
usage();
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -906,15 +974,16 @@ void OptionsParser::usage()
|
||||||
{
|
{
|
||||||
unsigned int indent = 0;
|
unsigned int indent = 0;
|
||||||
|
|
||||||
for (const Option &option : options_) {
|
for (const auto &opt : optionsMap_) {
|
||||||
|
const Option *option = opt.second;
|
||||||
unsigned int length = 14;
|
unsigned int length = 14;
|
||||||
if (option.hasLongOption())
|
if (option->hasLongOption())
|
||||||
length += 2 + strlen(option.name);
|
length += 2 + strlen(option->name);
|
||||||
if (option.argument != ArgumentNone)
|
if (option->argument != ArgumentNone)
|
||||||
length += 1 + strlen(option.argumentName);
|
length += 1 + strlen(option->argumentName);
|
||||||
if (option.argument == ArgumentOptional)
|
if (option->argument == ArgumentOptional)
|
||||||
length += 2;
|
length += 2;
|
||||||
if (option.isArray)
|
if (option->isArray)
|
||||||
length += 4;
|
length += 4;
|
||||||
|
|
||||||
if (length > indent)
|
if (length > indent)
|
||||||
|
@ -937,6 +1006,8 @@ void OptionsParser::usage()
|
||||||
void OptionsParser::usageOptions(const std::list<Option> &options,
|
void OptionsParser::usageOptions(const std::list<Option> &options,
|
||||||
unsigned int indent)
|
unsigned int indent)
|
||||||
{
|
{
|
||||||
|
std::vector<const Option *> parentOptions;
|
||||||
|
|
||||||
for (const Option &option : options) {
|
for (const Option &option : options) {
|
||||||
std::string argument;
|
std::string argument;
|
||||||
if (option.hasShortOption())
|
if (option.hasShortOption())
|
||||||
|
@ -981,5 +1052,91 @@ void OptionsParser::usageOptions(const std::list<Option> &options,
|
||||||
|
|
||||||
if (option.keyValueParser)
|
if (option.keyValueParser)
|
||||||
option.keyValueParser->usage(indent);
|
option.keyValueParser->usage(indent);
|
||||||
|
|
||||||
|
if (!option.children.empty())
|
||||||
|
parentOptions.push_back(&option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentOptions.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const Option *option : parentOptions) {
|
||||||
|
std::cerr << std::endl << "Options valid in the context of "
|
||||||
|
<< option->optionName() << ":" << std::endl;
|
||||||
|
usageOptions(option->children, indent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::tuple<OptionsParser::Options *, const Option *>
|
||||||
|
OptionsParser::childOption(const Option *parent, Options *options)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The parent argument points to the parent of the leaf node Option,
|
||||||
|
* and the options argument to the root node of the Options tree. Use
|
||||||
|
* recursive calls to traverse the Option tree up to the root node while
|
||||||
|
* traversing the Options tree down to the leaf node:
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - If we have no parent, we've reached the root node of the Option
|
||||||
|
* tree, the options argument is what we need.
|
||||||
|
*/
|
||||||
|
if (!parent)
|
||||||
|
return { options, nullptr };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - If the parent has a parent, use recursion to move one level up the
|
||||||
|
* Option tree. This returns the Options corresponding to parent, or
|
||||||
|
* nullptr if a suitable Options child isn't found.
|
||||||
|
*/
|
||||||
|
if (parent->parent) {
|
||||||
|
const Option *error;
|
||||||
|
std::tie(options, error) = childOption(parent->parent, options);
|
||||||
|
|
||||||
|
/* Propagate the error all the way back up the call stack. */
|
||||||
|
if (!error)
|
||||||
|
return { options, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - The parent has no parent, we're now one level down the root.
|
||||||
|
* Return the Options child corresponding to the parent. The child may
|
||||||
|
* not exist if options are specified in an incorrect order.
|
||||||
|
*/
|
||||||
|
if (!options->isSet(parent->opt))
|
||||||
|
return { nullptr, parent };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the child value is of array type, children are not stored in the
|
||||||
|
* value .children() list, but in the .children() of the value's array
|
||||||
|
* elements. Use the last array element in that case, as a child option
|
||||||
|
* relates to the last instance of its parent option.
|
||||||
|
*/
|
||||||
|
const OptionValue *value = &(*options)[parent->opt];
|
||||||
|
if (value->type() == OptionValue::ValueArray)
|
||||||
|
value = &value->toArray().back();
|
||||||
|
|
||||||
|
return { const_cast<Options *>(&value->children()), nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OptionsParser::parseValue(const Option &option, const char *arg,
|
||||||
|
Options *options)
|
||||||
|
{
|
||||||
|
const Option *error;
|
||||||
|
|
||||||
|
std::tie(options, error) = childOption(option.parent, options);
|
||||||
|
if (error) {
|
||||||
|
std::cerr << "Option " << option.optionName() << " requires a "
|
||||||
|
<< error->optionName() << " context" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options->parseValue(option.opt, option, arg)) {
|
||||||
|
std::cerr << "Can't parse " << option.typeName()
|
||||||
|
<< " argument for option " << option.optionName()
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class KeyValueParser;
|
class KeyValueParser;
|
||||||
|
@ -91,9 +92,11 @@ public:
|
||||||
bool addOption(int opt, OptionType type, const char *help,
|
bool addOption(int opt, OptionType type, const char *help,
|
||||||
const char *name = nullptr,
|
const char *name = nullptr,
|
||||||
OptionArgument argument = ArgumentNone,
|
OptionArgument argument = ArgumentNone,
|
||||||
const char *argumentName = nullptr, bool array = false);
|
const char *argumentName = nullptr, bool array = false,
|
||||||
|
int parent = 0);
|
||||||
bool addOption(int opt, KeyValueParser *parser, const char *help,
|
bool addOption(int opt, KeyValueParser *parser, const char *help,
|
||||||
const char *name = nullptr, bool array = false);
|
const char *name = nullptr, bool array = false,
|
||||||
|
int parent = 0);
|
||||||
|
|
||||||
Options parse(int argc, char *argv[]);
|
Options parse(int argc, char *argv[]);
|
||||||
void usage();
|
void usage();
|
||||||
|
@ -104,6 +107,10 @@ private:
|
||||||
|
|
||||||
void usageOptions(const std::list<Option> &options, unsigned int indent);
|
void usageOptions(const std::list<Option> &options, unsigned int indent);
|
||||||
|
|
||||||
|
std::tuple<OptionsParser::Options *, const Option *>
|
||||||
|
childOption(const Option *parent, Options *options);
|
||||||
|
bool parseValue(const Option &option, const char *arg, Options *options);
|
||||||
|
|
||||||
std::list<Option> options_;
|
std::list<Option> options_;
|
||||||
std::map<unsigned int, Option *> optionsMap_;
|
std::map<unsigned int, Option *> optionsMap_;
|
||||||
};
|
};
|
||||||
|
@ -139,12 +146,15 @@ public:
|
||||||
KeyValueParser::Options toKeyValues() const;
|
KeyValueParser::Options toKeyValues() const;
|
||||||
std::vector<OptionValue> toArray() const;
|
std::vector<OptionValue> toArray() const;
|
||||||
|
|
||||||
|
const OptionsParser::Options &children() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ValueType type_;
|
ValueType type_;
|
||||||
int integer_;
|
int integer_;
|
||||||
std::string string_;
|
std::string string_;
|
||||||
KeyValueParser::Options keyValues_;
|
KeyValueParser::Options keyValues_;
|
||||||
std::vector<OptionValue> array_;
|
std::vector<OptionValue> array_;
|
||||||
|
OptionsParser::Options children_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __CAM_OPTIONS_H__ */
|
#endif /* __CAM_OPTIONS_H__ */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue